diff --git a/.github/workflows/main_deploy.yaml b/.github/workflows/main_deploy.yaml
index 6bda0c1db..46f909e1f 100644
--- a/.github/workflows/main_deploy.yaml
+++ b/.github/workflows/main_deploy.yaml
@@ -79,5 +79,7 @@ jobs:
with:
name: web
path: build/web
+ - name: Update packages
+ run: flutter pub get
- name: Update sentry
run: flutter packages pub run sentry_dart_plugin
diff --git a/README.md b/README.md
index 7c27b6e2e..a1ad9f2b7 100644
--- a/README.md
+++ b/README.md
@@ -31,7 +31,7 @@
# 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)
+* Pangea Chat is a fork of [FluffyChat](https://fluffychat.im) which is a [[matrix](https://matrix.org)] client written in [Flutter](https://flutter.dev). You can [support the primary maker of FluffyChat directly here.](https://ko-fi.com/C1C86VN53)
* Fabiyamada is a graphics designer and has made the fluffychat logo and the banner. Big thanks for her great designs.
diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb
index 3281b68e4..7f44188a6 100644
--- a/assets/l10n/intl_en.arb
+++ b/assets/l10n/intl_en.arb
@@ -410,7 +410,6 @@
"type": "text",
"placeholders": {}
},
- "classes": "Classes",
"chooseAStrongPassword": "Choose a strong password",
"@chooseAStrongPassword": {
"type": "text",
@@ -616,16 +615,11 @@
}
},
"createGroup": "Create group",
- "createNewSpace": "Create an exchange space",
"createNewGroup": "Create a new chat",
"@createNewGroup": {
"type": "text",
"placeholders": {}
},
- "@createNewSpace": {
- "type": "text",
- "placeholders": {}
- },
"currentlyActive": "Currently active",
"@currentlyActive": {
"type": "text",
@@ -2401,7 +2395,7 @@
"@noKeyForThisMessage": {},
"newGroup": "New chat",
"@newGroup": {},
- "newSpace": "New class",
+ "newSpace": "New space",
"@newSpace": {},
"enterSpace": "Enter space",
"@enterSpace": {},
@@ -2540,7 +2534,7 @@
"type": "text",
"placeholders": {}
},
- "interactiveTranslatorNotAllowedDesc": "Translation assistance is disabled in space group chats for all participants. This restriction does not apply to Class/Exchange Admin or direct chats.",
+ "interactiveTranslatorNotAllowedDesc": "Translation assistance is disabled in space group chats for all participants. This restriction does not apply to space admins or direct chats.",
"@interactiveTranslatorNotAllowedDesc": {
"type": "text",
"placeholders": {}
@@ -2550,7 +2544,7 @@
"type": "text",
"placeholders": {}
},
- "interactiveTranslatorRequiredDesc": "Students cannot turn off translation assistance. They can choose not to accept the translation suggestions. This restriction does not apply to Class/Exchange or direct chats.",
+ "interactiveTranslatorRequiredDesc": "Students cannot turn off translation assistance. They can choose not to accept the translation suggestions. This restriction does not apply to spaces or direct chats.",
"@interactiveTranslatorRequiredDesc": {
"type": "text",
"placeholders": {}
@@ -2560,12 +2554,7 @@
"type": "text",
"placeholders": {}
},
- "multiLingualClass": "Multilingual Class",
- "classAnalytics": "Class Analytics",
- "@classAnalytics": {
- "type": "text",
- "placeholders": {}
- },
+ "multiLingualSpace": "Multilingual Space",
"allClasses": "All Classes",
"@allClasses": {
"type": "text",
@@ -2601,18 +2590,8 @@
"type": "text",
"placeholders": {}
},
- "requestAnExchange": "Request an Exchange",
- "@requestAnExchange": {
- "type": "text",
- "placeholders": {}
- },
- "findLanguageExchange": "Find a class exchange partner",
- "@findLanguageExchange": {
- "type": "text",
- "placeholders": {}
- },
- "classAnalyticsDesc": "Detailed information on student engagement and language use",
- "@classAnalyticsDesc": {
+ "spaceAnalyticsDesc": "Detailed information on student engagement and language use",
+ "@spaceAnalyticsDesc": {
"type": "text",
"placeholders": {}
},
@@ -2629,28 +2608,28 @@
"type": "text",
"placeholders": {}
},
- "classSettings": "Class Settings",
- "@classSettings": {
+ "languageSettings": "Language Settings",
+ "@languageSettings": {
"type": "text",
"placeholders": {}
},
- "classSettingsDesc": "Edit class languages and proficiency level.",
- "@classSettingsDesc": {
+ "languageSettingsDesc": "Edit space languages and proficiency level.",
+ "@languageSettingsDesc": {
"type": "text",
"placeholders": {}
},
- "selectClassRoomDominantLanguage": "What is the base language of your class?",
- "@selectClassRoomDominantLanguage": {
+ "selectSpaceDominantLanguage": "What's the most common language of space members?",
+ "@selectSpaceDominantLanguage": {
"type": "text",
"placeholders": {}
},
- "selectTargetLanguage": "What language are you teaching?",
- "@selectTargetLanguage": {
+ "selectSpaceTargetLanguage": "What is the most common target language of the space?",
+ "@selectSpaceTargetLanguage": {
"type": "text",
"placeholders": {}
},
- "whatIsYourClassLanguageLevel": "What is the average language level of your class?",
- "@whatIsYourClassLanguageLevel": {
+ "whatIsYourSpaceLanguageLevel": "What is the average language level of the space?",
+ "@whatIsYourSpaceLanguageLevel": {
"type": "text",
"placeholders": {}
},
@@ -2679,7 +2658,7 @@
"type": "text",
"placeholders": {}
},
- "createGroupChatsDesc": "Toggle this on to allow students to create group chats within the class/exchange space.",
+ "createGroupChatsDesc": "Toggle this on to allow students to create group chats within the space.",
"@createGroupChatsDesc": {
"type": "text",
"placeholders": {}
@@ -2794,12 +2773,12 @@
"type": "text",
"placeholders": {}
},
- "joinWithClassCode": "Join class or exchange",
+ "joinWithClassCode": "Join space",
"@joinWithClassCode": {
"type": "text",
"placeholders": {}
},
- "joinWithClassCodeDesc": "Connect to a class or exchange space with the 6-digit invite code provided by the space administrator.",
+ "joinWithClassCodeDesc": "Connect to a space with the 6-digit invite code provided by the space administrator.",
"@joinWithClassCodeDesc": {
"type": "text",
"placeholders": {}
@@ -2809,7 +2788,7 @@
"type": "text",
"placeholders": {}
},
- "unableToFindClass": "We are unable to find the class or exchange. Please double-check the information with the space administrator. If you are still experiencing an issue, please contact support@pangea.chat.",
+ "unableToFindClass": "We are unable to find the space. Please double-check the information with the space administrator. If you are still experiencing an issue, please contact support@pangea.chat.",
"@unableToFindClass": {
"type": "text",
"placeholders": {}
@@ -2869,12 +2848,12 @@
"type": "text",
"placeholders": {}
},
- "welcomeToPangea18Plus": "Welcome to Pangea Chat! 🙂\nWhat's next?\nCreate or join a class!\nOr search for a conversation partner!",
+ "welcomeToPangea18Plus": "Welcome to Pangea Chat! 🙂\nWhat's next?\nCreate or join a space!\nOr search for a conversation partner!",
"@welcomeToPangea18Plus": {
"type": "text",
"placeholders": {}
},
- "welcomeToPangeaMinor": "Welcome to Pangea Chat! 🙂\nWhat's next?\nJoin a class!\nAsk your teacher for an invite code.",
+ "welcomeToPangeaMinor": "Welcome to Pangea Chat! 🙂\nWhat's next?\nJoin a space!\nAsk your teacher for an invite code.",
"@welcomeToPangeaMinor": {
"type": "text",
"placeholders": {}
@@ -2915,21 +2894,23 @@
"type": "text",
"placeholders": {}
},
- "helpMeTranslate": "Help me translate!",
+ "helpMeTranslate": "Yes!",
"@helpMeTranslate": {
"type": "text",
"placeholders": {}
},
- "needsItShortMessage": "Try interactive translation!",
+ "needsItShortMessage": "Out of target",
"needsIGCShortMessage": "Try interactive grammar assistance!",
"@needsItShortMessage": {
"type": "text",
"placeholders": {}
},
- "needsItMessage": "This message has too many words in your base language.",
+ "needsItMessage": "Wait, that's not {targetLanguage}! Do you need help translating?",
"@needsItMessage": {
"type": "text",
- "placeholders": {}
+ "placeholders": {
+ "targetLanguage": {}
+ }
},
"needsIgcMessage": "This message has a grammar error.",
"tokenTranslationTitle": "A word is in your base language.",
@@ -3130,7 +3111,7 @@
"prettyGood": "Pretty good! Here's what I would have said.",
"letMeThink": "Hmm, let's see how you did!",
"clickMessageTitle": "Need help?",
- "clickMessageBody": "Click messages to access definitions, translations, and audio!",
+ "clickMessageBody": "Click a message for language help! Click and hold to react 😀.",
"understandingMessagesTitle": "Definitions and translations!",
"understandingMessagesBody": "Click underlined words for definitions. Translate with message options (upper right).",
"allDone": "All done!",
@@ -3174,7 +3155,7 @@
"igcToggleDescription": "This language learning tool will identify common spelling, grammar and punctuation errors in your message and suggest corrections. Though rare, the AI can make correction errors.",
"sendOnEnterDescription": "Turn this off to be able to add line spaces in messages. When the toggle is off on the browser app, you can press Shift + Enter to start a new line. When the toggle is off on mobile apps, just Enter will start a new line.",
"alreadyInClass": "You are already in this space.",
- "pleaseLoginFirst": "Please login or sign up first and then you will be added to your class/exchange space.",
+ "pleaseLoginFirst": "Please login or sign up first and then you will be added to your space.",
"originalMessage": "Original Message",
"sentMessage": "Sent Message",
"useType": "Use Type",
@@ -3185,16 +3166,13 @@
"definitionsToolDescription": "When enabled, words underlined in blue can be clicked for definitions. Click messages to access definitions.",
"translationsToolDescrption": "When enabled, click a message and the translation icon to see a message in your base language.",
"welcomeBack": "Welcome back! If you were part of the 2023-2024 pilot, please contact us for your special pilot subscription. If you are a teacher who has (or whose institution has) purchased licenses for your class, contact us for your teacher subscription.",
- "classExchanges": "Exchanges",
"createNewClass": "New class space",
- "newExchange": "New exchange space",
"kickAllStudents": "Kick All Students",
"kickAllStudentsConfirmation": "Are you sure you want to kick all students?",
"inviteAllStudents": "Invite All Students",
"inviteAllStudentsConfirmation": "Are you sure you want to invite all students?",
"inviteStudentsFromOtherClasses": "Invite students from other spaces",
"inviteUsersFromPangea": "Add teachers",
- "allExchanges": "All Exchanges",
"redeemPromoCode": "Redeem Promo Code",
"enterPromoCode": "Enter Promo Code",
"downloadTxtFile": "Download Text File",
@@ -3652,22 +3630,24 @@
"pay": "Pay",
"allPrivateChats": "Direct chats",
"unknownPrivateChat": "Unknown private chat",
- "copyClassCodeDesc": "Students who are already in the app can 'Join class or exchange' via the main menu.",
- "addToClass": "Add exchange to class",
- "addToClassDesc": "Adding an exchange to a class will make the exchange appear within the class for students and give them access to all chats within the exchange.",
- "addToClassOrExchange": "Add chat to class or exchange",
- "addToClassOrExchangeDesc": "Adding a chat to a class or exchange will make the chat appear within the class or exchange for students and give them access.",
- "invitedToClassOrExchange": "{user} has invited you to join a space: {classOrExchange}! Do you wish to accept?",
- "@invitedToClassOrExchange": {
+ "copyClassCodeDesc": "Students who are already in the app can 'Join space' via the main menu.",
+ "addToSpaceDesc": "Adding a chat to a space will make the chat appear within the space for students and give them access.",
+ "@addToSpaceDesc": {
"placeholders": {
- "classOrExchange": {},
+ "roomtype": {}
+ }
+ },
+ "invitedToSpace": "{user} has invited you to join a space: {space}! Do you wish to accept?",
+ "@invitedToSpace": {
+ "placeholders": {
+ "space": {},
"user": {}
}
},
"declinedInvitation": "Declined invitation",
"acceptedInvitation": "Accepted invitation",
"youreInvited": "📩 You're invited!",
- "studentPermissionsDesc": "Set permissions for this space. They will only apply to the class/exchange space. They will override individual user settings.",
+ "studentPermissionsDesc": "Set permissions for this space. They will only apply to the space. They will override individual user settings.",
"noEligibleSpaces": "There are no eligible spaces to add this to.",
"youAddedToSpace": "You added {child} to {space}",
"@youAddedToSpace": {
@@ -3705,9 +3685,9 @@
},
"emptyChatNameWarning": "Please enter a name for this chat",
"emptyClassNameWarning": "Please enter a name for this class",
- "emptyExchangeNameWarning": "Please enter a name for this exchange",
+ "emptySpaceNameWarning": "Please enter a name for this space",
"blurMeansTranslateTitle": "Why is the message blurred?",
- "blurMeansTranslateBody": "While Immersion Mode is on, messages that are sent in your base language will be blurred while Pangea Bot translates them to your target language. Immersion Mode can be toggled in individual and class settings.",
+ "blurMeansTranslateBody": "While Immersion Mode is on, messages that are sent in your base language will be blurred while Pangea Bot translates them to your target language. Immersion Mode can be toggled in individual and space settings.",
"someErrorTitle": "Hm, something's not right",
"someErrorBody": "It could be an error or something in your base language.",
"bestCorrectionFeedback": "That's correct!",
@@ -3717,7 +3697,7 @@
"practiceDefaultPrompt": "What is the best answer?",
"correctionDefaultPrompt": "What is the best replacement?",
"itStartDefaultPrompt": "Do you want help translating?",
- "languageLevelWarning": "Please select a class language level",
+ "languageLevelWarning": "Please select a space language level",
"lockedChatWarning": "🔒 This chat has been locked",
"lockSpace": "Lock Space",
"lockChat": "Lock Chat",
@@ -3730,7 +3710,6 @@
"why": "Why?",
"definition": "Definition",
"exampleSentence": "Example Sentence",
- "addToClassTitle": "Add Exchange to Class",
"reportToTeacher": "Who do you want to report this message to?",
"reportMessageTitle": "{reportingUserId} has reported a message from {reportedUserId} in the chat {roomName}",
"@reportMessageTitle": {
@@ -3777,7 +3756,6 @@
},
"searchChatsRooms": "Search for #chats, @users...",
"createClass": "Create class",
- "createExchange": "Create exchange",
"viewArchive": "View Archive",
"trialExpiration": "Your free trial expires on {expiration}",
"@trialExpiration": {
@@ -3787,10 +3765,10 @@
},
"freeTrialDesc": "New users recieve a one week free trial of Pangea Chat",
"activateTrial": "Activate Free Trial",
- "inNoSpaces": "You are not a member of any classes or exchanges",
+ "inNoSpaces": "You are not a member of any spaces",
"successfullySubscribed": "You have successfully subscribed!",
"clickToManageSubscription": "Click here to manage your subscription.",
- "emptyInviteWarning": "Add this chat to a class or exchange to invite other users.",
+ "emptyInviteWarning": "Add this chat to a space to invite other users.",
"errorGettingAudio": "Error getting audio. Please refresh and try again.",
"nothingFound": "Nothing found...",
"groupName": "Group name",
@@ -4022,6 +4000,10 @@
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel": "Hours between discussion prompts",
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel": "Responds on ⏩ reaction",
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel": "Reaction to send discussion prompt",
+ "conversationBotCustomZone_title": "Custom Settings",
+ "conversationBotCustomZone_customSystemPromptLabel": "System prompt",
+ "conversationBotCustomZone_customSystemPromptPlaceholder": "Set custom system prompt",
+ "conversationBotCustomZone_customTriggerReactionEnabledLabel": "Responds on ⏩ reaction",
"addConversationBotDialogTitleInvite": "Confirm inviting conversation bot",
"addConversationBotButtonInvite": "Invite",
"addConversationBotDialogInviteConfirmation": "Invite",
@@ -4034,9 +4016,9 @@
"wordsPerMinute": "Words per minute",
"autoIGCToolName": "Run Language Assistance Automatically",
"autoIGCToolDescription": "Automatically run language assistance after typing messages",
- "runGrammarCorrection": "Run grammar correction",
+ "runGrammarCorrection": "Check message",
"grammarCorrectionFailed": "Issues to address",
- "grammarCorrectionComplete": "Grammar correction complete",
+ "grammarCorrectionComplete": "Looks good!",
"leaveRoomDescription": "The chat will be moved to the archive. Other users will be able to see that you have left the chat.",
"archiveSpaceDescription": "All chats within this space will be moved to the archive for yourself and other non-admin users.",
"leaveSpaceDescription": "All chats within this space will be moved to the archive. Other users will be able to see that you have left the space.",
@@ -4069,27 +4051,39 @@
"nonexistentSelection": "Selection no longer exists.",
"cantAddSpaceChild": "You do not have permission to add a child to this space.",
"roomAddedToSpace": "Room(s) have been added to the selected space.",
- "versionNotFound": "Version Not Found",
- "fetchingVersion": "Fetching version...",
- "versionFetchError": "Error fetching version",
- "connectedToStaging": "Connected to Staging",
- "versionText": "Version: {version}+{buildNumber}",
- "@versionText": {
- "description": "Text displaying the app version and build number.",
+ "createNewSpace": "New space",
+ "@createNewSpace": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "addChatToSpaceDesc": "Adding a chat to a space will make the chat appear within the space for students and give them access.",
+ "addSpaceToSpaceDesc": "Adding a space to another space will make the child space appear within the parent space for students and give them access.",
+ "spaceAnalytics": "Space Analytics",
+ "changeAnalyticsLanguage": "Change Analytics Language",
+ "suggestToSpace": "Suggest this space",
+ "suggestToSpaceDesc": "Suggested spaces will appear in the chat lists for their parent spaces",
+ "practice": "Practice",
+ "noLanguagesSet": "No languages set",
+ "noActivitiesFound": "No practice activities found for this message",
+ "hintTitle": "Hint:",
+ "speechToTextBody": "See how well you did by looking at your Accuracy and Words Per Minute scores",
+ "previous": "Previous",
+ "languageButtonLabel": "Language: {currentLanguage}",
+ "@languageButtonLabel": {
"type": "text",
"placeholders": {
- "version": {
- "type": "String",
- "description": "The current version of the app."
- },
- "buildNumber": {
- "type": "String",
- "description": "The build number of the app."
- }
+ "currentLanguage": {}
}
- }
-
-
-
-
+ },
+ "interactiveTranslatorAutoPlaySliderHeader": "Autoplay translation",
+ "@interactiveTranslatorAutoPlaySliderHeader": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "interactiveTranslatorAutoPlayDesc": "Launches the interactive translator without asking.",
+ "@interactiveTranslatorAutoPlayDesc": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "changeAnalyticsView": "Change Analytics View"
}
\ No newline at end of file
diff --git a/assets/l10n/intl_es.arb b/assets/l10n/intl_es.arb
index 6cc89cc65..ac6b30c76 100644
--- a/assets/l10n/intl_es.arb
+++ b/assets/l10n/intl_es.arb
@@ -2752,31 +2752,11 @@
"type": "text",
"placeholders": {}
},
- "openToExchanges": "¿Abierto a intercambios?",
- "@openToExchanges": {
- "type": "text",
- "placeholders": {}
- },
- "oneToOneChatsWithinExchanges": "Chats Privados dentro de Intercambios",
- "@oneToOneChatsWithinExchanges": {
- "type": "text",
- "placeholders": {}
- },
"createGroupChats": "Crear Chats Grupales",
"@createGroupChats": {
"type": "text",
"placeholders": {}
},
- "createGroupChatsInExchanges": "Crear Chats Grupales en Intercambios",
- "@createGroupChatsInExchanges": {
- "type": "text",
- "placeholders": {}
- },
- "createGroupChatsInExchangesDesc": "Active esta opción para permitir que los estudiantes creen chats grupales dentro de los intercambios.",
- "@createGroupChatsInExchangesDesc": {
- "type": "text",
- "placeholders": {}
- },
"shareStories": "Subir historias",
"@shareStories": {
"type": "text",
@@ -2917,21 +2897,11 @@
"type": "text",
"placeholders": {}
},
- "findLanguageExchange": "Encuentre una clase de intercambio",
- "@findLanguageExchange": {
- "type": "text",
- "placeholders": {}
- },
"requestToEnroll": "Solicitar inscripción",
"@requestToEnroll": {
"type": "text",
"placeholders": {}
},
- "requestAnExchange": "Solicitar un intercambio",
- "@requestAnExchange": {
- "type": "text",
- "placeholders": {}
- },
"targetLanguage": "Idioma a aprender",
"@targetLanguage": {
"type": "text",
@@ -2982,11 +2952,6 @@
"type": "text",
"placeholders": {}
},
- "classSettings": "Ajustes de clase",
- "@classSettings": {
- "type": "text",
- "placeholders": {}
- },
"suggestToClass": "Sugiera el chat a",
"@suggestToClass": {
"type": "text",
@@ -3032,8 +2997,8 @@
"type": "text",
"placeholders": {}
},
- "classAnalyticsDesc": "Información detallada sobre la participación de los estudiantes y el uso del idioma",
- "@classAnalyticsDesc": {
+ "spaceAnalyticsDesc": "Información detallada sobre la participación de los estudiantes y el uso del idioma",
+ "@spaceAnalyticsDesc": {
"type": "text",
"placeholders": {}
},
@@ -3099,6 +3064,17 @@
"type": "text",
"placeholders": {}
},
+ "interactiveTranslatorAutoPlaySliderHeader": "Traducción de reproducción automática",
+ "interactiveTranslatorAutoPlay": "Traductora interactiva de reproducción automática",
+ "@interactiveTranslatorAutoPlay": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "interactiveTranslatorAutoPlayDesc": "Inicia el traductor interactivo sin preguntar.",
+ "@interactiveTranslatorAutoPlayDesc": {
+ "type": "text",
+ "placeholders": {}
+ },
"grammarAssistance": "Asistencia gramatical",
"@grammarAssistance": {
"type": "text",
@@ -3161,10 +3137,16 @@
"translationSeemsFinished": "La traducción parece estar terminada.",
"needsItShortMessage": "¡Pruebe la traducción interactiva!",
"needsIGCShortMessage": "¡Pruebe el corrector gramatical interactivo!",
- "needsItMessage": "Este mensaje tiene demasiadas palabras en su idioma base.",
+ "needsItMessage": "Espera, ¡ese no es {targetLanguage}! ¿Necesitas ayuda para traducir?",
+ "@needsItMessage": {
+ "type": "text",
+ "placeholders": {
+ "targetLanguage": {}
+ }
+ },
"needsIgcMessage": "Este mensaje tiene un error gramatical.",
"tokenTranslationTitle": "Una palabra está en su idioma base.",
- "helpMeTranslate": "¡Ayúdeme a traducir!",
+ "helpMeTranslate": "¡Sí!",
"setToPublicSettingsTitle": "¿Quiere encontrar un compañero de conversación?",
"setToPublicSettingsDesc": "Antes de que pueda buscar un compañero de conversación, debe configurar la visibilidad de su perfil como pública.",
"publicProfileTitle": "Perfil público",
@@ -3815,7 +3797,6 @@
"sentMessage": "Mensaje enviado",
"useType": "Tipo de Uso",
"notAvailable": "No disponible",
- "classExchanges": "Intercambios",
"kickAllStudents": "Patear a todos los estudiantes",
"kickAllStudentsConfirmation": "¿Estás seguro de que quieres echar a todos los estudiantes?",
"inviteAllStudents": "Invitar a todos los estudiantes",
@@ -4296,7 +4277,6 @@
"copyClassLink": "Copiar enlace de invitación",
"copyClassLinkDesc": "Al hacer clic en este enlace, los usuarios accederán a la aplicación, se abrirán una cuenta y se unirán automáticamente a este espacio.",
"copyClassCode": "Copiar código de invitación",
- "classSettingsDesc": "Editar idiomas de clase y nivel",
"createGroupChatsDesc": "Active esta opción para permitir a los estudiantes crear chats de grupo dentro del espacio de clase/intercambio.",
"joinWithClassCode": "Únete a una clase o a un intercambio",
"joinWithClassCodeDesc": "Conéctese a una clase o espacio de intercambio con el código de invitación de 6 dígitos proporcionado por el administrador del espacio.",
@@ -4304,8 +4284,6 @@
"unableToFindClass": "No podemos encontrar la clase o el intercambio. Por favor, vuelva a comprobar la información con el administrador del espacio. Si sigue teniendo problemas, póngase en contacto con support@pangea.chat.",
"welcomeToYourNewClass": "Bienvenido 🙂",
"welcomeToClass": "Bienvenido! 🙂\n- ¡Prueba a unirte a un chat!\n- ¡Diviértete chateando!",
- "welcomeToPangea18Plus": "Bienvenido al chat de Pangea 🙂 .\n¿Qué es lo siguiente?\n¡Crea una clase o únete a ella!\n¡O busca un compañero de conversación!",
- "welcomeToPangeaMinor": "Bienvenido al chat de Pangea 🙂 .\n¿Qué es lo siguiente?\n¡Únete a una clase!\nPide a tu profesor un código de invitación.",
"unableToFindClassCode": "No se puede encontrar el código.",
"errorDisableITClassDesc": "La ayuda a la traducción está desactivada para el espacio en el que se encuentra este chat.",
"errorDisableIGCClassDesc": "La asistencia gramatical está desactivada para el espacio en el que se encuentra este chat.",
@@ -4317,26 +4295,14 @@
"pleaseLoginFirst": "Inicie sesión o regístrese primero y, a continuación, se le añadirá a su clase/espacio de intercambio.",
"welcomeBack": "¡Bienvenido de nuevo! Si formó parte del piloto 2023-2024, póngase en contacto con nosotros para obtener su suscripción especial de piloto. Si es usted un profesor que ha adquirido (o cuya institución ha adquirido) licencias para su clase, póngase en contacto con nosotros para obtener su suscripción de profesor.",
"createNewClass": "Nuevo espacio para clases",
- "newExchange": "Nuevo espacio de intercambio",
"inviteStudentsFromOtherClasses": "Invitar a estudiantes de otros espacios",
- "allExchanges": "Todos los intercambios",
"creatingSpacePleaseWait": "Creando espacio. Por favor, espere...",
"pay": "Pagar",
"copyClassCodeDesc": "Los estudiantes que ya están en la aplicación pueden 'Unirse a una clase o a un intercambio' a través del menú principal.",
"inviteUsersFromPangea": "Añadir profesores",
"addToClass": "Añadir intercambio a la clase",
- "addToClassOrExchange": "Añadir chat a la clase o al intercambio",
- "classAnalytics": "Análisis de clase",
"myLearning": "Mis análisis",
"addToClassDesc": "Añadir un intercambio a una clase hará que el intercambio aparezca dentro de la clase para los estudiantes y les dará acceso a todos los chats dentro del intercambio.",
- "addToClassOrExchangeDesc": "Añadir un chat a una clase o intercambio hará que el chat aparezca dentro de la clase o intercambio para los estudiantes y les dará acceso.",
- "invitedToClassOrExchange": "{user} te ha invitado a unirte a ¡{classOrExchange}! ¿Deseas aceptar?",
- "@invitedToClassOrExchange": {
- "placeholders": {
- "classOrExchange": {},
- "user": {}
- }
- },
"decline": "Disminución",
"declinedInvitation": "Invitación rechazada",
"acceptedInvitation": "Invitación aceptada",
@@ -4369,14 +4335,11 @@
},
"emptyChatNameWarning": "Introduzca un nombre para este chat",
"emptyClassNameWarning": "Introduzca un nombre para esta clase",
- "emptyExchangeNameWarning": "Introduzca un nombre para este intercambio",
"blurMeansTranslateTitle": "¿Por qué está borroso el mensaje?",
- "blurMeansTranslateBody": "Mientras el Modo Inmersión esté activado, los mensajes que se envíen en su idioma base aparecerán borrosos mientras Pangea Bot los traduce a su idioma de destino. El modo inmersión puede activarse en los ajustes individuales y de clase.",
"monthlySubscription": "Mensualmente",
"yearlySubscription": "Anualmente",
"defaultSubscription": "Suscripción al chat de Pangea",
"freeTrial": "Prueba gratuita",
- "languageLevelWarning": "Seleccione un nivel de idioma de clase",
"lockedChatWarning": "🔒 Este chat ha sido bloqueado",
"lockSpace": "Espacio de bloqueo",
"lockChat": "Chat de bloqueo",
@@ -4393,7 +4356,6 @@
"itStartDefaultPrompt": "¿Quiere ayuda para traducir?",
"suggestTo": "Sugerir a {spaceName}",
"suggestChatDesc": "Los chats sugeridos aparecerán en la lista de chats de {spaceName}.",
- "suggestExchangeDesc": "Los intercambios sugeridos aparecerán en la lista de chat de {spaceName}.",
"acceptSelection": "Aceptar corrección",
"acceptSelectionAnyway": "Use esto de todos modos",
"makingActivity": "Actividad de fabricación",
@@ -4460,7 +4422,6 @@
"groupCanBeFoundViaSearch": "El grupo se puede encontrar a través de la búsqueda",
"inNoSpaces": "No es miembro de ninguna clase o bolsa",
"createClass": "Crear clase",
- "createExchange": "Crear intercambio",
"viewArchive": "Ver archivo",
"trialExpiration": "Su prueba gratuita caduca el {expiration}.",
"@trialExpiration": {
@@ -4568,7 +4529,7 @@
"definitions": "definiciones",
"subscribedToUnlockTools": "Suscríbase para desbloquear herramientas lingüísticas, como",
"clickMessageTitle": "¿Necesitas ayuda?",
- "clickMessageBody": "Haga clic en los mensajes para acceder a las definiciones, traducciones y audio.",
+ "clickMessageBody": "¡Lame un mensaje para obtener ayuda con el idioma! Haz clic y mantén presionado para reaccionar 😀",
"more": "Más",
"translationTooltip": "Traducir",
"audioTooltip": "Reproducir audio",
@@ -4665,9 +4626,9 @@
"enterNumber": "Introduzca un valor numérico entero.",
"autoIGCToolName": "Ejecutar automáticamente la asistencia lingüística",
"autoIGCToolDescription": "Ejecutar automáticamente la asistencia lingüística después de escribir mensajes",
- "runGrammarCorrection": "Corregir la gramática",
+ "runGrammarCorrection": "Comprobar mensaje",
"grammarCorrectionFailed": "Cuestiones a tratar",
- "grammarCorrectionComplete": "Corrección gramatical completa",
+ "grammarCorrectionComplete": "¡Se ve bien!",
"leaveRoomDescription": "El chat se moverá al archivo. Los demás usuarios podrán ver que has abandonado el chat.",
"archiveSpaceDescription": "Todos los chats de este espacio se moverán al archivo para ti y otros usuarios que no sean administradores.",
"leaveSpaceDescription": "Todos los chats dentro de este espacio se moverán al archivo. Los demás usuarios podrán ver que has abandonado el espacio.",
@@ -4676,24 +4637,85 @@
"tooltipInstructionsMobileBody": "Mantenga pulsados los elementos para ver la información sobre herramientas.",
"tooltipInstructionsBrowserBody": "Pase el ratón sobre los elementos para ver información sobre herramientas.",
"buildTranslation": "Construye tu traducción a partir de las opciones anteriores",
- "versionNotFound": "Versión no encontrada",
- "fetchingVersion": "Obteniendo versión...",
- "versionFetchError": "Error al obtener la versión",
- "connectedToStaging": "Conectado al entorno de pruebas",
- "versionText": "Versión: {version}+{buildNumber}",
- "@versionText": {
- "description": "Texto que muestra la versión y el número de compilación de la aplicación.",
+ "appLockDescription": "Bloquea la aplicación cuando no la uses con un código pin",
+ "swipeRightToLeftToReply": "Desliza el dedo de derecha a izquierda para responder",
+ "globalChatId": "ID global del chat",
+ "accessAndVisibility": "Acceso y visibilidad",
+ "accessAndVisibilityDescription": "Quién puede participar en este chat y cómo se puede descubrir el chat.",
+ "calls": "Llamadas",
+ "customEmojisAndStickers": "Emojis y pegatinas personalizados",
+ "customEmojisAndStickersBody": "Añade o comparte emojis o stickers personalizados que podrás utilizar en cualquier chat.",
+ "hideRedactedMessages": "Ocultar mensajes redactados",
+ "hideRedactedMessagesBody": "Si alguien borra un mensaje, éste ya no será visible en el chat.",
+ "hideInvalidOrUnknownMessageFormats": "Ocultar formatos de mensaje no válidos o desconocidos",
+ "hideMemberChangesInPublicChats": "Ocultar los cambios de los miembros en los chats públicos",
+ "hideMemberChangesInPublicChatsBody": "No mostrar en la línea de tiempo del chat si alguien se une o abandona un chat público para mejorar la legibilidad.",
+ "overview": "Visión general",
+ "notifyMeFor": "Notificarme para",
+ "passwordRecoverySettings": "Configuración de recuperación de contraseña",
+ "usersMustKnock": "Los usuarios deben golpear",
+ "userWouldLikeToChangeTheChat": "{user} desea unirse al chat.",
+ "@userWouldLikeToChangeTheChat": {
+ "placeholders": {
+ "user": {}
+ }
+ },
+ "noPublicLinkHasBeenCreatedYet": "Aún no se ha creado ningún enlace público",
+ "knock": "Knock",
+ "multiLingualSpace": "Espacio multilingüe",
+ "languageSettings": "Ajustes de idioma",
+ "languageSettingsDesc": "Editar idiomas espaciales y nivel de competencia.",
+ "selectSpaceDominantLanguage": "¿Cuál es la lengua más común de los miembros del espacio?",
+ "selectSpaceTargetLanguage": "¿Cuál es la lengua de destino más común del espacio?",
+ "whatIsYourSpaceLanguageLevel": "¿Cuál es el nivel lingüístico medio del espacio?",
+ "welcomeToPangea18Plus": "Bienvenido al chat de Pangea 🙂 .\n¿Qué es lo siguiente?\n¡Crea o únete a un espacio!\n¡O busca un compañero de conversación!",
+ "welcomeToPangeaMinor": "Bienvenido al chat de Pangea 🙂 .\n¿Qué es lo siguiente?\n¡Únete a un espacio!\nPide a tu profesor un código de invitación.",
+ "addToSpaceDesc": "Añadir un chat a un espacio hará que el chat aparezca dentro del espacio para los estudiantes y les dará acceso.",
+ "invitedToSpace": "{user} te ha invitado a unirte a un espacio: ¡{space}! ¿Deseas aceptar?",
+ "@invitedToSpace": {
+ "placeholders": {
+ "space": {},
+ "user": {}
+ }
+ },
+ "emptySpaceNameWarning": "Introduzca un nombre para este espacio",
+ "blurMeansTranslateBody": "Mientras el Modo Inmersión esté activado, los mensajes que se envíen en tu idioma base aparecerán borrosos mientras Pangea Bot los traduce a tu idioma de destino. El Modo Inmersión puede activarse en los ajustes individuales y espaciales.",
+ "languageLevelWarning": "Seleccione un nivel de lengua espacial",
+ "knocking": "Golpeando",
+ "chatCanBeDiscoveredViaSearchOnServer": "El chat puede descubrirse mediante la búsqueda en {server}.",
+ "@chatCanBeDiscoveredViaSearchOnServer": {
"type": "text",
"placeholders": {
- "version": {
- "type": "String",
- "description": "La versión actual de la aplicación."
- },
- "buildNumber": {
- "type": "String",
- "description": "El número de compilación de la aplicación."
- }
+ "server": {}
}
- }
-
+ },
+ "publicChatAddresses": "Direcciones de chat públicas",
+ "createNewAddress": "Crear una nueva dirección",
+ "userRole": "Función del usuario",
+ "minimumPowerLevel": "{level} es el nivel mínimo de potencia.",
+ "@minimumPowerLevel": {
+ "type": "text",
+ "placeholders": {
+ "level": {}
+ }
+ },
+ "searchMore": "Buscar más...",
+ "gallery": "Galería",
+ "files": "Archivos",
+ "addSpaceToSpaceDescription": "Seleccione un espacio para añadir como padre",
+ "noDatabaseEncryption": "La encriptación de la base de datos no es compatible con esta plataforma",
+ "thereAreCountUsersBlocked": "Ahora mismo hay {count} usuarios bloqueados.",
+ "@thereAreCountUsersBlocked": {
+ "type": "text",
+ "count": {}
+ },
+ "restricted": "Restringido",
+ "knockRestricted": "Golpe restringido",
+ "nonexistentSelection": "La selección ya no existe.",
+ "cantAddSpaceChild": "No tiene permiso para añadir un niño a este espacio.",
+ "roomAddedToSpace": "Se han añadido habitaciones al espacio seleccionado.",
+ "addChatToSpaceDesc": "Añadir un chat a un espacio hará que el chat aparezca dentro del espacio para los estudiantes y les dará acceso.",
+ "addSpaceToSpaceDesc": "Añadir un espacio a otro espacio hará que el espacio hijo aparezca dentro del espacio padre para los estudiantes y les dará acceso.",
+ "spaceAnalytics": "Analítica espacial",
+ "changeAnalyticsLanguage": "Cambiar el lenguaje analítico"
}
\ No newline at end of file
diff --git a/assets/pangea/bot_faces/pangea_bot.riv b/assets/pangea/bot_faces/pangea_bot.riv
new file mode 100644
index 000000000..94244a30f
Binary files /dev/null and b/assets/pangea/bot_faces/pangea_bot.riv differ
diff --git a/ios/Podfile b/ios/Podfile
index c8df069d3..fac4d8176 100644
--- a/ios/Podfile
+++ b/ios/Podfile
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
-platform :ios, '12.0'
+platform :ios, '13.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index 4d1ab7555..98f0adda2 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -475,7 +475,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -568,7 +568,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -617,7 +617,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
diff --git a/lib/config/routes.dart b/lib/config/routes.dart
index d4eaa5d85..51c631ff3 100644
--- a/lib/config/routes.dart
+++ b/lib/config/routes.dart
@@ -29,7 +29,6 @@ import 'package:fluffychat/pages/settings_style/settings_style.dart';
import 'package:fluffychat/pangea/enum/bar_chart_view_enum.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/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';
import 'package:fluffychat/pangea/pages/settings_learning/settings_learning.dart';
@@ -43,8 +42,8 @@ import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
-import '../pangea/pages/analytics/class_analytics/class_analytics.dart';
-import '../pangea/pages/analytics/class_list/class_list.dart';
+import '../pangea/pages/analytics/space_analytics/space_analytics.dart';
+import '../pangea/pages/analytics/space_list/space_list.dart';
abstract class AppRoutes {
static FutureOr loggedInRedirect(
@@ -171,79 +170,30 @@ abstract class AppRoutes {
pageBuilder: (context, state) => defaultPageBuilder(
context,
state,
- const StudentAnalyticsPage(),
+ const StudentAnalyticsPage(
+ selectedView: BarChartViewSelection.messages,
+ ),
),
redirect: loggedOutRedirect,
- routes: [
- GoRoute(
- path: 'messages',
- pageBuilder: (context, state) => defaultPageBuilder(
- context,
- state,
- const StudentAnalyticsPage(
- selectedView: BarChartViewSelection.messages,
- ),
- ),
- ),
- GoRoute(
- path: 'errors',
- pageBuilder: (context, state) => defaultPageBuilder(
- context,
- state,
- const StudentAnalyticsPage(
- selectedView: BarChartViewSelection.grammar,
- ),
- ),
- ),
- ],
),
GoRoute(
path: 'analytics',
pageBuilder: (context, state) => defaultPageBuilder(
context,
state,
- const AnalyticsClassList(),
+ const AnalyticsSpaceList(),
),
redirect: loggedOutRedirect,
routes: [
GoRoute(
- path: ':classid',
- redirect: loggedOutRedirect,
+ path: ':spaceid',
pageBuilder: (context, state) => defaultPageBuilder(
context,
state,
- const ClassAnalyticsPage(),
+ const SpaceAnalyticsPage(
+ selectedView: BarChartViewSelection.messages,
+ ),
),
- routes: [
- GoRoute(
- path: 'messages',
- pageBuilder: (context, state) => defaultPageBuilder(
- context,
- state,
- ClassAnalyticsPage(
- // when going to sub-space from within a parent space's analytics, the
- // analytics list tiles do not properly update. Adding a unique key to this page is the best fix
- // I can find at the moment
- key: UniqueKey(),
- selectedView: BarChartViewSelection.messages,
- ),
- ),
- ),
- GoRoute(
- path: 'errors',
- pageBuilder: (context, state) => defaultPageBuilder(
- context,
- state,
- ClassAnalyticsPage(
- // when going to sub-space from within a parent space's analytics, the
- // analytics list tiles do not properly update. Adding a unique key to this page is the best fix
- // I can find at the moment
- key: UniqueKey(),
- selectedView: BarChartViewSelection.grammar,
- ),
- ),
- ),
- ],
),
],
),
@@ -319,24 +269,6 @@ abstract class AppRoutes {
redirect: loggedOutRedirect,
),
// #Pangea
- GoRoute(
- path: 'newspace/:newexchange',
- pageBuilder: (context, state) => defaultPageBuilder(
- context,
- state,
- const NewSpace(),
- ),
- redirect: loggedOutRedirect,
- ),
- GoRoute(
- path: 'join_exchange/:exchangeid',
- pageBuilder: (context, state) => defaultPageBuilder(
- context,
- state,
- const AddExchangeToClass(),
- ),
- redirect: loggedOutRedirect,
- ),
GoRoute(
path: 'partner',
pageBuilder: (context, state) => defaultPageBuilder(
diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart
index b199a1b67..5629d809e 100644
--- a/lib/pages/chat/chat.dart
+++ b/lib/pages/chat/chat.dart
@@ -16,12 +16,11 @@ import 'package:fluffychat/pages/chat/recording_dialog.dart';
import 'package:fluffychat/pages/chat_details/chat_details.dart';
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
-import 'package:fluffychat/pangea/enum/use_type.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/models/choreo_record.dart';
-import 'package:fluffychat/pangea/models/class_model.dart';
import 'package:fluffychat/pangea/models/representation_content_model.dart';
+import 'package:fluffychat/pangea/models/space_model.dart';
import 'package:fluffychat/pangea/models/tokens_event_content_model.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/pangea/utils/firebase_analytics.dart';
@@ -317,10 +316,10 @@ class ChatController extends State
Future.delayed(const Duration(seconds: 1), () async {
if (!mounted) return;
debugPrint(
- "chat.dart l1 ${pangeaController.languageController.activeL1Code(roomID: roomId)}",
+ "chat.dart l1 ${pangeaController.languageController.userL1?.langCode}",
);
debugPrint(
- "chat.dart l2 ${pangeaController.languageController.activeL2Code(roomID: roomId)}",
+ "chat.dart l2 ${pangeaController.languageController.userL2?.langCode}",
);
if (mounted) {
pangeaController.languageController.showDialogOnEmptyLanguage(
@@ -586,7 +585,6 @@ class ChatController extends State
PangeaMessageTokens? tokensSent,
PangeaMessageTokens? tokensWritten,
ChoreoRecord? choreo,
- UseType? useType,
}) async {
// Pangea#
if (sendController.text.trim().isEmpty) return;
@@ -630,7 +628,6 @@ class ChatController extends State
tokensSent: tokensSent,
tokensWritten: tokensWritten,
choreo: choreo,
- useType: useType,
)
.then(
(String? msgEventId) async {
@@ -644,7 +641,6 @@ class ChatController extends State
GoogleAnalytics.sendMessage(
room.id,
room.classCode,
- useType ?? UseType.un,
);
if (msgEventId == null) {
@@ -654,8 +650,6 @@ class ChatController extends State
);
return;
}
- // ensure that analytics room exists / is created for the active langCode
- await room.ensureAnalyticsRoomExists();
},
onError: (err, stack) => ErrorHandler.logError(e: err, s: stack),
);
diff --git a/lib/pages/chat/chat_event_list.dart b/lib/pages/chat/chat_event_list.dart
index fed94554a..24508bb62 100644
--- a/lib/pages/chat/chat_event_list.dart
+++ b/lib/pages/chat/chat_event_list.dart
@@ -4,8 +4,8 @@ import 'package:fluffychat/pages/chat/events/message.dart';
import 'package:fluffychat/pages/chat/seen_by_row.dart';
import 'package:fluffychat/pages/chat/typing_indicators.dart';
import 'package:fluffychat/pages/user_bottom_sheet/user_bottom_sheet.dart';
+import 'package:fluffychat/pangea/enum/instructions_enum.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
-import 'package:fluffychat/pangea/utils/instructions.dart';
import 'package:fluffychat/pangea/widgets/chat/locked_chat_message.dart';
import 'package:fluffychat/utils/account_config.dart';
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
@@ -45,7 +45,8 @@ class ChatEventList extends StatelessWidget {
// after the chat event list mounts, if the user hasn't yet seen this instruction
// card, attach it on top of the first shown message
WidgetsBinding.instance.addPostFrameCallback((_) {
- controller.pangeaController.instructions.show(
+ if (events.isEmpty) return;
+ controller.pangeaController.instructions.showInstructionsPopup(
context,
InstructionsEnum.clickMessage,
events[0].eventId,
diff --git a/lib/pages/chat/chat_input_row.dart b/lib/pages/chat/chat_input_row.dart
index 6bf19a850..2ba6a0957 100644
--- a/lib/pages/chat/chat_input_row.dart
+++ b/lib/pages/chat/chat_input_row.dart
@@ -1,9 +1,8 @@
import 'package:animations/animations.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.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/pangea/constants/language_constants.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/matrix.dart';
@@ -55,9 +54,6 @@ class ChatInputRow extends StatelessWidget {
return Column(
children: [
- ITBar(
- choreographer: controller.choreographer,
- ),
Row(
// crossAxisAlignment: CrossAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.center,
diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart
index 5ee52c46b..5b4f96606 100644
--- a/lib/pages/chat/chat_view.dart
+++ b/lib/pages/chat/chat_view.dart
@@ -24,6 +24,7 @@ import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart';
+import '../../pangea/choreographer/widgets/it_bar.dart';
import '../../utils/stream_extension.dart';
import 'chat_emoji_picker.dart';
import 'chat_input_row.dart';
@@ -440,6 +441,9 @@ class ChatView extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
const ConnectionStatusHeader(),
+ ITBar(
+ choreographer: controller.choreographer,
+ ),
ReactionsPicker(controller),
ReplyDisplay(controller),
ChatInputRow(controller),
diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart
index dc129a192..3b2c1b2fb 100644
--- a/lib/pages/chat/events/message.dart
+++ b/lib/pages/chat/events/message.dart
@@ -3,6 +3,7 @@ import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/pangea/enum/use_type.dart';
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/models/language_model.dart';
+import 'package:fluffychat/pangea/widgets/chat/message_buttons.dart';
import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart';
import 'package:fluffychat/utils/date_time_extension.dart';
import 'package:fluffychat/utils/string_color.dart';
@@ -469,7 +470,7 @@ class Message extends StatelessWidget {
?.showUseType ??
false) ...[
pangeaMessageEvent!
- .useType
+ .msgUseType
.iconView(
context,
textColor
@@ -524,7 +525,14 @@ class Message extends StatelessWidget {
Widget container;
final showReceiptsRow =
event.hasAggregatedEvents(timeline, RelationshipTypes.reaction);
- if (showReceiptsRow || displayTime || selected || displayReadMarker) {
+ // #Pangea
+ // if (showReceiptsRow || displayTime || selected || displayReadMarker) {
+ if (showReceiptsRow ||
+ displayTime ||
+ selected ||
+ displayReadMarker ||
+ (pangeaMessageEvent?.showMessageButtons ?? false)) {
+ // Pangea#
container = Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment:
@@ -561,7 +569,11 @@ class Message extends StatelessWidget {
AnimatedSize(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
- child: !showReceiptsRow
+ // #Pangea
+ child: !showReceiptsRow &&
+ !(pangeaMessageEvent?.showMessageButtons ?? false)
+ // child: !showReceiptsRow
+ // Pangea#
? const SizedBox.shrink()
: Padding(
padding: EdgeInsets.only(
@@ -569,7 +581,19 @@ class Message extends StatelessWidget {
left: (ownMessage ? 0 : Avatar.defaultSize) + 12.0,
right: ownMessage ? 0 : 12.0,
),
- child: MessageReactions(event, timeline),
+ // #Pangea
+ child: Row(
+ mainAxisAlignment: ownMessage
+ ? MainAxisAlignment.end
+ : MainAxisAlignment.start,
+ children: [
+ if (pangeaMessageEvent?.showMessageButtons ?? false)
+ MessageButtons(toolbarController: toolbarController),
+ MessageReactions(event, timeline),
+ ],
+ ),
+ // child: MessageReactions(event, timeline),
+ // Pangea#
),
),
if (displayReadMarker)
diff --git a/lib/pages/chat_details/chat_details_view.dart b/lib/pages/chat_details/chat_details_view.dart
index ca8cb8b04..4f4e091d0 100644
--- a/lib/pages/chat_details/chat_details_view.dart
+++ b/lib/pages/chat_details/chat_details_view.dart
@@ -11,10 +11,8 @@ import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/class_nam
import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/room_capacity_button.dart';
import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/room_rules_editor.dart';
import 'package:fluffychat/pangea/utils/lock_room.dart';
-import 'package:fluffychat/pangea/widgets/class/add_class_and_invite.dart';
import 'package:fluffychat/pangea/widgets/class/add_space_toggles.dart';
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_settings.dart';
-import 'package:fluffychat/pangea/widgets/space/class_settings.dart';
import 'package:fluffychat/utils/fluffy_share.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import 'package:fluffychat/widgets/avatar.dart';
@@ -104,380 +102,171 @@ class ChatDetailsView extends StatelessWidget {
backgroundColor: Theme.of(context).appBarTheme.backgroundColor,
),
body: MaxWidthBody(
- child: ListView.builder(
- physics: const NeverScrollableScrollPhysics(),
- shrinkWrap: true,
- itemCount: members.length + 1 + (canRequestMoreMembers ? 1 : 0),
- itemBuilder: (BuildContext context, int i) => i == 0
- ? Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
- Row(
- children: [
- Padding(
- padding: const EdgeInsets.all(32.0),
- child: Stack(
- children: [
- Material(
- elevation: Theme.of(context)
- .appBarTheme
- .scrolledUnderElevation ??
- 4,
- shadowColor: Theme.of(context)
- .appBarTheme
- .shadowColor,
- shape: RoundedRectangleBorder(
- side: BorderSide(
- color: Theme.of(context).dividerColor,
+ // #Pangea
+ // Chat description title has its own scrollbar so we disable the parent one
+ // otherwise they scroll with each other
+ child: ScrollConfiguration(
+ behavior:
+ ScrollConfiguration.of(context).copyWith(scrollbars: false),
+ // Pangea#
+ child: ListView.builder(
+ physics: const NeverScrollableScrollPhysics(),
+ shrinkWrap: true,
+ itemCount: members.length + 1 + (canRequestMoreMembers ? 1 : 0),
+ itemBuilder: (BuildContext context, int i) => i == 0
+ ? Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ Row(
+ children: [
+ Padding(
+ padding: const EdgeInsets.all(32.0),
+ child: Stack(
+ children: [
+ Material(
+ elevation: Theme.of(context)
+ .appBarTheme
+ .scrolledUnderElevation ??
+ 4,
+ shadowColor: Theme.of(context)
+ .appBarTheme
+ .shadowColor,
+ shape: RoundedRectangleBorder(
+ side: BorderSide(
+ color: Theme.of(context).dividerColor,
+ ),
+ borderRadius: BorderRadius.circular(
+ Avatar.defaultSize * 2.5,
+ ),
),
- borderRadius: BorderRadius.circular(
- Avatar.defaultSize * 2.5,
- ),
- ),
- child: Hero(
- tag: controller
- .widget.embeddedCloseButton !=
- null
- ? 'embedded_content_banner'
- : 'content_banner',
- child: Avatar(
- mxContent: room.avatar,
- name: displayname,
- size: Avatar.defaultSize * 2.5,
- ),
- ),
- ),
- if (!room.isDirectChat &&
- room.canChangeStateEvent(
- EventTypes.RoomAvatar,
- ))
- Positioned(
- bottom: 0,
- right: 0,
- child: FloatingActionButton.small(
- onPressed: controller.setAvatarAction,
- heroTag: null,
- child: const Icon(
- Icons.camera_alt_outlined,
+ child: Hero(
+ tag: controller.widget
+ .embeddedCloseButton !=
+ null
+ ? 'embedded_content_banner'
+ : 'content_banner',
+ child: Avatar(
+ mxContent: room.avatar,
+ name: displayname,
+ size: Avatar.defaultSize * 2.5,
),
),
),
- ],
+ if (!room.isDirectChat &&
+ room.canChangeStateEvent(
+ EventTypes.RoomAvatar,
+ ))
+ Positioned(
+ bottom: 0,
+ right: 0,
+ child: FloatingActionButton.small(
+ onPressed: controller.setAvatarAction,
+ heroTag: null,
+ child: const Icon(
+ Icons.camera_alt_outlined,
+ ),
+ ),
+ ),
+ ],
+ ),
),
- ),
- Expanded(
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- TextButton.icon(
- onPressed: () => room.isDirectChat
- ? null
- : room.canChangeStateEvent(
- EventTypes.RoomName,
- )
- ? controller.setDisplaynameAction()
- : FluffyShare.share(
- displayname,
- context,
- copyOnly: true,
- ),
- icon: Icon(
- room.isDirectChat
- ? Icons.chat_bubble_outline
+ Expanded(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ TextButton.icon(
+ onPressed: () => room.isDirectChat
+ ? null
: room.canChangeStateEvent(
EventTypes.RoomName,
)
- ? Icons.edit_outlined
- : Icons.copy_outlined,
- size: 16,
- ),
- style: TextButton.styleFrom(
- foregroundColor: Theme.of(context)
- .colorScheme
- .onSurface,
- ),
- label: Text(
- room.isDirectChat
- ? L10n.of(context)!.directChat
- : displayname,
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
- // style: const TextStyle(fontSize: 18),
- ),
- ),
- TextButton.icon(
- onPressed: () => room.isDirectChat
- ? null
- : context.push(
- '/rooms/${controller.roomId}/details/members',
- ),
- icon: const Icon(
- Icons.group_outlined,
- size: 14,
- ),
- style: TextButton.styleFrom(
- foregroundColor: Theme.of(context)
- .colorScheme
- .secondary,
- ),
- label: Text(
- L10n.of(context)!.countParticipants(
- actualMembersCount,
+ ? controller
+ .setDisplaynameAction()
+ : FluffyShare.share(
+ displayname,
+ context,
+ copyOnly: true,
+ ),
+ icon: Icon(
+ room.isDirectChat
+ ? Icons.chat_bubble_outline
+ : room.canChangeStateEvent(
+ EventTypes.RoomName,
+ )
+ ? Icons.edit_outlined
+ : Icons.copy_outlined,
+ size: 16,
+ ),
+ style: TextButton.styleFrom(
+ foregroundColor: Theme.of(context)
+ .colorScheme
+ .onSurface,
+ ),
+ label: Text(
+ room.isDirectChat
+ ? L10n.of(context)!.directChat
+ : displayname,
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ // style: const TextStyle(fontSize: 18),
),
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
- // style: const TextStyle(fontSize: 12),
),
- ),
- ],
+ TextButton.icon(
+ onPressed: () => room.isDirectChat
+ ? null
+ : context.push(
+ '/rooms/${controller.roomId}/details/members',
+ ),
+ icon: const Icon(
+ Icons.group_outlined,
+ size: 14,
+ ),
+ style: TextButton.styleFrom(
+ foregroundColor: Theme.of(context)
+ .colorScheme
+ .secondary,
+ ),
+ label: Text(
+ L10n.of(context)!.countParticipants(
+ actualMembersCount,
+ ),
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ // style: const TextStyle(fontSize: 12),
+ ),
+ ),
+ ],
+ ),
),
- ),
- ],
- ),
- Divider(
- height: 1,
- color: Theme.of(context).dividerColor,
- ),
- // if (room.canSendEvent('m.room.name'))
- if (room.isRoomAdmin)
- // #Pangea
- ClassNameButton(
- room: room,
- controller: controller,
+ ],
),
- if (room.canSendEvent('m.room.topic'))
- ClassDescriptionButton(
- room: room,
- controller: controller,
+ Divider(
+ height: 1,
+ color: Theme.of(context).dividerColor,
),
- // #Pangea
- RoomCapacityButton(
- room: room,
- controller: controller,
- ),
- // Pangea#
- if ((room.isPangeaClass || room.isExchange) &&
- room.isRoomAdmin)
- ListTile(
- title: Text(
- L10n.of(context)!.classAnalytics,
- style: TextStyle(
- color: Theme.of(context).colorScheme.secondary,
- fontWeight: FontWeight.bold,
- ),
- ),
- leading: CircleAvatar(
- backgroundColor:
- Theme.of(context).scaffoldBackgroundColor,
- foregroundColor: iconColor,
- child: const Icon(
- Icons.analytics_outlined,
- ),
- ),
- onTap: () => context.go(
- '/rooms/analytics/${room.id}',
- ),
- ),
- if (room.classSettings != null && room.isRoomAdmin)
- ClassSettings(
- roomId: controller.roomId,
- startOpen: false,
- ),
- if (room.pangeaRoomRules != null)
- RoomRulesEditor(
- roomId: controller.roomId,
- startOpen: false,
- ),
- // if (!room.canChangeStateEvent(EventTypes.RoomTopic))
- // ListTile(
- // title: Text(
- // L10n.of(context)!.chatDescription,
- // style: TextStyle(
- // color: Theme.of(context).colorScheme.secondary,
- // fontWeight: FontWeight.bold,
- // ),
- // ),
- // )
- // else
- // Padding(
- // padding: const EdgeInsets.all(16.0),
- // child: TextButton.icon(
- // onPressed: controller.setTopicAction,
- // label: Text(L10n.of(context)!.setChatDescription),
- // icon: const Icon(Icons.edit_outlined),
- // style: TextButton.styleFrom(
- // backgroundColor: Theme.of(context)
- // .colorScheme
- // .secondaryContainer,
- // foregroundColor: Theme.of(context)
- // .colorScheme
- // .onSecondaryContainer,
- // ),
- // ),
- // ),
- // Padding(
- // padding: const EdgeInsets.symmetric(
- // horizontal: 16.0,
- // ),
- // child: SelectableLinkify(
- // text: room.topic.isEmpty
- // ? L10n.of(context)!.noChatDescriptionYet
- // : room.topic,
- // options: const LinkifyOptions(humanize: false),
- // linkStyle: const TextStyle(
- // color: Colors.blueAccent,
- // decorationColor: Colors.blueAccent,
- // ),
- // style: TextStyle(
- // fontSize: 14,
- // fontStyle: room.topic.isEmpty
- // ? FontStyle.italic
- // : FontStyle.normal,
- // color:
- // Theme.of(context).textTheme.bodyMedium!.color,
- // decorationColor:
- // Theme.of(context).textTheme.bodyMedium!.color,
- // ),
- // onOpen: (url) =>
- // UrlLauncher(context, url.url).launchUrl(),
- // ),
- // ),
- // const SizedBox(height: 16),
- // Divider(
- // height: 1,
- // color: Theme.of(context).dividerColor,
- // ),
- // ListTile(
- // leading: CircleAvatar(
- // backgroundColor:
- // Theme.of(context).scaffoldBackgroundColor,
- // foregroundColor: iconColor,
- // child: const Icon(
- // Icons.insert_emoticon_outlined,
- // ),
- // ),
- // title:
- // Text(L10n.of(context)!.customEmojisAndStickers),
- // subtitle: Text(L10n.of(context)!.setCustomEmotes),
- // onTap: controller.goToEmoteSettings,
- // trailing: const Icon(Icons.chevron_right_outlined),
- // ),
- // if (!room.isDirectChat)
- // ListTile(
- // leading: CircleAvatar(
- // backgroundColor:
- // Theme.of(context).scaffoldBackgroundColor,
- // foregroundColor: iconColor,
- // child: const Icon(Icons.shield_outlined),
- // ),
- // title: Text(
- // L10n.of(context)!.accessAndVisibility,
- // ),
- // subtitle: Text(
- // L10n.of(context)!.accessAndVisibilityDescription,
- // ),
- // onTap: () => context
- // .push('/rooms/${room.id}/details/access'),
- // trailing: const Icon(Icons.chevron_right_outlined),
- // ),
- // if (!room.isDirectChat)
- if (!room.isDirectChat &&
- !room.isSpace &&
- room.isRoomAdmin)
- // Pangea#
- ListTile(
- // #Pangea
- // title: Text(L10n.of(context)!.chatPermissions),
- title: Text(
- L10n.of(context)!.editChatPermissions,
- style: TextStyle(
- color: Theme.of(context).colorScheme.secondary,
- fontWeight: FontWeight.bold,
- ),
- ),
- // Pangea#
- subtitle: Text(
- L10n.of(context)!.whoCanPerformWhichAction,
- ),
- leading: CircleAvatar(
- backgroundColor:
- Theme.of(context).scaffoldBackgroundColor,
- foregroundColor: iconColor,
- child: const Icon(
- Icons.edit_attributes_outlined,
- ),
- ),
- // #Pangea
- // trailing: const Icon(Icons.chevron_right_outlined),
- // Pangea#
- onTap: () => context
- .push('/rooms/${room.id}/details/permissions'),
- ),
- Divider(
- height: 1,
- color: Theme.of(context).dividerColor,
- ),
- // #Pangea
- if (room.canInvite &&
- !room.isDirectChat &&
- (!room.isSpace || room.isRoomAdmin))
- ListTile(
- title: Text(
- room.isSpace
- ? L10n.of(context)!.inviteUsersFromPangea
- : L10n.of(context)!.inviteStudentByUserName,
- style: TextStyle(
- color: Theme.of(context).colorScheme.secondary,
- fontWeight: FontWeight.bold,
- ),
- ),
- leading: CircleAvatar(
- backgroundColor:
- Theme.of(context).scaffoldBackgroundColor,
- foregroundColor:
- Theme.of(context).textTheme.bodyLarge!.color,
- child: const Icon(
- Icons.add,
- ),
- ),
- onTap: () => context.go('/rooms/${room.id}/invite'),
- ),
- if (room.showClassEditOptions && room.isSpace)
- SpaceDetailsToggleAddStudentsTile(
- controller: controller,
- ),
- if (controller.displayAddStudentOptions &&
- room.showClassEditOptions)
- ClassInvitationButtons(roomId: controller.roomId!),
- const Divider(height: 1),
- if (!room.isSpace &&
- !room.isDirectChat &&
- room.canInvite)
- ConversationBotSettings(
- key: controller.addConversationBotKey,
- room: room,
- ),
- const Divider(height: 1),
- if (!room.isPangeaClass &&
- !room.isDirectChat &&
- room.isRoomAdmin)
- AddToSpaceToggles(
- roomId: room.id,
- key: controller.addToSpaceKey,
- startOpen: false,
- mode: room.isExchange
- ? AddToClassMode.exchange
- : AddToClassMode.chat,
- ),
- const Divider(height: 1),
- if (!room.isDirectChat)
+ // if (room.canSendEvent('m.room.name'))
if (room.isRoomAdmin)
+ // #Pangea
+ ClassNameButton(
+ room: room,
+ controller: controller,
+ ),
+ if (room.canSendEvent('m.room.topic'))
+ ClassDescriptionButton(
+ room: room,
+ controller: controller,
+ ),
+ // #Pangea
+ RoomCapacityButton(
+ room: room,
+ controller: controller,
+ ),
+ // Pangea#
+ if (room.isSpace && room.isRoomAdmin)
ListTile(
title: Text(
- room.isSpace
- ? L10n.of(context)!.archiveSpace
- : L10n.of(context)!.archive,
+ L10n.of(context)!.spaceAnalytics,
style: TextStyle(
color:
Theme.of(context).colorScheme.secondary,
@@ -489,199 +278,421 @@ class ChatDetailsView extends StatelessWidget {
Theme.of(context).scaffoldBackgroundColor,
foregroundColor: iconColor,
child: const Icon(
- Icons.archive_outlined,
+ Icons.analytics_outlined,
),
),
- onTap: () async {
- OkCancelResult confirmed = OkCancelResult.ok;
- bool shouldGo = false;
- // archiveSpace has its own popup; only show if not space
- if (!room.isSpace) {
- confirmed = await showOkCancelAlertDialog(
- useRootNavigator: false,
- context: context,
- title: L10n.of(context)!.areYouSure,
- okLabel: L10n.of(context)!.ok,
- cancelLabel: L10n.of(context)!.cancel,
- message: L10n.of(context)!
- .archiveRoomDescription,
- );
- }
- if (confirmed == OkCancelResult.ok) {
- if (room.isSpace) {
- shouldGo = await room.archiveSpace(
- context,
- Matrix.of(context).client,
- );
- } else {
- final success =
- await showFutureLoadingDialog(
+ onTap: () => context.go(
+ '/rooms/analytics/${room.id}',
+ ),
+ ),
+ // commenting out language settings in spaces for now
+ // if (room.languageSettings != null && room.isRoomAdmin)
+ // LanguageSettings(
+ // roomId: controller.roomId,
+ // startOpen: false,
+ // ),
+ if (room.pangeaRoomRules != null)
+ RoomRulesEditor(
+ roomId: controller.roomId,
+ startOpen: false,
+ ),
+ // if (!room.canChangeStateEvent(EventTypes.RoomTopic))
+ // ListTile(
+ // title: Text(
+ // L10n.of(context)!.chatDescription,
+ // style: TextStyle(
+ // color: Theme.of(context).colorScheme.secondary,
+ // fontWeight: FontWeight.bold,
+ // ),
+ // ),
+ // )
+ // else
+ // Padding(
+ // padding: const EdgeInsets.all(16.0),
+ // child: TextButton.icon(
+ // onPressed: controller.setTopicAction,
+ // label: Text(L10n.of(context)!.setChatDescription),
+ // icon: const Icon(Icons.edit_outlined),
+ // style: TextButton.styleFrom(
+ // backgroundColor: Theme.of(context)
+ // .colorScheme
+ // .secondaryContainer,
+ // foregroundColor: Theme.of(context)
+ // .colorScheme
+ // .onSecondaryContainer,
+ // ),
+ // ),
+ // ),
+ // Padding(
+ // padding: const EdgeInsets.symmetric(
+ // horizontal: 16.0,
+ // ),
+ // child: SelectableLinkify(
+ // text: room.topic.isEmpty
+ // ? L10n.of(context)!.noChatDescriptionYet
+ // : room.topic,
+ // options: const LinkifyOptions(humanize: false),
+ // linkStyle: const TextStyle(
+ // color: Colors.blueAccent,
+ // decorationColor: Colors.blueAccent,
+ // ),
+ // style: TextStyle(
+ // fontSize: 14,
+ // fontStyle: room.topic.isEmpty
+ // ? FontStyle.italic
+ // : FontStyle.normal,
+ // color:
+ // Theme.of(context).textTheme.bodyMedium!.color,
+ // decorationColor:
+ // Theme.of(context).textTheme.bodyMedium!.color,
+ // ),
+ // onOpen: (url) =>
+ // UrlLauncher(context, url.url).launchUrl(),
+ // ),
+ // ),
+ // const SizedBox(height: 16),
+ // Divider(
+ // height: 1,
+ // color: Theme.of(context).dividerColor,
+ // ),
+ // ListTile(
+ // leading: CircleAvatar(
+ // backgroundColor:
+ // Theme.of(context).scaffoldBackgroundColor,
+ // foregroundColor: iconColor,
+ // child: const Icon(
+ // Icons.insert_emoticon_outlined,
+ // ),
+ // ),
+ // title:
+ // Text(L10n.of(context)!.customEmojisAndStickers),
+ // subtitle: Text(L10n.of(context)!.setCustomEmotes),
+ // onTap: controller.goToEmoteSettings,
+ // trailing: const Icon(Icons.chevron_right_outlined),
+ // ),
+ // if (!room.isDirectChat)
+ // ListTile(
+ // leading: CircleAvatar(
+ // backgroundColor:
+ // Theme.of(context).scaffoldBackgroundColor,
+ // foregroundColor: iconColor,
+ // child: const Icon(Icons.shield_outlined),
+ // ),
+ // title: Text(
+ // L10n.of(context)!.accessAndVisibility,
+ // ),
+ // subtitle: Text(
+ // L10n.of(context)!.accessAndVisibilityDescription,
+ // ),
+ // onTap: () => context
+ // .push('/rooms/${room.id}/details/access'),
+ // trailing: const Icon(Icons.chevron_right_outlined),
+ // ),
+ // if (!room.isDirectChat)
+ if (!room.isDirectChat &&
+ !room.isSpace &&
+ room.isRoomAdmin)
+ // Pangea#
+ ListTile(
+ // #Pangea
+ // title: Text(L10n.of(context)!.chatPermissions),
+ title: Text(
+ L10n.of(context)!.editChatPermissions,
+ style: TextStyle(
+ color:
+ Theme.of(context).colorScheme.secondary,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ // Pangea#
+ subtitle: Text(
+ L10n.of(context)!.whoCanPerformWhichAction,
+ ),
+ leading: CircleAvatar(
+ backgroundColor:
+ Theme.of(context).scaffoldBackgroundColor,
+ foregroundColor: iconColor,
+ child: const Icon(
+ Icons.edit_attributes_outlined,
+ ),
+ ),
+ // #Pangea
+ // trailing: const Icon(Icons.chevron_right_outlined),
+ // Pangea#
+ onTap: () => context.push(
+ '/rooms/${room.id}/details/permissions',
+ ),
+ ),
+ Divider(
+ height: 1,
+ color: Theme.of(context).dividerColor,
+ ),
+ // #Pangea
+ if (room.canInvite &&
+ !room.isDirectChat &&
+ (!room.isSpace || room.isRoomAdmin))
+ ListTile(
+ title: Text(
+ room.isSpace
+ ? L10n.of(context)!.inviteUsersFromPangea
+ : L10n.of(context)!.inviteStudentByUserName,
+ style: TextStyle(
+ color:
+ Theme.of(context).colorScheme.secondary,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ leading: CircleAvatar(
+ backgroundColor:
+ Theme.of(context).scaffoldBackgroundColor,
+ foregroundColor: Theme.of(context)
+ .textTheme
+ .bodyLarge!
+ .color,
+ child: const Icon(
+ Icons.add,
+ ),
+ ),
+ onTap: () =>
+ context.go('/rooms/${room.id}/invite'),
+ ),
+ if (room.showClassEditOptions && room.isSpace)
+ SpaceDetailsToggleAddStudentsTile(
+ controller: controller,
+ ),
+ if (controller.displayAddStudentOptions &&
+ room.showClassEditOptions)
+ ClassInvitationButtons(roomId: controller.roomId!),
+ const Divider(height: 1),
+ if (!room.isSpace &&
+ !room.isDirectChat &&
+ room.canInvite)
+ ConversationBotSettings(
+ key: controller.addConversationBotKey,
+ room: room,
+ ),
+ const Divider(height: 1),
+ if (!room.isDirectChat && room.isRoomAdmin)
+ AddToSpaceToggles(
+ roomId: room.id,
+ key: controller.addToSpaceKey,
+ startOpen: false,
+ ),
+ const Divider(height: 1),
+ if (!room.isDirectChat)
+ if (room.isRoomAdmin)
+ ListTile(
+ title: Text(
+ room.isSpace
+ ? L10n.of(context)!.archiveSpace
+ : L10n.of(context)!.archive,
+ style: TextStyle(
+ color:
+ Theme.of(context).colorScheme.secondary,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ leading: CircleAvatar(
+ backgroundColor:
+ Theme.of(context).scaffoldBackgroundColor,
+ foregroundColor: iconColor,
+ child: const Icon(
+ Icons.archive_outlined,
+ ),
+ ),
+ onTap: () async {
+ OkCancelResult confirmed = OkCancelResult.ok;
+ bool shouldGo = false;
+ // archiveSpace has its own popup; only show if not space
+ if (!room.isSpace) {
+ confirmed = await showOkCancelAlertDialog(
+ useRootNavigator: false,
context: context,
- future: () async {
- await room.archive();
- },
+ title: L10n.of(context)!.areYouSure,
+ okLabel: L10n.of(context)!.ok,
+ cancelLabel: L10n.of(context)!.cancel,
+ message: L10n.of(context)!
+ .archiveRoomDescription,
);
- shouldGo = (success.error == null);
}
- if (shouldGo) {
- context.go('/rooms');
- }
- }
- },
- ),
- ListTile(
- title: Text(
- L10n.of(context)!.leave,
- style: TextStyle(
- color: Theme.of(context).colorScheme.secondary,
- fontWeight: FontWeight.bold,
- ),
- ),
- leading: CircleAvatar(
- backgroundColor:
- Theme.of(context).scaffoldBackgroundColor,
- foregroundColor: iconColor,
- child: const Icon(
- Icons.arrow_forward,
- ),
- ),
- onTap: () async {
- OkCancelResult confirmed = OkCancelResult.ok;
- bool shouldGo = false;
- // If user is only admin, room will be archived
- final bool onlyAdmin = await room.isOnlyAdmin();
- // archiveSpace has its own popup; only show if not space
- if (!room.isSpace) {
- confirmed = await showOkCancelAlertDialog(
- useRootNavigator: false,
- context: context,
- title: L10n.of(context)!.areYouSure,
- okLabel: L10n.of(context)!.ok,
- cancelLabel: L10n.of(context)!.cancel,
- message: onlyAdmin
- ? L10n.of(context)!.onlyAdminDescription
- : L10n.of(context)!.leaveRoomDescription,
- );
- }
- if (confirmed == OkCancelResult.ok) {
- if (room.isSpace) {
- shouldGo = onlyAdmin
- ? await room.archiveSpace(
- context,
- Matrix.of(context).client,
- onlyAdmin: true,
- )
- : await room.leaveSpace(
+ if (confirmed == OkCancelResult.ok) {
+ if (room.isSpace) {
+ shouldGo = await room.archiveSpace(
context,
Matrix.of(context).client,
);
- } else {
- final success = await showFutureLoadingDialog(
- context: context,
- future: () async {
- onlyAdmin
- ? await room.archive()
- : await room.leave();
- },
- );
- shouldGo = (success.error == null);
- }
- if (shouldGo) {
- context.go('/rooms');
- }
- }
- },
- ),
- if (room.isRoomAdmin && !room.isDirectChat)
- SwitchListTile.adaptive(
- activeColor: AppConfig.activeToggleColor,
+ } else {
+ final success =
+ await showFutureLoadingDialog(
+ context: context,
+ future: () async {
+ await room.archive();
+ },
+ );
+ shouldGo = (success.error == null);
+ }
+ if (shouldGo) {
+ context.go('/rooms');
+ }
+ }
+ },
+ ),
+ ListTile(
title: Text(
- room.isSpace
- ? L10n.of(context)!.lockSpace
- : L10n.of(context)!.lockChat,
+ L10n.of(context)!.leave,
style: TextStyle(
color: Theme.of(context).colorScheme.secondary,
fontWeight: FontWeight.bold,
),
),
- secondary: CircleAvatar(
+ leading: CircleAvatar(
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
foregroundColor: iconColor,
- child: Icon(
- room.isLocked
- ? Icons.lock_outlined
- : Icons.no_encryption_outlined,
+ child: const Icon(
+ Icons.arrow_forward,
),
),
- value: room.isLocked,
- onChanged: (value) => showFutureLoadingDialog(
- context: context,
- future: () => value
- ? lockRoom(
- room,
- Matrix.of(context).client,
- )
- : unlockRoom(
- room,
- Matrix.of(context).client,
- ),
+ onTap: () async {
+ OkCancelResult confirmed = OkCancelResult.ok;
+ bool shouldGo = false;
+ // If user is only admin, room will be archived
+ final bool onlyAdmin = await room.isOnlyAdmin();
+ // archiveSpace has its own popup; only show if not space
+ if (!room.isSpace) {
+ confirmed = await showOkCancelAlertDialog(
+ useRootNavigator: false,
+ context: context,
+ title: L10n.of(context)!.areYouSure,
+ okLabel: L10n.of(context)!.ok,
+ cancelLabel: L10n.of(context)!.cancel,
+ message: onlyAdmin
+ ? L10n.of(context)!.onlyAdminDescription
+ : L10n.of(context)!.leaveRoomDescription,
+ );
+ }
+ if (confirmed == OkCancelResult.ok) {
+ if (room.isSpace) {
+ shouldGo = onlyAdmin
+ ? await room.archiveSpace(
+ context,
+ Matrix.of(context).client,
+ onlyAdmin: true,
+ )
+ : await room.leaveSpace(
+ context,
+ Matrix.of(context).client,
+ );
+ } else {
+ final success = await showFutureLoadingDialog(
+ context: context,
+ future: () async {
+ onlyAdmin
+ ? await room.archive()
+ : await room.leave();
+ },
+ );
+ shouldGo = (success.error == null);
+ }
+ if (shouldGo) {
+ context.go('/rooms');
+ }
+ }
+ },
+ ),
+ if (room.isRoomAdmin && !room.isDirectChat)
+ SwitchListTile.adaptive(
+ activeColor: AppConfig.activeToggleColor,
+ title: Text(
+ room.isSpace
+ ? L10n.of(context)!.lockSpace
+ : L10n.of(context)!.lockChat,
+ style: TextStyle(
+ color:
+ Theme.of(context).colorScheme.secondary,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ secondary: CircleAvatar(
+ backgroundColor:
+ Theme.of(context).scaffoldBackgroundColor,
+ foregroundColor: iconColor,
+ child: Icon(
+ room.isLocked
+ ? Icons.lock_outlined
+ : Icons.no_encryption_outlined,
+ ),
+ ),
+ value: room.isLocked,
+ onChanged: (value) => showFutureLoadingDialog(
+ context: context,
+ future: () => value
+ ? lockRoom(
+ room,
+ Matrix.of(context).client,
+ )
+ : unlockRoom(
+ room,
+ Matrix.of(context).client,
+ ),
+ ),
+ ),
+ const Divider(height: 1),
+ // Pangea#
+ ListTile(
+ title: Text(
+ L10n.of(context)!.countParticipants(
+ actualMembersCount.toString(),
+ ),
+ style: TextStyle(
+ color: Theme.of(context).colorScheme.secondary,
+ fontWeight: FontWeight.bold,
+ ),
),
),
- const Divider(height: 1),
- // Pangea#
- ListTile(
- title: Text(
- L10n.of(context)!.countParticipants(
- actualMembersCount.toString(),
+ // #Pangea
+ // if (!room.isDirectChat && room.canInvite)
+ // ListTile(
+ // title: Text(L10n.of(context)!.inviteContact),
+ // leading: CircleAvatar(
+ // backgroundColor: Theme.of(context)
+ // .colorScheme
+ // .primaryContainer,
+ // foregroundColor: Theme.of(context)
+ // .colorScheme
+ // .onPrimaryContainer,
+ // radius: Avatar.defaultSize / 2,
+ // child: const Icon(Icons.add_outlined),
+ // ),
+ // trailing: const Icon(Icons.chevron_right_outlined),
+ // onTap: () => context.go('/rooms/${room.id}/invite'),
+ // ),
+ // Pangea#
+ ],
+ )
+ : i < members.length + 1
+ ? ParticipantListItem(members[i - 1])
+ : ListTile(
+ title: Text(
+ L10n.of(context)!.loadCountMoreParticipants(
+ (actualMembersCount - members.length)
+ .toString(),
+ ),
),
- style: TextStyle(
- color: Theme.of(context).colorScheme.secondary,
- fontWeight: FontWeight.bold,
+ leading: CircleAvatar(
+ backgroundColor:
+ Theme.of(context).scaffoldBackgroundColor,
+ child: const Icon(
+ Icons.group_outlined,
+ color: Colors.grey,
+ ),
),
- ),
- ),
- // #Pangea
- // if (!room.isDirectChat && room.canInvite)
- // ListTile(
- // title: Text(L10n.of(context)!.inviteContact),
- // leading: CircleAvatar(
- // backgroundColor: Theme.of(context)
- // .colorScheme
- // .primaryContainer,
- // foregroundColor: Theme.of(context)
- // .colorScheme
- // .onPrimaryContainer,
- // radius: Avatar.defaultSize / 2,
- // child: const Icon(Icons.add_outlined),
- // ),
- // trailing: const Icon(Icons.chevron_right_outlined),
- // onTap: () => context.go('/rooms/${room.id}/invite'),
- // ),
- // Pangea#
- ],
- )
- : i < members.length + 1
- ? ParticipantListItem(members[i - 1])
- : ListTile(
- title: Text(
- L10n.of(context)!.loadCountMoreParticipants(
- (actualMembersCount - members.length).toString(),
+ onTap: () => context.push(
+ '/rooms/${controller.roomId!}/details/members',
),
+ trailing: const Icon(Icons.chevron_right_outlined),
),
- leading: CircleAvatar(
- backgroundColor:
- Theme.of(context).scaffoldBackgroundColor,
- child: const Icon(
- Icons.group_outlined,
- color: Colors.grey,
- ),
- ),
- onTap: () => context.push(
- '/rooms/${controller.roomId!}/details/members',
- ),
- trailing: const Icon(Icons.chevron_right_outlined),
- ),
+ ),
),
),
);
diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart
index 9d7ff2440..89f31fd68 100644
--- a/lib/pages/chat_list/chat_list.dart
+++ b/lib/pages/chat_list/chat_list.dart
@@ -2,7 +2,6 @@ import 'dart:async';
import 'dart:io';
import 'package:adaptive_dialog/adaptive_dialog.dart';
-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';
@@ -213,32 +212,12 @@ class ChatListController extends State
}
List get filteredRooms => Matrix.of(context)
- .client
- .rooms
- .where(
- getRoomFilterByActiveFilter(activeFilter),
- )
- // #Pangea
- .sorted((roomA, roomB) {
- // put rooms with unread messages at the top of the list
- if (roomA.membership == Membership.invite &&
- roomB.membership != Membership.invite) {
- return -1;
- }
- if (roomA.membership != Membership.invite &&
- roomB.membership == Membership.invite) {
- return 1;
- }
-
- final bool aUnread = roomA.notificationCount > 0 || roomA.markedUnread;
- final bool bUnread = roomB.notificationCount > 0 || roomB.markedUnread;
- if (aUnread && !bUnread) return -1;
- if (!aUnread && bUnread) return 1;
-
- return 0;
- })
- // Pangea#
- .toList();
+ .client
+ .rooms
+ .where(
+ getRoomFilterByActiveFilter(activeFilter),
+ )
+ .toList();
bool isSearchMode = false;
Future? publicRoomsResponse;
@@ -938,7 +917,7 @@ class ChatListController extends State
if (mounted) {
GoogleAnalytics.analyticsUserUpdate(client.userID);
await pangeaController.subscriptionController.initialize();
- await pangeaController.myAnalytics.addEventsListener();
+ await pangeaController.myAnalytics.initialize();
pangeaController.afterSyncAndFirstLoginInitialization(context);
await pangeaController.inviteBotToExistingSpaces();
await pangeaController.setPangeaPushRules();
diff --git a/lib/pages/chat_list/client_chooser_button.dart b/lib/pages/chat_list/client_chooser_button.dart
index 56aa568c0..4ad107f6b 100644
--- a/lib/pages/chat_list/client_chooser_button.dart
+++ b/lib/pages/chat_list/client_chooser_button.dart
@@ -1,8 +1,9 @@
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:fluffychat/pangea/constants/class_default_values.dart';
-import 'package:fluffychat/pangea/utils/class_code.dart';
+import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
import 'package:fluffychat/pangea/utils/find_conversation_partner_dialog.dart';
import 'package:fluffychat/pangea/utils/logout.dart';
+import 'package:fluffychat/pangea/utils/space_code.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -58,17 +59,19 @@ class ClientChooserButton extends StatelessWidget {
room.isSpace &&
room.ownPowerLevel >= ClassDefaultValues.powerLevelOfAdmin,
),
- value: SettingsAction.classAnalytics,
+ value: SettingsAction.spaceAnalytics,
child: Row(
children: [
const Icon(Icons.analytics_outlined),
const SizedBox(width: 18),
- Expanded(child: Text(L10n.of(context)!.classAnalytics)),
+ Expanded(child: Text(L10n.of(context)!.spaceAnalytics)),
],
),
),
PopupMenuItem(
- enabled: matrix.client.rooms.isNotEmpty,
+ enabled: matrix.client.rooms.any(
+ (room) => !room.isSpace && !room.isArchived && !room.isAnalyticsRoom,
+ ),
value: SettingsAction.myAnalytics,
child: Row(
children: [
@@ -84,7 +87,7 @@ class ClientChooserButton extends StatelessWidget {
children: [
const Icon(Icons.school),
const SizedBox(width: 18),
- Expanded(child: Text(L10n.of(context)!.createNewClass)),
+ Expanded(child: Text(L10n.of(context)!.createNewSpace)),
],
),
),
@@ -98,16 +101,6 @@ class ClientChooserButton extends StatelessWidget {
// ],
// ),
// ),
- PopupMenuItem(
- value: SettingsAction.newExchange,
- child: Row(
- children: [
- const Icon(Icons.connecting_airports),
- const SizedBox(width: 18),
- Expanded(child: Text(L10n.of(context)!.newExchange)),
- ],
- ),
- ),
if (controller.pangeaController.permissionsController.isUser18())
PopupMenuItem(
value: SettingsAction.findAConversationPartner,
@@ -397,11 +390,8 @@ class ClientChooserButton extends StatelessWidget {
case SettingsAction.newClass:
context.go('/rooms/newspace');
break;
- case SettingsAction.newExchange:
- context.go('/rooms/newspace/exchange');
- break;
case SettingsAction.joinWithClassCode:
- ClassCodeUtil.joinWithClassCodeDialog(
+ SpaceCodeUtil.joinWithSpaceCodeDialog(
context,
controller.pangeaController,
);
@@ -412,7 +402,7 @@ class ClientChooserButton extends StatelessWidget {
controller.pangeaController,
);
break;
- case SettingsAction.classAnalytics:
+ case SettingsAction.spaceAnalytics:
context.go('/rooms/analytics');
break;
case SettingsAction.myAnalytics:
@@ -507,11 +497,10 @@ enum SettingsAction {
// #Pangea
learning,
joinWithClassCode,
- classAnalytics,
+ spaceAnalytics,
myAnalytics,
findAConversationPartner,
logout,
newClass,
- newExchange
// Pangea#
}
diff --git a/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart b/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart
index ea81c842c..2b1dff187 100644
--- a/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart
+++ b/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart
@@ -35,11 +35,26 @@ class ChatPermissionsSettingsView extends StatelessWidget {
final powerLevels = Map.from(powerLevelsContent)
// #Pangea
// ..removeWhere((k, v) => v is! int);
- ..removeWhere((k, v) => v is! int || k.equals("m.call.invite"));
+ ..removeWhere(
+ (k, v) =>
+ v is! int ||
+ k.equals("m.call.invite") ||
+ k.equals("historical") ||
+ k.equals("state_default"),
+ );
// Pangea#
final eventsPowerLevels = Map.from(
powerLevelsContent.tryGetMap('events') ?? {},
- )..removeWhere((k, v) => v is! int);
+ // #Pangea
+ )..removeWhere(
+ (k, v) =>
+ v is! int ||
+ k.equals("m.space.child") ||
+ k.equals("pangea.usranalytics") ||
+ k.equals(EventTypes.RoomPowerLevels),
+ );
+ // )..removeWhere((k, v) => v is! int);
+ // Pangea#
return Column(
children: [
Column(
@@ -57,51 +72,59 @@ class ChatPermissionsSettingsView extends StatelessWidget {
),
canEdit: room.canChangePowerLevel,
),
- Divider(color: Theme.of(context).dividerColor),
- ListTile(
- title: Text(
- L10n.of(context)!.notifications,
- style: TextStyle(
- color: Theme.of(context).colorScheme.primary,
- fontWeight: FontWeight.bold,
- ),
- ),
- ),
- Builder(
- builder: (context) {
- const key = 'rooms';
- final value = powerLevelsContent
- .containsKey('notifications')
- ? powerLevelsContent
- .tryGetMap('notifications')
- ?.tryGet('rooms') ??
- 0
- : 0;
- return PermissionsListTile(
- permissionKey: key,
- permission: value,
- category: 'notifications',
- canEdit: room.canChangePowerLevel,
- onChanged: (level) => controller.editPowerLevel(
- context,
- key,
- value,
- newLevel: level,
- category: 'notifications',
+ // #Pangea
+ // Why would teacher need to stop students from seeing notifications?
+ // Divider(color: Theme.of(context).dividerColor),
+ // ListTile(
+ // title: Text(
+ // L10n.of(context)!.notifications,
+ // style: TextStyle(
+ // color: Theme.of(context).colorScheme.primary,
+ // fontWeight: FontWeight.bold,
+ // ),
+ // ),
+ // ),
+ // Builder(
+ // builder: (context) {
+ // const key = 'rooms';
+ // final value = powerLevelsContent
+ // .containsKey('notifications')
+ // ? powerLevelsContent
+ // .tryGetMap('notifications')
+ // ?.tryGet('rooms') ??
+ // 0
+ // : 0;
+ // return PermissionsListTile(
+ // permissionKey: key,
+ // permission: value,
+ // category: 'notifications',
+ // canEdit: room.canChangePowerLevel,
+ // onChanged: (level) => controller.editPowerLevel(
+ // context,
+ // key,
+ // value,
+ // newLevel: level,
+ // category: 'notifications',
+ // ),
+ // );
+ // },
+ // ),
+ // Only show if there are actually items in this category
+ if (eventsPowerLevels.isNotEmpty)
+ // Pangea#
+ Divider(color: Theme.of(context).dividerColor),
+ // #Pangea
+ if (eventsPowerLevels.isNotEmpty)
+ // Pangea#
+ ListTile(
+ title: Text(
+ L10n.of(context)!.configureChat,
+ style: TextStyle(
+ color: Theme.of(context).colorScheme.primary,
+ fontWeight: FontWeight.bold,
),
- );
- },
- ),
- Divider(color: Theme.of(context).dividerColor),
- ListTile(
- title: Text(
- L10n.of(context)!.configureChat,
- style: TextStyle(
- color: Theme.of(context).colorScheme.primary,
- fontWeight: FontWeight.bold,
),
),
- ),
for (final entry in eventsPowerLevels.entries)
PermissionsListTile(
permissionKey: entry.key,
diff --git a/lib/pages/new_group/new_group.dart b/lib/pages/new_group/new_group.dart
index b3a1703af..7e77efe56 100644
--- a/lib/pages/new_group/new_group.dart
+++ b/lib/pages/new_group/new_group.dart
@@ -174,7 +174,7 @@ class NewGroupController extends State {
void initState() {
Future.delayed(Duration.zero, () {
chatTopic.langCode =
- pangeaController.languageController.activeL2Code(roomID: null) ??
+ pangeaController.languageController.userL2?.langCode ??
pangeaController.pLanguageStore.targetOptions.first.langCode;
setState(() {});
});
diff --git a/lib/pages/new_group/new_group_view.dart b/lib/pages/new_group/new_group_view.dart
index 277a7402f..b8b43c3f1 100644
--- a/lib/pages/new_group/new_group_view.dart
+++ b/lib/pages/new_group/new_group_view.dart
@@ -1,7 +1,6 @@
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/new_group/new_group.dart';
import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/room_capacity_button.dart';
-import 'package:fluffychat/pangea/widgets/class/add_class_and_invite.dart';
import 'package:fluffychat/pangea/widgets/class/add_space_toggles.dart';
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_settings.dart';
import 'package:fluffychat/utils/localized_exception_extension.dart';
@@ -100,7 +99,6 @@ class NewGroupView extends StatelessWidget {
key: controller.addToSpaceKey,
startOpen: true,
activeSpaceId: controller.activeSpaceId,
- mode: AddToClassMode.chat,
),
// const SizedBox(height: 16),
// SwitchListTile.adaptive(
diff --git a/lib/pages/new_space/new_space.dart b/lib/pages/new_space/new_space.dart
index 59818d3b2..7a7759349 100644
--- a/lib/pages/new_space/new_space.dart
+++ b/lib/pages/new_space/new_space.dart
@@ -8,16 +8,14 @@ import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/room_capa
import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/room_rules_editor.dart';
import 'package:fluffychat/pangea/utils/bot_name.dart';
import 'package:fluffychat/pangea/utils/class_chat_power_levels.dart';
-import 'package:fluffychat/pangea/utils/class_code.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/pangea/utils/firebase_analytics.dart';
+import 'package:fluffychat/pangea/utils/space_code.dart';
import 'package:fluffychat/pangea/widgets/class/add_space_toggles.dart';
-import 'package:fluffychat/pangea/widgets/space/class_settings.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
-import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart' as sdk;
import 'package:matrix/matrix.dart';
@@ -36,8 +34,9 @@ class NewSpaceController extends State {
bool publicGroup = true;
final GlobalKey rulesEditorKey = GlobalKey();
final GlobalKey addToSpaceKey = GlobalKey();
- final GlobalKey classSettingsKey =
- GlobalKey();
+ // commenting out language settings in spaces for now
+ // final GlobalKey languageSettingsKey =
+ // GlobalKey();
final GlobalKey addCapacityKey =
GlobalKey();
@@ -68,8 +67,6 @@ class NewSpaceController extends State {
void setPublicGroup(bool b) => setState(() => publicGroup = b);
// #Pangea
- bool newClassMode = true;
-
List get initialState {
final events = [];
@@ -95,11 +92,11 @@ class NewSpaceController extends State {
} else {
debugger(when: kDebugMode);
}
- if (classSettingsKey.currentState != null) {
- events.add(classSettingsKey.currentState!.classSettings.toStateEvent);
- } else {
- debugger(when: kDebugMode && newClassMode);
- }
+ // commenting out language settings in spaces for now
+ // if (languageSettingsKey.currentState != null) {
+ // events
+ // .add(languageSettingsKey.currentState!.languageSettings.toStateEvent);
+ // }
return events;
}
@@ -117,33 +114,30 @@ class NewSpaceController extends State {
debugger(when: kDebugMode);
return;
}
- if (classSettingsKey.currentState != null &&
- classSettingsKey.currentState!.sameLanguages) {
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(
- content: Text(L10n.of(context)!.noIdenticalLanguages),
- ),
- );
- return;
- }
- if (newClassMode) {
- final int? languageLevel =
- classSettingsKey.currentState!.classSettings.languageLevel;
- if (languageLevel == null) {
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(content: Text(L10n.of(context)!.languageLevelWarning)),
- );
- return;
- }
- }
+ // commenting out language settings in spaces for now
+ // if (languageSettingsKey.currentState != null &&
+ // languageSettingsKey.currentState!.sameLanguages) {
+ // ScaffoldMessenger.of(context).showSnackBar(
+ // SnackBar(
+ // content: Text(L10n.of(context)!.noIdenticalLanguages),
+ // ),
+ // );
+ // return;
+ // }
+ // final int? languageLevel =
+ // languageSettingsKey.currentState!.languageSettings.languageLevel;
+ // if (languageLevel == null) {
+ // ScaffoldMessenger.of(context).showSnackBar(
+ // SnackBar(content: Text(L10n.of(context)!.languageLevelWarning)),
+ // );
+ // return;
+ // }
// Pangea#
if (nameController.text.isEmpty) {
setState(() {
// #Pangea
// nameError = L10n.of(context)!.pleaseChoose;
- final String warning = newClassMode
- ? L10n.of(context)!.emptyClassNameWarning
- : L10n.of(context)!.emptyExchangeNameWarning;
+ final String warning = L10n.of(context)!.emptySpaceNameWarning;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(warning)),
);
@@ -168,7 +162,7 @@ class NewSpaceController extends State {
// roomAliasName: publicGroup
// ? nameController.text.trim().toLowerCase().replaceAll(' ', '_')
// : null,
- roomAliasName: ClassCodeUtil.generateClassCode(),
+ roomAliasName: SpaceCodeUtil.generateSpaceCode(),
// Pangea#
name: nameController.text.trim(),
topic: topicController.text.isEmpty ? null : topicController.text,
@@ -224,15 +218,10 @@ class NewSpaceController extends State {
}
room.setSpaceChild(newChatRoomId, suggested: true);
- newClassMode
- ? GoogleAnalytics.addParent(
- newChatRoomId,
- room.classCode,
- )
- : GoogleAnalytics.addChatToExchange(
- newChatRoomId,
- room.classCode,
- );
+ GoogleAnalytics.addParent(
+ newChatRoomId,
+ room.classCode,
+ );
GoogleAnalytics.createClass(room.name, room.classCode);
try {
@@ -267,8 +256,6 @@ class NewSpaceController extends State {
// #Pangea
// Widget build(BuildContext context) => NewSpaceView(this);
Widget build(BuildContext context) {
- newClassMode =
- GoRouterState.of(context).pathParameters['newexchange'] != 'exchange';
return NewSpaceView(this);
}
// Pangea#
diff --git a/lib/pages/new_space/new_space_view.dart b/lib/pages/new_space/new_space_view.dart
index 09abb7066..f8c7baf4e 100644
--- a/lib/pages/new_space/new_space_view.dart
+++ b/lib/pages/new_space/new_space_view.dart
@@ -1,17 +1,14 @@
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart';
-import 'package:fluffychat/pangea/models/class_model.dart';
+import 'package:fluffychat/pangea/models/space_model.dart';
import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/room_capacity_button.dart';
import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/room_rules_editor.dart';
-import 'package:fluffychat/pangea/widgets/class/add_class_and_invite.dart';
import 'package:fluffychat/pangea/widgets/class/add_space_toggles.dart';
-import 'package:fluffychat/pangea/widgets/space/class_settings.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
-import 'package:go_router/go_router.dart';
import 'new_space.dart';
@@ -32,29 +29,8 @@ class NewSpaceView extends StatelessWidget {
appBar: AppBar(
// #Pangea
centerTitle: true,
- title: Text(
- controller.newClassMode
- ? L10n.of(context)!.createNewClass
- : L10n.of(context)!.newExchange,
- ),
- actions: [
- IconButton(
- icon: const Icon(Icons.class_outlined),
- selectedIcon: const Icon(Icons.class_),
- color: controller.newClassMode ? activeColor : null,
- isSelected: controller.newClassMode,
- onPressed: () => context.go('/rooms/newspace'),
- ),
- IconButton(
- icon: const Icon(Icons.connecting_airports),
- selectedIcon: const Icon(Icons.connecting_airports),
- color: !controller.newClassMode ? activeColor : null,
- isSelected: !controller.newClassMode,
- onPressed: () => context.go('/rooms/newspace/exchange'),
- ),
- ],
- // title: Text(L10n.of(context)!.createNewSpace),
// Pangea#
+ title: Text(L10n.of(context)!.createNewSpace),
),
body: MaxWidthBody(
child: Column(
@@ -135,42 +111,48 @@ class NewSpaceView extends StatelessWidget {
RoomCapacityButton(
key: controller.addCapacityKey,
),
- if (controller.newClassMode)
- ClassSettings(
- key: controller.classSettingsKey,
- roomId: null,
- startOpen: true,
- initialSettings:
- Matrix.of(context).client.lastUpdatedClassSettings,
- ),
- if (!controller.newClassMode)
- AddToSpaceToggles(
- key: controller.addToSpaceKey,
- startOpen: true,
- mode: !controller.newClassMode
- ? AddToClassMode.exchange
- : AddToClassMode.chat,
- ),
- FutureBuilder(
- future: Matrix.of(context).client.lastUpdatedRoomRules,
- builder: (context, snapshot) {
- if (snapshot.connectionState == ConnectionState.done) {
- return RoomRulesEditor(
- key: controller.rulesEditorKey,
- roomId: null,
- startOpen: false,
- initialRules: snapshot.data,
- );
- } else {
- return const Padding(
- padding: EdgeInsets.all(16.0),
- child: Center(
- child: CircularProgressIndicator.adaptive(strokeWidth: 2),
- ),
- );
- }
- },
+ // commenting out language settings in spaces for now
+ // LanguageSettings(
+ // key: controller.languageSettingsKey,
+ // roomId: null,
+ // startOpen: true,
+ // initialSettings:
+ // Matrix.of(context).client.lastUpdatedLanguageSettings,
+ // ),
+ AddToSpaceToggles(
+ key: controller.addToSpaceKey,
+ startOpen: true,
+ spaceMode: true,
),
+ if (controller.rulesEditorKey.currentState != null)
+ RoomRulesEditor(
+ key: controller.rulesEditorKey,
+ roomId: null,
+ startOpen: false,
+ initialRules: controller.rulesEditorKey.currentState!.rules,
+ ),
+ if (controller.rulesEditorKey.currentState == null)
+ FutureBuilder(
+ future: Matrix.of(context).client.lastUpdatedRoomRules,
+ builder: (context, snapshot) {
+ if (snapshot.connectionState == ConnectionState.done) {
+ return RoomRulesEditor(
+ key: controller.rulesEditorKey,
+ roomId: null,
+ startOpen: false,
+ initialRules: snapshot.data,
+ );
+ } else {
+ return const Padding(
+ padding: EdgeInsets.all(16.0),
+ child: Center(
+ child:
+ CircularProgressIndicator.adaptive(strokeWidth: 2),
+ ),
+ );
+ }
+ },
+ ),
// SwitchListTile.adaptive(
// title: Text(L10n.of(context)!.spaceIsPublic),
// value: controller.publicGroup,
@@ -194,12 +176,7 @@ class NewSpaceView extends StatelessWidget {
children: [
Expanded(
child: Text(
- // #Pangea
- // L10n.of(context)!.createNewSpace,
- controller.newClassMode
- ? L10n.of(context)!.createClass
- : L10n.of(context)!.createExchange,
- // Pangea#
+ L10n.of(context)!.createNewSpace,
),
),
Icon(Icons.adaptive.arrow_forward_outlined),
diff --git a/lib/pages/settings/settings_view.dart b/lib/pages/settings/settings_view.dart
index e537f01af..06e4855ec 100644
--- a/lib/pages/settings/settings_view.dart
+++ b/lib/pages/settings/settings_view.dart
@@ -209,6 +209,12 @@ class SettingsView extends StatelessWidget {
Icons.chevron_right_outlined,
),
),
+ ListTile(
+ leading: const Icon(Icons.psychology_outlined),
+ title: Text(L10n.of(context)!.learningSettings),
+ onTap: () => context.go('/rooms/settings/learning'),
+ trailing: const Icon(Icons.chevron_right_outlined),
+ ),
// Pangea#
ListTile(
leading: const Icon(Icons.shield_outlined),
diff --git a/lib/pangea/choreographer/controllers/choreographer.dart b/lib/pangea/choreographer/controllers/choreographer.dart
index 3d84b0e1d..66e11808b 100644
--- a/lib/pangea/choreographer/controllers/choreographer.dart
+++ b/lib/pangea/choreographer/controllers/choreographer.dart
@@ -5,17 +5,16 @@ import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/pangea/choreographer/controllers/alternative_translator.dart';
import 'package:fluffychat/pangea/choreographer/controllers/igc_controller.dart';
import 'package:fluffychat/pangea/choreographer/controllers/message_options.dart';
-import 'package:fluffychat/pangea/constants/language_keys.dart';
-import 'package:fluffychat/pangea/constants/pangea_event_types.dart';
+import 'package:fluffychat/pangea/constants/language_constants.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/controllers/subscription_controller.dart';
+import 'package:fluffychat/pangea/enum/assistance_state_enum.dart';
import 'package:fluffychat/pangea/enum/edit_type.dart';
-import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
-import 'package:fluffychat/pangea/models/class_model.dart';
import 'package:fluffychat/pangea/models/it_step.dart';
-import 'package:fluffychat/pangea/models/language_detection_model.dart';
import 'package:fluffychat/pangea/models/representation_content_model.dart';
+import 'package:fluffychat/pangea/models/space_model.dart';
import 'package:fluffychat/pangea/models/tokens_event_content_model.dart';
+import 'package:fluffychat/pangea/models/user_model.dart';
import 'package:fluffychat/pangea/utils/any_state_holder.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/pangea/utils/overlay.dart';
@@ -25,7 +24,6 @@ import 'package:flutter/material.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import '../../../widgets/matrix.dart';
-import '../../enum/use_type.dart';
import '../../models/choreo_record.dart';
import '../../models/language_model.dart';
import '../../models/pangea_match_model.dart';
@@ -95,63 +93,59 @@ class Choreographer {
}
Future _sendWithIGC(BuildContext context) async {
- if (igc.canSendMessage) {
- final PangeaRepresentation? originalWritten =
- choreoRecord.includedIT && itController.sourceText != null
- ? PangeaRepresentation(
- langCode: l1LangCode ?? LanguageKeys.unknownLanguage,
- text: itController.sourceText!,
- originalWritten: true,
- originalSent: false,
- )
- : null;
-
- final PangeaRepresentation originalSent = PangeaRepresentation(
- langCode: langCodeOfCurrentText ?? LanguageKeys.unknownLanguage,
- text: currentText,
- originalSent: true,
- originalWritten: originalWritten == null,
- );
- final ChoreoRecord? applicableChoreo =
- isITandIGCEnabled && igc.igcTextData != null ? choreoRecord : null;
-
- // if the message has not been processed to determine its language
- // then run it through the language detection endpoint. If the detection
- // confidence is high enough, use that language code as the message's language
- // to save that pangea representation
- if (applicableChoreo == null) {
- final resp = await pangeaController.languageDetection.detectLanguage(
- currentText,
- pangeaController.languageController.userL2?.langCode,
- pangeaController.languageController.userL1?.langCode,
- );
- final LanguageDetection? bestDetection = resp.bestDetection();
- if (bestDetection != null) {
- originalSent.langCode = bestDetection.langCode;
- }
- }
-
- final UseType useType = useTypeCalculator(applicableChoreo);
- debugPrint("use type in choreographer $useType");
-
- chatController.send(
- // PTODO - turn this back on in conjunction with saving tokens
- // we need to save those tokens as well, in order for exchanges to work
- // properly. in an exchange, the other user will want
- // originalWritten: originalWritten,
- originalSent: originalSent,
- tokensSent: igc.igcTextData?.tokens != null
- ? PangeaMessageTokens(tokens: igc.igcTextData!.tokens)
- : null,
- //TODO - save originalwritten tokens
- choreo: applicableChoreo,
- useType: useType,
- );
-
- clear();
- } else {
+ if (!igc.canSendMessage) {
igc.showFirstMatch(context);
+ return;
}
+
+ final PangeaRepresentation? originalWritten =
+ choreoRecord.includedIT && itController.sourceText != null
+ ? PangeaRepresentation(
+ langCode: l1LangCode ?? LanguageKeys.unknownLanguage,
+ text: itController.sourceText!,
+ originalWritten: true,
+ originalSent: false,
+ )
+ : null;
+
+ // TODO - why does both it and igc need to be enabled for choreo to be applicable?
+ // final ChoreoRecord? applicableChoreo =
+ // isITandIGCEnabled && igc.igcTextData != null ? choreoRecord : null;
+
+ // if tokens or language detection are not available, we should get them
+ // notes
+ // 1) we probably need to move this to after we clear the input field
+ // or the user could experience some lag here.
+ // 2) that this call is being made after we've determined if we have an applicable choreo in order to
+ // say whether correction was run on the message. we may eventually want
+ // to edit the useType after
+ if (igc.igcTextData?.tokens == null ||
+ igc.igcTextData?.detectedLanguage == null) {
+ await igc.getIGCTextData(onlyTokensAndLanguageDetection: true);
+ }
+
+ final PangeaRepresentation originalSent = PangeaRepresentation(
+ langCode:
+ igc.igcTextData?.detectedLanguage ?? LanguageKeys.unknownLanguage,
+ text: currentText,
+ originalSent: true,
+ originalWritten: originalWritten == null,
+ );
+
+ final PangeaMessageTokens? tokensSent = igc.igcTextData?.tokens != null
+ ? PangeaMessageTokens(tokens: igc.igcTextData!.tokens)
+ : null;
+
+ chatController.send(
+ // originalWritten: originalWritten,
+ originalSent: originalSent,
+ tokensSent: tokensSent,
+ //TODO - save originalwritten tokens
+ // choreo: applicableChoreo,
+ choreo: choreoRecord,
+ );
+
+ clear();
}
_resetDebounceTimer() {
@@ -167,7 +161,7 @@ class Choreographer {
}
choreoMode = ChoreoMode.it;
itController.initializeIT(
- ITStartData(_textController.text, igc.detectedLangCode),
+ ITStartData(_textController.text, igc.igcTextData?.detectedLanguage),
);
itMatch.status = PangeaMatchStatus.accepted;
@@ -180,6 +174,7 @@ class Choreographer {
_textController.setSystemText("", EditType.itStart);
}
+ /// Handles any changes to the text input
_onChangeListener() {
if (_noChange) {
return;
@@ -188,21 +183,26 @@ class Choreographer {
if ([
EditType.igc,
].contains(_textController.editType)) {
+ // this may be unnecessary now that tokens are not used
+ // to allow click of words in the input field and we're getting this at the end
+ // TODO - turn it off and tested that this is fine
igc.justGetTokensAndAddThemToIGCTextData();
+
+ // we set editType to keyboard here because that is the default for it
+ // and we want to make sure that the next change is treated as a keyboard change
+ // unless the system explicity sets it to something else. this
textController.editType = EditType.keyboard;
return;
}
+ // not sure if this is necessary now
MatrixState.pAnyState.closeOverlay();
if (errorService.isError) {
return;
}
- // if (igc.igcTextData != null) {
igc.clear();
- // setState();
- // }
_resetDebounceTimer();
@@ -212,7 +212,9 @@ class Choreographer {
() => getLanguageHelp(),
);
} else {
- getLanguageHelp(ChoreoMode.it == choreoMode);
+ getLanguageHelp(
+ onlyTokensAndLanguageDetection: ChoreoMode.it == choreoMode,
+ );
}
//Note: we don't set the keyboard type on each keyboard stroke so this is how we default to
@@ -221,10 +223,14 @@ class Choreographer {
textController.editType = EditType.keyboard;
}
- Future getLanguageHelp([
- bool tokensOnly = false,
+ /// Fetches the language help for the current text, including grammar correction, language detection,
+ /// tokens, and translations. Includes logic to exit the flow if the user is not subscribed, if the tools are not enabled, or
+ /// or if autoIGC is not enabled and the user has not manually requested it.
+ /// [onlyTokensAndLanguageDetection] will
+ Future getLanguageHelp({
+ bool onlyTokensAndLanguageDetection = false,
bool manual = false,
- ]) async {
+ }) async {
try {
if (errorService.isError) return;
final CanSendStatus canSendStatus =
@@ -239,13 +245,15 @@ class Choreographer {
startLoading();
if (choreoMode == ChoreoMode.it &&
itController.isTranslationDone &&
- !tokensOnly) {
+ !onlyTokensAndLanguageDetection) {
// debugger(when: kDebugMode);
}
await (choreoMode == ChoreoMode.it && !itController.isTranslationDone
? itController.getTranslationData(_useCustomInput)
- : igc.getIGCTextData(tokensOnly: tokensOnly));
+ : igc.getIGCTextData(
+ onlyTokensAndLanguageDetection: onlyTokensAndLanguageDetection,
+ ));
} catch (err, stack) {
ErrorHandler.logError(e: err, s: stack);
} finally {
@@ -443,25 +451,15 @@ class Choreographer {
}
LanguageModel? get l2Lang {
- return pangeaController.languageController.activeL2Model(
- roomID: roomId,
- );
+ return pangeaController.languageController.activeL2Model();
}
String? get l2LangCode => l2Lang?.langCode;
LanguageModel? get l1Lang =>
- pangeaController.languageController.activeL1Model(
- roomID: roomId,
- );
- String? get l1LangCode => l1Lang?.langCode;
+ pangeaController.languageController.activeL1Model();
- String? get classId => roomId != null
- ? pangeaController.matrixState.client
- .getRoomById(roomId)
- ?.firstParentWithState(PangeaEventTypes.classSettings)
- ?.id
- : null;
+ String? get l1LangCode => l1Lang?.langCode;
String? get userId => pangeaController.userController.userId;
@@ -492,14 +490,6 @@ class Choreographer {
bool get editTypeIsKeyboard => EditType.keyboard == _textController.editType;
- String? get langCodeOfCurrentText {
- if (igc.detectedLangCode != null) return igc.detectedLangCode!;
-
- if (itController.isOpen) return l2LangCode!;
-
- return null;
- }
-
setState() {
if (!stateListener.isClosed) {
stateListener.add(0);
@@ -533,6 +523,12 @@ class Choreographer {
chatController.room,
);
+ bool get itAutoPlayEnabled =>
+ pangeaController.pStoreService.read(
+ MatrixProfile.itAutoPlay.title,
+ ) ??
+ false;
+
bool get definitionsEnabled =>
pangeaController.permissionsController.isToolEnabled(
ToolSetting.definitions,
@@ -582,13 +578,3 @@ class Choreographer {
return AssistanceState.complete;
}
}
-
-// assistance state is, user has not typed a message, user has typed a message and IGC has not run,
-// IGC is running, IGC has run and there are remaining steps (either IT or IGC), or all steps are done
-enum AssistanceState {
- noMessage,
- notFetched,
- fetching,
- fetched,
- complete,
-}
diff --git a/lib/pangea/choreographer/controllers/igc_controller.dart b/lib/pangea/choreographer/controllers/igc_controller.dart
index b882e4087..533b36c83 100644
--- a/lib/pangea/choreographer/controllers/igc_controller.dart
+++ b/lib/pangea/choreographer/controllers/igc_controller.dart
@@ -3,18 +3,17 @@ import 'dart:developer';
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart';
import 'package:fluffychat/pangea/choreographer/controllers/error_service.dart';
-import 'package:fluffychat/pangea/controllers/span_data_controller.dart';
+import 'package:fluffychat/pangea/choreographer/controllers/span_data_controller.dart';
import 'package:fluffychat/pangea/models/igc_text_data_model.dart';
import 'package:fluffychat/pangea/models/pangea_match_model.dart';
import 'package:fluffychat/pangea/repo/igc_repo.dart';
+import 'package:fluffychat/pangea/repo/tokens_repo.dart';
import 'package:fluffychat/pangea/widgets/igc/span_card.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
-import '../../models/language_detection_model.dart';
import '../../models/span_card_model.dart';
-import '../../repo/tokens_repo.dart';
import '../../utils/error_handler.dart';
import '../../utils/overlay.dart';
@@ -29,59 +28,42 @@ class IgcController {
spanDataController = SpanDataController(choreographer);
}
- Future getIGCTextData({required bool tokensOnly}) async {
+ Future getIGCTextData({
+ required bool onlyTokensAndLanguageDetection,
+ }) async {
try {
if (choreographer.currentText.isEmpty) return clear();
- // the error spans are going to be reloaded, so clear the cache
- spanDataController.clearCache();
debugPrint('getIGCTextData called with ${choreographer.currentText}');
- debugPrint('getIGCTextData called with tokensOnly = $tokensOnly');
+ debugPrint(
+ 'getIGCTextData called with tokensOnly = $onlyTokensAndLanguageDetection',
+ );
final IGCRequestBody reqBody = IGCRequestBody(
fullText: choreographer.currentText,
userL1: choreographer.l1LangCode!,
userL2: choreographer.l2LangCode!,
- enableIGC: choreographer.igcEnabled && !tokensOnly,
- enableIT: choreographer.itEnabled && !tokensOnly,
- tokensOnly: tokensOnly,
+ enableIGC: choreographer.igcEnabled && !onlyTokensAndLanguageDetection,
+ enableIT: choreographer.itEnabled && !onlyTokensAndLanguageDetection,
);
final IGCTextData igcTextDataResponse = await IgcRepo.getIGC(
await choreographer.accessToken,
igcRequest: reqBody,
);
- // temp fix
- igcTextDataResponse.originalInput = reqBody.fullText;
- //this will happen when the user changes the input while igc is fetching results
+ // this will happen when the user changes the input while igc is fetching results
if (igcTextDataResponse.originalInput != choreographer.currentText) {
- // final current = choreographer.currentText;
- // final igctext = igcTextDataResponse.originalInput;
- // Sentry.addBreadcrumb(
- // Breadcrumb(message: "igc return input does not match current text"),
- // );
- // debugger(when: kDebugMode);
return;
}
- //TO-DO: in api call, specify turning off IT and/or grammar checking
- if (!choreographer.igcEnabled) {
- igcTextDataResponse.matches = igcTextDataResponse.matches
- .where((match) => !match.isGrammarMatch)
- .toList();
- }
- if (!choreographer.itEnabled) {
- igcTextDataResponse.matches = igcTextDataResponse.matches
- .where((match) => !match.isOutOfTargetMatch)
- .toList();
- }
- if (!choreographer.itEnabled && !choreographer.igcEnabled) {
- igcTextDataResponse.matches = [];
- }
-
igcTextData = igcTextDataResponse;
+ // TODO - for each new match,
+ // check if existing igcTextData has one and only one match with the same error text and correction
+ // if so, keep the original match and discard the new one
+ // if not, add the new match to the existing igcTextData
+
// After fetching igc data, pre-call span details for each match optimistically.
// This will make the loading of span details faster for the user
if (igcTextData?.matches.isNotEmpty ?? false) {
@@ -170,6 +152,13 @@ class IgcController {
const int firstMatchIndex = 0;
final PangeaMatch match = igcTextData!.matches[firstMatchIndex];
+ if (match.isITStart &&
+ choreographer.itAutoPlayEnabled &&
+ igcTextData != null) {
+ choreographer.onITStart(igcTextData!.matches[firstMatchIndex]);
+ return;
+ }
+
OverlayUtil.showPositionedCard(
context: context,
cardToShow: SpanCard(
@@ -189,7 +178,7 @@ class IgcController {
),
roomId: choreographer.roomId,
),
- cardSize: match.isITStart ? const Size(350, 220) : const Size(350, 400),
+ cardSize: match.isITStart ? const Size(350, 260) : const Size(350, 400),
transformTargetId: choreographer.inputTransformTargetKey,
);
}
@@ -206,14 +195,6 @@ class IgcController {
return true;
}
- String? get detectedLangCode {
- if (!hasRelevantIGCTextData) return null;
-
- final LanguageDetection first = igcTextData!.detections.first;
-
- return first.langCode;
- }
-
clear() {
igcTextData = null;
spanDataController.clearCache();
diff --git a/lib/pangea/choreographer/controllers/it_controller.dart b/lib/pangea/choreographer/controllers/it_controller.dart
index 61c8c2312..3187d4a6a 100644
--- a/lib/pangea/choreographer/controllers/it_controller.dart
+++ b/lib/pangea/choreographer/controllers/it_controller.dart
@@ -3,7 +3,6 @@ import 'dart:developer';
import 'package:fluffychat/pangea/choreographer/controllers/error_service.dart';
import 'package:fluffychat/pangea/constants/choreo_constants.dart';
-import 'package:fluffychat/pangea/repo/full_text_translation_repo.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@@ -21,6 +20,7 @@ class ITController {
Choreographer choreographer;
bool _isOpen = false;
+ bool _willOpen = false;
bool _isEditingSourceText = false;
bool showChoiceFeedback = false;
@@ -36,6 +36,7 @@ class ITController {
void clear() {
_isOpen = false;
+ _willOpen = false;
showChoiceFeedback = false;
_isEditingSourceText = false;
@@ -54,6 +55,7 @@ class ITController {
}
Future initializeIT(ITStartData itStartData) async {
+ _willOpen = true;
Future.delayed(const Duration(microseconds: 100), () {
_isOpen = true;
});
@@ -61,17 +63,14 @@ class ITController {
}
void closeIT() {
- //if they close it before choosing anything, just put their text back
+ //if they close it before completing, just put their text back
//PTODO - explore using last itStep
- if (choreographer.currentText.isEmpty) {
- choreographer.textController.text = sourceText ?? "";
- }
+ choreographer.textController.text = sourceText ?? "";
clear();
}
/// if IGC isn't positive that text is full L1 then translate to L1
Future _setSourceText() async {
- // try {
if (_itStartData == null || _itStartData!.text.isEmpty) {
Sentry.addBreadcrumb(
Breadcrumb(
@@ -85,32 +84,23 @@ class ITController {
throw Exception("null _itStartData or empty text in _setSourceText");
}
debugPrint("_setSourceText with detectedLang ${_itStartData!.langCode}");
- if (_itStartData!.langCode == choreographer.l1LangCode) {
- sourceText = _itStartData!.text;
- return;
- }
-
- final FullTextTranslationResponseModel res =
- await FullTextTranslationRepo.translate(
- accessToken: await choreographer.accessToken,
- request: FullTextTranslationRequestModel(
- text: _itStartData!.text,
- tgtLang: choreographer.l1LangCode!,
- srcLang: choreographer.l2LangCode,
- userL1: choreographer.l1LangCode!,
- userL2: choreographer.l2LangCode!,
- ),
- );
- sourceText = res.bestTranslation;
- // } catch (err, stack) {
- // debugger(when: kDebugMode);
- // if (_itStartData?.text.isNotEmpty ?? false) {
- // ErrorHandler.logError(e: err, s: stack);
- // sourceText = _itStartData!.text;
- // } else {
- // rethrow;
- // }
+ // if (_itStartData!.langCode == choreographer.l1LangCode) {
+ sourceText = _itStartData!.text;
+ return;
// }
+
+ // final FullTextTranslationResponseModel res =
+ // await FullTextTranslationRepo.translate(
+ // accessToken: await choreographer.accessToken,
+ // request: FullTextTranslationRequestModel(
+ // text: _itStartData!.text,
+ // tgtLang: choreographer.l1LangCode!,
+ // srcLang: _itStartData!.langCode,
+ // userL1: choreographer.l1LangCode!,
+ // userL2: choreographer.l2LangCode!,
+ // ),
+ // );
+ // sourceText = res.bestTranslation;
}
// used 1) at very beginning (with custom input = null)
@@ -166,7 +156,7 @@ class ITController {
if (isTranslationDone) {
choreographer.altTranslator.setTranslationFeedback();
- choreographer.getLanguageHelp(true);
+ choreographer.getLanguageHelp(onlyTokensAndLanguageDetection: true);
} else {
getNextTranslationData();
}
@@ -217,31 +207,17 @@ class ITController {
Future onEditSourceTextSubmit(String newSourceText) async {
try {
- sourceText = newSourceText;
+ _isOpen = true;
_isEditingSourceText = false;
- final String currentText = choreographer.currentText;
+ _itStartData = ITStartData(newSourceText, choreographer.l1LangCode);
+ completedITSteps = [];
+ currentITStep = null;
+ nextITStep = null;
+ goldRouteTracker = GoldRouteTracker.defaultTracker;
+ payLoadIds = [];
- choreographer.startLoading();
-
- final List responses = await Future.wait([
- _customInputTranslation(""),
- _customInputTranslation(choreographer.currentText),
- ]);
- if (responses[0].goldContinuances != null &&
- responses[0].goldContinuances!.isNotEmpty) {
- goldRouteTracker = GoldRouteTracker(
- responses[0].goldContinuances!,
- sourceText!,
- );
- }
- currentITStep = CurrentITStep(
- sourceText: sourceText!,
- currentText: currentText,
- responseModel: responses[1],
- storedGoldContinuances: goldRouteTracker.continuances,
- );
-
- _addPayloadId(responses[1]);
+ _setSourceText();
+ getTranslationData(false);
} catch (err, stack) {
debugger(when: kDebugMode);
if (err is! http.Response) {
@@ -252,6 +228,7 @@ class ITController {
);
} finally {
choreographer.stopLoading();
+ choreographer.textController.text = "";
}
}
@@ -265,7 +242,6 @@ class ITController {
targetLangCode: targetLangCode,
userId: choreographer.userId!,
roomId: choreographer.roomId!,
- classId: choreographer.classId,
goldTranslation: goldRouteTracker.fullTranslation,
goldContinuances: goldRouteTracker.continuances,
),
@@ -283,7 +259,6 @@ class ITController {
translationId: translationId,
targetLangCode: targetLangCode,
sourceLangCode: sourceLangCode,
- classId: choreographer.classId,
),
);
@@ -336,15 +311,14 @@ class ITController {
bool get isOpen => _isOpen;
+ bool get willOpen => _willOpen;
+
String get targetLangCode => choreographer.l2LangCode!;
String get sourceLangCode => choreographer.l1LangCode!;
bool get isLoading => choreographer.isFetching;
- bool get correctChoicesSelected =>
- completedITSteps.every((ITStep step) => step.isCorrect);
-
String latestChoiceFeedback(BuildContext context) =>
completedITSteps.isNotEmpty
? completedITSteps.last.choiceFeedback(context)
diff --git a/lib/pangea/choreographer/controllers/message_options.dart b/lib/pangea/choreographer/controllers/message_options.dart
index 0e5e68461..96df5d921 100644
--- a/lib/pangea/choreographer/controllers/message_options.dart
+++ b/lib/pangea/choreographer/controllers/message_options.dart
@@ -1,7 +1,7 @@
import 'package:flutter/cupertino.dart';
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart';
-import 'package:fluffychat/pangea/constants/language_keys.dart';
+import 'package:fluffychat/pangea/constants/language_constants.dart';
import 'package:fluffychat/pangea/models/language_model.dart';
import 'package:fluffychat/pangea/utils/firebase_analytics.dart';
diff --git a/lib/pangea/controllers/span_data_controller.dart b/lib/pangea/choreographer/controllers/span_data_controller.dart
similarity index 100%
rename from lib/pangea/controllers/span_data_controller.dart
rename to lib/pangea/choreographer/controllers/span_data_controller.dart
diff --git a/lib/pangea/choreographer/widgets/choice_array.dart b/lib/pangea/choreographer/widgets/choice_array.dart
index 54fd601b9..adf30dcb1 100644
--- a/lib/pangea/choreographer/widgets/choice_array.dart
+++ b/lib/pangea/choreographer/widgets/choice_array.dart
@@ -3,14 +3,12 @@ import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
-
import 'package:flutter_gen/gen_l10n/l10n.dart';
-import 'package:matrix/matrix.dart';
import '../../utils/bot_style.dart';
import 'it_shimmer.dart';
-class ChoicesArray extends StatelessWidget {
+class ChoicesArray extends StatefulWidget {
final bool isLoading;
final List? choices;
final void Function(int) onPressed;
@@ -18,6 +16,10 @@ class ChoicesArray extends StatelessWidget {
final int? selectedChoiceIndex;
final String originalSpan;
final String Function(int) uniqueKeyForLayerLink;
+
+ /// some uses of this widget want to disable the choices
+ final bool isActive;
+
const ChoicesArray({
super.key,
required this.isLoading,
@@ -26,26 +28,55 @@ class ChoicesArray extends StatelessWidget {
required this.originalSpan,
required this.uniqueKeyForLayerLink,
required this.selectedChoiceIndex,
+ this.isActive = true,
this.onLongPress,
});
+ @override
+ ChoicesArrayState createState() => ChoicesArrayState();
+}
+
+class ChoicesArrayState extends State {
+ bool interactionDisabled = false;
+
+ void disableInteraction() {
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ setState(() {
+ interactionDisabled = true;
+ });
+ });
+ }
+
+ void enableInteractions() {
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ setState(() {
+ interactionDisabled = false;
+ });
+ });
+ }
+
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
- return isLoading && (choices == null || choices!.length <= 1)
- ? ItShimmer(originalSpan: originalSpan)
+ return widget.isLoading &&
+ (widget.choices == null || widget.choices!.length <= 1)
+ ? ItShimmer(originalSpan: widget.originalSpan)
: Wrap(
alignment: WrapAlignment.center,
- children: choices
+ children: widget.choices
?.asMap()
.entries
.map(
(entry) => ChoiceItem(
theme: theme,
- onLongPress: onLongPress,
- onPressed: onPressed,
+ onLongPress:
+ widget.isActive ? widget.onLongPress : null,
+ onPressed: widget.isActive ? widget.onPressed : (_) {},
entry: entry,
- isSelected: selectedChoiceIndex == entry.key,
+ interactionDisabled: interactionDisabled,
+ enableInteraction: enableInteractions,
+ disableInteraction: disableInteraction,
+ isSelected: widget.selectedChoiceIndex == entry.key,
),
)
.toList() ??
@@ -74,6 +105,9 @@ class ChoiceItem extends StatelessWidget {
required this.onPressed,
required this.entry,
required this.isSelected,
+ required this.interactionDisabled,
+ required this.enableInteraction,
+ required this.disableInteraction,
});
final MapEntry entry;
@@ -81,6 +115,9 @@ class ChoiceItem extends StatelessWidget {
final void Function(int p1)? onLongPress;
final void Function(int p1) onPressed;
final bool isSelected;
+ final bool interactionDisabled;
+ final VoidCallback enableInteraction;
+ final VoidCallback disableInteraction;
@override
Widget build(BuildContext context) {
@@ -94,6 +131,8 @@ class ChoiceItem extends StatelessWidget {
key: ValueKey(entry.value.text),
selected: entry.value.color != null,
isGold: entry.value.isGold,
+ enableInteraction: enableInteraction,
+ disableInteraction: disableInteraction,
child: Container(
margin: const EdgeInsets.all(2),
padding: EdgeInsets.zero,
@@ -109,27 +148,29 @@ class ChoiceItem extends StatelessWidget {
: null,
child: TextButton(
style: ButtonStyle(
- padding: MaterialStateProperty.all(
+ padding: WidgetStateProperty.all(
const EdgeInsets.symmetric(horizontal: 7),
),
//if index is selected, then give the background a slight primary color
- backgroundColor: MaterialStateProperty.all(
+ backgroundColor: WidgetStateProperty.all(
entry.value.color != null
? entry.value.color!.withOpacity(0.2)
: theme.colorScheme.primary.withOpacity(0.1),
),
- textStyle: MaterialStateProperty.all(
+ textStyle: WidgetStateProperty.all(
BotStyle.text(context),
),
- shape: MaterialStateProperty.all(
+ shape: WidgetStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
),
- onLongPress:
- onLongPress != null ? () => onLongPress!(entry.key) : null,
- onPressed: () => onPressed(entry.key),
+ onLongPress: onLongPress != null && !interactionDisabled
+ ? () => onLongPress!(entry.key)
+ : null,
+ onPressed:
+ interactionDisabled ? null : () => onPressed(entry.key),
child: Text(
entry.value.text,
style: BotStyle.text(context),
@@ -149,11 +190,15 @@ class ChoiceAnimationWidget extends StatefulWidget {
final Widget child;
final bool selected;
final bool isGold;
+ final VoidCallback enableInteraction;
+ final VoidCallback disableInteraction;
const ChoiceAnimationWidget({
super.key,
required this.child,
required this.selected,
+ required this.enableInteraction,
+ required this.disableInteraction,
this.isGold = false,
});
@@ -161,11 +206,13 @@ class ChoiceAnimationWidget extends StatefulWidget {
ChoiceAnimationWidgetState createState() => ChoiceAnimationWidgetState();
}
+enum AnimationState { ready, forward, reverse, finished }
+
class ChoiceAnimationWidgetState extends State
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
late final Animation _animation;
- bool animationPlayed = false;
+ AnimationState animationState = AnimationState.ready;
@override
void initState() {
@@ -177,33 +224,45 @@ class ChoiceAnimationWidgetState extends State
);
_animation = widget.isGold
- ? Tween(begin: 1.0, end: 1.2).animate(_controller)
- : TweenSequence([
- TweenSequenceItem(
- tween: Tween(begin: 0, end: -8 * pi / 180),
- weight: 1.0,
- ),
- TweenSequenceItem(
- tween: Tween(begin: -8 * pi / 180, end: 16 * pi / 180),
- weight: 2.0,
- ),
- TweenSequenceItem(
- tween: Tween(begin: 16 * pi / 180, end: 0),
- weight: 1.0,
- ),
- ]).animate(_controller);
+ ? Tween(begin: 1.0, end: 1.2).animate(_controller)
+ : TweenSequence([
+ TweenSequenceItem(
+ tween: Tween(begin: 0, end: -8 * pi / 180),
+ weight: 1.0,
+ ),
+ TweenSequenceItem(
+ tween: Tween(begin: -8 * pi / 180, end: 16 * pi / 180),
+ weight: 2.0,
+ ),
+ TweenSequenceItem(
+ tween: Tween(begin: 16 * pi / 180, end: 0),
+ weight: 1.0,
+ ),
+ ]).animate(_controller);
- if (widget.selected && !animationPlayed) {
+ widget.enableInteraction();
+
+ if (widget.selected && animationState == AnimationState.ready) {
+ widget.disableInteraction();
_controller.forward();
- animationPlayed = true;
- setState(() {});
+ setState(() {
+ animationState = AnimationState.forward;
+ });
}
_controller.addStatusListener((status) {
- if (status == AnimationStatus.completed) {
+ if (status == AnimationStatus.completed &&
+ animationState == AnimationState.forward) {
_controller.reverse();
- } else if (status == AnimationStatus.dismissed) {
- _controller.stop();
- _controller.reset();
+ setState(() {
+ animationState = AnimationState.reverse;
+ });
+ }
+ if (status == AnimationStatus.dismissed &&
+ animationState == AnimationState.reverse) {
+ widget.enableInteraction();
+ setState(() {
+ animationState = AnimationState.finished;
+ });
}
});
}
@@ -211,38 +270,40 @@ class ChoiceAnimationWidgetState extends State
@override
void didUpdateWidget(ChoiceAnimationWidget oldWidget) {
super.didUpdateWidget(oldWidget);
- if (widget.selected && !animationPlayed) {
+ if (widget.selected && animationState == AnimationState.ready) {
+ widget.disableInteraction();
_controller.forward();
- animationPlayed = true;
- setState(() {});
+ setState(() {
+ animationState = AnimationState.forward;
+ });
}
}
@override
Widget build(BuildContext context) {
return widget.isGold
- ? AnimatedBuilder(
- key: UniqueKey(),
- animation: _animation,
- builder: (context, child) {
- return Transform.scale(
- scale: _animation.value,
- child: child,
- );
- },
- child: widget.child,
- )
- : AnimatedBuilder(
- key: UniqueKey(),
- animation: _animation,
- builder: (context, child) {
- return Transform.rotate(
- angle: _animation.value,
- child: child,
- );
- },
- child: widget.child,
- );
+ ? AnimatedBuilder(
+ key: UniqueKey(),
+ animation: _animation,
+ builder: (context, child) {
+ return Transform.scale(
+ scale: _animation.value,
+ child: child,
+ );
+ },
+ child: widget.child,
+ )
+ : AnimatedBuilder(
+ key: UniqueKey(),
+ animation: _animation,
+ builder: (context, child) {
+ return Transform.rotate(
+ angle: _animation.value,
+ child: child,
+ );
+ },
+ child: widget.child,
+ );
}
@override
diff --git a/lib/pangea/choreographer/widgets/it_bar.dart b/lib/pangea/choreographer/widgets/it_bar.dart
index 4e26cba58..2ba630900 100644
--- a/lib/pangea/choreographer/widgets/it_bar.dart
+++ b/lib/pangea/choreographer/widgets/it_bar.dart
@@ -7,6 +7,7 @@ import 'package:fluffychat/pangea/choreographer/widgets/it_feedback_card.dart';
import 'package:fluffychat/pangea/choreographer/widgets/translation_finished_flow.dart';
import 'package:fluffychat/pangea/constants/choreo_constants.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
+import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@@ -21,93 +22,107 @@ class ITBar extends StatelessWidget {
final Choreographer choreographer;
const ITBar({super.key, required this.choreographer});
- ITController get controller => choreographer.itController;
+ ITController get itController => choreographer.itController;
@override
Widget build(BuildContext context) {
- if (!controller.isOpen) return const SizedBox();
- return CompositedTransformTarget(
- link: choreographer.itBarLinkAndKey.link,
- child: Container(
- key: choreographer.itBarLinkAndKey.key,
- decoration: BoxDecoration(
- color: Theme.of(context).brightness == Brightness.light
- ? Colors.white
- : Colors.black,
- borderRadius: const BorderRadius.only(
- topLeft: Radius.circular(AppConfig.borderRadius),
- topRight: Radius.circular(AppConfig.borderRadius),
- ),
- ),
- width: double.infinity,
- padding: const EdgeInsets.fromLTRB(0, 3, 3, 3),
- child: Stack(
- children: [
- SingleChildScrollView(
- child: Column(
+ return AnimatedSize(
+ duration: itController.willOpen
+ ? const Duration(milliseconds: 2000)
+ : const Duration(milliseconds: 500),
+ curve: Curves.fastOutSlowIn,
+ clipBehavior: Clip.none,
+ child: !itController.willOpen
+ ? const SizedBox()
+ : CompositedTransformTarget(
+ link: choreographer.itBarLinkAndKey.link,
+ child: AnimatedOpacity(
+ duration: itController.willOpen
+ ? const Duration(milliseconds: 2000)
+ : const Duration(milliseconds: 500),
+ opacity: itController.willOpen ? 1.0 : 0.0,
+ child: Container(
+ key: choreographer.itBarLinkAndKey.key,
+ decoration: BoxDecoration(
+ color: Theme.of(context).brightness == Brightness.light
+ ? Colors.white
+ : Colors.black,
+ borderRadius: const BorderRadius.only(
+ topLeft: Radius.circular(AppConfig.borderRadius),
+ topRight: Radius.circular(AppConfig.borderRadius),
+ ),
+ ),
+ width: double.infinity,
+ padding: const EdgeInsets.fromLTRB(0, 3, 3, 3),
+ child: Stack(
children: [
- // Row(
- // mainAxisAlignment: MainAxisAlignment.spaceBetween,
- // crossAxisAlignment: CrossAxisAlignment.start,
- // children: [
- // // Row(
- // // mainAxisAlignment: MainAxisAlignment.start,
- // // crossAxisAlignment: CrossAxisAlignment.start,
- // // children: [
- // // CounterDisplay(
- // // correct: controller.correctChoices,
- // // custom: controller.customChoices,
- // // incorrect: controller.incorrectChoices,
- // // yellow: controller.wildcardChoices,
- // // ),
- // // CompositedTransformTarget(
- // // link: choreographer.itBotLayerLinkAndKey.link,
- // // child: ITBotButton(
- // // key: choreographer.itBotLayerLinkAndKey.key,
- // // choreographer: choreographer,
- // // ),
- // // ),
- // // ],
- // // ),
- // ITCloseButton(choreographer: choreographer),
- // ],
- // ),
- // const SizedBox(height: 40.0),
- OriginalText(controller: controller),
- const SizedBox(height: 7.0),
- IntrinsicHeight(
- child: Container(
- constraints: const BoxConstraints(minHeight: 80),
- width: double.infinity,
- padding: const EdgeInsets.symmetric(horizontal: 4.0),
- child: Center(
- child: controller.choreographer.errorService.isError
- ? ITError(
- error: controller
+ SingleChildScrollView(
+ child: Column(
+ children: [
+ // Row(
+ // mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ // crossAxisAlignment: CrossAxisAlignment.start,
+ // children: [
+ // // Row(
+ // // mainAxisAlignment: MainAxisAlignment.start,
+ // // crossAxisAlignment: CrossAxisAlignment.start,
+ // // children: [
+ // // CounterDisplay(
+ // // correct: controller.correctChoices,
+ // // custom: controller.customChoices,
+ // // incorrect: controller.incorrectChoices,
+ // // yellow: controller.wildcardChoices,
+ // // ),
+ // // CompositedTransformTarget(
+ // // link: choreographer.itBotLayerLinkAndKey.link,
+ // // child: ITBotButton(
+ // // key: choreographer.itBotLayerLinkAndKey.key,
+ // // choreographer: choreographer,
+ // // ),
+ // // ),
+ // // ],
+ // // ),
+ // ITCloseButton(choreographer: choreographer),
+ // ],
+ // ),
+ // const SizedBox(height: 40.0),
+ OriginalText(controller: itController),
+ const SizedBox(height: 7.0),
+ IntrinsicHeight(
+ child: Container(
+ constraints: const BoxConstraints(minHeight: 80),
+ width: double.infinity,
+ padding: const EdgeInsets.symmetric(horizontal: 4.0),
+ child: Center(
+ child: itController.choreographer.errorService.isError
+ ? ITError(
+ error: itController
.choreographer.errorService.error!,
- controller: controller,
+ controller: itController,
)
- : controller.showChoiceFeedback
- ? ChoiceFeedbackText(controller: controller)
- : controller.isTranslationDone
- ? TranslationFeedback(
- controller: controller,
- )
- : ITChoices(controller: controller),
- ),
+ : itController.showChoiceFeedback
+ ? ChoiceFeedbackText(controller: itController)
+ : itController.isTranslationDone
+ ? TranslationFeedback(
+ controller: itController,
+ )
+ : ITChoices(controller: itController),
+ ),
+ ),
+ ),
+ ],
),
),
+ Positioned(
+ top: 0.0,
+ right: 0.0,
+ child: ITCloseButton(choreographer: choreographer),
+ ),
],
),
),
- Positioned(
- top: 0.0,
- right: 0.0,
- child: ITCloseButton(choreographer: choreographer),
- ),
- ],
- ),
+ ),
),
);
}
@@ -184,10 +199,23 @@ class OriginalText extends StatelessWidget {
),
),
),
- if (!controller.isEditingSourceText && controller.sourceText != null)
- IconButton(
- onPressed: () => controller.setIsEditingSourceText(true),
- icon: const Icon(Icons.edit_outlined),
+ if (
+ !controller.isEditingSourceText
+ && controller.sourceText != null
+ )
+ AnimatedOpacity(
+ duration: const Duration(milliseconds: 500),
+ opacity: controller.nextITStep != null
+ ? 1.0
+ : 0.0,
+ child: IconButton(
+ onPressed: () => {
+ if (controller.nextITStep != null) {
+ controller.setIsEditingSourceText(true),
+ },
+ },
+ icon: const Icon(Icons.edit_outlined),
+ ),
),
],
),
diff --git a/lib/pangea/choreographer/widgets/it_bar_buttons.dart b/lib/pangea/choreographer/widgets/it_bar_buttons.dart
index 815020d17..9fcaa927e 100644
--- a/lib/pangea/choreographer/widgets/it_bar_buttons.dart
+++ b/lib/pangea/choreographer/widgets/it_bar_buttons.dart
@@ -1,8 +1,8 @@
+import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
+import 'package:fluffychat/pangea/enum/instructions_enum.dart';
+import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
-import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
-import 'package:fluffychat/pangea/utils/instructions.dart';
-import 'package:fluffychat/widgets/matrix.dart';
import '../../widgets/common/bot_face_svg.dart';
import '../controllers/choreographer.dart';
import '../controllers/it_controller.dart';
@@ -37,7 +37,7 @@ class ITBotButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
- choreographer.pangeaController.instructions.show(
+ choreographer.pangeaController.instructions.showInstructionsPopup(
context,
InstructionsEnum.itInstructions,
choreographer.itBotTransformTargetKey,
@@ -45,8 +45,9 @@ class ITBotButton extends StatelessWidget {
);
return IconButton(
- icon: const BotFace(width: 40.0, expression: BotExpression.right),
- onPressed: () => choreographer.pangeaController.instructions.show(
+ icon: const BotFace(width: 40.0, expression: BotExpression.idle),
+ onPressed: () =>
+ choreographer.pangeaController.instructions.showInstructionsPopup(
context,
InstructionsEnum.itInstructions,
choreographer.itBotTransformTargetKey,
diff --git a/lib/pangea/choreographer/widgets/it_feedback_card.dart b/lib/pangea/choreographer/widgets/it_feedback_card.dart
index 072d24e1a..5424310a8 100644
--- a/lib/pangea/choreographer/widgets/it_feedback_card.dart
+++ b/lib/pangea/choreographer/widgets/it_feedback_card.dart
@@ -129,7 +129,7 @@ class ITFeedbackCardView extends StatelessWidget {
children: [
CardHeader(
text: controller.widget.req.chosenContinuance,
- botExpression: BotExpression.down,
+ botExpression: BotExpression.nonGold,
),
Text(
controller.widget.choiceFeedback,
diff --git a/lib/pangea/choreographer/widgets/language_permissions_warning_buttons.dart b/lib/pangea/choreographer/widgets/language_permissions_warning_buttons.dart
index 9d3302910..0abe90925 100644
--- a/lib/pangea/choreographer/widgets/language_permissions_warning_buttons.dart
+++ b/lib/pangea/choreographer/widgets/language_permissions_warning_buttons.dart
@@ -2,7 +2,7 @@ import 'dart:developer';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart';
-import 'package:fluffychat/pangea/models/class_model.dart';
+import 'package:fluffychat/pangea/models/space_model.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
diff --git a/lib/pangea/choreographer/widgets/start_igc_button.dart b/lib/pangea/choreographer/widgets/start_igc_button.dart
index ceb5af193..e8625da95 100644
--- a/lib/pangea/choreographer/widgets/start_igc_button.dart
+++ b/lib/pangea/choreographer/widgets/start_igc_button.dart
@@ -1,10 +1,10 @@
import 'dart:async';
import 'dart:math' as math;
-import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart';
-import 'package:fluffychat/pangea/constants/colors.dart';
import 'package:fluffychat/pangea/controllers/subscription_controller.dart';
+import 'package:fluffychat/pangea/enum/assistance_state_enum.dart';
+import 'package:fluffychat/pangea/widgets/user_settings/p_language_dialog.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
@@ -53,15 +53,15 @@ class StartIGCButtonState extends State
setState(() => prevState = assistanceState);
}
+ bool get itEnabled => widget.controller.choreographer.itEnabled;
+ bool get igcEnabled => widget.controller.choreographer.igcEnabled;
+ CanSendStatus get canSendStatus =>
+ widget.controller.pangeaController.subscriptionController.canSendStatus;
+ bool get grammarCorrectionEnabled =>
+ (itEnabled || igcEnabled) && canSendStatus == CanSendStatus.subscribed;
+
@override
Widget build(BuildContext context) {
- final bool itEnabled = widget.controller.choreographer.itEnabled;
- final bool igcEnabled = widget.controller.choreographer.igcEnabled;
- final CanSendStatus canSendStatus =
- widget.controller.pangeaController.subscriptionController.canSendStatus;
- final bool grammarCorrectionEnabled =
- (itEnabled || igcEnabled) && canSendStatus == CanSendStatus.subscribed;
-
if (!grammarCorrectionEnabled ||
widget.controller.choreographer.isAutoIGCEnabled ||
widget.controller.choreographer.choreoMode == ChoreoMode.it) {
@@ -77,92 +77,67 @@ class StartIGCButtonState extends State
return SizedBox(
height: 50,
width: 50,
- child: FloatingActionButton(
- tooltip: assistanceState.tooltip(
- L10n.of(context)!,
- ),
- backgroundColor: Theme.of(context).scaffoldBackgroundColor,
- disabledElevation: 0,
- shape: const CircleBorder(),
- onPressed: () {
- if (assistanceState != AssistanceState.complete) {
- widget.controller.choreographer
- .getLanguageHelp(
- false,
- true,
- )
- .then((_) {
- if (widget.controller.choreographer.igc.igcTextData != null &&
- widget.controller.choreographer.igc.igcTextData!.matches
- .isNotEmpty) {
- widget.controller.choreographer.igc.showFirstMatch(context);
- }
- });
- }
- },
- child: Stack(
- alignment: Alignment.center,
- children: [
- _controller != null
- ? RotationTransition(
- turns: Tween(begin: 0.0, end: math.pi * 2)
- .animate(_controller!),
- child: icon,
- )
- : icon,
- Container(
- width: 26,
- height: 26,
- decoration: BoxDecoration(
- shape: BoxShape.circle,
+ child: InkWell(
+ customBorder: const CircleBorder(),
+ onLongPress: () => pLanguageDialog(context, () {}),
+ child: FloatingActionButton(
+ tooltip: assistanceState.tooltip(
+ L10n.of(context)!,
+ ),
+ backgroundColor: Theme.of(context).scaffoldBackgroundColor,
+ disabledElevation: 0,
+ shape: const CircleBorder(),
+ onPressed: () {
+ if (assistanceState != AssistanceState.fetching) {
+ widget.controller.choreographer
+ .getLanguageHelp(
+ onlyTokensAndLanguageDetection: false,
+ manual: true,
+ )
+ .then((_) {
+ if (widget.controller.choreographer.igc.igcTextData != null &&
+ widget.controller.choreographer.igc.igcTextData!.matches
+ .isNotEmpty) {
+ widget.controller.choreographer.igc.showFirstMatch(context);
+ }
+ });
+ }
+ },
+ child: Stack(
+ alignment: Alignment.center,
+ children: [
+ _controller != null
+ ? RotationTransition(
+ turns: Tween(begin: 0.0, end: math.pi * 2)
+ .animate(_controller!),
+ child: icon,
+ )
+ : icon,
+ Container(
+ width: 26,
+ height: 26,
+ decoration: BoxDecoration(
+ shape: BoxShape.circle,
+ color: Theme.of(context).scaffoldBackgroundColor,
+ ),
+ ),
+ Container(
+ width: 20,
+ height: 20,
+ decoration: BoxDecoration(
+ shape: BoxShape.circle,
+ color: assistanceState.stateColor(context),
+ ),
+ ),
+ Icon(
+ size: 16,
+ Icons.check,
color: Theme.of(context).scaffoldBackgroundColor,
),
- ),
- Container(
- width: 20,
- height: 20,
- decoration: BoxDecoration(
- shape: BoxShape.circle,
- color: assistanceState.stateColor(context),
- ),
- ),
- Icon(
- size: 16,
- Icons.check,
- color: Theme.of(context).scaffoldBackgroundColor,
- ),
- ],
+ ],
+ ),
),
),
);
}
}
-
-extension AssistanceStateExtension on AssistanceState {
- Color stateColor(context) {
- switch (this) {
- case AssistanceState.noMessage:
- case AssistanceState.notFetched:
- case AssistanceState.fetching:
- return Theme.of(context).colorScheme.primary;
- case AssistanceState.fetched:
- return PangeaColors.igcError;
- case AssistanceState.complete:
- return AppConfig.success;
- }
- }
-
- String tooltip(L10n l10n) {
- switch (this) {
- case AssistanceState.noMessage:
- case AssistanceState.notFetched:
- return l10n.runGrammarCorrection;
- case AssistanceState.fetching:
- return "";
- case AssistanceState.fetched:
- return l10n.grammarCorrectionFailed;
- case AssistanceState.complete:
- return l10n.grammarCorrectionComplete;
- }
- }
-}
diff --git a/lib/pangea/config/environment.dart b/lib/pangea/config/environment.dart
index 4d4378999..de7039f9d 100644
--- a/lib/pangea/config/environment.dart
+++ b/lib/pangea/config/environment.dart
@@ -5,7 +5,7 @@ class Environment {
DateTime.utc(2023, 1, 25).isBefore(DateTime.now());
static String get fileName {
- return ".env";
+ return ".local_choreo.env";
}
static bool get isStaging => synapsURL.contains("staging");
diff --git a/lib/pangea/constants/keys.dart b/lib/pangea/constants/keys.dart
deleted file mode 100644
index 092b1b0a9..000000000
--- a/lib/pangea/constants/keys.dart
+++ /dev/null
@@ -1,4 +0,0 @@
-class PrefKey {
- static const lastFetched = 'LAST_FETCHED';
- static const flags = 'flags';
-}
diff --git a/lib/pangea/constants/language_constants.dart b/lib/pangea/constants/language_constants.dart
new file mode 100644
index 000000000..73137a300
--- /dev/null
+++ b/lib/pangea/constants/language_constants.dart
@@ -0,0 +1,24 @@
+import 'package:fluffychat/pangea/models/language_detection_model.dart';
+
+class LanguageKeys {
+ static const unknownLanguage = "unk";
+ static const mixedLanguage = "mixed";
+ static const defaultLanguage = "en";
+ static const multiLanguage = "multi";
+}
+
+class LanguageLevelType {
+ static List get allInts => [0, 1, 2, 3, 4, 5, 6];
+}
+
+class PrefKey {
+ static const lastFetched = 'p_lang_lastfetched';
+ static const flags = 'p_lang_flag';
+}
+
+final LanguageDetection unknownLanguageDetection = LanguageDetection(
+ langCode: LanguageKeys.unknownLanguage,
+ confidence: 0.5,
+);
+
+const double languageDetectionConfidenceThreshold = 0.95;
diff --git a/lib/pangea/constants/language_keys.dart b/lib/pangea/constants/language_keys.dart
deleted file mode 100644
index cfe0c96c0..000000000
--- a/lib/pangea/constants/language_keys.dart
+++ /dev/null
@@ -1,6 +0,0 @@
-class LanguageKeys {
- static const unknownLanguage = "unk";
- static const mixedLanguage = "mixed";
- static const defaultLanguage = "en";
- static const multiLanguage = "multi";
-}
diff --git a/lib/pangea/constants/language_level_type.dart b/lib/pangea/constants/language_level_type.dart
deleted file mode 100644
index 49136ca77..000000000
--- a/lib/pangea/constants/language_level_type.dart
+++ /dev/null
@@ -1,3 +0,0 @@
-class LanguageLevelType {
- static List get allInts => [0, 1, 2, 3, 4, 5, 6];
-}
diff --git a/lib/pangea/constants/language_list_keys.dart b/lib/pangea/constants/language_list_keys.dart
deleted file mode 100644
index 7fff924a9..000000000
--- a/lib/pangea/constants/language_list_keys.dart
+++ /dev/null
@@ -1,4 +0,0 @@
-class PrefKey {
- static const lastFetched = 'p_lang_lastfetched';
- static const flags = 'p_lang_flag';
-}
diff --git a/lib/pangea/constants/local.key.dart b/lib/pangea/constants/local.key.dart
index 743fe1143..40dd393c5 100644
--- a/lib/pangea/constants/local.key.dart
+++ b/lib/pangea/constants/local.key.dart
@@ -11,5 +11,6 @@ class PLocalKey {
static const String dismissedPaywall = 'dismissedPaywall';
static const String paywallBackoff = 'paywallBackoff';
static const String autoPlayMessages = 'autoPlayMessages';
+ static const String itAutoPlay = 'itAutoPlay';
static const String messagesSinceUpdate = 'messagesSinceLastUpdate';
}
diff --git a/lib/pangea/constants/model_keys.dart b/lib/pangea/constants/model_keys.dart
index 0cd14e5a4..372e72606 100644
--- a/lib/pangea/constants/model_keys.dart
+++ b/lib/pangea/constants/model_keys.dart
@@ -27,11 +27,8 @@ class ModelKey {
static const String clientIsPublic = "isPublic";
static const String clientIsOpenEnrollment = 'isOpenEnrollment';
- static const String clientIsOpenExchange = 'isOpenExchange';
static const String clientIsOneToOneChatClass = 'oneToOneChatClass';
- static const String clientIsOneToOneChatExchange = 'oneToOneChatExchange';
static const String clientIsCreateRooms = 'isCreateRooms';
- static const String clientIsCreateRoomsExchange = 'isCreateRoomsExchange';
static const String clientIsShareVideo = 'isShareVideo';
static const String clientIsSharePhoto = 'isSharePhoto';
static const String clientIsShareFiles = 'isShareFiles';
@@ -39,7 +36,6 @@ class ModelKey {
static const String clientIsCreateStories = 'isCreateStories';
static const String clientIsVoiceNotes = 'isVoiceNotes';
static const String clientIsInviteOnlyStudents = 'isInviteOnlyStudents';
- static const String clientIsInviteOnlyExchanges = 'isInviteOnlyExchanges';
static const String userL1 = "user_l1";
static const String userL2 = "user_l2";
@@ -70,7 +66,6 @@ class ModelKey {
static const String tokensSent = "tokens_sent";
static const String tokensWritten = "tokens_written";
static const String choreoRecord = "choreo_record";
- static const String useType = "use_type";
static const String baseDefinition = "base_definition";
static const String targetDefinition = "target_definition";
@@ -99,17 +94,16 @@ class ModelKey {
static const String languageLevel = "difficulty";
static const String safetyModeration = "safety_moderation";
static const String mode = "mode";
- static const String custom = "custom";
static const String discussionTopic = "discussion_topic";
static const String discussionKeywords = "discussion_keywords";
- static const String discussionTriggerScheduleEnabled =
- "discussion_trigger_schedule_enabled";
- static const String discussionTriggerScheduleHourInterval =
- "discussion_trigger_schedule_hour_interval";
static const String discussionTriggerReactionEnabled =
"discussion_trigger_reaction_enabled";
static const String discussionTriggerReactionKey =
"discussion_trigger_reaction_key";
+ static const String customSystemPrompt = "custom_system_prompt";
+ static const String customTriggerReactionEnabled =
+ "custom_trigger_reaction_enabled";
+ static const String customTriggerReactionKey = "custom_trigger_reaction_key";
static const String prevEventId = "prev_event_id";
static const String prevLastUpdated = "prev_last_updated";
diff --git a/lib/pangea/constants/pangea_event_types.dart b/lib/pangea/constants/pangea_event_types.dart
index a182728dd..9ca975dc0 100644
--- a/lib/pangea/constants/pangea_event_types.dart
+++ b/lib/pangea/constants/pangea_event_types.dart
@@ -1,6 +1,5 @@
class PangeaEventTypes {
- static const classSettings = "pangea.class";
- static const pangeaExchange = "p.exchange";
+ static const languageSettings = "pangea.class";
static const transcript = "pangea.transcript";
@@ -26,4 +25,14 @@ class PangeaEventTypes {
static const String report = 'm.report';
static const textToSpeechRule = "p.rule.text_to_speech";
+
+ /// A request to the server to generate activities
+ static const activityRequest = "pangea.activity_req";
+
+ /// A practice activity that is related to a message
+ static const pangeaActivity = "pangea.activity_res";
+
+ /// A record of completion of an activity. There
+ /// can be one per user per activity.
+ static const activityRecord = "pangea.activity_completion";
}
diff --git a/lib/pangea/constants/pangea_room_types.dart b/lib/pangea/constants/pangea_room_types.dart
index dcceadd36..804d2be86 100644
--- a/lib/pangea/constants/pangea_room_types.dart
+++ b/lib/pangea/constants/pangea_room_types.dart
@@ -1,4 +1,3 @@
class PangeaRoomTypes {
static const analytics = 'p.analytics';
- static const exchange = 'p.exchange';
}
diff --git a/lib/pangea/controllers/class_controller.dart b/lib/pangea/controllers/class_controller.dart
index 1e2febf21..b4bc18b87 100644
--- a/lib/pangea/controllers/class_controller.dart
+++ b/lib/pangea/controllers/class_controller.dart
@@ -7,9 +7,9 @@ import 'package:fluffychat/pangea/constants/pangea_event_types.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
-import 'package:fluffychat/pangea/models/class_model.dart';
-import 'package:fluffychat/pangea/utils/class_code.dart';
+import 'package:fluffychat/pangea/models/space_model.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
+import 'package:fluffychat/pangea/utils/space_code.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
@@ -34,12 +34,11 @@ class ClassController extends BaseController {
Future fixClassPowerLevels() async {
try {
- final List> classFixes = [];
- final teacherSpaces = await _pangeaController
- .matrixState.client.classesAndExchangesImTeaching;
- for (final room in teacherSpaces) {
- classFixes.add(room.setClassPowerLevels());
- }
+ final teacherSpaces =
+ await _pangeaController.matrixState.client.spacesImTeaching;
+ final List> classFixes = List.from(teacherSpaces)
+ .map((adminSpace) => adminSpace.setClassPowerLevels())
+ .toList();
await Future.wait(classFixes);
} catch (err, stack) {
debugger(when: kDebugMode);
@@ -65,7 +64,7 @@ class ClassController extends BaseController {
classCode,
).onError(
(error, stackTrace) =>
- ClassCodeUtil.messageSnack(context, ErrorCopy(context, error).body),
+ SpaceCodeUtil.messageSnack(context, ErrorCopy(context, error).body),
);
}
}
@@ -78,8 +77,7 @@ class ClassController extends BaseController {
if (!room.isDirectChat) return [];
final List existingParentsIds =
room.pangeaSpaceParents.map((e) => e.id).toList();
- final List spaces =
- _pangeaController.matrixState.client.classesAndExchangesImIn;
+ final List spaces = _pangeaController.matrixState.client.spacesImIn;
//make sure we have the latest participants
await Future.wait(spaces.map((e) => e.requestParticipants()));
@@ -121,7 +119,7 @@ class ClassController extends BaseController {
});
if (classChunk == null) {
- ClassCodeUtil.messageSnack(
+ SpaceCodeUtil.messageSnack(
context,
L10n.of(context)!.unableToFindClass,
);
@@ -131,7 +129,7 @@ class ClassController extends BaseController {
if (_pangeaController.matrixState.client.rooms
.any((room) => room.id == classChunk.roomId)) {
setActiveSpaceIdInChatListController(classChunk.roomId);
- ClassCodeUtil.messageSnack(context, L10n.of(context)!.alreadyInClass);
+ SpaceCodeUtil.messageSnack(context, L10n.of(context)!.alreadyInClass);
return;
}
@@ -170,9 +168,6 @@ class ClassController extends BaseController {
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();
@@ -181,7 +176,7 @@ class ClassController extends BaseController {
GoogleAnalytics.joinClass(classCode);
return;
} catch (err) {
- ClassCodeUtil.messageSnack(
+ SpaceCodeUtil.messageSnack(
context,
ErrorCopy(context, err).body,
);
@@ -199,7 +194,7 @@ class ClassController extends BaseController {
final Room? room = _pangeaController.matrixState.client.getRoomById(roomId);
if (room == null) return;
- if (room.classSettings != null && room.pangeaRoomRules == null) {
+ if (room.isSpace && room.isRoomAdmin && room.pangeaRoomRules == null) {
try {
await _pangeaController.matrixState.client.setRoomStateWithKey(
roomId,
diff --git a/lib/pangea/controllers/language_controller.dart b/lib/pangea/controllers/language_controller.dart
index e11053fe2..b1bc02380 100644
--- a/lib/pangea/controllers/language_controller.dart
+++ b/lib/pangea/controllers/language_controller.dart
@@ -1,14 +1,11 @@
import 'dart:developer';
-import 'package:fluffychat/pangea/constants/language_keys.dart';
+import 'package:fluffychat/pangea/constants/language_constants.dart';
import 'package:fluffychat/pangea/controllers/language_list_controller.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
-import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
-import 'package:fluffychat/pangea/models/class_model.dart';
import 'package:fluffychat/pangea/models/language_model.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
-import 'package:matrix/matrix.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import '../widgets/user_settings/p_language_dialog.dart';
@@ -63,50 +60,51 @@ class LanguageController {
return _userL2Code != null ? PangeaLanguage.byLangCode(_userL2Code!) : null;
}
- String? activeL1Code({String? roomID}) {
- final String? activeL2 = activeL2Code(roomID: roomID);
- if (roomID == null || activeL2 != _userL1Code) {
- return _userL1Code;
- }
- final ClassSettingsModel? classContext = _pangeaController
- .matrixState.client
- .getRoomById(roomID)
- ?.firstLanguageSettings;
- final String? classL1 = classContext?.dominantLanguage;
- if (classL1 == LanguageKeys.mixedLanguage ||
- classL1 == LanguageKeys.multiLanguage ||
- classL1 == null) {
- if (_userL2Code != _userL1Code) {
- return _userL2Code;
- }
- return LanguageKeys.unknownLanguage;
- }
- return classL1;
+ String? activeL1Code() {
+ return _userL1Code;
+ // final String? activeL2 = activeL2Code(roomID: roomID);
+ // if (roomID == null || activeL2 != _userL1Code) {
+ // return _userL1Code;
+ // }
+ // final LanguageSettingsModel? classContext = _pangeaController
+ // .matrixState.client
+ // .getRoomById(roomID)
+ // ?.firstLanguageSettings;
+ // final String? classL1 = classContext?.dominantLanguage;
+ // if (classL1 == LanguageKeys.mixedLanguage ||
+ // classL1 == LanguageKeys.multiLanguage ||
+ // classL1 == null) {
+ // if (_userL2Code != _userL1Code) {
+ // return _userL2Code;
+ // }
+ // return LanguageKeys.unknownLanguage;
+ // }
+ // return classL1;
}
/// Class languages override user languages within a class context
- String? activeL2Code({String? roomID}) {
- if (roomID == null) {
- return _userL2Code;
- }
- final ClassSettingsModel? classContext = _pangeaController
- .matrixState.client
- .getRoomById(roomID)
- ?.firstLanguageSettings;
- return classContext?.targetLanguage ?? _userL2Code;
+ String? activeL2Code() {
+ return _userL2Code;
+ // if (roomID == null) {
+ // return _userL2Code;
+ // }
+ // final LanguageSettingsModel? classContext = _pangeaController
+ // .matrixState.client
+ // .getRoomById(roomID)
+ // ?.firstLanguageSettings;
+ // return classContext?.targetLanguage ?? _userL2Code;
}
- LanguageModel? activeL1Model({String? roomID}) {
- final activeL1 = activeL1Code(roomID: roomID);
- return activeL1 != null ? PangeaLanguage.byLangCode(activeL1) : null;
+ LanguageModel? activeL1Model() {
+ return userL1;
+ // final activeL1 = activeL1Code(roomID: roomID);
+ // return activeL1 != null ? PangeaLanguage.byLangCode(activeL1) : null;
}
- LanguageModel? activeL2Model({String? roomID}) {
- final activeL2 = activeL2Code(roomID: roomID);
- final model = activeL2 != null ? PangeaLanguage.byLangCode(activeL2) : null;
- return model;
+ LanguageModel? activeL2Model() {
+ return userL2;
+ // final activeL2 = activeL2Code(roomID: roomID);
+ // final model = activeL2 != null ? PangeaLanguage.byLangCode(activeL2) : null;
+ // return model;
}
-
- bool equalActiveL1AndActiveL2({Room? room}) =>
- activeL1Code() == activeL2Code(roomID: room?.id);
}
diff --git a/lib/pangea/controllers/language_detection_controller.dart b/lib/pangea/controllers/language_detection_controller.dart
index 4ddfabc88..a3e07b0a3 100644
--- a/lib/pangea/controllers/language_detection_controller.dart
+++ b/lib/pangea/controllers/language_detection_controller.dart
@@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:convert';
import 'package:fluffychat/pangea/config/environment.dart';
+import 'package:fluffychat/pangea/constants/language_constants.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/models/language_detection_model.dart';
import 'package:fluffychat/pangea/network/urls.dart';
@@ -75,19 +76,21 @@ class LanguageDetectionResponse {
};
}
- LanguageDetection? get _bestDetection {
+ /// Return the highest confidence detection.
+ /// If there are no detections, the unknown language detection is returned.
+ LanguageDetection get highestConfidenceDetection {
detections.sort((a, b) => b.confidence.compareTo(a.confidence));
- return detections.isNotEmpty ? detections.first : null;
+ return detections.firstOrNull ?? unknownLanguageDetection;
}
- final double _confidenceThreshold = 0.95;
-
- LanguageDetection? bestDetection({double? threshold}) {
- threshold ??= _confidenceThreshold;
- return (_bestDetection?.confidence ?? 0) >= _confidenceThreshold
- ? _bestDetection!
- : null;
- }
+ /// Returns the highest validated detection based on the confidence threshold.
+ /// If the highest confidence detection is below the threshold, the unknown language
+ /// detection is returned.
+ LanguageDetection highestValidatedDetection({double? threshold}) =>
+ highestConfidenceDetection.confidence >=
+ (threshold ?? languageDetectionConfidenceThreshold)
+ ? highestConfidenceDetection
+ : unknownLanguageDetection;
}
class _LanguageDetectionCacheItem {
diff --git a/lib/pangea/controllers/language_list_controller.dart b/lib/pangea/controllers/language_list_controller.dart
index 345d5b5e7..31e4513fa 100644
--- a/lib/pangea/controllers/language_list_controller.dart
+++ b/lib/pangea/controllers/language_list_controller.dart
@@ -1,13 +1,12 @@
import 'dart:async';
import 'dart:developer';
-import 'package:fluffychat/pangea/constants/language_keys.dart';
+import 'package:fluffychat/pangea/constants/language_constants.dart';
import 'package:fluffychat/pangea/models/language_model.dart';
import 'package:fluffychat/pangea/repo/language_repo.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:flutter/foundation.dart';
-import '../constants/language_list_keys.dart';
import '../utils/shared_prefs.dart';
class PangeaLanguage {
@@ -27,7 +26,7 @@ class PangeaLanguage {
static Future initialize() async {
try {
- _langList = await _getCahedFlags();
+ _langList = await _getCachedFlags();
if (await _shouldFetch || _langList.isEmpty) {
_langList = await LanguageRepo.fetchLanguages();
@@ -77,7 +76,7 @@ class PangeaLanguage {
await MyShared.saveJson(PrefKey.flags, flagMap);
}
- static Future> _getCahedFlags() async {
+ static Future> _getCachedFlags() async {
final Map? flagsMap =
await MyShared.readJson(PrefKey.flags);
if (flagsMap == null) {
diff --git a/lib/pangea/controllers/local_settings.dart b/lib/pangea/controllers/local_settings.dart
index 2dc30cfe7..95b88ae14 100644
--- a/lib/pangea/controllers/local_settings.dart
+++ b/lib/pangea/controllers/local_settings.dart
@@ -1,5 +1,5 @@
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
-import 'package:fluffychat/pangea/models/class_model.dart';
+import 'package:fluffychat/pangea/models/space_model.dart';
class LocalSettings {
late PangeaController _pangeaController;
diff --git a/lib/pangea/controllers/message_analytics_controller.dart b/lib/pangea/controllers/message_analytics_controller.dart
index c0c8ecd1a..3739b2596 100644
--- a/lib/pangea/controllers/message_analytics_controller.dart
+++ b/lib/pangea/controllers/message_analytics_controller.dart
@@ -4,11 +4,13 @@ import 'dart:developer';
import 'package:collection/collection.dart';
import 'package:fluffychat/pangea/constants/match_rule_ids.dart';
import 'package:fluffychat/pangea/constants/pangea_event_types.dart';
+import 'package:fluffychat/pangea/controllers/language_list_controller.dart';
import 'package:fluffychat/pangea/enum/construct_type_enum.dart';
import 'package:fluffychat/pangea/enum/time_span.dart';
import 'package:fluffychat/pangea/models/analytics/analytics_event.dart';
import 'package:fluffychat/pangea/models/analytics/constructs_event.dart';
import 'package:fluffychat/pangea/models/analytics/summary_analytics_event.dart';
+import 'package:fluffychat/pangea/models/language_model.dart';
import 'package:fluffychat/pangea/pages/analytics/base_analytics.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:flutter/foundation.dart';
@@ -60,35 +62,65 @@ class AnalyticsController extends BaseController {
timeSpan.toString(),
local: true,
);
+ setState();
}
+ ///////// SPACE ANALYTICS LANGUAGES //////////
+ String get _analyticsSpaceLangKey => "ANALYTICS_SPACE_LANG_KEY";
+
+ LanguageModel get currentAnalyticsLang {
+ try {
+ final String? str = _pangeaController.pStoreService.read(
+ _analyticsSpaceLangKey,
+ local: true,
+ );
+ return str != null
+ ? PangeaLanguage.byLangCode(str)
+ : _pangeaController.languageController.userL2 ??
+ _pangeaController.pLanguageStore.targetOptions.first;
+ } catch (err) {
+ debugger(when: kDebugMode);
+ return _pangeaController.pLanguageStore.targetOptions.first;
+ }
+ }
+
+ Future setCurrentAnalyticsLang(LanguageModel lang) async {
+ await _pangeaController.pStoreService.save(
+ _analyticsSpaceLangKey,
+ lang.langCode,
+ local: true,
+ );
+ setState();
+ }
+
+ /// given an analytics event type and the current analytics language,
+ /// get the last time the user updated their analytics
Future myAnalyticsLastUpdated(String type) async {
- // given an analytics event type, get the last updated times
- // for each of the user's analytics rooms and return the most recent
- // Most Recent instead of the oldest because, for instance:
- // My last Spanish event was sent 3 days ago.
- // My last English event was sent 1 day ago.
- // When I go to check if the cached data is out of date, the cached item was set 2 days ago.
- // I know there’s new data available because the English update data (the most recent) is after the cache’s creation time.
- // So, I should update the cache.
final List analyticsRooms = _pangeaController
.matrixState.client.allMyAnalyticsRooms
.where((room) => room.isAnalyticsRoom)
.toList();
- final List lastUpdates = [];
+ final Map langCodeLastUpdates = {};
for (final Room analyticsRoom in analyticsRooms) {
+ final String? roomLang = analyticsRoom.madeForLang;
+ if (roomLang == null) continue;
final DateTime? lastUpdated = await analyticsRoom.analyticsLastUpdated(
type,
_pangeaController.matrixState.client.userID!,
);
if (lastUpdated != null) {
- lastUpdates.add(lastUpdated);
+ langCodeLastUpdates[roomLang] = lastUpdated;
}
}
- if (lastUpdates.isEmpty) return null;
- return lastUpdates.reduce(
+ if (langCodeLastUpdates.isEmpty) return null;
+ final String? l2Code =
+ _pangeaController.languageController.userL2?.langCode;
+ if (l2Code != null && langCodeLastUpdates.containsKey(l2Code)) {
+ return langCodeLastUpdates[l2Code];
+ }
+ return langCodeLastUpdates.values.reduce(
(check, mostRecent) => check.isAfter(mostRecent) ? check : mostRecent,
);
}
@@ -96,7 +128,6 @@ class AnalyticsController extends BaseController {
Future spaceAnalyticsLastUpdated(
String type,
Room space,
- String langCode,
) async {
// check if any students have recently updated their analytics
// if any have, then the cache needs to be updated
@@ -106,7 +137,7 @@ class AnalyticsController extends BaseController {
final List> lastUpdatedFutures = [];
for (final student in space.students) {
final Room? analyticsRoom = _pangeaController.matrixState.client
- .analyticsRoomLocal(langCode, student.id);
+ .analyticsRoomLocal(currentAnalyticsLang.langCode, student.id);
if (analyticsRoom == null) continue;
lastUpdatedFutures.add(
analyticsRoom.analyticsLastUpdated(
@@ -132,35 +163,37 @@ class AnalyticsController extends BaseController {
// private chat analytics to determine which children are already visible
// in the chat list
final Map> _lastFetchedHierarchies = {};
+
void setLatestHierarchy(String spaceId, GetSpaceHierarchyResponse resp) {
final List roomIds = resp.rooms.map((room) => room.roomId).toList();
_lastFetchedHierarchies[spaceId] = roomIds;
}
+ Future> getLatestSpaceHierarchy(String spaceId) async {
+ if (!_lastFetchedHierarchies.containsKey(spaceId)) {
+ final resp =
+ await _pangeaController.matrixState.client.getSpaceHierarchy(spaceId);
+ setLatestHierarchy(spaceId, resp);
+ }
+ return _lastFetchedHierarchies[spaceId] ?? [];
+ }
+
//////////////////////////// MESSAGE SUMMARY ANALYTICS ////////////////////////////
+ /// get all the summary analytics events for the current user
+ /// in the current language's analytics room
Future> mySummaryAnalytics() async {
- // gets all the summary analytics events for the user
- // since the current timespace's cut off date
- final analyticsRooms =
- _pangeaController.matrixState.client.allMyAnalyticsRooms;
+ final Room? analyticsRoom = _pangeaController.matrixState.client
+ .analyticsRoomLocal(currentAnalyticsLang.langCode);
+ if (analyticsRoom == null) return [];
- final List allEvents = [];
-
- // TODO switch to using list of futures
- for (final Room analyticsRoom in analyticsRooms) {
- final List? roomEvents =
- await analyticsRoom.getAnalyticsEvents(
- type: PangeaEventTypes.summaryAnalytics,
- since: currentAnalyticsTimeSpan.cutOffDate,
- userId: _pangeaController.matrixState.client.userID!,
- );
-
- allEvents.addAll(
- roomEvents?.cast() ?? [],
- );
- }
- return allEvents;
+ final List? roomEvents =
+ await analyticsRoom.getAnalyticsEvents(
+ type: PangeaEventTypes.summaryAnalytics,
+ since: currentAnalyticsTimeSpan.cutOffDate,
+ userId: _pangeaController.matrixState.client.userID!,
+ );
+ return roomEvents?.cast() ?? [];
}
Future> spaceMemberAnalytics(
@@ -168,9 +201,6 @@ class AnalyticsController extends BaseController {
) async {
// gets all the summary analytics events for the students
// in a space since the current timespace's cut off date
- final langCode = _pangeaController.languageController.activeL2Code(
- roomID: space.id,
- );
// ensure that all the space's events are loaded (mainly the for langCode)
// and that the participants are loaded
@@ -181,7 +211,7 @@ class AnalyticsController extends BaseController {
final List analyticsEvents = [];
for (final student in space.students) {
final Room? analyticsRoom = _pangeaController.matrixState.client
- .analyticsRoomLocal(langCode, student.id);
+ .analyticsRoomLocal(currentAnalyticsLang.langCode, student.id);
if (analyticsRoom != null) {
final List? roomEvents =
@@ -225,7 +255,8 @@ class AnalyticsController extends BaseController {
(e.defaultSelected.id == defaultSelected.id) &&
(e.defaultSelected.type == defaultSelected.type) &&
(e.selected?.id == selected?.id) &&
- (e.selected?.type == selected?.type),
+ (e.selected?.type == selected?.type) &&
+ (e.langCode == currentAnalyticsLang.langCode),
);
if (index != -1) {
@@ -253,6 +284,7 @@ class AnalyticsController extends BaseController {
chartAnalyticsModel: chartAnalyticsModel,
defaultSelected: defaultSelected,
selected: selected,
+ langCode: currentAnalyticsLang.langCode,
),
);
}
@@ -267,16 +299,16 @@ class AnalyticsController extends BaseController {
return filtered;
}
- List filterRoomAnalytics(
+ Future> filterRoomAnalytics(
List unfiltered,
String? roomID,
- ) {
+ ) async {
List filtered = [...unfiltered];
Room? room;
if (roomID != null) {
room = _pangeaController.matrixState.client.getRoomById(roomID);
if (room?.isSpace == true) {
- return filterSpaceAnalytics(unfiltered, roomID);
+ return await filterSpaceAnalytics(unfiltered, roomID);
}
}
@@ -295,16 +327,10 @@ class AnalyticsController extends BaseController {
Future> filterPrivateChatAnalytics(
List unfiltered,
- Room? space,
+ Room space,
) async {
- if (space != null && !_lastFetchedHierarchies.containsKey(space.id)) {
- final resp = await _pangeaController.matrixState.client
- .getSpaceHierarchy(space.id);
- setLatestHierarchy(space.id, resp);
- }
-
- final List privateChatIds = space?.allSpaceChildRoomIds ?? [];
- final List lastFetched = _lastFetchedHierarchies[space!.id] ?? [];
+ final List privateChatIds = space.allSpaceChildRoomIds;
+ final List lastFetched = await getLatestSpaceHierarchy(space.id);
for (final id in lastFetched) {
privateChatIds.removeWhere((e) => e == id);
}
@@ -324,19 +350,11 @@ class AnalyticsController extends BaseController {
return filtered;
}
- List filterSpaceAnalytics(
+ Future> filterSpaceAnalytics(
List unfiltered,
String spaceId,
- ) {
- final selectedSpace =
- _pangeaController.matrixState.client.getRoomById(spaceId);
- final List chatIds = selectedSpace?.spaceChildren
- .map((e) => e.roomId)
- .where((e) => e != null)
- .cast()
- .toList() ??
- [];
-
+ ) async {
+ final List chatIds = await getLatestSpaceHierarchy(spaceId);
List filtered =
List.from(unfiltered);
@@ -384,12 +402,15 @@ class AnalyticsController extends BaseController {
if (defaultSelected.type == AnalyticsEntryType.student) {
throw "private chat filtering not available for my analytics";
}
+ if (space == null) {
+ throw "space is null in filterAnalytics with selected type privateChats";
+ }
return await filterPrivateChatAnalytics(
unfilteredAnalytics,
space,
);
case AnalyticsEntryType.space:
- return filterSpaceAnalytics(unfilteredAnalytics, selected!.id);
+ return await filterSpaceAnalytics(unfilteredAnalytics, selected!.id);
default:
throw Exception("invalid filter type - ${selected?.type}");
}
@@ -405,7 +426,6 @@ class AnalyticsController extends BaseController {
// if the user is looking at space analytics, then fetch the space
Room? space;
- String? langCode;
if (defaultSelected.type == AnalyticsEntryType.space) {
space = _pangeaController.matrixState.client.getRoomById(
defaultSelected.id,
@@ -423,23 +443,7 @@ class AnalyticsController extends BaseController {
timeSpan: currentAnalyticsTimeSpan,
);
}
-
await space.postLoad();
- langCode = _pangeaController.languageController.activeL2Code(
- roomID: space.id,
- );
- if (langCode == null) {
- ErrorHandler.logError(
- m: "langCode missing in getAnalytics",
- data: {
- "space": space,
- },
- );
- return ChartAnalyticsModel(
- msgs: [],
- timeSpan: currentAnalyticsTimeSpan,
- );
- }
}
DateTime? lastUpdated;
@@ -456,7 +460,6 @@ class AnalyticsController extends BaseController {
lastUpdated = await spaceAnalyticsLastUpdated(
PangeaEventTypes.summaryAnalytics,
space!,
- langCode!,
);
}
@@ -467,8 +470,10 @@ class AnalyticsController extends BaseController {
lastUpdated: lastUpdated,
);
if (local != null && !forceUpdate) {
+ debugPrint("returning local analytics");
return local;
}
+ debugPrint("fetching new analytics");
// get all the relevant summary analytics events for the current timespan
final List summaryEvents =
@@ -515,20 +520,18 @@ class AnalyticsController extends BaseController {
//////////////////////////// CONSTRUCTS ////////////////////////////
Future> allMyConstructs() async {
- final List analyticsRooms =
- _pangeaController.matrixState.client.allMyAnalyticsRooms;
+ final Room? analyticsRoom = _pangeaController.matrixState.client
+ .analyticsRoomLocal(currentAnalyticsLang.langCode);
+ if (analyticsRoom == null) return [];
- final List allConstructs = [];
- for (final Room analyticsRoom in analyticsRooms) {
- final List? roomEvents =
- (await analyticsRoom.getAnalyticsEvents(
- type: PangeaEventTypes.construct,
- since: currentAnalyticsTimeSpan.cutOffDate,
- userId: _pangeaController.matrixState.client.userID!,
- ))
- ?.cast();
- allConstructs.addAll(roomEvents ?? []);
- }
+ final List? roomEvents =
+ (await analyticsRoom.getAnalyticsEvents(
+ type: PangeaEventTypes.construct,
+ since: currentAnalyticsTimeSpan.cutOffDate,
+ userId: _pangeaController.matrixState.client.userID!,
+ ))
+ ?.cast();
+ final List allConstructs = roomEvents ?? [];
final List adminSpaceRooms =
await _pangeaController.matrixState.client.teacherRoomIds;
@@ -548,24 +551,10 @@ class AnalyticsController extends BaseController {
) async {
await space.postLoad();
await space.requestParticipants();
- final String? langCode = _pangeaController.languageController.activeL2Code(
- roomID: space.id,
- );
-
- if (langCode == null) {
- ErrorHandler.logError(
- m: "langCode missing in allSpaceMemberConstructs",
- data: {
- "space": space,
- },
- );
- return [];
- }
-
final List constructEvents = [];
for (final student in space.students) {
final Room? analyticsRoom = _pangeaController.matrixState.client
- .analyticsRoomLocal(langCode, student.id);
+ .analyticsRoomLocal(currentAnalyticsLang.langCode, student.id);
if (analyticsRoom != null) {
final List? roomEvents =
(await analyticsRoom.getAnalyticsEvents(
@@ -614,31 +603,30 @@ class AnalyticsController extends BaseController {
return filtered;
}
- List filterPrivateChatConstructs(
+ Future> filterPrivateChatConstructs(
List unfilteredConstructs,
- Room parentSpace,
- ) {
- final List directChatIds = [];
+ Room space,
+ ) async {
+ final List privateChatIds = space.allSpaceChildRoomIds;
+ final List lastFetched = await getLatestSpaceHierarchy(space.id);
+ for (final id in lastFetched) {
+ privateChatIds.removeWhere((e) => e == id);
+ }
final List filtered =
List.from(unfilteredConstructs);
for (final construct in filtered) {
construct.content.uses.removeWhere(
- (use) => !directChatIds.contains(use.chatId),
+ (use) => !privateChatIds.contains(use.chatId),
);
}
return filtered;
}
- List filterSpaceConstructs(
+ Future> filterSpaceConstructs(
List unfilteredConstructs,
Room space,
- ) {
- final List chatIds = space.spaceChildren
- .map((e) => e.roomId)
- .where((e) => e != null)
- .cast()
- .toList();
-
+ ) async {
+ final List chatIds = await getLatestSpaceHierarchy(space.id);
final List filtered =
List.from(unfilteredConstructs);
@@ -653,7 +641,7 @@ class AnalyticsController extends BaseController {
List? getConstructsLocal({
required TimeSpan timeSpan,
- required ConstructType constructType,
+ required ConstructTypeEnum constructType,
required AnalyticsSelected defaultSelected,
AnalyticsSelected? selected,
DateTime? lastUpdated,
@@ -665,7 +653,8 @@ class AnalyticsController extends BaseController {
e.defaultSelected.id == defaultSelected.id &&
e.defaultSelected.type == defaultSelected.type &&
e.selected?.id == selected?.id &&
- e.selected?.type == selected?.type,
+ e.selected?.type == selected?.type &&
+ e.langCode == currentAnalyticsLang.langCode,
);
if (index > -1) {
@@ -680,7 +669,7 @@ class AnalyticsController extends BaseController {
}
void cacheConstructs({
- required ConstructType constructType,
+ required ConstructTypeEnum constructType,
required List events,
required AnalyticsSelected defaultSelected,
AnalyticsSelected? selected,
@@ -691,13 +680,14 @@ class AnalyticsController extends BaseController {
events: List.from(events),
defaultSelected: defaultSelected,
selected: selected,
+ langCode: currentAnalyticsLang.langCode,
);
_cachedConstructs.add(entry);
}
Future> getMyConstructs({
required AnalyticsSelected defaultSelected,
- required ConstructType constructType,
+ required ConstructTypeEnum constructType,
AnalyticsSelected? selected,
}) async {
final List unfilteredConstructs =
@@ -716,7 +706,7 @@ class AnalyticsController extends BaseController {
}
Future> getSpaceConstructs({
- required ConstructType constructType,
+ required ConstructTypeEnum constructType,
required Room space,
required AnalyticsSelected defaultSelected,
AnalyticsSelected? selected,
@@ -769,25 +759,25 @@ class AnalyticsController extends BaseController {
case AnalyticsEntryType.privateChats:
return defaultSelected.type == AnalyticsEntryType.student
? throw "private chat filtering not available for my analytics"
- : filterPrivateChatConstructs(unfilteredConstructs, space!);
+ : await filterPrivateChatConstructs(unfilteredConstructs, space!);
case AnalyticsEntryType.space:
- return filterSpaceConstructs(unfilteredConstructs, space!);
+ return await filterSpaceConstructs(unfilteredConstructs, space!);
default:
throw Exception("invalid filter type - ${selected?.type}");
}
}
Future?> getConstructs({
- required ConstructType constructType,
+ required ConstructTypeEnum constructType,
required AnalyticsSelected defaultSelected,
AnalyticsSelected? selected,
bool removeIT = true,
bool forceUpdate = false,
}) async {
+ debugPrint("getting constructs");
await _pangeaController.matrixState.client.roomsLoading;
Room? space;
- String? langCode;
if (defaultSelected.type == AnalyticsEntryType.space) {
space = _pangeaController.matrixState.client.getRoomById(
defaultSelected.id,
@@ -803,18 +793,6 @@ class AnalyticsController extends BaseController {
return [];
}
await space.postLoad();
- langCode = _pangeaController.languageController.activeL2Code(
- roomID: space.id,
- );
- if (langCode == null) {
- ErrorHandler.logError(
- m: "langCode missing in setConstructs",
- data: {
- "space": space,
- },
- );
- return [];
- }
}
DateTime? lastUpdated;
@@ -831,7 +809,6 @@ class AnalyticsController extends BaseController {
lastUpdated = await spaceAnalyticsLastUpdated(
PangeaEventTypes.construct,
space!,
- langCode!,
);
}
@@ -843,8 +820,10 @@ class AnalyticsController extends BaseController {
lastUpdated: lastUpdated,
);
if (local != null && !forceUpdate) {
+ debugPrint("returning local constructs");
return local;
}
+ debugPrint("fetching new constructs");
final filteredConstructs = space == null
? await getMyConstructs(
@@ -884,6 +863,7 @@ class AnalyticsController extends BaseController {
}
abstract class CacheEntry {
+ final String langCode;
final TimeSpan timeSpan;
final AnalyticsSelected defaultSelected;
AnalyticsSelected? selected;
@@ -892,6 +872,7 @@ abstract class CacheEntry {
CacheEntry({
required this.timeSpan,
required this.defaultSelected,
+ required this.langCode,
this.selected,
}) {
_createdAt = DateTime.now();
@@ -917,24 +898,26 @@ abstract class CacheEntry {
}
class ConstructCacheEntry extends CacheEntry {
- final ConstructType type;
+ final ConstructTypeEnum type;
final List events;
ConstructCacheEntry({
required this.type,
required this.events,
required super.timeSpan,
+ required super.langCode,
required super.defaultSelected,
super.selected,
});
}
class AnalyticsCacheModel extends CacheEntry {
- ChartAnalyticsModel chartAnalyticsModel;
+ final ChartAnalyticsModel chartAnalyticsModel;
AnalyticsCacheModel({
required this.chartAnalyticsModel,
required super.timeSpan,
+ required super.langCode,
required super.defaultSelected,
super.selected,
});
diff --git a/lib/pangea/controllers/message_data_controller.dart b/lib/pangea/controllers/message_data_controller.dart
index 4a0668880..7b3a3e6d2 100644
--- a/lib/pangea/controllers/message_data_controller.dart
+++ b/lib/pangea/controllers/message_data_controller.dart
@@ -176,14 +176,20 @@ class MessageDataController extends BaseController {
required String target,
required Room room,
}) async {
+ if (_pangeaController.languageController.userL2 == null ||
+ _pangeaController.languageController.userL1 == null) {
+ ErrorHandler.logError(
+ e: "userL1 or userL2 is null in _getPangeaRepresentation",
+ s: StackTrace.current,
+ );
+ return null;
+ }
final req = FullTextTranslationRequestModel(
text: text,
tgtLang: target,
srcLang: source,
- userL2:
- _pangeaController.languageController.activeL2Code(roomID: room.id)!,
- userL1:
- _pangeaController.languageController.activeL1Code(roomID: room.id)!,
+ userL2: _pangeaController.languageController.userL2!.langCode,
+ userL1: _pangeaController.languageController.userL1!.langCode,
);
try {
diff --git a/lib/pangea/controllers/my_analytics_controller.dart b/lib/pangea/controllers/my_analytics_controller.dart
index 77a644863..535af6b8b 100644
--- a/lib/pangea/controllers/my_analytics_controller.dart
+++ b/lib/pangea/controllers/my_analytics_controller.dart
@@ -3,11 +3,11 @@ import 'dart:developer';
import 'package:fluffychat/pangea/constants/local.key.dart';
import 'package:fluffychat/pangea/constants/pangea_event_types.dart';
-import 'package:fluffychat/pangea/controllers/base_controller.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
-import 'package:fluffychat/pangea/models/analytics/analytics_event.dart';
-import 'package:fluffychat/pangea/models/analytics/analytics_model.dart';
+import 'package:fluffychat/pangea/matrix_event_wrappers/practice_activity_record_event.dart';
+import 'package:fluffychat/pangea/models/analytics/constructs_model.dart';
+import 'package:fluffychat/pangea/models/analytics/summary_analytics_model.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:flutter/foundation.dart';
import 'package:matrix/matrix.dart';
@@ -15,53 +15,72 @@ import 'package:matrix/matrix.dart';
import '../extensions/client_extension/client_extension.dart';
import '../extensions/pangea_room_extension/pangea_room_extension.dart';
-// controls the sending of analytics events
-class MyAnalyticsController extends BaseController {
+/// handles the processing of analytics for
+/// 1) messages sent by the user and
+/// 2) constructs used by the user, both in sending messages and doing practice activities
+class MyAnalyticsController {
late PangeaController _pangeaController;
Timer? _updateTimer;
+
+ /// the max number of messages that will be cached before
+ /// an automatic update is triggered
final int _maxMessagesCached = 10;
+
+ /// the number of minutes before an automatic update is triggered
final int _minutesBeforeUpdate = 5;
+ /// the time since the last update that will trigger an automatic update
+ final Duration _timeSinceUpdate = const Duration(days: 1);
+
MyAnalyticsController(PangeaController pangeaController) {
_pangeaController = pangeaController;
}
- // adds the listener that handles when to run automatic updates
- // to analytics - either after a certain number of messages sent/
- // received or after a certain amount of time without an update
- Future addEventsListener() async {
- final Client client = _pangeaController.matrixState.client;
+ /// adds the listener that handles when to run automatic updates
+ /// to analytics - either after a certain number of messages sent
+ /// received or after a certain amount of time [_timeSinceUpdate] without an update
+ Future initialize() async {
+ final lastUpdated = await _refreshAnalyticsIfOutdated();
- // if analytics haven't been updated in the last day, update them
- DateTime? lastUpdated = await _pangeaController.analytics
- .myAnalyticsLastUpdated(PangeaEventTypes.summaryAnalytics);
- final DateTime yesterday = DateTime.now().subtract(const Duration(days: 1));
- if (lastUpdated?.isBefore(yesterday) ?? true) {
- debugPrint("analytics out-of-date, updating");
- await updateAnalytics();
- lastUpdated = await _pangeaController.analytics
- .myAnalyticsLastUpdated(PangeaEventTypes.summaryAnalytics);
- }
-
- client.onSync.stream
+ // listen for new messages and updateAnalytics timer
+ // we are doing this in an attempt to update analytics when activitiy is low
+ // both in messages sent by this client and other clients that you're connected with
+ // doesn't account for messages sent by other clients that you're not connected with
+ _client.onSync.stream
.where((SyncUpdate update) => update.rooms?.join != null)
.listen((update) {
updateAnalyticsTimer(update, lastUpdated);
});
}
- // given an update from sync stream, check if the update contains
- // messages for which analytics will be saved. If so, reset the timer
- // and add the event ID to the cache of un-added event IDs
+ /// If analytics haven't been updated in the last day, update them
+ Future _refreshAnalyticsIfOutdated() async {
+ DateTime? lastUpdated = await _pangeaController.analytics
+ .myAnalyticsLastUpdated(PangeaEventTypes.summaryAnalytics);
+ final DateTime yesterday = DateTime.now().subtract(_timeSinceUpdate);
+
+ if (lastUpdated?.isBefore(yesterday) ?? true) {
+ debugPrint("analytics out-of-date, updating");
+ await updateAnalytics();
+ lastUpdated = await _pangeaController.analytics
+ .myAnalyticsLastUpdated(PangeaEventTypes.summaryAnalytics);
+ }
+ return lastUpdated;
+ }
+
+ Client get _client => _pangeaController.matrixState.client;
+
+ /// Given an update from sync stream, check if the update contains
+ /// messages for which analytics will be saved. If so, reset the timer
+ /// and add the event ID to the cache of un-added event IDs
void updateAnalyticsTimer(SyncUpdate update, DateTime? lastUpdated) {
for (final entry in update.rooms!.join!.entries) {
- final Room room =
- _pangeaController.matrixState.client.getRoomById(entry.key)!;
+ final Room room = _client.getRoomById(entry.key)!;
// get the new events in this sync that are messages
final List? events = entry.value.timeline?.events
?.map((event) => Event.fromMatrixEvent(event, room))
- .where((event) => eventHasAnalytics(event, lastUpdated))
+ .where((event) => hasUserAnalyticsToCache(event, lastUpdated))
.toList();
// add their event IDs to the cache of un-added event IDs
@@ -81,8 +100,9 @@ class MyAnalyticsController extends BaseController {
}
// checks if event from sync update is a message that should have analytics
- bool eventHasAnalytics(Event event, DateTime? lastUpdated) {
- return (lastUpdated == null || event.originServerTs.isAfter(lastUpdated)) &&
+ bool hasUserAnalyticsToCache(Event event, DateTime? lastUpdated) {
+ return event.senderId == _client.userID &&
+ (lastUpdated == null || event.originServerTs.isAfter(lastUpdated)) &&
event.type == EventTypes.Message &&
event.messageType == MessageTypes.Text &&
!(event.eventId.contains("web") &&
@@ -157,6 +177,7 @@ class MyAnalyticsController extends BaseController {
_updateCompleter = Completer();
try {
await _updateAnalytics();
+ clearMessagesSinceUpdate();
} catch (err, s) {
ErrorHandler.logError(
e: err,
@@ -169,170 +190,135 @@ class MyAnalyticsController extends BaseController {
}
}
+ String? get userL2 => _pangeaController.languageController.activeL2Code();
+
+ /// top level analytics sending function. Gather recent messages and activity records,
+ /// convert them into the correct formats, and send them to the analytics room
Future _updateAnalytics() async {
- // top level analytics sending function. Send analytics
- // for each type of analytics event
- // to each of the applicable analytics rooms
- clearMessagesSinceUpdate();
-
- // fetch a list of all the chats that the user is studying
- // and a list of all the spaces in which the user is studying
- await setStudentChats();
- await setStudentSpaces();
-
- // get all the analytics rooms that the user has
- // and create any missing analytics rooms (if the user is studying
- // in a class but doesn't have an analytics room for that class's L2)
- final List analyticsRooms =
- _pangeaController.matrixState.client.allMyAnalyticsRooms;
- analyticsRooms.addAll(await createMissingAnalyticsRooms());
-
- // finally, send an analytics event for each analytics room and
- // each type of analytics event
- for (final Room analyticsRoom in analyticsRooms) {
- for (final String type in AnalyticsEvent.analyticsEventTypes) {
- await sendAnalyticsEvent(analyticsRoom, type);
- }
- }
- }
-
- Future sendAnalyticsEvent(
- Room analyticsRoom,
- String type,
- ) async {
- // given an analytics room for a language and a type of analytics event
- // gathers all the relevant data and sends it to the analytics room
-
- // get the language code for the analytics room
- final String? langCode = analyticsRoom.madeForLang;
- if (langCode == null) {
- ErrorHandler.logError(
- e: "no lang code found for analytics room: ${analyticsRoom.id}",
- s: StackTrace.current,
- );
+ // if missing important info, don't send analytics
+ if (userL2 == null || _client.userID == null) {
+ debugger(when: kDebugMode);
return;
}
- // get the last time an analytics event of this type was sent to this room
- final DateTime? lastUpdated = await analyticsRoom.analyticsLastUpdated(
- type,
- _pangeaController.matrixState.client.userID!,
+ // analytics room for the user and current target language
+ final Room analyticsRoom = await _client.getMyAnalyticsRoom(userL2!);
+
+ // get the last time analytics were updated for this room
+ final DateTime? l2AnalyticsLastUpdated =
+ await analyticsRoom.analyticsLastUpdated(
+ PangeaEventTypes.summaryAnalytics,
+ _client.userID!,
);
- // each type of analytics event has a format for storing per-message data
- // for SummaryAnalytics events, this is RecentMessageRecord
- // for Construct events, this is OneConstructUse
- // analyticsContent is a list of these formatted data
- final List analyticsContent = [];
+ // all chats in which user is a student
+ final List chats = _client.rooms
+ .where((room) => !room.isSpace && !room.isAnalyticsRoom)
+ .toList();
- for (final Room chat in _studentChats) {
- // for each chat the student studies in, check if the langCode
- // matches the langCode of the analytics room
- final String? chatLangCode =
- _pangeaController.languageController.activeL2Code(roomID: chat.id);
- if (chatLangCode != langCode) continue;
+ // get the recent message events and activity records for each chat
+ final List>> recentMsgFutures = [];
+ final List>> recentActivityFutures = [];
+ for (final Room chat in chats) {
+ recentMsgFutures.add(
+ chat.getEventsBySender(
+ type: EventTypes.Message,
+ sender: _client.userID!,
+ since: l2AnalyticsLastUpdated,
+ ),
+ );
+ recentActivityFutures.add(
+ chat.getEventsBySender(
+ type: PangeaEventTypes.activityRecord,
+ sender: _client.userID!,
+ since: l2AnalyticsLastUpdated,
+ ),
+ );
+ }
+ final List> recentMsgs =
+ (await Future.wait(recentMsgFutures)).toList();
+ final List recentActivityRecords =
+ (await Future.wait(recentActivityFutures))
+ .expand((e) => e)
+ .map((event) => PracticeActivityRecordEvent(event: event))
+ .toList();
- // get messages the logged in user has sent in all chats
- // since the last analytics event was sent
- List? recentMsgs;
- try {
- recentMsgs = await chat.myMessageEventsInChat(
- since: lastUpdated,
- );
- } catch (err) {
- debugPrint("failed to fetch messages for chat ${chat.id}");
- continue;
- }
+ // get the timelines for each chat
+ final List> timelineFutures = [];
+ for (final chat in chats) {
+ timelineFutures.add(chat.getTimeline());
+ }
+ final List timelines = await Future.wait(timelineFutures);
+ final Map timelineMap =
+ Map.fromIterables(chats.map((e) => e.id), timelines);
- if (lastUpdated != null) {
- recentMsgs.removeWhere(
- (msg) => msg.event.originServerTs.isBefore(lastUpdated),
- );
- }
-
- // then format that data into analytics data and add the formatted
- // data to the list of analyticsContent
- analyticsContent.addAll(
- AnalyticsModel.formatAnalyticsContent(recentMsgs, type),
+ //convert into PangeaMessageEvents
+ final List> recentPangeaMessageEvents = [];
+ for (final (index, eventList) in recentMsgs.indexed) {
+ recentPangeaMessageEvents.add(
+ eventList
+ .map(
+ (event) => PangeaMessageEvent(
+ event: event,
+ timeline: timelines[index],
+ ownMessage: true,
+ ),
+ )
+ .toList(),
);
}
- // send the analytics data to the analytics room
- // if there is no data to send, don't send an event,
- // unless no events have been sent yet. In that case, send an event
- // with no data to indicate that the the system checked for data
- // and found none, so the system doesn't repeatedly check for data
- if (analyticsContent.isEmpty && lastUpdated != null) return;
- await AnalyticsEvent.sendEvent(
- analyticsRoom,
- type,
- analyticsContent,
- );
- }
+ final List allRecentMessages =
+ recentPangeaMessageEvents.expand((e) => e).toList();
- // on the off chance that the user is in a class but doesn't have an analytics
- // room for the target language of that class, create the analytics room(s)
- Future> createMissingAnalyticsRooms() async {
- List targetLangs = [];
- final String? userL2 = _pangeaController.languageController.activeL2Code();
- if (userL2 != null) targetLangs.add(userL2);
- final List spaceL2s = studentSpaces
- .map(
- (space) => _pangeaController.languageController.activeL2Code(
- roomID: space.id,
- ),
- )
- .toList();
- targetLangs.addAll(spaceL2s.where((l2) => l2 != null).cast());
- targetLangs = targetLangs.toSet().toList();
- for (final String langCode in targetLangs) {
- await _pangeaController.matrixState.client.getMyAnalyticsRoom(langCode);
+ final List summaryContent =
+ SummaryAnalyticsModel.formatSummaryContent(allRecentMessages);
+ // if there's new content to be sent, or if lastUpdated hasn't been
+ // set yet for this room, send the analytics events
+ if (summaryContent.isNotEmpty || l2AnalyticsLastUpdated == null) {
+ await analyticsRoom.sendSummaryAnalyticsEvent(
+ summaryContent,
+ );
}
- return _pangeaController.matrixState.client.allMyAnalyticsRooms;
- }
- List _studentChats = [];
-
- Future setStudentChats() async {
- final List teacherRoomIds =
- await _pangeaController.matrixState.client.teacherRoomIds;
- _studentChats = _pangeaController.matrixState.client.rooms
- .where(
- (r) =>
- !r.isSpace &&
- !r.isAnalyticsRoom &&
- !teacherRoomIds.contains(r.id),
- )
- .toList();
- setState(data: _studentChats);
- }
-
- List get studentChats {
- try {
- if (_studentChats.isNotEmpty) return _studentChats;
- setStudentChats();
- return _studentChats;
- } catch (err) {
- debugger(when: kDebugMode);
- return [];
+ // get constructs for messages
+ final List recentConstructUses = [];
+ for (final PangeaMessageEvent message in allRecentMessages) {
+ recentConstructUses.addAll(message.allConstructUses);
}
- }
- List _studentSpaces = [];
+ // get constructs for practice activities
+ final List>> constructFutures = [];
+ for (final PracticeActivityRecordEvent activity in recentActivityRecords) {
+ final Timeline? timeline = timelineMap[activity.event.roomId!];
+ if (timeline == null) {
+ debugger(when: kDebugMode);
+ ErrorHandler.logError(
+ m: "PracticeActivityRecordEvent has null timeline",
+ data: activity.event.toJson(),
+ );
+ continue;
+ }
+ constructFutures.add(activity.uses(timeline));
+ }
+ final List> constructLists =
+ await Future.wait(constructFutures);
- Future setStudentSpaces() async {
- _studentSpaces = await _pangeaController
- .matrixState.client.classesAndExchangesImStudyingIn;
- }
+ recentConstructUses.addAll(constructLists.expand((e) => e));
- List get studentSpaces {
- try {
- if (_studentSpaces.isNotEmpty) return _studentSpaces;
- setStudentSpaces();
- return _studentSpaces;
- } catch (err) {
- debugger(when: kDebugMode);
- return [];
+ //TODO - confirm that this is the correct construct content
+ // debugger(
+ // when: kDebugMode,
+ // );
+ // ; debugger(
+ // when: kDebugMode &&
+ // (allRecentMessages.isNotEmpty || recentActivityRecords.isNotEmpty),
+ // );
+
+ if (recentConstructUses.isNotEmpty) {
+ await analyticsRoom.sendConstructsEvent(
+ recentConstructUses,
+ );
}
}
}
diff --git a/lib/pangea/controllers/pangea_controller.dart b/lib/pangea/controllers/pangea_controller.dart
index 1d1a00c05..7fbf91fd0 100644
--- a/lib/pangea/controllers/pangea_controller.dart
+++ b/lib/pangea/controllers/pangea_controller.dart
@@ -1,3 +1,4 @@
+import 'dart:async';
import 'dart:developer';
import 'dart:math';
@@ -12,6 +13,8 @@ import 'package:fluffychat/pangea/controllers/local_settings.dart';
import 'package:fluffychat/pangea/controllers/message_data_controller.dart';
import 'package:fluffychat/pangea/controllers/my_analytics_controller.dart';
import 'package:fluffychat/pangea/controllers/permissions_controller.dart';
+import 'package:fluffychat/pangea/controllers/practice_activity_generation_controller.dart';
+import 'package:fluffychat/pangea/controllers/practice_activity_record_controller.dart';
import 'package:fluffychat/pangea/controllers/speech_to_text_controller.dart';
import 'package:fluffychat/pangea/controllers/subscription_controller.dart';
import 'package:fluffychat/pangea/controllers/text_to_speech_controller.dart';
@@ -53,6 +56,8 @@ class PangeaController {
late TextToSpeechController textToSpeech;
late SpeechToTextController speechToText;
late LanguageDetectionController languageDetection;
+ late PracticeActivityRecordController activityRecordController;
+ late PracticeGenerationController practiceGenerationController;
///store Services
late PLocalStore pStoreService;
@@ -101,6 +106,8 @@ class PangeaController {
textToSpeech = TextToSpeechController(this);
speechToText = SpeechToTextController(this);
languageDetection = LanguageDetectionController(this);
+ activityRecordController = PracticeActivityRecordController(this);
+ practiceGenerationController = PracticeGenerationController();
PAuthGaurd.pController = this;
}
diff --git a/lib/pangea/controllers/permissions_controller.dart b/lib/pangea/controllers/permissions_controller.dart
index f83903473..363accf8e 100644
--- a/lib/pangea/controllers/permissions_controller.dart
+++ b/lib/pangea/controllers/permissions_controller.dart
@@ -3,7 +3,7 @@ import 'package:fluffychat/pangea/constants/pangea_event_types.dart';
import 'package:fluffychat/pangea/controllers/base_controller.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
-import 'package:fluffychat/pangea/models/class_model.dart';
+import 'package:fluffychat/pangea/models/space_model.dart';
import 'package:fluffychat/pangea/models/user_model.dart';
import 'package:fluffychat/pangea/utils/p_extension.dart';
import 'package:matrix/matrix.dart';
@@ -117,27 +117,4 @@ class PermissionsController extends BaseController {
return isToolEnabled(ToolSetting.interactiveTranslator, room) &&
isToolEnabled(ToolSetting.interactiveGrammar, room);
}
-
- // bool get showChatListStartChatFloatingActionButton {
- // //for now, I'm turning off chat button when not in the context of a clas
- // //it will still be possible to private chat outside of a class
- // //need to investigate if private chats can be put in a space. i suppose they can
- // //if so, do we want that?
- // try {
- // if (_pangeaController.classController.activeClass == null) return false;
-
- // // final isExchange =
- // // (_pangeaController.classController.activeClass?.isExchange ?? false);
- // const isExchange = false;
- // final regular = (canUserPrivateChat() || canUserGroupChat());
- // final inExchange =
- // (canUserPrivateChatExchanges() || canUserGroupChatExchanges());
- // final theAnswer = isExchange ? inExchange : regular;
- // // debugger(when: kDebugMode && !theAnswer);
- // return theAnswer;
- // } catch (e, s) {
- // ErrorHandler.logError(e: e, s: s);
- // return false;
- // }
- // }
}
diff --git a/lib/pangea/controllers/practice_activity_generation_controller.dart b/lib/pangea/controllers/practice_activity_generation_controller.dart
new file mode 100644
index 000000000..9b7f6b66e
--- /dev/null
+++ b/lib/pangea/controllers/practice_activity_generation_controller.dart
@@ -0,0 +1,102 @@
+import 'dart:async';
+
+import 'package:fluffychat/pangea/constants/pangea_event_types.dart';
+import 'package:fluffychat/pangea/enum/activity_type_enum.dart';
+import 'package:fluffychat/pangea/enum/construct_type_enum.dart';
+import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
+import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
+import 'package:fluffychat/pangea/matrix_event_wrappers/practice_activity_event.dart';
+import 'package:fluffychat/pangea/models/practice_activities.dart/multiple_choice_activity_model.dart';
+import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_model.dart';
+import 'package:matrix/matrix.dart';
+
+/// Represents an item in the completion cache.
+class _RequestCacheItem {
+ PracticeActivityRequest req;
+
+ Future practiceActivityEvent;
+
+ _RequestCacheItem({
+ required this.req,
+ required this.practiceActivityEvent,
+ });
+}
+
+/// Controller for handling activity completions.
+class PracticeGenerationController {
+ static final Map _cache = {};
+ Timer? _cacheClearTimer;
+
+ PracticeGenerationController() {
+ _initializeCacheClearing();
+ }
+
+ void _initializeCacheClearing() {
+ const duration = Duration(minutes: 2);
+ _cacheClearTimer = Timer.periodic(duration, (Timer t) => _clearCache());
+ }
+
+ void _clearCache() {
+ _cache.clear();
+ }
+
+ void dispose() {
+ _cacheClearTimer?.cancel();
+ }
+
+ Future _sendAndPackageEvent(
+ PracticeActivityModel model,
+ PangeaMessageEvent pangeaMessageEvent,
+ ) async {
+ final Event? activityEvent = await pangeaMessageEvent.room.sendPangeaEvent(
+ content: model.toJson(),
+ parentEventId: pangeaMessageEvent.eventId,
+ type: PangeaEventTypes.pangeaActivity,
+ );
+
+ if (activityEvent == null) {
+ return null;
+ }
+
+ return PracticeActivityEvent(
+ event: activityEvent,
+ timeline: pangeaMessageEvent.timeline,
+ );
+ }
+
+ Future getPracticeActivity(
+ PracticeActivityRequest req,
+ PangeaMessageEvent event,
+ ) async {
+ final int cacheKey = req.hashCode;
+
+ if (_cache.containsKey(cacheKey)) {
+ return _cache[cacheKey]!.practiceActivityEvent;
+ } else {
+ //TODO - send request to server/bot, either via API or via event of type pangeaActivityReq
+ // for now, just make and send the event from the client
+ final Future eventFuture =
+ _sendAndPackageEvent(dummyModel(event), event);
+
+ _cache[cacheKey] =
+ _RequestCacheItem(req: req, practiceActivityEvent: eventFuture);
+
+ return _cache[cacheKey]!.practiceActivityEvent;
+ }
+ }
+
+ PracticeActivityModel dummyModel(PangeaMessageEvent event) =>
+ PracticeActivityModel(
+ tgtConstructs: [
+ ConstructIdentifier(lemma: "be", type: ConstructTypeEnum.vocab),
+ ],
+ activityType: ActivityTypeEnum.multipleChoice,
+ langCode: event.messageDisplayLangCode,
+ msgId: event.eventId,
+ multipleChoice: MultipleChoice(
+ question: "What is a synonym for 'happy'?",
+ choices: ["sad", "angry", "joyful", "tired"],
+ answer: "joyful",
+ ),
+ );
+}
diff --git a/lib/pangea/controllers/practice_activity_record_controller.dart b/lib/pangea/controllers/practice_activity_record_controller.dart
new file mode 100644
index 000000000..b075fa553
--- /dev/null
+++ b/lib/pangea/controllers/practice_activity_record_controller.dart
@@ -0,0 +1,94 @@
+import 'dart:async';
+import 'dart:developer';
+
+import 'package:fluffychat/pangea/constants/pangea_event_types.dart';
+import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
+import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
+import 'package:fluffychat/pangea/matrix_event_wrappers/practice_activity_event.dart';
+import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_record_model.dart';
+import 'package:fluffychat/pangea/utils/error_handler.dart';
+import 'package:flutter/foundation.dart';
+import 'package:matrix/matrix.dart';
+
+/// Represents an item in the completion cache.
+class _RecordCacheItem {
+ PracticeActivityRecordModel data;
+
+ Future recordEvent;
+
+ _RecordCacheItem({required this.data, required this.recordEvent});
+}
+
+/// Controller for handling activity completions.
+class PracticeActivityRecordController {
+ static final Map _cache = {};
+ late final PangeaController _pangeaController;
+ Timer? _cacheClearTimer;
+
+ PracticeActivityRecordController(this._pangeaController) {
+ _initializeCacheClearing();
+ }
+
+ void _initializeCacheClearing() {
+ const duration = Duration(minutes: 2);
+ _cacheClearTimer = Timer.periodic(duration, (Timer t) => _clearCache());
+ }
+
+ void _clearCache() {
+ _cache.clear();
+ }
+
+ void dispose() {
+ _cacheClearTimer?.cancel();
+ }
+
+ /// Sends a practice activity record to the server and returns the corresponding event.
+ ///
+ /// The [recordModel] parameter is the model representing the practice activity record.
+ /// The [practiceActivityEvent] parameter is the event associated with the practice activity.
+ /// Note that the system will send a new event if the model has changed in any way ie it is
+ /// a new completion of the practice activity. However, it will cache previous sends to ensure
+ /// that opening and closing of the widget does not result in multiple sends of the same data.
+ /// It allows checks the data to make sure that it contains responses to the practice activity
+ /// and does not represent a blank record with no actual completion to be saved.
+ ///
+ /// Returns a [Future] that completes with the corresponding [Event] object.
+ Future send(
+ PracticeActivityRecordModel recordModel,
+ PracticeActivityEvent practiceActivityEvent,
+ ) async {
+ final int cacheKey = recordModel.hashCode;
+
+ if (recordModel.responses.isEmpty) {
+ return null;
+ }
+
+ if (_cache.containsKey(cacheKey)) {
+ return _cache[cacheKey]!.recordEvent;
+ } else {
+ final Future eventFuture = practiceActivityEvent.event.room
+ .sendPangeaEvent(
+ content: recordModel.toJson(),
+ parentEventId: practiceActivityEvent.event.eventId,
+ type: PangeaEventTypes.activityRecord,
+ )
+ .catchError((e) {
+ debugger(when: kDebugMode);
+ ErrorHandler.logError(
+ e: e,
+ s: StackTrace.current,
+ data: {
+ 'recordModel': recordModel.toJson(),
+ 'practiceActivityEvent': practiceActivityEvent.event.toJson(),
+ },
+ );
+ return null;
+ });
+
+ _cache[cacheKey] =
+ _RecordCacheItem(data: recordModel, recordEvent: eventFuture);
+
+ return _cache[cacheKey]!.recordEvent;
+ }
+ }
+}
diff --git a/lib/pangea/controllers/space_rules_edit_controller.dart b/lib/pangea/controllers/space_rules_edit_controller.dart
index a13fd9219..6142e273f 100644
--- a/lib/pangea/controllers/space_rules_edit_controller.dart
+++ b/lib/pangea/controllers/space_rules_edit_controller.dart
@@ -2,7 +2,7 @@ import 'package:fluffychat/pangea/constants/pangea_event_types.dart';
import 'package:matrix/matrix.dart';
import '../extensions/pangea_room_extension/pangea_room_extension.dart';
-import '../models/class_model.dart';
+import '../models/space_model.dart';
class RoomRulesEditController {
final Room? room;
diff --git a/lib/pangea/controllers/user_controller.dart b/lib/pangea/controllers/user_controller.dart
index c7a35b3e8..873397969 100644
--- a/lib/pangea/controllers/user_controller.dart
+++ b/lib/pangea/controllers/user_controller.dart
@@ -1,7 +1,7 @@
import 'dart:async';
import 'package:collection/collection.dart';
-import 'package:fluffychat/pangea/constants/language_keys.dart';
+import 'package:fluffychat/pangea/constants/language_constants.dart';
import 'package:fluffychat/pangea/constants/model_keys.dart';
import 'package:fluffychat/pangea/controllers/base_controller.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
@@ -123,6 +123,7 @@ class UserController extends BaseController {
: null;
final bool? autoPlay = migratedProfileInfo(MatrixProfile.autoPlayMessages);
+ final bool? itAutoPlay = migratedProfileInfo(MatrixProfile.itAutoPlay);
final bool? trial = migratedProfileInfo(MatrixProfile.activatedFreeTrial);
final bool? interactiveTranslator =
migratedProfileInfo(MatrixProfile.interactiveTranslator);
@@ -142,6 +143,7 @@ class UserController extends BaseController {
await updateMatrixProfile(
dateOfBirth: dob,
autoPlayMessages: autoPlay,
+ itAutoPlay: itAutoPlay,
activatedFreeTrial: trial,
interactiveTranslator: interactiveTranslator,
interactiveGrammar: interactiveGrammar,
@@ -223,6 +225,7 @@ class UserController extends BaseController {
Future updateMatrixProfile({
String? dateOfBirth,
bool? autoPlayMessages,
+ bool? itAutoPlay,
bool? activatedFreeTrial,
bool? interactiveTranslator,
bool? interactiveGrammar,
@@ -251,6 +254,12 @@ class UserController extends BaseController {
autoPlayMessages,
);
}
+ if (itAutoPlay != null) {
+ await _pangeaController.pStoreService.save(
+ MatrixProfile.itAutoPlay.title,
+ itAutoPlay,
+ );
+ }
if (activatedFreeTrial != null) {
await _pangeaController.pStoreService.save(
MatrixProfile.activatedFreeTrial.title,
diff --git a/lib/pangea/controllers/word_net_controller.dart b/lib/pangea/controllers/word_net_controller.dart
index ce8324ebe..a67941465 100644
--- a/lib/pangea/controllers/word_net_controller.dart
+++ b/lib/pangea/controllers/word_net_controller.dart
@@ -1,7 +1,7 @@
import 'package:collection/collection.dart';
import 'package:http/http.dart' as http;
-import 'package:fluffychat/pangea/constants/language_keys.dart';
+import 'package:fluffychat/pangea/constants/language_constants.dart';
import 'package:fluffychat/pangea/repo/word_repo.dart';
import '../models/word_data_model.dart';
import 'base_controller.dart';
diff --git a/lib/pangea/enum/activity_type_enum.dart b/lib/pangea/enum/activity_type_enum.dart
new file mode 100644
index 000000000..d429aa038
--- /dev/null
+++ b/lib/pangea/enum/activity_type_enum.dart
@@ -0,0 +1,16 @@
+enum ActivityTypeEnum { multipleChoice, freeResponse, listening, speaking }
+
+extension ActivityTypeExtension on ActivityTypeEnum {
+ String get string {
+ switch (this) {
+ case ActivityTypeEnum.multipleChoice:
+ return 'multiple_choice';
+ case ActivityTypeEnum.freeResponse:
+ return 'free_response';
+ case ActivityTypeEnum.listening:
+ return 'listening';
+ case ActivityTypeEnum.speaking:
+ return 'speaking';
+ }
+ }
+}
diff --git a/lib/pangea/enum/assistance_state_enum.dart b/lib/pangea/enum/assistance_state_enum.dart
new file mode 100644
index 000000000..6d3a853da
--- /dev/null
+++ b/lib/pangea/enum/assistance_state_enum.dart
@@ -0,0 +1,43 @@
+// assistance state is, user has not typed a message, user has typed a message and IGC has not run,
+// IGC is running, IGC has run and there are remaining steps (either IT or IGC), or all steps are done
+import 'package:fluffychat/config/app_config.dart';
+import 'package:fluffychat/pangea/constants/colors.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_gen/gen_l10n/l10n.dart';
+
+enum AssistanceState {
+ noMessage,
+ notFetched,
+ fetching,
+ fetched,
+ complete,
+}
+
+extension AssistanceStateExtension on AssistanceState {
+ Color stateColor(context) {
+ switch (this) {
+ case AssistanceState.noMessage:
+ case AssistanceState.notFetched:
+ case AssistanceState.fetching:
+ return Theme.of(context).colorScheme.primary;
+ case AssistanceState.fetched:
+ return PangeaColors.igcError;
+ case AssistanceState.complete:
+ return AppConfig.success;
+ }
+ }
+
+ String tooltip(L10n l10n) {
+ switch (this) {
+ case AssistanceState.noMessage:
+ case AssistanceState.notFetched:
+ return l10n.runGrammarCorrection;
+ case AssistanceState.fetching:
+ return "";
+ case AssistanceState.fetched:
+ return l10n.grammarCorrectionFailed;
+ case AssistanceState.complete:
+ return l10n.grammarCorrectionComplete;
+ }
+ }
+}
diff --git a/lib/pangea/enum/bar_chart_view_enum.dart b/lib/pangea/enum/bar_chart_view_enum.dart
index 3fe812634..aba0652af 100644
--- a/lib/pangea/enum/bar_chart_view_enum.dart
+++ b/lib/pangea/enum/bar_chart_view_enum.dart
@@ -29,15 +29,4 @@ extension BarChartViewSelectionExtension on BarChartViewSelection {
return Icons.spellcheck_outlined;
}
}
-
- String get route {
- switch (this) {
- case BarChartViewSelection.messages:
- return 'messages';
- // case BarChartViewSelection.vocab:
- // return 'vocab';
- case BarChartViewSelection.grammar:
- return 'errors';
- }
- }
}
diff --git a/lib/pangea/enum/construct_type_enum.dart b/lib/pangea/enum/construct_type_enum.dart
index 2a7d5583d..7db7f9cd5 100644
--- a/lib/pangea/enum/construct_type_enum.dart
+++ b/lib/pangea/enum/construct_type_enum.dart
@@ -1,30 +1,30 @@
-enum ConstructType {
+enum ConstructTypeEnum {
grammar,
vocab,
}
-extension ConstructExtension on ConstructType {
+extension ConstructExtension on ConstructTypeEnum {
String get string {
switch (this) {
- case ConstructType.grammar:
+ case ConstructTypeEnum.grammar:
return 'grammar';
- case ConstructType.vocab:
+ case ConstructTypeEnum.vocab:
return 'vocab';
}
}
}
class ConstructTypeUtil {
- static ConstructType fromString(String? string) {
+ static ConstructTypeEnum fromString(String? string) {
switch (string) {
case 'g':
case 'grammar':
- return ConstructType.grammar;
+ return ConstructTypeEnum.grammar;
case 'v':
case 'vocab':
- return ConstructType.vocab;
+ return ConstructTypeEnum.vocab;
default:
- return ConstructType.vocab;
+ return ConstructTypeEnum.vocab;
}
}
}
diff --git a/lib/pangea/enum/construct_use_type_enum.dart b/lib/pangea/enum/construct_use_type_enum.dart
new file mode 100644
index 000000000..0e3c52bbb
--- /dev/null
+++ b/lib/pangea/enum/construct_use_type_enum.dart
@@ -0,0 +1,93 @@
+import 'package:flutter/material.dart';
+
+enum ConstructUseTypeEnum {
+ /// produced in chat by user, igc was run, and we've judged it to be a correct use
+ wa,
+
+ /// produced in chat by user, igc was run, and we've judged it to be a incorrect use
+ /// Note: if the IGC match is ignored, this is not counted as an incorrect use
+ ga,
+
+ /// produced in chat by user and igc was not run
+ unk,
+
+ /// selected correctly in IT flow
+ corIt,
+
+ /// encountered as IT distractor and correctly ignored it
+ ignIt,
+
+ /// encountered as it distractor and selected it
+ incIt,
+
+ /// encountered in igc match and ignored match
+ ignIGC,
+
+ /// selected correctly in IGC flow
+ corIGC,
+
+ /// encountered as distractor in IGC flow and selected it
+ incIGC,
+
+ /// selected correctly in practice activity flow
+ corPA,
+
+ /// was target construct in practice activity but user did not select correctly
+ incPA,
+}
+
+extension ConstructUseTypeExtension on ConstructUseTypeEnum {
+ String get string {
+ switch (this) {
+ case ConstructUseTypeEnum.ga:
+ return 'ga';
+ case ConstructUseTypeEnum.wa:
+ return 'wa';
+ case ConstructUseTypeEnum.corIt:
+ return 'corIt';
+ case ConstructUseTypeEnum.incIt:
+ return 'incIt';
+ case ConstructUseTypeEnum.ignIt:
+ return 'ignIt';
+ case ConstructUseTypeEnum.ignIGC:
+ return 'ignIGC';
+ case ConstructUseTypeEnum.corIGC:
+ return 'corIGC';
+ case ConstructUseTypeEnum.incIGC:
+ return 'incIGC';
+ case ConstructUseTypeEnum.unk:
+ return 'unk';
+ case ConstructUseTypeEnum.corPA:
+ return 'corPA';
+ case ConstructUseTypeEnum.incPA:
+ return 'incPA';
+ }
+ }
+
+ IconData get icon {
+ switch (this) {
+ case ConstructUseTypeEnum.ga:
+ return Icons.check;
+ case ConstructUseTypeEnum.wa:
+ return Icons.thumb_up_sharp;
+ case ConstructUseTypeEnum.corIt:
+ return Icons.check;
+ case ConstructUseTypeEnum.incIt:
+ return Icons.close;
+ case ConstructUseTypeEnum.ignIt:
+ return Icons.close;
+ case ConstructUseTypeEnum.ignIGC:
+ return Icons.close;
+ case ConstructUseTypeEnum.corIGC:
+ return Icons.check;
+ case ConstructUseTypeEnum.incIGC:
+ return Icons.close;
+ case ConstructUseTypeEnum.corPA:
+ return Icons.check;
+ case ConstructUseTypeEnum.incPA:
+ return Icons.close;
+ case ConstructUseTypeEnum.unk:
+ return Icons.help;
+ }
+ }
+}
diff --git a/lib/pangea/enum/instructions_enum.dart b/lib/pangea/enum/instructions_enum.dart
new file mode 100644
index 000000000..c684d5b14
--- /dev/null
+++ b/lib/pangea/enum/instructions_enum.dart
@@ -0,0 +1,45 @@
+import 'package:fluffychat/utils/platform_infos.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_gen/gen_l10n/l10n.dart';
+
+enum InstructionsEnum {
+ itInstructions,
+ clickMessage,
+ blurMeansTranslate,
+ tooltipInstructions,
+ speechToText,
+}
+
+extension Copy on InstructionsEnum {
+ String title(BuildContext context) {
+ switch (this) {
+ case InstructionsEnum.itInstructions:
+ return L10n.of(context)!.itInstructionsTitle;
+ case InstructionsEnum.clickMessage:
+ return L10n.of(context)!.clickMessageTitle;
+ case InstructionsEnum.blurMeansTranslate:
+ return L10n.of(context)!.blurMeansTranslateTitle;
+ case InstructionsEnum.tooltipInstructions:
+ return L10n.of(context)!.tooltipInstructionsTitle;
+ case InstructionsEnum.speechToText:
+ return L10n.of(context)!.hintTitle;
+ }
+ }
+
+ String body(BuildContext context) {
+ switch (this) {
+ case InstructionsEnum.itInstructions:
+ return L10n.of(context)!.itInstructionsBody;
+ case InstructionsEnum.clickMessage:
+ return L10n.of(context)!.clickMessageBody;
+ case InstructionsEnum.blurMeansTranslate:
+ return L10n.of(context)!.blurMeansTranslateBody;
+ case InstructionsEnum.speechToText:
+ return L10n.of(context)!.speechToTextBody;
+ case InstructionsEnum.tooltipInstructions:
+ return PlatformInfos.isMobile
+ ? L10n.of(context)!.tooltipInstructionsMobileBody
+ : L10n.of(context)!.tooltipInstructionsBrowserBody;
+ }
+ }
+}
diff --git a/lib/pangea/enum/message_mode_enum.dart b/lib/pangea/enum/message_mode_enum.dart
index 25948d23b..58753e5b5 100644
--- a/lib/pangea/enum/message_mode_enum.dart
+++ b/lib/pangea/enum/message_mode_enum.dart
@@ -1,9 +1,16 @@
+import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
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 }
+enum MessageMode {
+ translation,
+ definition,
+ speechToText,
+ textToSpeech,
+ practiceActivity
+}
extension MessageModeExtension on MessageMode {
IconData get icon {
@@ -17,6 +24,8 @@ extension MessageModeExtension on MessageMode {
//TODO change icon for audio messages
case MessageMode.definition:
return Icons.book;
+ case MessageMode.practiceActivity:
+ return Symbols.fitness_center;
default:
return Icons.error; // Icon to indicate an error or unsupported mode
}
@@ -32,6 +41,8 @@ extension MessageModeExtension on MessageMode {
return L10n.of(context)!.speechToTextTooltip;
case MessageMode.definition:
return L10n.of(context)!.definitions;
+ case MessageMode.practiceActivity:
+ return L10n.of(context)!.practice;
default:
return L10n.of(context)!
.oopsSomethingWentWrong; // Title to indicate an error or unsupported mode
@@ -48,6 +59,8 @@ extension MessageModeExtension on MessageMode {
return L10n.of(context)!.speechToTextTooltip;
case MessageMode.definition:
return L10n.of(context)!.define;
+ case MessageMode.practiceActivity:
+ return L10n.of(context)!.practice;
default:
return L10n.of(context)!
.oopsSomethingWentWrong; // Title to indicate an error or unsupported mode
@@ -58,6 +71,7 @@ extension MessageModeExtension on MessageMode {
switch (this) {
case MessageMode.translation:
case MessageMode.textToSpeech:
+ case MessageMode.practiceActivity:
case MessageMode.definition:
return event.messageType == MessageTypes.Text;
case MessageMode.speechToText:
@@ -66,4 +80,29 @@ extension MessageModeExtension on MessageMode {
return true;
}
}
+
+ Color? iconColor(
+ PangeaMessageEvent event,
+ MessageMode? currentMode,
+ BuildContext context,
+ ) {
+ final bool isPracticeActivity = this == MessageMode.practiceActivity;
+ final bool practicing = currentMode == MessageMode.practiceActivity;
+ final bool practiceEnabled = event.hasUncompletedActivity;
+
+ // if this is the practice activity icon, and there's no practice activities available,
+ // and the current mode is not practice, return lower opacity color.
+ if (isPracticeActivity && !practicing && !practiceEnabled) {
+ return Theme.of(context).iconTheme.color?.withOpacity(0.5);
+ }
+
+ // if this is not a practice activity icon, and practice activities are available,
+ // then return lower opacity color if the current mode is practice.
+ if (!isPracticeActivity && practicing && practiceEnabled) {
+ return Theme.of(context).iconTheme.color?.withOpacity(0.5);
+ }
+
+ // if this is the current mode, return primary color.
+ return currentMode == this ? Theme.of(context).colorScheme.primary : null;
+ }
}
diff --git a/lib/pangea/enum/span_data_type.dart b/lib/pangea/enum/span_data_type.dart
index 5e4fdf8cb..38315fd50 100644
--- a/lib/pangea/enum/span_data_type.dart
+++ b/lib/pangea/enum/span_data_type.dart
@@ -1,5 +1,5 @@
+import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
-
import 'package:flutter_gen/gen_l10n/l10n.dart';
enum SpanDataTypeEnum {
@@ -32,7 +32,10 @@ extension SpanDataTypeEnumExt on SpanDataTypeEnum {
case SpanDataTypeEnum.correction:
return L10n.of(context)!.correctionDefaultPrompt;
case SpanDataTypeEnum.itStart:
- return L10n.of(context)!.needsItMessage;
+ return L10n.of(context)!.needsItMessage(
+ MatrixState.pangeaController.languageController.userL2?.displayName ??
+ L10n.of(context)!.targetLanguage,
+ );
}
}
}
diff --git a/lib/pangea/enum/use_type.dart b/lib/pangea/enum/use_type.dart
index 771b26220..56a4fa3b0 100644
--- a/lib/pangea/enum/use_type.dart
+++ b/lib/pangea/enum/use_type.dart
@@ -1,8 +1,6 @@
import 'package:flutter/material.dart';
-
import 'package:flutter_gen/gen_l10n/l10n.dart';
-import '../models/choreo_record.dart';
import '../utils/bot_style.dart';
enum UseType { wa, ta, ga, un }
@@ -93,17 +91,3 @@ extension UseTypeMethods on UseType {
}
}
}
-
-UseType useTypeCalculator(
- ChoreoRecord? choreoRecord,
-) {
- if (choreoRecord == null) {
- return UseType.un;
- } else if (choreoRecord.includedIT) {
- return UseType.ta;
- } else if (choreoRecord.hasAcceptedMatches) {
- return UseType.ga;
- } else {
- return UseType.wa;
- }
-}
diff --git a/lib/pangea/extensions/client_extension/classes_and_exchanges_extension.dart b/lib/pangea/extensions/client_extension/classes_and_exchanges_extension.dart
deleted file mode 100644
index af1df62a0..000000000
--- a/lib/pangea/extensions/client_extension/classes_and_exchanges_extension.dart
+++ /dev/null
@@ -1,83 +0,0 @@
-part of "client_extension.dart";
-
-extension ClassesAndExchangesClientExtension on Client {
- List get _classes => rooms.where((e) => e.isPangeaClass).toList();
-
- List get _classesImTeaching => rooms
- .where(
- (e) =>
- e.isPangeaClass &&
- e.ownPowerLevel == ClassDefaultValues.powerLevelOfAdmin,
- )
- .toList();
-
- Future> get _classesAndExchangesImTeaching async {
- final allSpaces = rooms.where((room) => room.isSpace);
- for (final Room space in allSpaces) {
- if (space.getState(EventTypes.RoomPowerLevels) == null) {
- await space.postLoad();
- }
- }
-
- final spaces = rooms
- .where(
- (e) =>
- (e.isPangeaClass || e.isExchange) &&
- e.ownPowerLevel == ClassDefaultValues.powerLevelOfAdmin,
- )
- .toList();
- return spaces;
- }
-
- List get _classesImIn => rooms
- .where(
- (e) =>
- e.isPangeaClass &&
- e.ownPowerLevel < ClassDefaultValues.powerLevelOfAdmin,
- )
- .toList();
-
- Future> get _classesAndExchangesImStudyingIn async {
- final List joinedSpaces = rooms
- .where(
- (room) => room.isSpace && room.membership == Membership.join,
- )
- .toList();
-
- for (final Room space in joinedSpaces) {
- if (space.getState(EventTypes.RoomPowerLevels) == null) {
- await space.postLoad();
- }
- }
-
- final spaces = rooms
- .where(
- (e) =>
- (e.isPangeaClass || e.isExchange) &&
- e.ownPowerLevel < ClassDefaultValues.powerLevelOfAdmin,
- )
- .toList();
- return spaces;
- }
-
- List get _classesAndExchangesImIn =>
- rooms.where((e) => e.isPangeaClass || e.isExchange).toList();
-
- Future get _lastUpdatedRoomRules async =>
- (await _classesAndExchangesImTeaching)
- .where((space) => space.rulesUpdatedAt != null)
- .sorted(
- (a, b) => b.rulesUpdatedAt!.compareTo(a.rulesUpdatedAt!),
- )
- .firstOrNull
- ?.pangeaRoomRules;
-
- ClassSettingsModel? get _lastUpdatedClassSettings => classesImTeaching
- .where((space) => space.classSettingsUpdatedAt != null)
- .sorted(
- (a, b) =>
- b.classSettingsUpdatedAt!.compareTo(a.classSettingsUpdatedAt!),
- )
- .firstOrNull
- ?.classSettings;
-}
diff --git a/lib/pangea/extensions/client_extension/client_analytics_extension.dart b/lib/pangea/extensions/client_extension/client_analytics_extension.dart
index d7c97e38c..fde4d8e54 100644
--- a/lib/pangea/extensions/client_extension/client_analytics_extension.dart
+++ b/lib/pangea/extensions/client_extension/client_analytics_extension.dart
@@ -123,7 +123,7 @@ extension AnalyticsClientExtension on Client {
// Allows teachers to join analytics rooms without being invited
Future _joinAnalyticsRoomsInAllSpaces() async {
final List joinFutures = [];
- for (final Room space in (await _classesAndExchangesImTeaching)) {
+ for (final Room space in (await _spacesImTeaching)) {
joinFutures.add(space.joinAnalyticsRoomsInSpace());
}
await Future.wait(joinFutures);
@@ -154,4 +154,17 @@ extension AnalyticsClientExtension on Client {
await _joinInvitedAnalyticsRooms();
await _joinAnalyticsRoomsInAllSpaces();
}
+
+ Future