Merge branch 'main' into 375-introduce-bot-custom-mode

This commit is contained in:
WilsonLe 2024-06-25 11:56:00 -04:00
commit f62f2a86a6
96 changed files with 4330 additions and 3586 deletions

View file

@ -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": {}
@ -3174,7 +3153,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 +3164,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 +3628,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 +3683,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 +3695,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 +3708,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 +3754,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 +3763,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",
@ -4072,5 +4048,16 @@
"@knockRestricted": {},
"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."
"roomAddedToSpace": "Room(s) have been added to the selected space.",
"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"
}

View file

@ -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": {}
},
@ -3815,7 +3780,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 +4260,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 +4267,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 +4278,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 +4318,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 +4339,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 +4405,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": {
@ -4675,5 +4619,86 @@
"tooltipInstructionsTitle": "¿No sabes para qué sirve?",
"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"
"buildTranslation": "Construye tu traducción a partir de las opciones anteriores",
"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": {
"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"
}

870
assets/l10n/intl_fil 2.arb Normal file
View file

@ -0,0 +1,870 @@
{
"remove": "Tanggalin",
"@remove": {
"type": "text",
"placeholders": {}
},
"importNow": "I-import ngayon",
"@importNow": {},
"importEmojis": "I-import ang mga Emoji",
"@importEmojis": {},
"importFromZipFile": "Mag-import mula sa .zip file",
"@importFromZipFile": {},
"exportEmotePack": "I-export ang Emote pack bilang .zip",
"@exportEmotePack": {},
"accept": "Tanggapin",
"@accept": {
"type": "text",
"placeholders": {}
},
"account": "Account",
"@account": {
"type": "text",
"placeholders": {}
},
"addEmail": "Magdagdag ng email",
"@addEmail": {
"type": "text",
"placeholders": {}
},
"confirmMatrixId": "Paki-kumpirma ang iyong Matrix ID para burahin ang iyong account.",
"@confirmMatrixId": {},
"addChatDescription": "Magdagdag ng deskripsyon ng chat...",
"@addChatDescription": {},
"admin": "Admin",
"@admin": {
"type": "text",
"placeholders": {}
},
"alias": "alyas",
"@alias": {
"type": "text",
"placeholders": {}
},
"all": "Lahat",
"@all": {
"type": "text",
"placeholders": {}
},
"allChats": "Lahat ng mga chat",
"@allChats": {
"type": "text",
"placeholders": {}
},
"commandHint_googly": "Magpadala ng mga googly eye",
"@commandHint_googly": {},
"commandHint_cuddle": "Magpadala ng yakap",
"@commandHint_cuddle": {},
"cuddleContent": "Niyakap ka ni {senderName}",
"@cuddleContent": {
"type": "text",
"placeholders": {
"senderName": {}
}
},
"hugContent": "Niyakap ka ni {senderName}",
"@hugContent": {
"type": "text",
"placeholders": {
"senderName": {}
}
},
"anyoneCanJoin": "Pwede sumali ang anumang tao",
"@anyoneCanJoin": {
"type": "text",
"placeholders": {}
},
"appLock": "Lock ng app",
"@appLock": {
"type": "text",
"placeholders": {}
},
"archive": "Archive",
"@archive": {
"type": "text",
"placeholders": {}
},
"areGuestsAllowedToJoin": "Pwede ba sumali ang mga bisita",
"@areGuestsAllowedToJoin": {
"type": "text",
"placeholders": {}
},
"areYouSure": "Sigurado ka?",
"@areYouSure": {
"type": "text",
"placeholders": {}
},
"askVerificationRequest": "Tanggapin ang hiling ng verification mula sa {username}?",
"@askVerificationRequest": {
"type": "text",
"placeholders": {
"username": {}
}
},
"autoplayImages": "Awtomatikong i-play ang mga gumagalaw na sticker at emote",
"@autoplayImages": {
"type": "text",
"placeholder": {}
},
"sendTypingNotifications": "Ipadala ang mga typing notification",
"@sendTypingNotifications": {},
"blockDevice": "I-block ang Device",
"@blockDevice": {
"type": "text",
"placeholders": {}
},
"blocked": "Na-block",
"@blocked": {
"type": "text",
"placeholders": {}
},
"changeDeviceName": "Palitan ang pangalan ng device",
"@changeDeviceName": {
"type": "text",
"placeholders": {}
},
"changedTheChatAvatar": "Pinalitan ni {username} ang avatar ng chat",
"@changedTheChatAvatar": {
"type": "text",
"placeholders": {
"username": {}
}
},
"changedTheGuestAccessRules": "Pinalitan ni {username} ang mga tuntunin sa pag-access ng bisita",
"@changedTheGuestAccessRules": {
"type": "text",
"placeholders": {
"username": {}
}
},
"changedTheHistoryVisibility": "Pinalitan ni {username} ang kakayahan ng pagkikita ng history",
"@changedTheHistoryVisibility": {
"type": "text",
"placeholders": {
"username": {}
}
},
"changedTheHistoryVisibilityTo": "Pinalitan ni {username} ang kakayahan ng pagkikita ng history sa: {rules}",
"@changedTheHistoryVisibilityTo": {
"type": "text",
"placeholders": {
"username": {},
"rules": {}
}
},
"changedTheRoomAliases": "Pinalitan ni {username} ang mga alias ng room",
"@changedTheRoomAliases": {
"type": "text",
"placeholders": {
"username": {}
}
},
"changePassword": "Palitan ang password",
"@changePassword": {
"type": "text",
"placeholders": {}
},
"changeYourAvatar": "Palitan ang iyong avatar",
"@changeYourAvatar": {
"type": "text",
"placeholders": {}
},
"channelCorruptedDecryptError": "Nasira ang encryption",
"@channelCorruptedDecryptError": {
"type": "text",
"placeholders": {}
},
"chat": "Chat",
"@chat": {
"type": "text",
"placeholders": {}
},
"chatBackup": "Pag-backup ng chat",
"@chatBackup": {
"type": "text",
"placeholders": {}
},
"chatDetails": "Mga detalye ng chat",
"@chatDetails": {
"type": "text",
"placeholders": {}
},
"chatHasBeenAddedToThisSpace": "Nadagdag ang chat sa space na ito",
"@chatHasBeenAddedToThisSpace": {},
"chats": "Mga Chat",
"@chats": {
"type": "text",
"placeholders": {}
},
"chooseAStrongPassword": "Pumili ng malakas na password",
"@chooseAStrongPassword": {
"type": "text",
"placeholders": {}
},
"clearArchive": "I-clear ang archive",
"@clearArchive": {},
"close": "Isara",
"@close": {
"type": "text",
"placeholders": {}
},
"commandHint_markasgroup": "Markahan bilang grupo",
"@commandHint_markasgroup": {},
"commandHint_ban": "Pagbawalan ang ibinigay na user sa room na ito",
"@commandHint_ban": {
"type": "text",
"description": "Usage hint for the command /ban"
},
"repeatPassword": "Ulitin ang password",
"@repeatPassword": {},
"notAnImage": "Hindi isang file na larawan.",
"@notAnImage": {},
"replace": "Palitan",
"@replace": {},
"about": "Tungkol sa",
"@about": {
"type": "text",
"placeholders": {}
},
"acceptedTheInvitation": "👍 Tinanggap ni {username} ang imbitasyon",
"@acceptedTheInvitation": {
"type": "text",
"placeholders": {
"username": {}
}
},
"activatedEndToEndEncryption": "🔐 Na-activate ni {username} ang end to end encryption",
"@activatedEndToEndEncryption": {
"type": "text",
"placeholders": {
"username": {}
}
},
"supposedMxid": "Dapat ito ay {mxid}",
"@supposedMxid": {
"type": "text",
"placeholders": {
"mxid": {}
}
},
"addToSpace": "Idagdag sa space",
"@addToSpace": {},
"commandHint_hug": "Magpadala ng yakap",
"@commandHint_hug": {},
"googlyEyesContent": "Nagpadala si {senderName} ng googly eyes",
"@googlyEyesContent": {
"type": "text",
"placeholders": {
"senderName": {}
}
},
"answeredTheCall": "Sinagot ni {senderName} ang tawag",
"@answeredTheCall": {
"type": "text",
"placeholders": {
"senderName": {}
}
},
"areYouSureYouWantToLogout": "Sigurado kang gusto mong mag-log out?",
"@areYouSureYouWantToLogout": {
"type": "text",
"placeholders": {}
},
"askSSSSSign": "Para i-sign ang isa pang tao, pakilagay ang iyong secure store passphrase o recovery key.",
"@askSSSSSign": {
"type": "text",
"placeholders": {}
},
"badServerLoginTypesException": "Ang homeserver ay sinusuportahan ang sumusunod na uri ng login:\n{serverVersions}\nNgunit sinusuportahan lang ng app ang:\n{supportedVersions}",
"@badServerLoginTypesException": {
"type": "text",
"placeholders": {
"serverVersions": {},
"supportedVersions": {}
}
},
"sendOnEnter": "Ipadala sa pagpindot ng enter",
"@sendOnEnter": {},
"badServerVersionsException": "Ang homeserver ay sinusuportahan ang mga Spec bersyon:\n{serverVersions}\nNgunit sinusuportahan lang ng app ang {supportedVersions}",
"@badServerVersionsException": {
"type": "text",
"placeholders": {
"serverVersions": {},
"supportedVersions": {}
}
},
"banFromChat": "Pagbawalan sa chat",
"@banFromChat": {
"type": "text",
"placeholders": {}
},
"banned": "Pinagbawalan",
"@banned": {
"type": "text",
"placeholders": {}
},
"botMessages": "Mga mensahe ng bot",
"@botMessages": {
"type": "text",
"placeholders": {}
},
"cancel": "Kanselahin",
"@cancel": {
"type": "text",
"placeholders": {}
},
"bannedUser": "Pinagbawalan ni {username} si {targetName}",
"@bannedUser": {
"type": "text",
"placeholders": {
"username": {},
"targetName": {}
}
},
"cantOpenUri": "Hindi mabuksan ang URI na {uri}",
"@cantOpenUri": {
"type": "text",
"placeholders": {
"uri": {}
}
},
"changedTheJoinRules": "Pinalitan ni {username} ang mga tuntunin sa pagsali",
"@changedTheJoinRules": {
"type": "text",
"placeholders": {
"username": {}
}
},
"changedTheJoinRulesTo": "Pinalitan ni {username} ang mga tuntunin sa pagsali sa: {joinRules}",
"@changedTheJoinRulesTo": {
"type": "text",
"placeholders": {
"username": {},
"joinRules": {}
}
},
"changedTheChatDescriptionTo": "Pinalitan ni {username} ang deskripsyon ng chat sa: '{description}'",
"@changedTheChatDescriptionTo": {
"type": "text",
"placeholders": {
"username": {},
"description": {}
}
},
"changedTheProfileAvatar": "Pinalitan ni {username} ang kanilang avatar",
"@changedTheProfileAvatar": {
"type": "text",
"placeholders": {
"username": {}
}
},
"changedTheChatNameTo": "Pinalitan ni {username} ang pangalan ng chat sa: '{chatname}'",
"@changedTheChatNameTo": {
"type": "text",
"placeholders": {
"username": {},
"chatname": {}
}
},
"changedTheRoomInvitationLink": "Pinalitan ni {username} ang link ng imbitasyon",
"@changedTheRoomInvitationLink": {
"type": "text",
"placeholders": {
"username": {}
}
},
"changeTheHomeserver": "Palitan ang homeserver",
"@changeTheHomeserver": {
"type": "text",
"placeholders": {}
},
"changeTheme": "Palitan ang iyong istilio",
"@changeTheme": {
"type": "text",
"placeholders": {}
},
"changedTheChatPermissions": "Pinalitan ni {username} ang mga pahintulot ng chat",
"@changedTheChatPermissions": {
"type": "text",
"placeholders": {
"username": {}
}
},
"changeTheNameOfTheGroup": "Palitan ng pangalan ng grupo",
"@changeTheNameOfTheGroup": {
"type": "text",
"placeholders": {}
},
"changedTheDisplaynameTo": "Pinalitan ni {username} ang kanilang displayname sa: '{displayname}'",
"@changedTheDisplaynameTo": {
"type": "text",
"placeholders": {
"username": {},
"displayname": {}
}
},
"yourChatBackupHasBeenSetUp": "Na-set up na ang iyong chat backup.",
"@yourChatBackupHasBeenSetUp": {},
"chatBackupDescription": "Naka-secure ang iyong mga lumang mensahe gamit ng recovery key. Siguraduhing hindi mo ito mawalan.",
"@chatBackupDescription": {
"type": "text",
"placeholders": {}
},
"commandHint_markasdm": "Markahan bilang direktang mensahe na room para sa ibinigay na Matrix ID",
"@commandHint_markasdm": {},
"changedTheGuestAccessRulesTo": "Pinalitan ni {username} ang mga tuntunin sa pag-access ng bisita sa: {rules}",
"@changedTheGuestAccessRulesTo": {
"type": "text",
"placeholders": {
"username": {},
"rules": {}
}
},
"commandHint_clearcache": "I-clear ang cache",
"@commandHint_clearcache": {
"type": "text",
"description": "Usage hint for the command /clearcache"
},
"commandHint_discardsession": "Iwaksi ang sesyon",
"@commandHint_discardsession": {
"type": "text",
"description": "Usage hint for the command /discardsession"
},
"commandHint_create": "Gumawa ng walang lamang group chat\nGumamit ng --no-encryption para i-disable ang encryption",
"@commandHint_create": {
"type": "text",
"description": "Usage hint for the command /create"
},
"configureChat": "I-configure ang chat",
"@configureChat": {
"type": "text",
"placeholders": {}
},
"confirm": "Kumpirmahin",
"@confirm": {
"type": "text",
"placeholders": {}
},
"compareNumbersMatch": "Paki-kumpara ang mga numero",
"@compareNumbersMatch": {
"type": "text",
"placeholders": {}
},
"copiedToClipboard": "Kinopya sa clipboard",
"@copiedToClipboard": {
"type": "text",
"placeholders": {}
},
"copy": "Kopyahin",
"@copy": {
"type": "text",
"placeholders": {}
},
"copyToClipboard": "Kopyahin sa clipboard",
"@copyToClipboard": {
"type": "text",
"placeholders": {}
},
"countParticipants": "{count} mga kasali",
"@countParticipants": {
"type": "text",
"placeholders": {
"count": {}
}
},
"createdTheChat": "💬 Ginawa ni {username} ang chat",
"@createdTheChat": {
"type": "text",
"placeholders": {
"username": {}
}
},
"createGroup": "Gumawa ng grupo",
"@createGroup": {},
"createNewSpace": "Bagong space",
"@createNewSpace": {
"type": "text",
"placeholders": {}
},
"currentlyActive": "Kasalukuyang aktibo",
"@currentlyActive": {
"type": "text",
"placeholders": {}
},
"darkTheme": "Madilim",
"@darkTheme": {
"type": "text",
"placeholders": {}
},
"displaynameHasBeenChanged": "Pinalitan na ang display name",
"@displaynameHasBeenChanged": {
"type": "text",
"placeholders": {}
},
"directChats": "Mga Direktang Chat",
"@directChats": {
"type": "text",
"placeholders": {}
},
"allRooms": "Lahat ng Mga Group Chat",
"@allRooms": {
"type": "text",
"placeholders": {}
},
"downloadFile": "I-download ang file",
"@downloadFile": {
"type": "text",
"placeholders": {}
},
"editBlockedServers": "I-edit ang mga naka-block na server",
"@editBlockedServers": {
"type": "text",
"placeholders": {}
},
"chatPermissions": "Mga pahintulot ng chat",
"@chatPermissions": {},
"editDisplayname": "I-edit ang display name",
"@editDisplayname": {
"type": "text",
"placeholders": {}
},
"editRoomAliases": "I-edit ang mga alyas ng room",
"@editRoomAliases": {
"type": "text",
"placeholders": {}
},
"edit": "I-edit",
"@edit": {
"type": "text",
"placeholders": {}
},
"editRoomAvatar": "I-edit ang avatar ng room",
"@editRoomAvatar": {
"type": "text",
"placeholders": {}
},
"emoteExists": "Umiiral na ang emote!",
"@emoteExists": {
"type": "text",
"placeholders": {}
},
"emptyChat": "Walang lamang chat",
"@emptyChat": {
"type": "text",
"placeholders": {}
},
"enableEncryption": "I-enable ang encryption",
"@enableEncryption": {
"type": "text",
"placeholders": {}
},
"encryption": "Pag-encrypt",
"@encryption": {
"type": "text",
"placeholders": {}
},
"encrypted": "Naka-encrypt",
"@encrypted": {
"type": "text",
"placeholders": {}
},
"encryptionNotEnabled": "Hindi naka-enable ang encryption",
"@encryptionNotEnabled": {
"type": "text",
"placeholders": {}
},
"everythingReady": "Handa na ang lahat!",
"@everythingReady": {
"type": "text",
"placeholders": {}
},
"appLockDescription": "I-lock ang app kapag hindi ginagamit sa pamamagitan ng pin code",
"@appLockDescription": {},
"commandHint_dm": "Magsimula ng direktong chat\nGumamit ng --no-encryptiom para i-disable ang encryption",
"@commandHint_dm": {
"type": "text",
"description": "Usage hint for the command /dm"
},
"commandHint_html": "Magpadala ng HTML-formatted na text",
"@commandHint_html": {
"type": "text",
"description": "Usage hint for the command /html"
},
"commandHint_invite": "Imbitahan ang ibinigay na user sa room na ito",
"@commandHint_invite": {
"type": "text",
"description": "Usage hint for the command /invite"
},
"commandHint_join": "Sumali sa ibinigay na room",
"@commandHint_join": {
"type": "text",
"description": "Usage hint for the command /join"
},
"commandHint_kick": "Tanggalin ang ibinigay na user sa room na ito",
"@commandHint_kick": {
"type": "text",
"description": "Usage hint for the command /kick"
},
"commandHint_leave": "Umalis sa room na ito",
"@commandHint_leave": {
"type": "text",
"description": "Usage hint for the command /leave"
},
"commandHint_me": "Ilarawan ang iyong sarili",
"@commandHint_me": {
"type": "text",
"description": "Usage hint for the command /me"
},
"commandHint_myroomavatar": "Ilapat ang iyong larawan para sa room na ito (bilang mxc-uri)",
"@commandHint_myroomavatar": {
"type": "text",
"description": "Usage hint for the command /myroomavatar"
},
"commandHint_myroomnick": "Ilapat ang iyong display name para sa room na ito",
"@commandHint_myroomnick": {
"type": "text",
"description": "Usage hint for the command /myroomnick"
},
"commandHint_op": "Ilapat ang level ng lakas sa ibinigay na user (default: 50)",
"@commandHint_op": {
"type": "text",
"description": "Usage hint for the command /op"
},
"commandHint_react": "Magpadala ng reply bilang reaksyon",
"@commandHint_react": {
"type": "text",
"description": "Usage hint for the command /react"
},
"commandHint_send": "Magpadala ng text",
"@commandHint_send": {
"type": "text",
"description": "Usage hint for the command /send"
},
"commandHint_unban": "I-unban ang ibinigay na user sa room na ito",
"@commandHint_unban": {
"type": "text",
"description": "Usage hint for the command /unban"
},
"commandInvalid": "Hindi wastong command",
"@commandInvalid": {
"type": "text"
},
"compareEmojiMatch": "Paki-kumpara ang mga emoji",
"@compareEmojiMatch": {
"type": "text",
"placeholders": {}
},
"connect": "Kumonekta",
"@connect": {
"type": "text",
"placeholders": {}
},
"containsDisplayName": "Naglalaman ng display name",
"@containsDisplayName": {
"type": "text",
"placeholders": {}
},
"create": "Gumawa",
"@create": {
"type": "text",
"placeholders": {}
},
"dateAndTimeOfDay": "{date}, {timeOfDay}",
"@dateAndTimeOfDay": {
"type": "text",
"placeholders": {
"date": {},
"timeOfDay": {}
}
},
"dateWithoutYear": "{month}/{day}",
"@dateWithoutYear": {
"type": "text",
"placeholders": {
"month": {},
"day": {}
}
},
"dateWithYear": "{month}/{day}/{year}",
"@dateWithYear": {
"type": "text",
"placeholders": {
"year": {},
"month": {},
"day": {}
}
},
"deactivateAccountWarning": "Ide-deactivate nito ang iyong user account. Hindi na ito maaaring bawiin! Sigurado ka?",
"@deactivateAccountWarning": {
"type": "text",
"placeholders": {}
},
"delete": "Burahin",
"@delete": {
"type": "text",
"placeholders": {}
},
"deleteMessage": "Burahin ang mensahe",
"@deleteMessage": {
"type": "text",
"placeholders": {}
},
"device": "Device",
"@device": {
"type": "text",
"placeholders": {}
},
"deviceId": "ID ng Device",
"@deviceId": {
"type": "text",
"placeholders": {}
},
"devices": "Mga Device",
"@devices": {
"type": "text",
"placeholders": {}
},
"emoteInvalid": "Hindi wastong shortcode ng emote!",
"@emoteInvalid": {
"type": "text",
"placeholders": {}
},
"emoteKeyboardNoRecents": "Ang mga kamakailang ginamit na emote ay lalabas dito...",
"@emoteKeyboardNoRecents": {
"type": "text",
"placeholders": {}
},
"calls": "Mga Tawag",
"@calls": {},
"customEmojisAndStickers": "Mga custom emoji at sticker",
"@customEmojisAndStickers": {},
"customEmojisAndStickersBody": "Magdagdag o magbahagi ng mga custom emoji o sticker na maaring gamitin sa anumang chat.",
"@customEmojisAndStickersBody": {},
"emoteShortcode": "Shortcode ng emoji",
"@emoteShortcode": {
"type": "text",
"placeholders": {}
},
"emoteWarnNeedToPick": "Kailangan mong pumili ng emote shortcode at isang larawan!",
"@emoteWarnNeedToPick": {
"type": "text",
"placeholders": {}
},
"enableEmotesGlobally": "I-enable ang emote pack globally",
"@enableEmotesGlobally": {
"type": "text",
"placeholders": {}
},
"endedTheCall": "Tinapos ni {senderName} ang tawag",
"@endedTheCall": {
"type": "text",
"placeholders": {
"senderName": {}
}
},
"enterAnEmailAddress": "Maglagay ng email address",
"@enterAnEmailAddress": {
"type": "text",
"placeholders": {}
},
"homeserver": "Homeserver",
"@homeserver": {},
"enterYourHomeserver": "Ilagay ang iyong homeserver",
"@enterYourHomeserver": {
"type": "text",
"placeholders": {}
},
"extremeOffensive": "Lubhang nakakasakit",
"@extremeOffensive": {
"type": "text",
"placeholders": {}
},
"commandHint_plain": "Magpadala ng hindi na-format na text",
"@commandHint_plain": {
"type": "text",
"description": "Usage hint for the command /plain"
},
"commandMissing": "Hindi isang command ang {command}.",
"@commandMissing": {
"type": "text",
"placeholders": {
"command": {}
},
"description": "State that {command} is not a valid /command."
},
"contactHasBeenInvitedToTheGroup": "Inimbita ang contact sa group",
"@contactHasBeenInvitedToTheGroup": {
"type": "text",
"placeholders": {}
},
"containsUserName": "Naglalaman ng username",
"@containsUserName": {
"type": "text",
"placeholders": {}
},
"contentHasBeenReported": "Inulat ang nilalaman sa mga pangangasiwa ng server",
"@contentHasBeenReported": {
"type": "text",
"placeholders": {}
},
"couldNotDecryptMessage": "Hindi ma-decrypt ang mensahe: {error}",
"@couldNotDecryptMessage": {
"type": "text",
"placeholders": {
"error": {}
}
},
"defaultPermissionLevel": "Default na antas ng pahintulot",
"@defaultPermissionLevel": {
"type": "text",
"placeholders": {}
},
"deleteAccount": "Burahin ang account",
"@deleteAccount": {
"type": "text",
"placeholders": {}
},
"emotePacks": "Mga emote pack para sa room",
"@emotePacks": {
"type": "text",
"placeholders": {}
},
"emoteSettings": "Mga Setting ng Emote",
"@emoteSettings": {
"type": "text",
"placeholders": {}
},
"globalChatId": "Global chat ID",
"@globalChatId": {},
"accessAndVisibility": "Pag-access at visibility",
"@accessAndVisibility": {},
"accessAndVisibilityDescription": "Sino ang pinapayagang sumali sa chat at paano matutuklas ang chat.",
"@accessAndVisibilityDescription": {},
"enableEncryptionWarning": "Hindi mo madi-disable ang encryption. Sigurado ka ba?",
"@enableEncryptionWarning": {
"type": "text",
"placeholders": {}
},
"errorObtainingLocation": "Hindi makuha ang lokasyon: {error}",
"@errorObtainingLocation": {
"type": "text",
"placeholders": {
"error": {}
}
},
"fileName": "Pangalan ng file",
"@fileName": {
"type": "text",
"placeholders": {}
},
"fluffychat": "FluffyChat",
"@fluffychat": {
"type": "text",
"placeholders": {}
},
"fontSize": "Laki ng font",
"@fontSize": {
"type": "text",
"placeholders": {}
}
}

744
assets/l10n/intl_ka 2.arb Normal file
View file

@ -0,0 +1,744 @@
{
"alias": "მეტსახელი",
"@alias": {
"type": "text",
"placeholders": {}
},
"appLockDescription": "პინკოდის გამოყენების გარეშე აპლიკაციის ბლოკირება",
"@appLockDescription": {},
"commandHint_hug": "მეგობრული ჩახუტვის გაგზავნა",
"@commandHint_hug": {},
"areYouSure": "დარწმუნებული ხართ?",
"@areYouSure": {
"type": "text",
"placeholders": {}
},
"areYouSureYouWantToLogout": "დარწმუნებული ხართ, რომ გამოსვლა გსურთ?",
"@areYouSureYouWantToLogout": {
"type": "text",
"placeholders": {}
},
"hugContent": "{senderName} მეგობრულად გეხუტება",
"@hugContent": {
"type": "text",
"placeholders": {
"senderName": {}
}
},
"askSSSSSign": "სხვა მომხმარებლის დადასტურებლად, გთხოვთ, ჩაწეროთ თქვენი ან საიდუმლო ფრაზა, ან აღდგენის გასაღები.",
"@askSSSSSign": {
"type": "text",
"placeholders": {}
},
"autoplayImages": "ანიმირებული სტიკერებისა და ემოჯების ავტომატური ჩართვა",
"@autoplayImages": {
"type": "text",
"placeholder": {}
},
"banFromChat": "ჩატიდან გაგდება და ბლოკირება",
"@banFromChat": {
"type": "text",
"placeholders": {}
},
"banned": "დაბლოკილია",
"@banned": {
"type": "text",
"placeholders": {}
},
"badServerLoginTypesException": "ამ სერვერს აქვს შესვლის მეთოდების მხარდაჭერა:\n{serverVersions}\nმაგრამ ამ აპლიკაციას აქვს მხარდაჭერა მხოლოდ:\n{supportedVersions}",
"@badServerLoginTypesException": {
"type": "text",
"placeholders": {
"serverVersions": {},
"supportedVersions": {}
}
},
"sendOnEnter": "გაგზავნა enter-ის დაჭერისას",
"@sendOnEnter": {},
"bannedUser": "{username} დაბლოკა {targetName}",
"@bannedUser": {
"type": "text",
"placeholders": {
"username": {},
"targetName": {}
}
},
"blockDevice": "მოწყობილების ბლოკირება",
"@blockDevice": {
"type": "text",
"placeholders": {}
},
"blocked": "დაბლოკილია",
"@blocked": {
"type": "text",
"placeholders": {}
},
"botMessages": "ბოტის შეტყობინებები",
"@botMessages": {
"type": "text",
"placeholders": {}
},
"cancel": "გაუქმება",
"@cancel": {
"type": "text",
"placeholders": {}
},
"changedTheHistoryVisibilityTo": "{username} შეცვალა ისტორიის ხილვადობა: {rules}",
"@changedTheHistoryVisibilityTo": {
"type": "text",
"placeholders": {
"username": {},
"rules": {}
}
},
"changedTheJoinRules": "{username} გაწევრიანების წესები შეცვალა",
"@changedTheJoinRules": {
"type": "text",
"placeholders": {
"username": {}
}
},
"changedTheProfileAvatar": "{username} შეცვალა პროფილის ფოტო",
"@changedTheProfileAvatar": {
"type": "text",
"placeholders": {
"username": {}
}
},
"chat": "ჩატი",
"@chat": {
"type": "text",
"placeholders": {}
},
"changeYourAvatar": "პროფილის ფოტოს შეცვლა",
"@changeYourAvatar": {
"type": "text",
"placeholders": {}
},
"yourChatBackupHasBeenSetUp": "თქვენი ჩატის სარეზერვო საშუალება კონფიგურირებული იქნა.",
"@yourChatBackupHasBeenSetUp": {},
"channelCorruptedDecryptError": "დაშიფვრა დაზიანდა",
"@channelCorruptedDecryptError": {
"type": "text",
"placeholders": {}
},
"chatBackupDescription": "თქვენი ძველი შეტყობინებები დაცულია აღდგების გასაღებით. არ დაკარგოთ ის.",
"@chatBackupDescription": {
"type": "text",
"placeholders": {}
},
"commandHint_discardsession": "სესიის გაუქმება",
"@commandHint_discardsession": {
"type": "text",
"description": "Usage hint for the command /discardsession"
},
"commandHint_invite": "მოცემული მომხმარებლის მოწვევა ამ ოთახში",
"@commandHint_invite": {
"type": "text",
"description": "Usage hint for the command /invite"
},
"commandHint_plain": "არაფორმატირებული ტექსტის გაგზავნა",
"@commandHint_plain": {
"type": "text",
"description": "Usage hint for the command /plain"
},
"commandHint_send": "ტექსტის გაგზავნა",
"@commandHint_send": {
"type": "text",
"description": "Usage hint for the command /send"
},
"commandMissing": "{command} არაა ბრძანება.",
"@commandMissing": {
"type": "text",
"placeholders": {
"command": {}
},
"description": "State that {command} is not a valid /command."
},
"confirm": "დადასტურება",
"@confirm": {
"type": "text",
"placeholders": {}
},
"connect": "დაკავშირება",
"@connect": {
"type": "text",
"placeholders": {}
},
"countParticipants": "{count} მონაწილე",
"@countParticipants": {
"type": "text",
"placeholders": {
"count": {}
}
},
"createGroup": "ჯგუფის შექმნა",
"@createGroup": {},
"deactivateAccountWarning": "ეს გააუქმებს თქვენს ანგარიშს. ამის გაუქმება შეუძლებელია. დარწმუნებული ხართ?",
"@deactivateAccountWarning": {
"type": "text",
"placeholders": {}
},
"devices": "მოწყობილებები",
"@devices": {
"type": "text",
"placeholders": {}
},
"darkTheme": "ბნელი",
"@darkTheme": {
"type": "text",
"placeholders": {}
},
"chatPermissions": "ჩატის უფლებები",
"@chatPermissions": {},
"dateAndTimeOfDay": "{date}, {timeOfDay}",
"@dateAndTimeOfDay": {
"type": "text",
"placeholders": {
"date": {},
"timeOfDay": {}
}
},
"editRoomAliases": "ოთახის მეტსახელების შეცვლა",
"@editRoomAliases": {
"type": "text",
"placeholders": {}
},
"emoteExists": "ეს ემოცია უკვე არსებობს!",
"@emoteExists": {
"type": "text",
"placeholders": {}
},
"emoteInvalid": "ემოციის არასწორი მოკლე კოდი!",
"@emoteInvalid": {
"type": "text",
"placeholders": {}
},
"importNow": "იმპორტი",
"@importNow": {},
"importEmojis": "ემოჯის იმპორტი",
"@importEmojis": {},
"importFromZipFile": "იმპორტი .zip ფაილიდან",
"@importFromZipFile": {},
"exportEmotePack": "ემოციების .zip ფაილში ექსპორტი",
"@exportEmotePack": {},
"replace": "ჩანაცვლება",
"@replace": {},
"accept": "თანხმობა",
"@accept": {
"type": "text",
"placeholders": {}
},
"acceptedTheInvitation": "👍 {username} მიიღო მოწვევა",
"@acceptedTheInvitation": {
"type": "text",
"placeholders": {
"username": {}
}
},
"account": "ანგარიში",
"@account": {
"type": "text",
"placeholders": {}
},
"addEmail": "ელ.ფოსტის დამატება",
"@addEmail": {
"type": "text",
"placeholders": {}
},
"confirmMatrixId": "გთხოვთ, დაადასტუროთ თქვენი Matrix ID ანგარიშის წაშლისათვის.",
"@confirmMatrixId": {},
"addChatDescription": "ჩატის აღწერილობის დამატება...",
"@addChatDescription": {},
"addToSpace": "სივრცეში დამატება",
"@addToSpace": {},
"admin": "ადმინი",
"@admin": {
"type": "text",
"placeholders": {}
},
"all": "ყველა",
"@all": {
"type": "text",
"placeholders": {}
},
"allChats": "ყველა ჩატი",
"@allChats": {
"type": "text",
"placeholders": {}
},
"commandHint_cuddle": "ჩახუტების გაგზავნა",
"@commandHint_cuddle": {},
"answeredTheCall": "{senderName} უპასუხა ზარს",
"@answeredTheCall": {
"type": "text",
"placeholders": {
"senderName": {}
}
},
"anyoneCanJoin": "ყველას შეუძლია გაწევრიანება",
"@anyoneCanJoin": {
"type": "text",
"placeholders": {}
},
"appLock": "აპლიკაციის ბლოკირება",
"@appLock": {
"type": "text",
"placeholders": {}
},
"archive": "არქივი",
"@archive": {
"type": "text",
"placeholders": {}
},
"commandHint_googly": "გამოშტერილი თვალების გაგზავნა",
"@commandHint_googly": {},
"googlyEyesContent": "{senderName} გამოშტერილ თვალებს გიგზავნის",
"@googlyEyesContent": {
"type": "text",
"placeholders": {
"senderName": {}
}
},
"cuddleContent": "{senderName} გეხუტება",
"@cuddleContent": {
"type": "text",
"placeholders": {
"senderName": {}
}
},
"areGuestsAllowedToJoin": "შეუძლიათ თუ არა სტუმარ მომხმარებლებს გაწევრიანება",
"@areGuestsAllowedToJoin": {
"type": "text",
"placeholders": {}
},
"askVerificationRequest": "მიიღებთ {username} დადასტურების მოთხოვნას?",
"@askVerificationRequest": {
"type": "text",
"placeholders": {
"username": {}
}
},
"sendTypingNotifications": "წერის შეტყობინების გაგზავნა",
"@sendTypingNotifications": {},
"cantOpenUri": "ვერ იხსნება ბმული {uri}",
"@cantOpenUri": {
"type": "text",
"placeholders": {
"uri": {}
}
},
"changeDeviceName": "მოწყობილების გადარქმევა",
"@changeDeviceName": {
"type": "text",
"placeholders": {}
},
"changedTheChatAvatar": "{username} ჩატის ფოტო შეცვალა",
"@changedTheChatAvatar": {
"type": "text",
"placeholders": {
"username": {}
}
},
"changedTheChatDescriptionTo": "{username} ჩატის ახალი აღწერილობა დააყენა: '{description}'",
"@changedTheChatDescriptionTo": {
"type": "text",
"placeholders": {
"username": {},
"description": {}
}
},
"changedTheChatNameTo": "{username} ჩატი გადაარქვა: '{chatname}'",
"@changedTheChatNameTo": {
"type": "text",
"placeholders": {
"username": {},
"chatname": {}
}
},
"changedTheChatPermissions": "{username} ჩატის უფლებები შეცვალა",
"@changedTheChatPermissions": {
"type": "text",
"placeholders": {
"username": {}
}
},
"changedTheGuestAccessRules": "{username} შეცვალა სტუმრების წვდომის წესები",
"@changedTheGuestAccessRules": {
"type": "text",
"placeholders": {
"username": {}
}
},
"changedTheGuestAccessRulesTo": "{username} შეცვალა სტუმრების წვდომის წესები: {rules}",
"@changedTheGuestAccessRulesTo": {
"type": "text",
"placeholders": {
"username": {},
"rules": {}
}
},
"changedTheHistoryVisibility": "{username} შეცვალა ისტორიის ხილვადობა",
"@changedTheHistoryVisibility": {
"type": "text",
"placeholders": {
"username": {}
}
},
"changedTheJoinRulesTo": "{username} გაწევრიანების წესები შეცვალა: {joinRules}",
"@changedTheJoinRulesTo": {
"type": "text",
"placeholders": {
"username": {},
"joinRules": {}
}
},
"changedTheRoomAliases": "{username} ოთახის მეტსახელები შეცვალა",
"@changedTheRoomAliases": {
"type": "text",
"placeholders": {
"username": {}
}
},
"changedTheRoomInvitationLink": "{username} მოწვევის ბმული შეცვალა",
"@changedTheRoomInvitationLink": {
"type": "text",
"placeholders": {
"username": {}
}
},
"changePassword": "პაროლის შეცვლა",
"@changePassword": {
"type": "text",
"placeholders": {}
},
"changeTheHomeserver": "სახლის სერვერის შეცვლა",
"@changeTheHomeserver": {
"type": "text",
"placeholders": {}
},
"changeTheme": "სტილის შეცვლა",
"@changeTheme": {
"type": "text",
"placeholders": {}
},
"changeTheNameOfTheGroup": "ჯგუფის გადარქმევა",
"@changeTheNameOfTheGroup": {
"type": "text",
"placeholders": {}
},
"chatBackup": "ჩატის სარეზერვო საშუალება",
"@chatBackup": {
"type": "text",
"placeholders": {}
},
"chatDetails": "ჩატის დეტალები",
"@chatDetails": {
"type": "text",
"placeholders": {}
},
"chatHasBeenAddedToThisSpace": "ჩატი დაემატა ამ სივრცეს",
"@chatHasBeenAddedToThisSpace": {},
"chats": "ჩატები",
"@chats": {
"type": "text",
"placeholders": {}
},
"chooseAStrongPassword": "ძლიერი პაროლი აარჩიეთ",
"@chooseAStrongPassword": {
"type": "text",
"placeholders": {}
},
"clearArchive": "არქივის გაწმენდა",
"@clearArchive": {},
"close": "დახურვა",
"@close": {
"type": "text",
"placeholders": {}
},
"commandHint_markasgroup": "აღნიშვნა, როგორც ჯგუფის",
"@commandHint_markasgroup": {},
"commandHint_ban": "მოცემული მომხმარებლის ბლოკირება ამ ოთახში",
"@commandHint_ban": {
"type": "text",
"description": "Usage hint for the command /ban"
},
"commandHint_clearcache": "­ქეშის გაწმენდა",
"@commandHint_clearcache": {
"type": "text",
"description": "Usage hint for the command /clearcache"
},
"commandHint_join": "მოცემულ ოთახში გაწევრიანება",
"@commandHint_join": {
"type": "text",
"description": "Usage hint for the command /join"
},
"commandHint_kick": "მოცემული მომხმარებლის წაშლა ამ ოთახიდან",
"@commandHint_kick": {
"type": "text",
"description": "Usage hint for the command /kick"
},
"commandHint_leave": "ამ ოთახიდან გასვლა",
"@commandHint_leave": {
"type": "text",
"description": "Usage hint for the command /leave"
},
"commandHint_me": "აღწერეთ თქვენი თავი",
"@commandHint_me": {
"type": "text",
"description": "Usage hint for the command /me"
},
"commandHint_unban": "ამ ოთახში მომხმარებლისგან ბლოკის მოხსნა",
"@commandHint_unban": {
"type": "text",
"description": "Usage hint for the command /unban"
},
"commandInvalid": "არასწორი ბრძანება",
"@commandInvalid": {
"type": "text"
},
"compareEmojiMatch": "გთხოვთ, შეადაროთ ეს ემოჯი",
"@compareEmojiMatch": {
"type": "text",
"placeholders": {}
},
"compareNumbersMatch": "გთხოვთ, შეადაროთ ეს რიცხვები",
"@compareNumbersMatch": {
"type": "text",
"placeholders": {}
},
"configureChat": "ჩატის კონფიგურაცია",
"@configureChat": {
"type": "text",
"placeholders": {}
},
"contactHasBeenInvitedToTheGroup": "კონტაქტი მოწვეული იქნა ჯგუფში",
"@contactHasBeenInvitedToTheGroup": {
"type": "text",
"placeholders": {}
},
"containsUserName": "შეიცავს სახელს",
"@containsUserName": {
"type": "text",
"placeholders": {}
},
"copiedToClipboard": "კოპირებულია ბუფერში",
"@copiedToClipboard": {
"type": "text",
"placeholders": {}
},
"copy": "კოპირება",
"@copy": {
"type": "text",
"placeholders": {}
},
"copyToClipboard": "კოპირება ბუფერში",
"@copyToClipboard": {
"type": "text",
"placeholders": {}
},
"couldNotDecryptMessage": "შეტყობინების გაშიფვრის შეცდომა: {error}",
"@couldNotDecryptMessage": {
"type": "text",
"placeholders": {
"error": {}
}
},
"create": "შექმნა",
"@create": {
"type": "text",
"placeholders": {}
},
"createdTheChat": "💬 {username} შექმნა ჩატი",
"@createdTheChat": {
"type": "text",
"placeholders": {
"username": {}
}
},
"createNewSpace": "ახალი სივრცე",
"@createNewSpace": {
"type": "text",
"placeholders": {}
},
"currentlyActive": "ახლა აქტიურია",
"@currentlyActive": {
"type": "text",
"placeholders": {}
},
"dateWithoutYear": "{day}-{month}",
"@dateWithoutYear": {
"type": "text",
"placeholders": {
"month": {},
"day": {}
}
},
"dateWithYear": "{day}-{month}-{year}",
"@dateWithYear": {
"type": "text",
"placeholders": {
"year": {},
"month": {},
"day": {}
}
},
"delete": "წაშლა",
"@delete": {
"type": "text",
"placeholders": {}
},
"deleteAccount": "ანგარიშის წაშლა",
"@deleteAccount": {
"type": "text",
"placeholders": {}
},
"deleteMessage": "შეტყობინების წაშლა",
"@deleteMessage": {
"type": "text",
"placeholders": {}
},
"device": "მოწყობილება",
"@device": {
"type": "text",
"placeholders": {}
},
"deviceId": "მოწყობილების ID",
"@deviceId": {
"type": "text",
"placeholders": {}
},
"directChats": "პირდაპირი ჩატები",
"@directChats": {
"type": "text",
"placeholders": {}
},
"allRooms": "ყველა ჯგუფური ჩატები",
"@allRooms": {
"type": "text",
"placeholders": {}
},
"downloadFile": "ფაილის ჩატვირთვა",
"@downloadFile": {
"type": "text",
"placeholders": {}
},
"edit": "რედაქტირება",
"@edit": {
"type": "text",
"placeholders": {}
},
"editBlockedServers": "ბლოკირებული სერვერების რედაქტირება",
"@editBlockedServers": {
"type": "text",
"placeholders": {}
},
"editRoomAvatar": "ოთახის ფოტოს შეცვლა",
"@editRoomAvatar": {
"type": "text",
"placeholders": {}
},
"emoteSettings": "ემოციების პარამეტრები",
"@emoteSettings": {
"type": "text",
"placeholders": {}
},
"globalChatId": "გლობალური ჩატის ID",
"@globalChatId": {},
"repeatPassword": "გაიმეორეთ პაროლი",
"@repeatPassword": {},
"notAnImage": "ფაილი არაა სურათი.",
"@notAnImage": {},
"remove": "წაშლა",
"@remove": {
"type": "text",
"placeholders": {}
},
"activatedEndToEndEncryption": "🔐 {username} გააქტიურა end to end დაშიფვრა",
"@activatedEndToEndEncryption": {
"type": "text",
"placeholders": {
"username": {}
}
},
"supposedMxid": "ეს უნდა იყოს {mxid}",
"@supposedMxid": {
"type": "text",
"placeholders": {
"mxid": {}
}
},
"about": "შესახებ",
"@about": {
"type": "text",
"placeholders": {}
},
"changedTheDisplaynameTo": "{username} შეცვალა ნაჩვენები სახელი: '{displayname}'",
"@changedTheDisplaynameTo": {
"type": "text",
"placeholders": {
"username": {},
"displayname": {}
}
},
"commandHint_create": "ცარიელი ჯგუფური ჩატის შექმნა\nგამოიენეთ --no-encryption გაშიფვრის გასათიშად",
"@commandHint_create": {
"type": "text",
"description": "Usage hint for the command /create"
},
"commandHint_dm": "პირდაპირი ჩატის დაწყება\nგამოიენეთ --no-encryption გაშიფვრის გასათიშად",
"@commandHint_dm": {
"type": "text",
"description": "Usage hint for the command /dm"
},
"commandHint_html": "HTML ფორმატირებული ტექსტის გაგზავნა",
"@commandHint_html": {
"type": "text",
"description": "Usage hint for the command /html"
},
"commandHint_myroomavatar": "თქვენი ფოტოს დაყენება ამ ოთახისათვის(mxc-uri-ს დახმარებით)",
"@commandHint_myroomavatar": {
"type": "text",
"description": "Usage hint for the command /myroomavatar"
},
"commandHint_myroomnick": "ამ ოთახისათვის ნაჩვენები სახელის დაყენება",
"@commandHint_myroomnick": {
"type": "text",
"description": "Usage hint for the command /myroomnick"
},
"commandHint_op": "მოცემული მომხმარებლისათვის უფლებების დონის დაყენება (ჩვეულებრივ: 50)",
"@commandHint_op": {
"type": "text",
"description": "Usage hint for the command /op"
},
"commandHint_react": "რეაქციის სახით პასუხის გაგზავნა",
"@commandHint_react": {
"type": "text",
"description": "Usage hint for the command /react"
},
"containsDisplayName": "ნაჩვენებ სახელს შეიცავს",
"@containsDisplayName": {
"type": "text",
"placeholders": {}
},
"contentHasBeenReported": "ეს კონტენტი გაგზავნილ იქნა სერვერის ადმინისტრატორებთან",
"@contentHasBeenReported": {
"type": "text",
"placeholders": {}
},
"defaultPermissionLevel": "ნაგულისხმევი უფლების დონე",
"@defaultPermissionLevel": {
"type": "text",
"placeholders": {}
},
"displaynameHasBeenChanged": "ნაჩვენები სახელი შეიცვალა",
"@displaynameHasBeenChanged": {
"type": "text",
"placeholders": {}
},
"editDisplayname": "ნაჩვენები სახელის შეცვლა",
"@editDisplayname": {
"type": "text",
"placeholders": {}
}
}

3
devtools_options 2.yaml Normal file
View file

@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:

View file

@ -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'

View file

@ -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;

View file

@ -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<String?> loggedInRedirect(
@ -202,17 +201,17 @@ abstract class AppRoutes {
pageBuilder: (context, state) => defaultPageBuilder(
context,
state,
const AnalyticsClassList(),
const AnalyticsSpaceList(),
),
redirect: loggedOutRedirect,
routes: [
GoRoute(
path: ':classid',
path: ':spaceid',
redirect: loggedOutRedirect,
pageBuilder: (context, state) => defaultPageBuilder(
context,
state,
const ClassAnalyticsPage(),
const SpaceAnalyticsPage(),
),
routes: [
GoRoute(
@ -220,11 +219,7 @@ abstract class AppRoutes {
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(),
const SpaceAnalyticsPage(
selectedView: BarChartViewSelection.messages,
),
),
@ -234,11 +229,7 @@ abstract class AppRoutes {
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(),
const SpaceAnalyticsPage(
selectedView: BarChartViewSelection.grammar,
),
),
@ -319,24 +310,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(

View file

@ -20,8 +20,8 @@ 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 +317,10 @@ class ChatController extends State<ChatPageWithRoom>
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(
@ -654,8 +654,6 @@ class ChatController extends State<ChatPageWithRoom>
);
return;
}
// ensure that analytics room exists / is created for the active langCode
await room.ensureAnalyticsRoomExists();
},
onError: (err, stack) => ErrorHandler.logError(e: err, s: stack),
);

View file

@ -45,6 +45,7 @@ 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((_) {
if (events.isEmpty) return;
controller.pangeaController.instructions.show(
context,
InstructionsEnum.clickMessage,

View file

@ -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';
@ -257,11 +255,10 @@ class ChatDetailsView extends StatelessWidget {
controller: controller,
),
// Pangea#
if ((room.isPangeaClass || room.isExchange) &&
room.isRoomAdmin)
if (room.isSpace && room.isRoomAdmin)
ListTile(
title: Text(
L10n.of(context)!.classAnalytics,
L10n.of(context)!.spaceAnalytics,
style: TextStyle(
color: Theme.of(context).colorScheme.secondary,
fontWeight: FontWeight.bold,
@ -279,11 +276,12 @@ class ChatDetailsView extends StatelessWidget {
'/rooms/analytics/${room.id}',
),
),
if (room.classSettings != null && room.isRoomAdmin)
ClassSettings(
roomId: controller.roomId,
startOpen: false,
),
// 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,
@ -459,16 +457,11 @@ class ChatDetailsView extends StatelessWidget {
room: room,
),
const Divider(height: 1),
if (!room.isPangeaClass &&
!room.isDirectChat &&
room.isRoomAdmin)
if (!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)

View file

@ -1,8 +1,8 @@
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/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,12 +58,12 @@ 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)),
],
),
),
@ -84,7 +84,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 +98,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 +387,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 +399,7 @@ class ClientChooserButton extends StatelessWidget {
controller.pangeaController,
);
break;
case SettingsAction.classAnalytics:
case SettingsAction.spaceAnalytics:
context.go('/rooms/analytics');
break;
case SettingsAction.myAnalytics:
@ -507,11 +494,10 @@ enum SettingsAction {
// #Pangea
learning,
joinWithClassCode,
classAnalytics,
spaceAnalytics,
myAnalytics,
findAConversationPartner,
logout,
newClass,
newExchange
// Pangea#
}

View file

@ -174,7 +174,7 @@ class NewGroupController extends State<NewGroup> {
void initState() {
Future.delayed(Duration.zero, () {
chatTopic.langCode =
pangeaController.languageController.activeL2Code(roomID: null) ??
pangeaController.languageController.userL2?.langCode ??
pangeaController.pLanguageStore.targetOptions.first.langCode;
setState(() {});
});

View file

@ -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(

View file

@ -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<NewSpace> {
bool publicGroup = true;
final GlobalKey<RoomRulesState> rulesEditorKey = GlobalKey<RoomRulesState>();
final GlobalKey<AddToSpaceState> addToSpaceKey = GlobalKey<AddToSpaceState>();
final GlobalKey<ClassSettingsState> classSettingsKey =
GlobalKey<ClassSettingsState>();
// commenting out language settings in spaces for now
// final GlobalKey<LanguageSettingsState> languageSettingsKey =
// GlobalKey<LanguageSettingsState>();
final GlobalKey<RoomCapacityButtonState> addCapacityKey =
GlobalKey<RoomCapacityButtonState>();
@ -68,8 +67,6 @@ class NewSpaceController extends State<NewSpace> {
void setPublicGroup(bool b) => setState(() => publicGroup = b);
// #Pangea
bool newClassMode = true;
List<StateEvent> get initialState {
final events = <StateEvent>[];
@ -95,11 +92,11 @@ class NewSpaceController extends State<NewSpace> {
} 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<NewSpace> {
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<NewSpace> {
// 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<NewSpace> {
}
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<NewSpace> {
// #Pangea
// Widget build(BuildContext context) => NewSpaceView(this);
Widget build(BuildContext context) {
newClassMode =
GoRouterState.of(context).pathParameters['newexchange'] != 'exchange';
return NewSpaceView(this);
}
// Pangea#

View file

@ -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<PangeaRoomRules?>(
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<PangeaRoomRules?>(
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),

View file

@ -194,6 +194,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),

View file

@ -6,15 +6,13 @@ import 'package:fluffychat/pangea/choreographer/controllers/alternative_translat
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/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/controllers/subscription_controller.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/utils/any_state_holder.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
@ -443,25 +441,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;

View file

@ -265,7 +265,6 @@ class ITController {
targetLangCode: targetLangCode,
userId: choreographer.userId!,
roomId: choreographer.roomId!,
classId: choreographer.classId,
goldTranslation: goldRouteTracker.fullTranslation,
goldContinuances: goldRouteTracker.continuances,
),
@ -283,7 +282,6 @@ class ITController {
translationId: translationId,
targetLangCode: targetLangCode,
sourceLangCode: sourceLangCode,
classId: choreographer.classId,
),
);

View file

@ -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';

View file

@ -0,0 +1,168 @@
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:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import '../../../pages/chat/chat.dart';
class StartIGCButton extends StatefulWidget {
const StartIGCButton({
super.key,
required this.controller,
});
final ChatController controller;
@override
State<StartIGCButton> createState() => StartIGCButtonState();
}
class StartIGCButtonState extends State<StartIGCButton>
with SingleTickerProviderStateMixin {
AssistanceState get assistanceState =>
widget.controller.choreographer.assistanceState;
AnimationController? _controller;
StreamSubscription? choreoListener;
AssistanceState? prevState;
@override
void initState() {
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
);
choreoListener = widget.controller.choreographer.stateListener.stream
.listen(updateSpinnerState);
super.initState();
}
void updateSpinnerState(_) {
if (prevState != AssistanceState.fetching &&
assistanceState == AssistanceState.fetching) {
_controller?.repeat();
} else if (prevState == AssistanceState.fetching &&
assistanceState != AssistanceState.fetching) {
_controller?.stop();
_controller?.reverse();
}
setState(() => prevState = assistanceState);
}
@override
Widget build(BuildContext context) {
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) {
return const SizedBox.shrink();
}
final Widget icon = Icon(
Icons.autorenew_rounded,
size: 46,
color: assistanceState.stateColor(context),
);
return SizedBox(
height: 50,
width: 50,
child: FloatingActionButton(
tooltip: assistanceState.tooltip(
L10n.of(context)!,
),
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
disabledElevation: 0,
shape: const CircleBorder(),
onPressed: () {
if (assistanceState != AssistanceState.complete) {
widget.controller.choreographer
.getLanguageHelp(
false,
true,
)
.then((_) {
if (widget.controller.choreographer.igc.igcTextData != null &&
widget.controller.choreographer.igc.igcTextData!.matches
.isNotEmpty) {
widget.controller.choreographer.igc.showFirstMatch(context);
}
});
}
},
child: Stack(
alignment: Alignment.center,
children: [
_controller != null
? RotationTransition(
turns: Tween(begin: 0.0, end: math.pi * 2)
.animate(_controller!),
child: icon,
)
: icon,
Container(
width: 26,
height: 26,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).scaffoldBackgroundColor,
),
),
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: assistanceState.stateColor(context),
),
),
Icon(
size: 16,
Icons.check,
color: Theme.of(context).scaffoldBackgroundColor,
),
],
),
),
);
}
}
extension AssistanceStateExtension on AssistanceState {
Color stateColor(context) {
switch (this) {
case AssistanceState.noMessage:
case AssistanceState.notFetched:
case AssistanceState.fetching:
return Theme.of(context).colorScheme.primary;
case AssistanceState.fetched:
return PangeaColors.igcError;
case AssistanceState.complete:
return AppConfig.success;
}
}
String tooltip(L10n l10n) {
switch (this) {
case AssistanceState.noMessage:
case AssistanceState.notFetched:
return l10n.runGrammarCorrection;
case AssistanceState.fetching:
return "";
case AssistanceState.fetched:
return l10n.grammarCorrectionFailed;
case AssistanceState.complete:
return l10n.grammarCorrectionComplete;
}
}
}

View file

@ -5,6 +5,7 @@ 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/widgets/user_settings/p_language_dialog.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
@ -77,61 +78,65 @@ class StartIGCButtonState extends State<StartIGCButton>
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.complete) {
widget.controller.choreographer
.getLanguageHelp(
false,
true,
)
.then((_) {
if (widget.controller.choreographer.igc.igcTextData != null &&
widget.controller.choreographer.igc.igcTextData!.matches
.isNotEmpty) {
widget.controller.choreographer.igc.showFirstMatch(context);
}
});
}
},
child: Stack(
alignment: Alignment.center,
children: [
_controller != null
? RotationTransition(
turns: Tween(begin: 0.0, end: math.pi * 2)
.animate(_controller!),
child: icon,
)
: icon,
Container(
width: 26,
height: 26,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).scaffoldBackgroundColor,
),
),
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: assistanceState.stateColor(context),
),
),
Icon(
size: 16,
Icons.check,
color: Theme.of(context).scaffoldBackgroundColor,
),
),
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: assistanceState.stateColor(context),
),
),
Icon(
size: 16,
Icons.check,
color: Theme.of(context).scaffoldBackgroundColor,
),
],
],
),
),
),
);

View file

@ -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";

View file

@ -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";

View file

@ -1,4 +1,3 @@
class PangeaRoomTypes {
static const analytics = 'p.analytics';
static const exchange = 'p.exchange';
}

View file

@ -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<void> fixClassPowerLevels() async {
try {
final List<Future<void>> 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<Future<void>> classFixes = List<Room>.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<String> existingParentsIds =
room.pangeaSpaceParents.map((e) => e.id).toList();
final List<Room> spaces =
_pangeaController.matrixState.client.classesAndExchangesImIn;
final List<Room> 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,

View file

@ -3,12 +3,9 @@ import 'dart:developer';
import 'package:fluffychat/pangea/constants/language_keys.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);
}

View file

@ -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;

View file

@ -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';
@ -62,6 +64,33 @@ class AnalyticsController extends BaseController {
);
}
///////// SPACE ANALYTICS LANGUAGES //////////
String get _analyticsSpaceLangKey => "ANALYTICS_SPACE_LANG_KEY";
LanguageModel get currentAnalyticsSpaceLang {
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<void> setCurrentAnalyticsSpaceLang(LanguageModel lang) async {
await _pangeaController.pStoreService.save(
_analyticsSpaceLangKey,
lang.langCode,
local: true,
);
}
Future<DateTime?> 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
@ -96,7 +125,6 @@ class AnalyticsController extends BaseController {
Future<DateTime?> 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 +134,7 @@ class AnalyticsController extends BaseController {
final List<Future<DateTime?>> lastUpdatedFutures = [];
for (final student in space.students) {
final Room? analyticsRoom = _pangeaController.matrixState.client
.analyticsRoomLocal(langCode, student.id);
.analyticsRoomLocal(currentAnalyticsSpaceLang.langCode, student.id);
if (analyticsRoom == null) continue;
lastUpdatedFutures.add(
analyticsRoom.analyticsLastUpdated(
@ -132,11 +160,21 @@ class AnalyticsController extends BaseController {
// private chat analytics to determine which children are already visible
// in the chat list
final Map<String, List<String>> _lastFetchedHierarchies = {};
void setLatestHierarchy(String spaceId, GetSpaceHierarchyResponse resp) {
final List<String> roomIds = resp.rooms.map((room) => room.roomId).toList();
_lastFetchedHierarchies[spaceId] = roomIds;
}
Future<List<String>> 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 ////////////////////////////
Future<List<SummaryAnalyticsEvent>> mySummaryAnalytics() async {
@ -168,9 +206,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 +216,7 @@ class AnalyticsController extends BaseController {
final List<SummaryAnalyticsEvent> analyticsEvents = [];
for (final student in space.students) {
final Room? analyticsRoom = _pangeaController.matrixState.client
.analyticsRoomLocal(langCode, student.id);
.analyticsRoomLocal(currentAnalyticsSpaceLang.langCode, student.id);
if (analyticsRoom != null) {
final List<AnalyticsEvent>? roomEvents =
@ -225,7 +260,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 == currentAnalyticsSpaceLang.langCode),
);
if (index != -1) {
@ -253,6 +289,7 @@ class AnalyticsController extends BaseController {
chartAnalyticsModel: chartAnalyticsModel,
defaultSelected: defaultSelected,
selected: selected,
langCode: currentAnalyticsSpaceLang.langCode,
),
);
}
@ -267,16 +304,16 @@ class AnalyticsController extends BaseController {
return filtered;
}
List<SummaryAnalyticsEvent> filterRoomAnalytics(
Future<List<SummaryAnalyticsEvent>> filterRoomAnalytics(
List<SummaryAnalyticsEvent> unfiltered,
String? roomID,
) {
) async {
List<SummaryAnalyticsEvent> 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 +332,10 @@ class AnalyticsController extends BaseController {
Future<List<SummaryAnalyticsEvent>> filterPrivateChatAnalytics(
List<SummaryAnalyticsEvent> 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<String> privateChatIds = space?.allSpaceChildRoomIds ?? [];
final List<String> lastFetched = _lastFetchedHierarchies[space!.id] ?? [];
final List<String> privateChatIds = space.allSpaceChildRoomIds;
final List<String> lastFetched = await getLatestSpaceHierarchy(space.id);
for (final id in lastFetched) {
privateChatIds.removeWhere((e) => e == id);
}
@ -324,19 +355,11 @@ class AnalyticsController extends BaseController {
return filtered;
}
List<SummaryAnalyticsEvent> filterSpaceAnalytics(
Future<List<SummaryAnalyticsEvent>> filterSpaceAnalytics(
List<SummaryAnalyticsEvent> unfiltered,
String spaceId,
) {
final selectedSpace =
_pangeaController.matrixState.client.getRoomById(spaceId);
final List<String> chatIds = selectedSpace?.spaceChildren
.map((e) => e.roomId)
.where((e) => e != null)
.cast<String>()
.toList() ??
[];
) async {
final List<String> chatIds = await getLatestSpaceHierarchy(spaceId);
List<SummaryAnalyticsEvent> filtered =
List<SummaryAnalyticsEvent>.from(unfiltered);
@ -384,12 +407,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 +431,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 +448,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 +465,6 @@ class AnalyticsController extends BaseController {
lastUpdated = await spaceAnalyticsLastUpdated(
PangeaEventTypes.summaryAnalytics,
space!,
langCode!,
);
}
@ -467,8 +475,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<SummaryAnalyticsEvent> summaryEvents =
@ -548,24 +558,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<ConstructAnalyticsEvent> constructEvents = [];
for (final student in space.students) {
final Room? analyticsRoom = _pangeaController.matrixState.client
.analyticsRoomLocal(langCode, student.id);
.analyticsRoomLocal(currentAnalyticsSpaceLang.langCode, student.id);
if (analyticsRoom != null) {
final List<ConstructAnalyticsEvent>? roomEvents =
(await analyticsRoom.getAnalyticsEvents(
@ -614,31 +610,30 @@ class AnalyticsController extends BaseController {
return filtered;
}
List<ConstructAnalyticsEvent> filterPrivateChatConstructs(
Future<List<ConstructAnalyticsEvent>> filterPrivateChatConstructs(
List<ConstructAnalyticsEvent> unfilteredConstructs,
Room parentSpace,
) {
final List<String> directChatIds = [];
Room space,
) async {
final List<String> privateChatIds = space.allSpaceChildRoomIds;
final List<String> lastFetched = await getLatestSpaceHierarchy(space.id);
for (final id in lastFetched) {
privateChatIds.removeWhere((e) => e == id);
}
final List<ConstructAnalyticsEvent> filtered =
List<ConstructAnalyticsEvent>.from(unfilteredConstructs);
for (final construct in filtered) {
construct.content.uses.removeWhere(
(use) => !directChatIds.contains(use.chatId),
(use) => !privateChatIds.contains(use.chatId),
);
}
return filtered;
}
List<ConstructAnalyticsEvent> filterSpaceConstructs(
Future<List<ConstructAnalyticsEvent>> filterSpaceConstructs(
List<ConstructAnalyticsEvent> unfilteredConstructs,
Room space,
) {
final List<String> chatIds = space.spaceChildren
.map((e) => e.roomId)
.where((e) => e != null)
.cast<String>()
.toList();
) async {
final List<String> chatIds = await getLatestSpaceHierarchy(space.id);
final List<ConstructAnalyticsEvent> filtered =
List<ConstructAnalyticsEvent>.from(unfilteredConstructs);
@ -665,7 +660,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 == currentAnalyticsSpaceLang.langCode,
);
if (index > -1) {
@ -691,6 +687,7 @@ class AnalyticsController extends BaseController {
events: List.from(events),
defaultSelected: defaultSelected,
selected: selected,
langCode: currentAnalyticsSpaceLang.langCode,
);
_cachedConstructs.add(entry);
}
@ -769,9 +766,9 @@ 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}");
}
@ -784,10 +781,10 @@ class AnalyticsController extends BaseController {
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 +800,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 +816,6 @@ class AnalyticsController extends BaseController {
lastUpdated = await spaceAnalyticsLastUpdated(
PangeaEventTypes.construct,
space!,
langCode!,
);
}
@ -843,8 +827,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 +870,7 @@ class AnalyticsController extends BaseController {
}
abstract class CacheEntry {
final String langCode;
final TimeSpan timeSpan;
final AnalyticsSelected defaultSelected;
AnalyticsSelected? selected;
@ -892,6 +879,7 @@ abstract class CacheEntry {
CacheEntry({
required this.timeSpan,
required this.defaultSelected,
required this.langCode,
this.selected,
}) {
_createdAt = DateTime.now();
@ -924,17 +912,19 @@ class ConstructCacheEntry extends CacheEntry {
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,
});

View file

@ -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 {

View file

@ -1,13 +1,16 @@
import 'dart:async';
import 'dart:developer';
import 'package:fluffychat/pangea/constants/language_keys.dart';
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/models/analytics/constructs_event.dart';
import 'package:fluffychat/pangea/models/analytics/constructs_model.dart';
import 'package:fluffychat/pangea/models/analytics/summary_analytics_event.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';
@ -170,6 +173,12 @@ class MyAnalyticsController extends BaseController {
}
Future<void> _updateAnalytics() async {
// if the user's l2 is not sent, don't send analytics
final String? userL2 = _pangeaController.languageController.activeL2Code();
if (userL2 == null) {
return;
}
// top level analytics sending function. Send analytics
// for each type of analytics event
// to each of the applicable analytics rooms
@ -180,115 +189,121 @@ class MyAnalyticsController extends BaseController {
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<Room> analyticsRooms =
_pangeaController.matrixState.client.allMyAnalyticsRooms;
analyticsRooms.addAll(await createMissingAnalyticsRooms());
// get the last updated time for each analytics room
// and the least recent update, which will be used to determine
// how far to go back in the chat history to get messages
final Map<String, DateTime?> lastUpdatedMap = await _pangeaController
.matrixState.client
.allAnalyticsRoomsLastUpdated();
final List<DateTime> lastUpdates = lastUpdatedMap.values
.where((lastUpdate) => lastUpdate != null)
.cast<DateTime>()
.toList();
lastUpdates.sort((a, b) => a.compareTo(b));
final DateTime? leastRecentUpdate =
lastUpdates.isNotEmpty ? lastUpdates.first : null;
// 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);
}
// for each chat the user is studying in, get all the messages
// since the least recent update analytics update, and sort them
// by their langCodes
final Map<String, List<PangeaMessageEvent>> langCodeToMsgs =
await getLangCodesToMsgs(
userL2,
leastRecentUpdate,
);
final List<String> langCodes = langCodeToMsgs.keys.toList();
for (final String langCode in langCodes) {
// for each of the langs that the user has sent message in, get
// the corresponding analytics room (or create it)
final Room analyticsRoom = await _pangeaController.matrixState.client
.getMyAnalyticsRoom(langCode);
// if there is no analytics room for this langCode, then user hadn't sent
// message in this language at the time of the last analytics update
// so fallback to the least recent update time
final DateTime? lastUpdated =
lastUpdatedMap[analyticsRoom.id] ?? leastRecentUpdate;
// get the corresponding list of recent messages for this langCode
final List<PangeaMessageEvent> recentMsgs =
langCodeToMsgs[langCode] ?? [];
// finally, send the analytics events to the analytics room
await sendAnalyticsEvents(
analyticsRoom,
recentMsgs,
lastUpdated,
);
}
}
Future<void> sendAnalyticsEvent(
Room analyticsRoom,
String type,
Future<Map<String, List<PangeaMessageEvent>>> getLangCodesToMsgs(
String userL2,
DateTime? since,
) 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,
);
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!,
);
// 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<dynamic> analyticsContent = [];
// get a map of langCodes to messages for each chat the user is studying in
final Map<String, List<PangeaMessageEvent>> langCodeToMsgs = {};
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 messages the logged in user has sent in all chats
// since the last analytics event was sent
List<PangeaMessageEvent>? recentMsgs;
try {
recentMsgs = await chat.myMessageEventsInChat(
since: lastUpdated,
since: since,
);
} catch (err) {
debugPrint("failed to fetch messages for chat ${chat.id}");
continue;
}
if (lastUpdated != null) {
recentMsgs.removeWhere(
(msg) => msg.event.originServerTs.isBefore(lastUpdated),
);
// sort those messages by their langCode
// langCode is hopefully based on the original sent rep, but if that
// is null or unk, it will be based on the user's current l2
for (final msg in recentMsgs) {
final String msgLangCode = (msg.originalSent?.langCode != null &&
msg.originalSent?.langCode != LanguageKeys.unknownLanguage)
? msg.originalSent!.langCode
: userL2;
langCodeToMsgs[msgLangCode] ??= [];
langCodeToMsgs[msgLangCode]!.add(msg);
}
}
return langCodeToMsgs;
}
// then format that data into analytics data and add the formatted
// data to the list of analyticsContent
analyticsContent.addAll(
AnalyticsModel.formatAnalyticsContent(recentMsgs, type),
Future<void> sendAnalyticsEvents(
Room analyticsRoom,
List<PangeaMessageEvent> recentMsgs,
DateTime? lastUpdated,
) async {
// remove messages that were sent before the last update
if (recentMsgs.isEmpty) return;
if (lastUpdated != null) {
recentMsgs.removeWhere(
(msg) => msg.event.originServerTs.isBefore(lastUpdated),
);
}
// 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,
);
}
// format the analytics data
final List<RecentMessageRecord> summaryContent =
SummaryAnalyticsModel.formatSummaryContent(recentMsgs);
final List<OneConstructUse> constructContent =
ConstructAnalyticsModel.formatConstructsContent(recentMsgs);
// 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<List<Room>> createMissingAnalyticsRooms() async {
List<String> targetLangs = [];
final String? userL2 = _pangeaController.languageController.activeL2Code();
if (userL2 != null) targetLangs.add(userL2);
final List<String?> spaceL2s = studentSpaces
.map(
(space) => _pangeaController.languageController.activeL2Code(
roomID: space.id,
),
)
.toList();
targetLangs.addAll(spaceL2s.where((l2) => l2 != null).cast<String>());
targetLangs = targetLangs.toSet().toList();
for (final String langCode in targetLangs) {
await _pangeaController.matrixState.client.getMyAnalyticsRoom(langCode);
// 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 || lastUpdated == null) {
await SummaryAnalyticsEvent.sendSummaryAnalyticsEvent(
analyticsRoom,
summaryContent,
);
}
if (constructContent.isNotEmpty) {
await ConstructAnalyticsEvent.sendConstructsEvent(
analyticsRoom,
constructContent,
);
}
return _pangeaController.matrixState.client.allMyAnalyticsRooms;
}
List<Room> _studentChats = [];
@ -321,8 +336,8 @@ class MyAnalyticsController extends BaseController {
List<Room> _studentSpaces = [];
Future<void> setStudentSpaces() async {
_studentSpaces = await _pangeaController
.matrixState.client.classesAndExchangesImStudyingIn;
_studentSpaces =
await _pangeaController.matrixState.client.spacesImStudyingIn;
}
List<Room> get studentSpaces {

View file

@ -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;
// }
// }
}

View file

@ -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;

View file

@ -0,0 +1,89 @@
import 'dart:async';
import 'dart:developer';
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart';
import 'package:fluffychat/pangea/models/span_data.dart';
import 'package:fluffychat/pangea/repo/span_data_repo.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:flutter/foundation.dart';
class _SpanDetailsCacheItem {
Future<SpanDetailsRepoReqAndRes> data;
_SpanDetailsCacheItem({required this.data});
}
class SpanDataController {
late Choreographer choreographer;
final Map<int, _SpanDetailsCacheItem> _cache = {};
Timer? _cacheClearTimer;
SpanDataController(this.choreographer) {
_initializeCacheClearing();
}
void _initializeCacheClearing() {
const duration = Duration(minutes: 2);
_cacheClearTimer = Timer.periodic(duration, (Timer t) => clearCache());
}
void clearCache() {
_cache.clear();
}
void dispose() {
_cacheClearTimer?.cancel();
}
Future<void> getSpanDetails(int matchIndex) async {
if (choreographer.igc.igcTextData == null ||
choreographer.igc.igcTextData!.matches.isEmpty ||
matchIndex < 0 ||
matchIndex >= choreographer.igc.igcTextData!.matches.length) {
debugger(when: kDebugMode);
return;
}
/// Retrieves the span data from the `igcTextData` matches at the specified `matchIndex`.
/// Creates a `SpanDetailsRepoReqAndRes` object with the retrieved span data and other parameters.
/// Generates a cache key based on the created `SpanDetailsRepoReqAndRes` object.
final SpanData span =
choreographer.igc.igcTextData!.matches[matchIndex].match;
final req = SpanDetailsRepoReqAndRes(
userL1: choreographer.l1LangCode!,
userL2: choreographer.l2LangCode!,
enableIGC: choreographer.igcEnabled,
enableIT: choreographer.itEnabled,
span: span,
);
final int cacheKey = req.hashCode;
/// Retrieves the [SpanDetailsRepoReqAndRes] response from the cache if it exists,
/// otherwise makes an API call to get the response and stores it in the cache.
Future<SpanDetailsRepoReqAndRes> response;
if (_cache.containsKey(cacheKey)) {
response = _cache[cacheKey]!.data;
} else {
response = SpanDataRepo.getSpanDetails(
await choreographer.accessToken,
request: SpanDetailsRepoReqAndRes(
userL1: choreographer.l1LangCode!,
userL2: choreographer.l2LangCode!,
enableIGC: choreographer.igcEnabled,
enableIT: choreographer.itEnabled,
span: span,
),
);
_cache[cacheKey] = _SpanDetailsCacheItem(data: response);
}
try {
choreographer.igc.igcTextData!.matches[matchIndex].match =
(await response).span;
} catch (err, s) {
ErrorHandler.logError(e: err, s: s);
}
choreographer.setState();
}
}

View file

@ -1,83 +0,0 @@
part of "client_extension.dart";
extension ClassesAndExchangesClientExtension on Client {
List<Room> get _classes => rooms.where((e) => e.isPangeaClass).toList();
List<Room> get _classesImTeaching => rooms
.where(
(e) =>
e.isPangeaClass &&
e.ownPowerLevel == ClassDefaultValues.powerLevelOfAdmin,
)
.toList();
Future<List<Room>> 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<Room> get _classesImIn => rooms
.where(
(e) =>
e.isPangeaClass &&
e.ownPowerLevel < ClassDefaultValues.powerLevelOfAdmin,
)
.toList();
Future<List<Room>> get _classesAndExchangesImStudyingIn async {
final List<Room> 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<Room> get _classesAndExchangesImIn =>
rooms.where((e) => e.isPangeaClass || e.isExchange).toList();
Future<PangeaRoomRules?> 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;
}

View file

@ -123,7 +123,7 @@ extension AnalyticsClientExtension on Client {
// Allows teachers to join analytics rooms without being invited
Future<void> _joinAnalyticsRoomsInAllSpaces() async {
final List<Future> 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<Map<String, DateTime?>> _allAnalyticsRoomsLastUpdated() async {
// get the last updated time for each analytics room
final Map<String, DateTime?> lastUpdatedMap = {};
for (final analyticsRoom in allMyAnalyticsRooms) {
final DateTime? lastUpdated = await analyticsRoom.analyticsLastUpdated(
PangeaEventTypes.summaryAnalytics,
userID!,
);
lastUpdatedMap[analyticsRoom.id] = lastUpdated;
}
return lastUpdatedMap;
}
}

View file

@ -3,17 +3,18 @@ import 'dart:developer';
import 'package:collection/collection.dart';
import 'package:fluffychat/pangea/constants/class_default_values.dart';
import 'package:fluffychat/pangea/constants/model_keys.dart';
import 'package:fluffychat/pangea/constants/pangea_event_types.dart';
import 'package:fluffychat/pangea/constants/pangea_room_types.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
import 'package:fluffychat/pangea/models/class_model.dart';
import 'package:fluffychat/pangea/models/space_model.dart';
import 'package:fluffychat/pangea/utils/bot_name.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:flutter/foundation.dart';
import 'package:matrix/matrix.dart';
part "classes_and_exchanges_extension.dart";
part "client_analytics_extension.dart";
part "general_info_extension.dart";
part "space_extension.dart";
extension PangeaClient on Client {
// analytics
@ -43,27 +44,20 @@ extension PangeaClient on Client {
Future<void> migrateAnalyticsRooms() async => await _migrateAnalyticsRooms();
// classes_and_exchanges
Future<Map<String, DateTime?>> allAnalyticsRoomsLastUpdated() async =>
await _allAnalyticsRoomsLastUpdated();
List<Room> get classes => _classes;
// spaces
List<Room> get classesImTeaching => _classesImTeaching;
Future<List<Room>> get spacesImTeaching async => await _spacesImTeaching;
Future<List<Room>> get classesAndExchangesImTeaching async =>
await _classesAndExchangesImTeaching;
Future<List<Room>> get spacesImStudyingIn async => await _spacesImStudyingIn;
List<Room> get classesImIn => _classesImIn;
Future<List<Room>> get classesAndExchangesImStudyingIn async =>
await _classesAndExchangesImStudyingIn;
List<Room> get classesAndExchangesImIn => _classesAndExchangesImIn;
List<Room> get spacesImIn => _spacesImIn;
Future<PangeaRoomRules?> get lastUpdatedRoomRules async =>
await _lastUpdatedRoomRules;
ClassSettingsModel? get lastUpdatedClassSettings => _lastUpdatedClassSettings;
// general_info
Future<List<String>> get teacherRoomIds async => await _teacherRoomIds;

View file

@ -3,7 +3,7 @@ part of "client_extension.dart";
extension GeneralInfoClientExtension on Client {
Future<List<String>> get _teacherRoomIds async {
final List<String> adminRoomIds = [];
for (final Room adminSpace in (await _classesAndExchangesImTeaching)) {
for (final Room adminSpace in (await _spacesImTeaching)) {
adminRoomIds.add(adminSpace.id);
final List<String> adminSpaceRooms = adminSpace.allSpaceChildRoomIds;
adminRoomIds.addAll(adminSpaceRooms);
@ -13,7 +13,7 @@ extension GeneralInfoClientExtension on Client {
Future<List<User>> get _myTeachers async {
final List<User> teachers = [];
for (final classRoom in classesAndExchangesImIn) {
for (final classRoom in spacesImIn) {
for (final teacher in await classRoom.teachers) {
// If person requesting list of teachers is a teacher in another classroom, don't add them to the list
if (!teachers.any((e) => e.id == teacher.id) && userID != teacher.id) {

View file

@ -0,0 +1,64 @@
part of "client_extension.dart";
extension SpaceClientExtension on Client {
Future<List<Room>> get _spacesImTeaching 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.isSpace) &&
e.ownPowerLevel == ClassDefaultValues.powerLevelOfAdmin,
)
.toList();
return spaces;
}
Future<List<Room>> get _spacesImStudyingIn async {
final List<Room> 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.isSpace &&
e.ownPowerLevel < ClassDefaultValues.powerLevelOfAdmin,
)
.toList();
return spaces;
}
List<Room> get _spacesImIn => rooms.where((e) => e.isSpace).toList();
Future<PangeaRoomRules?> get _lastUpdatedRoomRules async =>
(await _spacesImTeaching)
.where((space) => space.rulesUpdatedAt != null)
.sorted(
(a, b) => b.rulesUpdatedAt!.compareTo(a.rulesUpdatedAt!),
)
.firstOrNull
?.pangeaRoomRules;
// LanguageSettingsModel? get _lastUpdatedLanguageSettings => rooms
// .where((room) => room.isSpace && room.languageSettingsUpdatedAt != null)
// .sorted(
// (a, b) => b.languageSettingsUpdatedAt!
// .compareTo(a.languageSettingsUpdatedAt!),
// )
// .firstOrNull
// ?.languageSettings;
}

View file

@ -60,7 +60,7 @@ extension ChildrenAndParentsRoomExtension on Room {
//resolve somehow if multiple rooms have the state?
//check logic
Room? _firstParentWithState(String stateType) {
if (![PangeaEventTypes.classSettings, PangeaEventTypes.rules]
if (![PangeaEventTypes.languageSettings, PangeaEventTypes.rules]
.contains(stateType)) {
return null;
}
@ -77,13 +77,6 @@ extension ChildrenAndParentsRoomExtension on Room {
return null;
}
/// find any parents and return the rooms
List<Room> get _immediateClassParents => pangeaSpaceParents
.where(
(element) => element.isPangeaClass,
)
.toList();
List<Room> get _pangeaSpaceParents => client.rooms
.where(
(r) => r.isSpace,
@ -98,16 +91,16 @@ extension ChildrenAndParentsRoomExtension on Room {
String _nameIncludingParents(BuildContext context) {
String nameSoFar = getLocalizedDisplayname(MatrixLocals(L10n.of(context)!));
Room currentRoom = this;
if (currentRoom.immediateClassParents.isEmpty) {
if (currentRoom.pangeaSpaceParents.isEmpty) {
return nameSoFar;
}
currentRoom = currentRoom.immediateClassParents.first;
currentRoom = currentRoom.pangeaSpaceParents.first;
var nameToAdd =
currentRoom.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!));
nameToAdd =
nameToAdd.length <= 10 ? nameToAdd : "${nameToAdd.substring(0, 10)}...";
nameSoFar = '$nameToAdd > $nameSoFar';
if (currentRoom.immediateClassParents.isEmpty) {
if (currentRoom.pangeaSpaceParents.isEmpty) {
return nameSoFar;
}
return "... > $nameSoFar";
@ -130,11 +123,19 @@ extension ChildrenAndParentsRoomExtension on Room {
// Checks if has permissions to add child chat
// Or whether potential child space is ancestor of this
bool _canAddAsParentOf(Room? child) {
if (child == null || !child.isSpace) {
return _canIAddSpaceChild(child);
}
if (id == child.id) return false;
return !child._allSpaceChildRoomIds.contains(id);
bool _canAddAsParentOf(Room? child, {bool spaceMode = false}) {
// don't add a room to itself
if (id == child?.id) return false;
spaceMode = child?.isSpace ?? spaceMode;
// get the bool for adding chats to spaces
final bool canAddChild = _canIAddSpaceChild(child, spaceMode: spaceMode);
if (!spaceMode) return canAddChild;
// if adding space to a space, check if the child is an ancestor
// to prevent cycles
final bool isCycle =
child != null ? child._allSpaceChildRoomIds.contains(id) : false;
return canAddChild && !isCycle;
}
}

View file

@ -12,7 +12,7 @@ import 'package:fluffychat/pangea/models/analytics/constructs_event.dart';
import 'package:fluffychat/pangea/models/analytics/summary_analytics_event.dart';
import 'package:fluffychat/pangea/models/analytics/summary_analytics_model.dart';
import 'package:fluffychat/pangea/models/bot_options_model.dart';
import 'package:fluffychat/pangea/models/class_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/bot_name.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
@ -36,11 +36,11 @@ import '../../models/representation_content_model.dart';
import '../client_extension/client_extension.dart';
part "children_and_parents_extension.dart";
part "class_and_exchange_settings_extension.dart";
part "events_extension.dart";
part "room_analytics_extension.dart";
part "room_information_extension.dart";
part "room_settings_extension.dart";
part "space_settings_extension.dart";
part "user_permissions_extension.dart";
extension PangeaRoom on Room {
@ -49,9 +49,6 @@ extension PangeaRoom on Room {
Future<void> joinAnalyticsRoomsInSpace() async =>
await _joinAnalyticsRoomsInSpace();
Future<void> ensureAnalyticsRoomExists() async =>
await _ensureAnalyticsRoomExists();
Future<void> addAnalyticsRoomToSpace(Room analyticsRoom) async =>
await _addAnalyticsRoomToSpace(analyticsRoom);
@ -105,8 +102,6 @@ extension PangeaRoom on Room {
Room? firstParentWithState(String stateType) =>
_firstParentWithState(stateType);
List<Room> get immediateClassParents => _immediateClassParents;
List<Room> get pangeaSpaceParents => _pangeaSpaceParents;
String nameIncludingParents(BuildContext context) =>
@ -114,7 +109,9 @@ extension PangeaRoom on Room {
List<String> get allSpaceChildRoomIds => _allSpaceChildRoomIds;
bool canAddAsParentOf(Room? child) => _canAddAsParentOf(child);
bool canAddAsParentOf(Room? child, {bool spaceMode = false}) {
return _canAddAsParentOf(child, spaceMode: spaceMode);
}
// class_and_exchange_settings
@ -130,16 +127,8 @@ extension PangeaRoom on Room {
Future<void> setClassPowerLevels() async => await _setClassPowerLevels();
DateTime? get classSettingsUpdatedAt => _classSettingsUpdatedAt;
ClassSettingsModel? get classSettings => _classSettings;
Event? get languageSettingsStateEvent => _languageSettingsStateEvent;
Event? get pangeaRoomRulesStateEvent => _pangeaRoomRulesStateEvent;
ClassSettingsModel? get firstLanguageSettings => _firstLanguageSettings;
// events
Future<bool> leaveIfFull() async => await _leaveIfFull();
@ -222,8 +211,6 @@ extension PangeaRoom on Room {
bool isFirstOrSecondChild(String roomId) => _isFirstOrSecondChild(roomId);
bool get isExchange => _isExchange;
bool get isDirectChatWithoutMe => _isDirectChatWithoutMe;
// bool isMadeForLang(String langCode) => _isMadeForLang(langCode);
@ -234,8 +221,6 @@ extension PangeaRoom on Room {
bool get isLocked => _isLocked;
bool get isPangeaClass => _isPangeaClass;
bool isAnalyticsRoomOfUser(String userId) => _isAnalyticsRoomOfUser(userId);
bool get isAnalyticsRoom => _isAnalyticsRoom;
@ -283,7 +268,9 @@ extension PangeaRoom on Room {
bool get canDelete => _canDelete;
bool canIAddSpaceChild(Room? room) => _canIAddSpaceChild(room);
bool canIAddSpaceChild(Room? room, {bool spaceMode = false}) {
return _canIAddSpaceChild(room, spaceMode: spaceMode);
}
bool get canIAddSpaceParents => _canIAddSpaceParents;

View file

@ -55,14 +55,6 @@ extension AnalyticsRoomExtension on Room {
}
}
// check if analytics room exists for a given language code
// and if not, create it
Future<void> _ensureAnalyticsRoomExists() async {
await postLoad();
if (firstLanguageSettings?.targetLanguage == null) return;
await client.getMyAnalyticsRoom(firstLanguageSettings!.targetLanguage);
}
// add 1 analytics room to 1 space
Future<void> _addAnalyticsRoomToSpace(Room analyticsRoom) async {
if (!isSpace) {
@ -107,7 +99,7 @@ extension AnalyticsRoomExtension on Room {
return;
}
for (final Room space in (await client.classesAndExchangesImStudyingIn)) {
for (final Room space in (await client.spacesImStudyingIn)) {
if (space.spaceChildren.any((sc) => sc.roomId == id)) continue;
await space.addAnalyticsRoomToSpace(this);
}
@ -183,7 +175,7 @@ extension AnalyticsRoomExtension on Room {
return;
}
for (final Room space in (await client.classesAndExchangesImStudyingIn)) {
for (final Room space in (await client.spacesImStudyingIn)) {
await space.inviteSpaceTeachersToAnalyticsRoom(this);
}
}

View file

@ -40,11 +40,6 @@ extension RoomInformationRoomExtension on Room {
));
}
bool get _isExchange =>
isSpace &&
languageSettingsStateEvent == null &&
pangeaRoomRulesStateEvent != null;
bool get _isDirectChatWithoutMe =>
isDirectChat && !getParticipants().any((e) => e.id == client.userID);
@ -83,8 +78,6 @@ extension RoomInformationRoomExtension on Room {
ClassDefaultValues.powerLevelOfAdmin;
}
bool get _isPangeaClass => isSpace && languageSettingsStateEvent != null;
bool _isAnalyticsRoomOfUser(String userId) =>
isAnalyticsRoom && isMadeByUser(userId);

View file

@ -40,8 +40,7 @@ extension RoomSettingsRoomExtension on Room {
IconData? get _roomTypeIcon {
if (membership == Membership.invite) return Icons.add;
if (isPangeaClass) return Icons.school;
if (isExchange) return Icons.connecting_airports;
if (isSpace) return Icons.school;
if (isAnalyticsRoom) return Icons.analytics;
if (isDirectChat) return Icons.forum;
return Icons.group;

View file

@ -1,6 +1,6 @@
part of "pangea_room_extension.dart";
extension ClassAndExchangeSettingsRoomExtension on Room {
extension SpaceRoomExtension on Room {
DateTime? get _rulesUpdatedAt {
if (!isSpace) return null;
return pangeaRoomRulesStateEvent?.originServerTs ?? creationTime;
@ -9,7 +9,7 @@ extension ClassAndExchangeSettingsRoomExtension on Room {
String get _classCode {
if (!isSpace) {
for (final Room potentialClassRoom in pangeaSpaceParents) {
if (potentialClassRoom.isPangeaClass) {
if (potentialClassRoom.isSpace) {
return potentialClassRoom.classCode;
}
}
@ -84,46 +84,6 @@ extension ClassAndExchangeSettingsRoomExtension on Room {
}
}
DateTime? get _classSettingsUpdatedAt {
if (!isSpace) return null;
return languageSettingsStateEvent?.originServerTs ?? creationTime;
}
/// the pangeaClass event is listed an importantStateEvent so, if event exists,
/// it's already local. If it's an old class and doesn't, then the class_controller
/// should automatically migrate during this same session, when the space is first loaded
ClassSettingsModel? get _classSettings {
try {
if (!isSpace) {
return null;
}
final Map<String, dynamic>? content = languageSettingsStateEvent?.content;
if (content != null) {
final ClassSettingsModel classSettings =
ClassSettingsModel.fromJson(content);
return classSettings;
}
return null;
} catch (err, s) {
Sentry.addBreadcrumb(
Breadcrumb(
message: "Error in classSettings",
data: {"room": toJson()},
),
);
ErrorHandler.logError(e: err, s: s);
return null;
}
}
Event? get _languageSettingsStateEvent {
final dynamic classSettings = getState(PangeaEventTypes.classSettings);
if (classSettings is Event) {
return classSettings;
}
return null;
}
Event? get _pangeaRoomRulesStateEvent {
final dynamic roomRules = getState(PangeaEventTypes.rules);
if (roomRules is Event) {
@ -132,7 +92,48 @@ extension ClassAndExchangeSettingsRoomExtension on Room {
return null;
}
ClassSettingsModel? get _firstLanguageSettings =>
classSettings ??
firstParentWithState(PangeaEventTypes.classSettings)?.classSettings;
// DateTime? get _languageSettingsUpdatedAt {
// if (!isSpace) return null;
// return languageSettingsStateEvent?.originServerTs ?? creationTime;
// }
/// the pangeaClass event is listed an importantStateEvent so, if event exists,
/// it's already local. If it's an old class and doesn't, then the class_controller
/// should automatically migrate during this same session, when the space is first loaded
// LanguageSettingsModel? get _languageSettings {
// try {
// if (!isSpace) {
// return null;
// }
// final Map<String, dynamic>? content = languageSettingsStateEvent?.content;
// if (content != null) {
// final LanguageSettingsModel languageSettings =
// LanguageSettingsModel.fromJson(content);
// return languageSettings;
// }
// return null;
// } catch (err, s) {
// Sentry.addBreadcrumb(
// Breadcrumb(
// message: "Error in languageSettings",
// data: {"room": toJson()},
// ),
// );
// ErrorHandler.logError(e: err, s: s);
// return null;
// }
// }
// Event? get _languageSettingsStateEvent {
// final dynamic languageSettings =
// getState(PangeaEventTypes.languageSettings);
// if (languageSettings is Event) {
// return languageSettings;
// }
// return null;
// }
// LanguageSettingsModel? get _firstLanguageSettings =>
// languageSettings ??
// firstParentWithState(PangeaEventTypes.languageSettings)?.languageSettings;
}

View file

@ -78,39 +78,30 @@ extension UserPermissionsRoomExtension on Room {
bool get _canDelete => isSpaceAdmin;
bool _canIAddSpaceChild(Room? room) {
bool _canIAddSpaceChild(Room? room, {bool spaceMode = false}) {
if (!isSpace) {
ErrorHandler.logError(
m: "should not call canIAddSpaceChildren on non-space room",
m: "should not call canIAddSpaceChildren on non-space room. Room id: $id",
data: toJson(),
s: StackTrace.current,
);
return false;
}
if (room != null && !room._isRoomAdmin) {
return false;
}
if (!pangeaCanSendEvent(EventTypes.SpaceChild) && !_isRoomAdmin) {
return false;
}
if (room == null) {
return isRoomAdmin || (pangeaRoomRules?.isCreateRooms ?? false);
}
if (room.isExchange) {
return isRoomAdmin;
}
if (!room.isSpace) {
return pangeaRoomRules?.isCreateRooms ?? false;
}
if (room.isPangeaClass) {
ErrorHandler.logError(
m: "should not call canIAddSpaceChild with class",
data: room.toJson(),
s: StackTrace.current,
);
return false;
}
return false;
final isSpaceAdmin = isRoomAdmin;
final isChildRoomAdmin = room?.isRoomAdmin ?? true;
// if user is not admin of child room, return false
if (!isChildRoomAdmin) return false;
// if the child room is a space, or will be a space,
// then the user must be an admin of the parent space
if (room?.isSpace ?? spaceMode) return isSpaceAdmin;
// otherwise, the user can add the child room to the parent
// if they're the admin of the parent or if the parent creation
// of group chats
return isSpaceAdmin || (pangeaRoomRules?.isCreateRooms ?? false);
}
bool get _canIAddSpaceParents =>

View file

@ -7,9 +7,9 @@ import 'package:fluffychat/pangea/enum/audio_encoding_enum.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_representation_event.dart';
import 'package:fluffychat/pangea/models/choreo_record.dart';
import 'package:fluffychat/pangea/models/class_model.dart';
import 'package:fluffychat/pangea/models/pangea_match_model.dart';
import 'package:fluffychat/pangea/models/representation_content_model.dart';
import 'package:fluffychat/pangea/models/space_model.dart';
import 'package:fluffychat/pangea/models/speech_to_text_models.dart';
import 'package:fluffychat/pangea/models/tokens_event_content_model.dart';
import 'package:fluffychat/pangea/utils/bot_name.dart';
@ -553,8 +553,8 @@ class PangeaMessageEvent {
final bool immersionMode = MatrixState
.pangeaController.permissionsController
.isToolEnabled(ToolSetting.immersionMode, room);
final String? l2Code = MatrixState.pangeaController.languageController
.activeL2Code(roomID: room.id);
final String? l2Code =
MatrixState.pangeaController.languageController.activeL2Code();
final String? originalLangCode =
(originalWritten ?? originalSent)?.langCode;

View file

@ -8,7 +8,6 @@ class CustomInputRequestModel {
String targetLangCode;
String userId;
String roomId;
String? classId;
String? goldTranslation;
List<Continuance>? goldContinuances;
@ -20,7 +19,6 @@ class CustomInputRequestModel {
required this.targetLangCode,
required this.userId,
required this.roomId,
required this.classId,
required this.goldTranslation,
required this.goldContinuances,
});
@ -32,7 +30,6 @@ class CustomInputRequestModel {
targetLangCode: json[ModelKey.tgtLang],
userId: json['user_id'],
roomId: json['room_id'],
classId: json['class_id'],
goldTranslation: json['gold_translation'],
goldContinuances: json['gold_continuances'] != null
? List.from(json['gold_continuances'])
@ -48,7 +45,6 @@ class CustomInputRequestModel {
ModelKey.tgtLang: targetLangCode,
'user_id': userId,
'room_id': roomId,
'class_id': classId,
'gold_translation': goldTranslation,
'gold_continuances': goldContinuances != null
? List.from(goldContinuances!.map((e) => e.toJson()))

View file

@ -1,40 +0,0 @@
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:fluffychat/pangea/constants/model_keys.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'class_model.dart';
class ExchangeModel {
PangeaRoomRules permissions;
ExchangeModel({
required this.permissions,
});
factory ExchangeModel.fromJson(Map<String, dynamic> json) {
return ExchangeModel(
permissions: PangeaRoomRules.fromJson(json[ModelKey.permissions]),
);
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
try {
data[ModelKey.permissions] = permissions.toJson();
return data;
} catch (e, s) {
debugger(when: kDebugMode);
ErrorHandler.logError(e: e, s: s);
return data;
}
}
updateEditableClassField(String key, dynamic value) {
switch (key) {
default:
throw Exception('Invalid key for setting permissions - $key');
}
}
}

View file

@ -79,8 +79,8 @@ class LanguageModel {
static LanguageModel multiLingual([BuildContext? context]) => LanguageModel(
displayName: context != null
? L10n.of(context)!.multiLingualClass
: "Multilingual Class",
? L10n.of(context)!.multiLingualSpace
: "Multilingual Space",
l2: false,
l1: false,
langCode: LanguageKeys.multiLanguage,

View file

@ -1,6 +1,5 @@
import 'dart:developer';
import 'package:fluffychat/pangea/constants/model_keys.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@ -12,7 +11,7 @@ import '../constants/language_keys.dart';
import '../constants/pangea_event_types.dart';
import 'language_model.dart';
class ClassSettingsModel {
class LanguageSettingsModel {
String? city;
String? country;
String? schoolName;
@ -20,7 +19,7 @@ class ClassSettingsModel {
String dominantLanguage;
String targetLanguage;
ClassSettingsModel({
LanguageSettingsModel({
this.dominantLanguage = ClassDefaultValues.defaultDominantLanguage,
this.targetLanguage = ClassDefaultValues.defaultTargetLanguage,
this.languageLevel,
@ -29,17 +28,8 @@ class ClassSettingsModel {
this.schoolName,
});
static ClassSettingsModel get newClass => ClassSettingsModel(
city: null,
country: null,
dominantLanguage: ClassDefaultValues.defaultDominantLanguage,
languageLevel: null,
schoolName: null,
targetLanguage: ClassDefaultValues.defaultTargetLanguage,
);
factory ClassSettingsModel.fromJson(Map<String, dynamic> json) {
return ClassSettingsModel(
factory LanguageSettingsModel.fromJson(Map<String, dynamic> json) {
return LanguageSettingsModel(
city: json['city'],
country: json['country'],
dominantLanguage: LanguageModel.codeFromNameOrCode(
@ -72,35 +62,9 @@ class ClassSettingsModel {
}
}
//TODO: define enum with all possible values
updateEditableClassField(String key, dynamic value) {
switch (key) {
case ModelKey.clientClassCity:
city = value;
break;
case ModelKey.clientClassCountry:
country = value;
break;
case ModelKey.clientClassDominantLanguage:
dominantLanguage = value;
break;
case ModelKey.clientClassTargetLanguage:
targetLanguage = value;
break;
case ModelKey.clientLanguageLevel:
languageLevel = value;
break;
case ModelKey.clientSchool:
schoolName = value;
break;
default:
throw Exception('Invalid key for setting permissions - $key');
}
}
StateEvent get toStateEvent => StateEvent(
content: toJson(),
type: PangeaEventTypes.classSettings,
type: PangeaEventTypes.languageSettings,
);
}

View file

@ -4,7 +4,7 @@ import 'package:country_picker/country_picker.dart';
import 'package:fluffychat/pangea/constants/local.key.dart';
import 'package:fluffychat/pangea/constants/model_keys.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/models/class_model.dart';
import 'package:fluffychat/pangea/models/space_model.dart';
import 'package:fluffychat/pangea/utils/instructions.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';

View file

@ -0,0 +1,38 @@
import 'package:fluffychat/pangea/models/language_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
class AnalyticsLanguageButton extends StatelessWidget {
final List<LanguageModel> languages;
final LanguageModel value;
final void Function(LanguageModel) onChange;
const AnalyticsLanguageButton({
super.key,
required this.value,
required this.onChange,
required this.languages,
});
@override
Widget build(BuildContext context) {
return PopupMenuButton<LanguageModel>(
icon: const Icon(Icons.language_outlined),
tooltip: L10n.of(context)!.changeAnalyticsLanguage,
initialValue: value,
onSelected: (LanguageModel? lang) {
if (lang == null) {
debugPrint("when is lang null?");
return;
}
onChange(lang);
},
itemBuilder: (BuildContext context) =>
languages.map((LanguageModel lang) {
return PopupMenuItem<LanguageModel>(
value: lang,
child: Text(lang.getDisplayName(context) ?? lang.langCode),
);
}).toList(),
);
}
}

View file

@ -26,8 +26,6 @@ class AnalyticsListTile extends StatefulWidget {
required this.onTap,
required this.pangeaController,
this.controller,
// this.isEnabled = true,
// this.showSpaceAnalytics = true,
this.refreshStream,
});
@ -40,8 +38,6 @@ class AnalyticsListTile extends StatefulWidget {
final bool allowNavigateOnSelect;
final bool isSelected;
// final bool isEnabled;
// final bool showSpaceAnalytics;
final PangeaController pangeaController;
final BaseAnalyticsController? controller;
@ -64,6 +60,14 @@ class AnalyticsListTileState extends State<AnalyticsListTile> {
});
}
@override
void didUpdateWidget(covariant AnalyticsListTile oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.selected != widget.selected) {
setTileData();
}
}
@override
void dispose() {
refreshSubscription?.cancel();
@ -87,73 +91,68 @@ class AnalyticsListTileState extends State<AnalyticsListTile> {
color: widget.isSelected
? Theme.of(context).colorScheme.secondaryContainer
: Colors.transparent,
child: Tooltip(
message: widget.selected.type == AnalyticsEntryType.room
? L10n.of(context)!.joinToView
: L10n.of(context)!.studentAnalyticsNotAvailable,
child: ListTile(
leading: widget.selected.type == AnalyticsEntryType.privateChats
? CircleAvatar(
backgroundColor: Theme.of(context).primaryColor,
foregroundColor: Colors.white,
radius: Avatar.defaultSize / 2,
child: const Icon(Icons.forum),
)
: Avatar(
mxContent: widget.avatar,
name: widget.selected.displayName,
littleIcon: room?.roomTypeIcon,
),
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
widget.selected.displayName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
softWrap: false,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Theme.of(context).textTheme.bodyLarge!.color,
),
child: ListTile(
leading: widget.selected.type == AnalyticsEntryType.privateChats
? CircleAvatar(
backgroundColor: Theme.of(context).primaryColor,
foregroundColor: Colors.white,
radius: Avatar.defaultSize / 2,
child: const Icon(Icons.forum),
)
: Avatar(
mxContent: widget.avatar,
name: widget.selected.displayName,
littleIcon: room?.roomTypeIcon,
),
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
widget.selected.displayName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
softWrap: false,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Theme.of(context).textTheme.bodyLarge!.color,
),
),
Tooltip(
message: L10n.of(context)!.timeOfLastMessage,
child: Text(
tileData?.lastMessageTime?.localizedTimeShort(context) ?? "",
style: TextStyle(
fontSize: 13,
color: Theme.of(context).textTheme.bodyMedium!.color,
),
),
Tooltip(
message: L10n.of(context)!.timeOfLastMessage,
child: Text(
tileData?.lastMessageTime?.localizedTimeShort(context) ?? "",
style: TextStyle(
fontSize: 13,
color: Theme.of(context).textTheme.bodyMedium!.color,
),
),
],
),
subtitle: ListSummaryAnalytics(
chartAnalytics: tileData,
),
selected: widget.isSelected,
onTap: () {
if (widget.controller?.widget.selectedView == null) {
widget.onTap(widget.selected);
return;
}
if ((room?.isSpace ?? false) && widget.allowNavigateOnSelect) {
final String selectedView =
widget.controller!.widget.selectedView!.route;
context.go('/rooms/analytics/${room!.id}/$selectedView');
return;
}
widget.onTap(widget.selected);
},
trailing: (room?.isSpace ?? false) &&
widget.selected.type != AnalyticsEntryType.privateChats &&
widget.allowNavigateOnSelect
? const Icon(Icons.chevron_right)
: null,
),
],
),
subtitle: ListSummaryAnalytics(
chartAnalytics: tileData,
),
selected: widget.isSelected,
onTap: () {
if (widget.controller?.widget.selectedView == null) {
widget.onTap(widget.selected);
return;
}
if ((room?.isSpace ?? false) && widget.allowNavigateOnSelect) {
final String selectedView =
widget.controller!.widget.selectedView!.route;
context.go('/rooms/analytics/${room!.id}/$selectedView');
return;
}
widget.onTap(widget.selected);
},
trailing: (room?.isSpace ?? false) &&
widget.selected.type != AnalyticsEntryType.privateChats &&
widget.allowNavigateOnSelect
? const Icon(Icons.chevron_right)
: null,
),
);
}

View file

@ -4,6 +4,7 @@ import 'package:fluffychat/pangea/constants/pangea_event_types.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/analytics/analytics_event.dart';
import 'package:fluffychat/pangea/models/language_model.dart';
import 'package:fluffychat/pangea/pages/analytics/base_analytics_view.dart';
import 'package:fluffychat/pangea/pages/analytics/student_analytics/student_analytics.dart';
import 'package:flutter/material.dart';
@ -157,6 +158,12 @@ class BaseAnalyticsController extends State<BaseAnalyticsPage> {
refreshStream.add(false);
}
Future<void> toggleSpaceLang(LanguageModel lang) async {
await pangeaController.analytics.setCurrentAnalyticsSpaceLang(lang);
await setChartData();
refreshStream.add(false);
}
void setCurrentLemma(String? lemma) {
currentLemma = lemma;
setState(() {});

View file

@ -3,6 +3,7 @@ import 'dart:math';
import 'package:fluffychat/pangea/enum/bar_chart_view_enum.dart';
import 'package:fluffychat/pangea/enum/construct_type_enum.dart';
import 'package:fluffychat/pangea/enum/time_span.dart';
import 'package:fluffychat/pangea/pages/analytics/analytics_language_button.dart';
import 'package:fluffychat/pangea/pages/analytics/analytics_list_tile.dart';
import 'package:fluffychat/pangea/pages/analytics/base_analytics.dart';
import 'package:fluffychat/pangea/pages/analytics/construct_list.dart';
@ -121,6 +122,15 @@ class BaseAnalyticsView extends StatelessWidget {
onChange: (TimeSpan value) =>
controller.toggleTimeSpan(context, value),
),
if (controller.widget.defaultSelected.type ==
AnalyticsEntryType.space)
AnalyticsLanguageButton(
value: controller.pangeaController.analytics
.currentAnalyticsSpaceLang,
onChange: (lang) => controller.toggleSpaceLang(lang),
languages: controller
.pangeaController.pLanguageStore.targetOptions,
),
],
),
Expanded(

View file

@ -14,48 +14,47 @@ import 'package:matrix/matrix.dart';
import '../../../../widgets/matrix.dart';
import '../../../utils/sync_status_util_v2.dart';
import 'class_analytics_view.dart';
import 'space_analytics_view.dart';
enum AnalyticsPageType { classList, student, classDetails }
class ClassAnalyticsPage extends StatefulWidget {
class SpaceAnalyticsPage extends StatefulWidget {
final BarChartViewSelection? selectedView;
const ClassAnalyticsPage({super.key, this.selectedView});
const SpaceAnalyticsPage({super.key, this.selectedView});
@override
State<ClassAnalyticsPage> createState() => ClassAnalyticsV2Controller();
State<SpaceAnalyticsPage> createState() => SpaceAnalyticsV2Controller();
}
class ClassAnalyticsV2Controller extends State<ClassAnalyticsPage> {
class SpaceAnalyticsV2Controller extends State<SpaceAnalyticsPage> {
bool _initialized = false;
// StreamSubscription<Event>? stateSub;
// Timer? refreshTimer;
List<SpaceRoomsChunk> chats = [];
List<User> students = [];
String? get classId => GoRouterState.of(context).pathParameters['classid'];
Room? _classRoom;
String? get spaceId => GoRouterState.of(context).pathParameters['spaceid'];
Room? _spaceRoom;
Room? get classRoom {
if (_classRoom == null || _classRoom!.id != classId) {
debugPrint("updating _classRoom");
_classRoom = classId != null
? Matrix.of(context).client.getRoomById(classId!)
Room? get spaceRoom {
if (_spaceRoom == null || _spaceRoom!.id != spaceId) {
debugPrint("updating _spaceRoom");
_spaceRoom = spaceId != null
? Matrix.of(context).client.getRoomById(spaceId!)
: null;
if (_classRoom == null) {
if (_spaceRoom == null) {
context.go('/rooms/analytics');
return null;
}
getChatAndStudents();
}
return _classRoom;
return _spaceRoom;
}
@override
void initState() {
super.initState();
debugPrint("init class analytics");
debugPrint("init space analytics");
Future.delayed(Duration.zero, () async {
if (classRoom == null || (!(classRoom?.isSpace ?? false))) {
if (spaceRoom == null || (!(spaceRoom?.isSpace ?? false))) {
context.go('/rooms');
}
getChatAndStudents();
@ -64,25 +63,25 @@ class ClassAnalyticsV2Controller extends State<ClassAnalyticsPage> {
Future<void> getChatAndStudents() async {
try {
await classRoom?.postLoad();
await classRoom?.requestParticipants();
await spaceRoom?.postLoad();
await spaceRoom?.requestParticipants();
if (classRoom != null) {
if (spaceRoom != null) {
final response = await Matrix.of(context).client.getSpaceHierarchy(
classRoom!.id,
spaceRoom!.id,
);
// set the latest fetched full hierarchy in message analytics controller
// we want to avoid calling this endpoint again and again, so whenever the
// data is made available, set it in the controller
MatrixState.pangeaController.analytics
.setLatestHierarchy(_classRoom!.id, response);
.setLatestHierarchy(_spaceRoom!.id, response);
students = classRoom!.students;
students = spaceRoom!.students;
chats = response.rooms
.where(
(room) =>
room.roomId != classRoom!.id &&
room.roomId != spaceRoom!.id &&
room.roomType != PangeaRoomTypes.analytics,
)
.toList();
@ -116,7 +115,7 @@ class ClassAnalyticsV2Controller extends State<ClassAnalyticsPage> {
// onFinish: () {
// getChatAndStudentAnalytics(context);
// },
child: ClassAnalyticsView(this),
child: SpaceAnalyticsView(this),
);
}
}

View file

@ -3,17 +3,15 @@ import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import '../base_analytics.dart';
import 'class_analytics.dart';
import 'space_analytics.dart';
class ClassAnalyticsView extends StatelessWidget {
final ClassAnalyticsV2Controller controller;
const ClassAnalyticsView(this.controller, {super.key});
class SpaceAnalyticsView extends StatelessWidget {
final SpaceAnalyticsV2Controller controller;
const SpaceAnalyticsView(this.controller, {super.key});
@override
Widget build(BuildContext context) {
// final String pageTitle =
// "${L10n.of(context)!.classAnalytics}: ${controller.className(context)}";
final String pageTitle = L10n.of(context)!.classAnalytics;
final String pageTitle = L10n.of(context)!.spaceAnalytics;
final TabData tab1 = TabData(
type: AnalyticsEntryType.room,
icon: Icons.chat_bubble_outline,
@ -46,20 +44,20 @@ class ClassAnalyticsView extends StatelessWidget {
.toList(),
);
return controller.classId != null
return controller.spaceId != null
? BaseAnalyticsPage(
selectedView: controller.widget.selectedView,
pageTitle: pageTitle,
tabs: [tab1, tab2],
alwaysSelected: AnalyticsSelected(
controller.classId!,
controller.spaceId!,
AnalyticsEntryType.space,
controller.classRoom?.name ?? "",
controller.spaceRoom?.name ?? "",
),
defaultSelected: AnalyticsSelected(
controller.classId!,
controller.spaceId!,
AnalyticsEntryType.space,
controller.classRoom?.name ?? "",
controller.spaceRoom?.name ?? "",
),
)
: const SizedBox();

View file

@ -2,33 +2,31 @@ import 'dart:async';
import 'package:fluffychat/pangea/enum/time_span.dart';
import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart';
import 'package:fluffychat/pangea/pages/analytics/base_analytics.dart';
import 'package:fluffychat/pangea/pages/analytics/class_list/class_list_view.dart';
import 'package:fluffychat/pangea/models/language_model.dart';
import 'package:fluffychat/pangea/pages/analytics/space_list/space_list_view.dart';
import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
import '../../../../widgets/matrix.dart';
import '../../../controllers/pangea_controller.dart';
import '../../../models/analytics/chart_analytics_model.dart';
import '../../../utils/sync_status_util_v2.dart';
import '../../../widgets/common/list_placeholder.dart';
class AnalyticsClassList extends StatefulWidget {
const AnalyticsClassList({super.key});
class AnalyticsSpaceList extends StatefulWidget {
const AnalyticsSpaceList({super.key});
@override
State<AnalyticsClassList> createState() => AnalyticsClassListController();
State<AnalyticsSpaceList> createState() => AnalyticsSpaceListController();
}
class AnalyticsClassListController extends State<AnalyticsClassList> {
class AnalyticsSpaceListController extends State<AnalyticsSpaceList> {
PangeaController pangeaController = MatrixState.pangeaController;
List<ChartAnalyticsModel> models = [];
List<Room> spaces = [];
@override
void initState() {
super.initState();
Matrix.of(context).client.classesAndExchangesImTeaching.then((spaceList) {
Matrix.of(context).client.spacesImTeaching.then((spaceList) {
spaceList = spaceList
.where(
(space) => !spaceList.any(
@ -42,38 +40,28 @@ class AnalyticsClassListController extends State<AnalyticsClassList> {
});
}
StreamController refreshStream = StreamController.broadcast();
void toggleTimeSpan(BuildContext context, TimeSpan timeSpan) {
pangeaController.analytics.setCurrentAnalyticsTimeSpan(timeSpan);
refreshStream.add(false);
setState(() {});
}
Future<void> toggleSpaceLang(LanguageModel lang) async {
await pangeaController.analytics.setCurrentAnalyticsSpaceLang(lang);
refreshStream.add(false);
setState(() {});
}
@override
Widget build(BuildContext context) {
return PLoadingStatusV2(
shimmerChild: const ListPlaceholder(),
child: AnalyticsClassListView(this),
child: AnalyticsSpaceListView(this),
onFinish: () {
// getAllClassAnalytics(context);
},
);
}
Future<ChartAnalyticsModel?> updateClassAnalytics(
Room? space,
) async {
if (space == null) {
return null;
}
final data = await pangeaController.analytics.getAnalytics(
defaultSelected: AnalyticsSelected(
space.id,
AnalyticsEntryType.space,
space.name,
),
forceUpdate: true,
);
setState(() {});
return data;
}
void toggleTimeSpan(BuildContext context, TimeSpan timeSpan) {
pangeaController.analytics.setCurrentAnalyticsTimeSpan(timeSpan);
setState(() {});
}
}

View file

@ -1,3 +1,4 @@
import 'package:fluffychat/pangea/pages/analytics/analytics_language_button.dart';
import 'package:fluffychat/pangea/pages/analytics/analytics_list_tile.dart';
import 'package:fluffychat/pangea/pages/analytics/time_span_menu_button.dart';
import 'package:flutter/material.dart';
@ -6,11 +7,11 @@ import 'package:go_router/go_router.dart';
import '../../../enum/time_span.dart';
import '../base_analytics.dart';
import 'class_list.dart';
import 'space_list.dart';
class AnalyticsClassListView extends StatelessWidget {
final AnalyticsClassListController controller;
const AnalyticsClassListView(this.controller, {super.key});
class AnalyticsSpaceListView extends StatelessWidget {
final AnalyticsSpaceListController controller;
const AnalyticsSpaceListView(this.controller, {super.key});
@override
Widget build(BuildContext context) {
@ -18,7 +19,7 @@ class AnalyticsClassListView extends StatelessWidget {
appBar: AppBar(
centerTitle: true,
title: Text(
L10n.of(context)!.classAnalytics,
L10n.of(context)!.spaceAnalytics,
style: TextStyle(
color: Theme.of(context).textTheme.bodyLarge!.color,
fontSize: 18,
@ -35,8 +36,16 @@ class AnalyticsClassListView extends StatelessWidget {
TimeSpanMenuButton(
value:
controller.pangeaController.analytics.currentAnalyticsTimeSpan,
onChange: (TimeSpan value) =>
controller.toggleTimeSpan(context, value),
onChange: (TimeSpan value) => controller.toggleTimeSpan(
context,
value,
),
),
AnalyticsLanguageButton(
value:
controller.pangeaController.analytics.currentAnalyticsSpaceLang,
onChange: (lang) => controller.toggleSpaceLang(lang),
languages: controller.pangeaController.pLanguageStore.targetOptions,
),
],
),
@ -49,7 +58,7 @@ class AnalyticsClassListView extends StatelessWidget {
defaultSelected: AnalyticsSelected(
controller.spaces[i].id,
AnalyticsEntryType.space,
"",
controller.spaces[i].name,
),
avatar: controller.spaces[i].avatar,
selected: AnalyticsSelected(
@ -65,6 +74,7 @@ class AnalyticsClassListView extends StatelessWidget {
allowNavigateOnSelect: true,
isSelected: false,
pangeaController: controller.pangeaController,
refreshStream: controller.refreshStream,
),
),
),

View file

@ -15,7 +15,6 @@ class TimeSpanMenuButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return PopupMenuButton<TimeSpan>(
offset: const Offset(0, 100),
icon: const Icon(Icons.calendar_month_outlined),
tooltip: L10n.of(context)!.changeDateRange,
initialValue: value,

View file

@ -3,11 +3,13 @@ import 'package:flutter/scheduler.dart';
class MeasurableWidget extends StatefulWidget {
final Widget child;
Function? triggerMeasure;
final Function(Size? size, Offset? position) onChange;
MeasurableWidget({super.key, required this.onChange, required this.child});
const MeasurableWidget({
super.key,
required this.onChange,
required this.child,
});
@override
_WidgetSizeState createState() => _WidgetSizeState();
@ -26,20 +28,22 @@ class _WidgetSizeState extends State<MeasurableWidget> {
final context = widgetKey.currentContext;
if (context == null) return;
final newSize = context.size;
final RenderBox? box =
widgetKey.currentContext?.findRenderObject() as RenderBox?;
final Offset? position = box?.localToGlobal(Offset.zero);
if (oldPosition != null) {
if (oldPosition!.dx == position!.dx && oldPosition!.dy == position.dy) {
return;
if (box != null && box.hasSize) {
final Offset position = box.localToGlobal(Offset.zero);
if (oldPosition != null) {
if (oldPosition!.dx == position.dx && oldPosition!.dy == position.dy) {
return;
}
}
}
oldPosition = position;
oldPosition = position;
widget.onChange(newSize, position);
final newSize = context.size;
widget.onChange(newSize, position);
}
}
@override

View file

@ -1,35 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:go_router/go_router.dart';
class ClassAnalyticsButton extends StatelessWidget {
const ClassAnalyticsButton({super.key});
@override
Widget build(BuildContext context) {
final roomId = GoRouterState.of(context).pathParameters['roomid'];
final iconColor = Theme.of(context).textTheme.bodyLarge!.color;
return Column(
children: [
ListTile(
title: Text(
L10n.of(context)!.classAnalytics,
style: TextStyle(
color: Theme.of(context).colorScheme.secondary,
fontWeight: FontWeight.bold,
),
),
subtitle: Text(L10n.of(context)!.classAnalyticsDesc),
leading: CircleAvatar(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
foregroundColor: iconColor,
child: const Icon(Icons.analytics_outlined),
),
onTap: () => context.go('/rooms/analytics/$roomId'),
),
],
);
}
}

View file

@ -0,0 +1,157 @@
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:fluffychat/pages/chat_details/chat_details.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:matrix/matrix.dart';
class RoomCapacityButton extends StatefulWidget {
final Room? room;
final ChatDetailsController? controller;
const RoomCapacityButton({
super.key,
this.room,
this.controller,
});
@override
RoomCapacityButtonState createState() => RoomCapacityButtonState();
}
class RoomCapacityButtonState extends State<RoomCapacityButton> {
int? capacity;
String? nonAdmins;
RoomCapacityButtonState({Key? key});
@override
void initState() {
super.initState();
capacity = widget.room?.capacity;
widget.room?.numNonAdmins.then(
(value) => setState(() {
nonAdmins = value.toString();
overCapacity();
}),
);
}
@override
void didUpdateWidget(RoomCapacityButton oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.room != widget.room) {
capacity = widget.room?.capacity;
widget.room?.numNonAdmins.then(
(value) => setState(() {
nonAdmins = value.toString();
overCapacity();
}),
);
}
}
Future<void> overCapacity() async {
if ((widget.room?.isRoomAdmin ?? false) &&
capacity != null &&
nonAdmins != null &&
int.parse(nonAdmins!) > capacity!) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
L10n.of(context)!.roomExceedsCapacity,
),
),
);
}
}
@override
Widget build(BuildContext context) {
final iconColor = Theme.of(context).textTheme.bodyLarge!.color;
return Column(
children: [
ListTile(
onTap: (widget.room?.isRoomAdmin ?? true) ? setRoomCapacity : null,
leading: CircleAvatar(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
foregroundColor: iconColor,
child: const Icon(Icons.reduce_capacity),
),
subtitle: Text(
(capacity == null)
? L10n.of(context)!.capacityNotSet
: (nonAdmins != null)
? '$nonAdmins/$capacity'
: '$capacity',
),
title: Text(
L10n.of(context)!.roomCapacity,
style: TextStyle(
color: Theme.of(context).colorScheme.secondary,
fontWeight: FontWeight.bold,
),
),
),
],
);
}
Future<void> setCapacity(int newCapacity) async {
capacity = newCapacity;
}
Future<void> setRoomCapacity() async {
final input = await showTextInputDialog(
context: context,
title: L10n.of(context)!.roomCapacity,
message: L10n.of(context)!.roomCapacityExplanation,
okLabel: L10n.of(context)!.ok,
cancelLabel: L10n.of(context)!.cancel,
textFields: [
DialogTextField(
initialText: ((capacity != null) ? '$capacity' : ''),
keyboardType: TextInputType.number,
maxLength: 3,
validator: (value) {
if (value == null ||
value.isEmpty ||
int.tryParse(value) == null ||
int.parse(value) < 0) {
return L10n.of(context)!.enterNumber;
}
if (nonAdmins != null && int.parse(value) < int.parse(nonAdmins!)) {
return L10n.of(context)!.capacitySetTooLow;
}
return null;
},
),
],
);
if (input == null ||
input.first == "" ||
int.tryParse(input.first) == null) {
return;
}
final newCapacity = int.parse(input.first);
final success = await showFutureLoadingDialog(
context: context,
future: () => ((widget.room != null)
? (widget.room!.updateRoomCapacity(
capacity = newCapacity,
))
: setCapacity(newCapacity)),
);
if (success.error == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
L10n.of(context)!.roomCapacityHasBeenChanged,
),
),
);
setState(() {});
}
}
}

View file

@ -1,4 +1,4 @@
import 'package:fluffychat/pangea/models/class_model.dart';
import 'package:fluffychat/pangea/models/space_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';

View file

@ -1,59 +0,0 @@
import 'package:fluffychat/pangea/widgets/class/add_class_and_invite.dart';
import 'package:fluffychat/pangea/widgets/class/add_space_toggles.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';
class AddExchangeToClass extends StatefulWidget {
const AddExchangeToClass({super.key});
@override
AddExchangeToClassState createState() => AddExchangeToClassState();
}
class AddExchangeToClassState extends State<AddExchangeToClass> {
final GlobalKey<AddToSpaceState> addToSpaceKey = GlobalKey<AddToSpaceState>();
@override
Widget build(BuildContext context) {
final String? spaceId =
GoRouterState.of(context).pathParameters['exchangeid'];
if (spaceId == null) {
return const SizedBox();
}
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text(L10n.of(context)!.addToClassTitle),
),
body: FutureBuilder(
future:
Matrix.of(context).client.waitForRoomInSync(spaceId, join: true),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView(
children: <Widget>[
const SizedBox(height: 40),
AddToSpaceToggles(
roomId:
GoRouterState.of(context).pathParameters['exchangeid'],
key: addToSpaceKey,
startOpen: true,
mode: AddToClassMode.exchange,
),
],
);
} else {
return const Center(child: CircularProgressIndicator());
}
},
),
floatingActionButton: FloatingActionButton(
onPressed: () => context.go("/rooms"),
child: const Icon(Icons.arrow_forward_outlined),
),
);
}
}

View file

@ -1,4 +1,5 @@
import 'package:country_picker/country_picker.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pangea/models/user_model.dart';
import 'package:fluffychat/pangea/widgets/common/list_placeholder.dart';
@ -272,9 +273,16 @@ class UserProfileEntry extends StatelessWidget {
),
title: Row(
children: [
Text(
//PTODO - get matrix u and show displayName
matrixProfile?.displayName ?? pangeaProfile.pangeaUserId,
Flexible(
child: Text(
//PTODO - get matrix u and show displayName
matrixProfile?.displayName ??
pangeaProfile.pangeaUserId.replaceAll(
":${AppConfig.defaultHomeserver.replaceAll("matrix.", "")}",
"",
),
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 20),
RichText(

View file

@ -1,5 +1,5 @@
import 'package:fluffychat/pangea/constants/local.key.dart';
import 'package:fluffychat/pangea/models/class_model.dart';
import 'package:fluffychat/pangea/models/space_model.dart';
import 'package:fluffychat/pangea/pages/settings_learning/settings_learning.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/pangea/widgets/user_settings/country_picker_tile.dart';

View file

@ -1,76 +0,0 @@
// import 'dart:convert';
// import 'dart:developer';
// import 'package:fluffychat/pangea/network/requests.dart';
// import 'package:fluffychat/pangea/utils/analytics_util.dart';
// import 'package:flutter/foundation.dart';
// import 'package:http/http.dart';
// import '../../config/environment.dart';
// import '../models/analytics_model_oldest.dart';
// import '../network/urls.dart';
class PClassAnalyticsRepo {
/// deprecated in favor of new analytics
static Future<dynamic> repoGetAnalyticsByIds(
String accessToken,
String timeSpan, {
List<String>? classIds,
List<String>? userIds,
List<String>? chatIds,
}) async {
// if (!AnalyticsUtil.isValidSpan(timeSpan)) throw "Invalid span";
// final Requests req = Requests(
// accessToken: accessToken, choreoApiKey: Environment.choreoApiKey);
// final body = {};
// body["timespan"] = timeSpan;
// if (classIds != null) body["class_ids"] = classIds;
// if (chatIds != null) body["chat_ids"] = chatIds;
// if (userIds != null) body["user_ids"] = userIds;
// final Response res =
// await req.post(url: PApiUrls.classAnalytics, body: body);
// final json = jsonDecode(res.body);
// final Iterable<dynamic>? classJson = json["class_analytics"];
// final Iterable<dynamic>? chatJson = json["chat_analytics"];
// final Iterable<dynamic>? userJson = json["user_analytics"];
// final classInfo = classJson != null
// ? (classJson)
// .map((e) {
// e["timespan"] = timeSpan;
// return chartAnalytics(e);
// })
// .toList()
// .cast<chartAnalytics>()
// : [];
// final chatInfo = chatJson != null
// ? (chatJson)
// .map((e) {
// e["timespan"] = timeSpan;
// return chartAnalytics(e);
// })
// .toList()
// .cast<chartAnalytics>()
// : [];
// final userInfo = userJson != null
// ? (userJson)
// .map((e) {
// e["timespan"] = timeSpan;
// return chartAnalytics(e);
// })
// .toList()
// .cast<chartAnalytics>()
// : [];
// final List<chartAnalytics> allAnalytics = [
// ...classInfo,
// ...chatInfo,
// ...userInfo
// ];
// return allAnalytics;
}
}

View file

@ -1,34 +0,0 @@
import 'package:flutter/material.dart';
class PExchangeRepo {
static fetchExchangeClassInfo(String exchangePangeaId) async {}
static saveExchangeRecord(
String requestFromClass,
String requestToClass,
String requestTeacher,
String requestToClassAuthor,
String exchangePangeaId,
) async {}
static exchangeRejectRequest(String roomId, String teacherName) async {}
static validateExchange({
required String requestFromClass,
required String requestToClass,
required BuildContext context,
}) async {}
static createExchangeRequest({
required String roomId,
required String teacherID,
required String toClass,
required BuildContext context,
}) async {}
static isExchange(
BuildContext context,
String accessToken,
String exchangeId,
) async {}
}

View file

@ -56,7 +56,7 @@ void chatListHandleSpaceTap(
title: L10n.of(context)!.youreInvited,
message: space.isSpace
? L10n.of(context)!
.invitedToClassOrExchange(space.name, space.creatorId ?? "???")
.invitedToSpace(space.name, space.creatorId ?? "???")
: L10n.of(context)!
.invitedToChat(space.name, space.creatorId ?? "???"),
okLabel: L10n.of(context)!.accept,

View file

@ -1,10 +1,11 @@
import 'dart:async';
import 'dart:io';
import 'package:csv/csv.dart';
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:csv/csv.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:intl/intl.dart';
import 'package:matrix/matrix.dart';
@ -15,16 +16,12 @@ import 'package:permission_handler/permission_handler.dart';
import 'package:syncfusion_flutter_xlsio/xlsio.dart';
import 'package:universal_html/html.dart' as webFile;
import 'package:fluffychat/pangea/models/class_model.dart';
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import '../models/choreo_record.dart';
enum DownloadType { txt, csv, xlsx }
Future<void> downloadChat(
Room room,
ClassSettingsModel classSettings,
DownloadType type,
Client client,
BuildContext context,
@ -43,7 +40,6 @@ Future<void> downloadChat(
allEvents,
timeline,
room,
classSettings.targetLanguage,
);
} catch (err) {
ErrorHandler.logError(
@ -123,7 +119,6 @@ List<PangeaMessageEvent> getPangeaMessageEvents(
List<Event> events,
Timeline timeline,
Room room,
String? targetLang,
) {
final List<PangeaMessageEvent> allPangeaMessages = events
.where(

View file

@ -68,13 +68,6 @@ class GoogleAnalytics {
);
}
static createExchange(String exchangeName, String classCode) {
logEvent(
'create_exchange',
parameters: {'name': exchangeName, 'group_id': classCode},
);
}
static createChat(String newChatRoomId) {
logEvent('create_chat', parameters: {"chat_id": newChatRoomId});
}
@ -93,27 +86,6 @@ class GoogleAnalytics {
);
}
static addChatToExchange(String chatRoomId, String classCode) {
logEvent(
'add_chat_to_exchange',
parameters: {"chat_id": chatRoomId, 'group_id': classCode},
);
}
static inviteClassToExchange(String classId, String exchangeId) {
logEvent(
'invite_class_to_exchange',
parameters: {'group_id': classId, 'exchange_id': exchangeId},
);
}
static kickClassFromExchange(String classId, String exchangeId) {
logEvent(
'kick_class_from_exchange',
parameters: {'group_id': classId, 'exchange_id': exchangeId},
);
}
static joinClass(String classCode) {
logEvent('join_group', parameters: {'group_id': classCode});
}

View file

@ -3,7 +3,7 @@ import 'package:fluffychat/pangea/constants/language_keys.dart';
import 'package:fluffychat/pangea/constants/model_keys.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/class_model.dart';
import 'package:fluffychat/pangea/models/space_model.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/matrix.dart';
@ -69,9 +69,7 @@ class GetChatListItemSubtitle {
timeline: timeline,
ownMessage: false,
);
final l2Code = pangeaController.languageController
.activeL2Code(roomID: event.roomId);
final l2Code = pangeaController.languageController.activeL2Code();
if (l2Code == null || l2Code == LanguageKeys.unknownLanguage) {
return event.body;
}

View file

@ -76,10 +76,14 @@ class OverlayUtil {
try {
final LayerLinkAndKey layerLinkAndKey =
MatrixState.pAnyState.layerLinkAndKey(transformTargetId);
if (layerLinkAndKey.key.currentContext == null) {
debugPrint("layerLinkAndKey.key.currentContext is null");
return;
}
final Offset cardOffset = _calculateCardOffset(
cardSize: cardSize,
transformTargetKey: layerLinkAndKey.key,
transformTargetContext: layerLinkAndKey.key.currentContext!,
);
final Widget child = Material(
@ -112,16 +116,16 @@ class OverlayUtil {
/// identified by [transformTargetKey]
static Offset _calculateCardOffset({
required Size cardSize,
required LabeledGlobalKey transformTargetKey,
required BuildContext transformTargetContext,
final double minPadding = 10.0,
}) {
// debugger(when: kDebugMode);
//Note: assumes overlay in chatview
final OverlayConstraints constraints =
ChatViewConstraints(transformTargetKey.currentContext!);
ChatViewConstraints(transformTargetContext);
final RenderObject? targetRenderBox =
transformTargetKey.currentContext!.findRenderObject();
transformTargetContext.findRenderObject();
if (targetRenderBox == null) return Offset.zero;
final Offset transformTargetOffset =
(targetRenderBox as RenderBox).localToGlobal(Offset.zero);

View file

@ -101,7 +101,7 @@ Future<List<SpaceTeacher>> getReportTeachers(
final List<Room> otherSpaces = Matrix.of(context)
.client
.classesAndExchangesImIn
.spacesImIn
.where((space) => !reportRoomParentSpaces.contains(space))
.toList();

View file

@ -6,25 +6,25 @@ import 'package:flutter_gen/gen_l10n/l10n.dart';
import '../controllers/pangea_controller.dart';
class ClassCodeUtil {
class SpaceCodeUtil {
static const codeLength = 6;
static bool isValidCode(String? classcode) {
return classcode == null || classcode.length > 4;
static bool isValidCode(String? spacecode) {
return spacecode == null || spacecode.length > 4;
}
static String generateClassCode() {
static String generateSpaceCode() {
final r = Random();
const chars = 'AaBbCcDdEeFfGgHhiJjKkLMmNnoPpQqRrSsTtUuVvWwXxYyZz1234567890';
return List.generate(codeLength, (index) => chars[r.nextInt(chars.length)])
.join();
}
static Future<void> joinWithClassCodeDialog(
static Future<void> joinWithSpaceCodeDialog(
BuildContext context,
PangeaController pangeaController,
) async {
final List<String>? classCode = await showTextInputDialog(
final List<String>? spaceCode = await showTextInputDialog(
context: context,
title: L10n.of(context)!.joinWithClassCode,
okLabel: L10n.of(context)!.ok,
@ -33,10 +33,10 @@ class ClassCodeUtil {
DialogTextField(hintText: L10n.of(context)!.joinWithClassCodeHint),
],
);
if (classCode == null || classCode.single.isEmpty) return;
if (spaceCode == null || spaceCode.single.isEmpty) return;
await pangeaController.classController.joinClasswithCode(
context,
classCode.first,
spaceCode.first,
);
}

View file

@ -35,13 +35,9 @@ class MessageSpeechToTextCardState extends State<MessageSpeechToTextCard> {
STTToken? selectedToken;
String? get l1Code =>
MatrixState.pangeaController.languageController.activeL1Code(
roomID: widget.messageEvent.room.id,
);
MatrixState.pangeaController.languageController.activeL1Code();
String? get l2Code =>
MatrixState.pangeaController.languageController.activeL2Code(
roomID: widget.messageEvent.room.id,
);
MatrixState.pangeaController.languageController.activeL2Code();
// look for transcription in message event
// if not found, call API to transcribe audio

View file

@ -37,7 +37,9 @@ class MessageTranslationCardState extends State<MessageTranslationCard> {
if (widget.immersionMode) return l1Code;
final String? originalWrittenCode =
widget.messageEvent.originalWritten?.content.langCode;
return l1Code == originalWrittenCode ? l2Code : l1Code;
return (l1Code == originalWrittenCode || originalWrittenCode == null)
? l2Code
: l1Code;
}
Future<void> fetchRepresentation(BuildContext context) async {
@ -107,12 +109,8 @@ class MessageTranslationCardState extends State<MessageTranslationCard> {
@override
void initState() {
super.initState();
l1Code = MatrixState.pangeaController.languageController.activeL1Code(
roomID: widget.messageEvent.room.id,
);
l2Code = MatrixState.pangeaController.languageController.activeL2Code(
roomID: widget.messageEvent.room.id,
);
l1Code = MatrixState.pangeaController.languageController.activeL1Code();
l2Code = MatrixState.pangeaController.languageController.activeL2Code();
if (mounted) {
setState(() {});
}

View file

@ -1,298 +0,0 @@
import 'dart:developer';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart';
import '../../../widgets/matrix.dart';
import '../../utils/error_handler.dart';
import '../../utils/firebase_analytics.dart';
enum AddToClassMode { exchange, chat }
class AddToClassAndInviteToggles extends StatefulWidget {
final String? roomId;
final bool startOpen;
final Function? setParentState;
final AddToClassMode mode;
const AddToClassAndInviteToggles({
super.key,
this.roomId,
this.startOpen = false,
this.setParentState,
required this.mode,
});
@override
AddToClassAndInviteState createState() => AddToClassAndInviteState();
}
class AddToClassAndInviteState extends State<AddToClassAndInviteToggles> {
late Room? room;
late List<Room> parents;
late List<Room> possibleParents;
late bool isOpen;
AddToClassAndInviteState({Key? key});
@override
void initState() {
room = widget.roomId != null
? Matrix.of(context).client.getRoomById(widget.roomId!)
: null;
if (room != null && room!.isPangeaClass) {
debugger(when: kDebugMode);
ErrorHandler.logError(
m: "should not be able to add class to space, not yet at least",
);
context.go('/rooms');
}
possibleParents = Matrix.of(context)
.client
.rooms
.where(
widget.mode == AddToClassMode.exchange
? (Room r) => r.isPangeaClass && widget.roomId != r.id
: (Room r) =>
(r.isPangeaClass || r.isExchange) && widget.roomId != r.id,
)
.toList();
parents = widget.roomId != null
? possibleParents
.where(
(r) =>
r.spaceChildren.any((room) => room.roomId == widget.roomId),
)
.toList()
: [];
isOpen = widget.startOpen;
super.initState();
}
Future<void> addParents(String roomToAddId) async {
final List<Future<void>> addFutures = [];
for (final Room newParent in parents) {
addFutures.add(_addSingleParent(roomToAddId, newParent));
}
await addFutures.wait;
}
Future<void> _addSingleParent(String roomToAddId, Room newParent) async {
GoogleAnalytics.addParent(roomToAddId, newParent.classCode);
final List<List<User>> existingMembers = await Future.wait([
room?.requestParticipants() ?? Future.value([]),
newParent.requestParticipants(),
]);
final List<User> roomMembers = existingMembers[0];
final List<User> spaceMembers = existingMembers[1];
final List<Future<void>> inviteFutures = [
newParent.setSpaceChild(roomToAddId, suggested: true),
];
for (final spaceMember
in spaceMembers.where((element) => element.id != room?.client.userID)) {
if (!roomMembers.any(
(m) => m.id == spaceMember.id && m.membership == Membership.join,
)) {
inviteFutures.add(_inviteSpaceMember(spaceMember));
} else {
debugPrint('User ${spaceMember.id} is already in the room');
}
}
await Future.wait(inviteFutures);
return;
}
//function for kicking single student and haandling error
Future<void> _kickSpaceMember(User spaceMember) async {
try {
await room?.kick(spaceMember.id);
debugPrint('Kicked ${spaceMember.id}');
} catch (e) {
debugger(when: kDebugMode);
ErrorHandler.logError(e: e);
}
}
//function for adding single student and haandling error
Future<void> _inviteSpaceMember(User spaceMember) async {
try {
await room?.invite(spaceMember.id);
debugPrint('added ${spaceMember.id}');
} catch (e) {
debugger(when: kDebugMode);
ErrorHandler.logError(e: e);
}
}
//remove single class
Future<void> _removeSingleSpaceFromParents(
String roomToRemoveId,
Room spaceToRemove,
) async {
GoogleAnalytics.removeChatFromClass(
roomToRemoveId,
spaceToRemove.classCode,
);
if (room == null) {
ErrorHandler.logError(m: 'Room is null in kickSpaceMembers');
debugger(when: kDebugMode);
return;
}
final List<List<User>> roomsMembers = await Future.wait([
room?.requestParticipants() ?? Future.value([]),
spaceToRemove.requestParticipants(),
]);
final List<User> toKick = roomsMembers[1]
.where(
(element) =>
element.id != room?.client.userID &&
roomsMembers[0].any((m) => m.id == element.id),
)
.toList();
final List<Future<void>> kickFutures = [
spaceToRemove.removeSpaceChild(roomToRemoveId),
];
for (final spaceMember in toKick) {
kickFutures.add(_kickSpaceMember(spaceMember));
}
await Future.wait(kickFutures);
// if (widget.setParentState != null) {
// widget.setParentState!();
// }
await room?.requestParticipants();
if (room != null) {
GoogleAnalytics.kickClassFromExchange(room!.id, spaceToRemove.id);
}
return;
}
// ignore: curly_braces_in_flow_control_structures
Future<void> _handleAdd(bool add, Room possibleParent) async {
//in this case, the room has already been made so we handle adding as it happens
if (room != null) {
await showFutureLoadingDialog(
context: context,
future: () async {
await (add
? _addSingleParent(room!.id, possibleParent)
: _removeSingleSpaceFromParents(room!.id, possibleParent));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
add
? L10n.of(context)!.youAddedToSpace(
room!.name,
possibleParent.name,
)
: L10n.of(context)!.youRemovedFromSpace(
room!.name,
possibleParent.name,
),
),
),
);
},
);
}
setState(
() => add
? parents.add(possibleParent)
: parents.removeWhere((r) => r.id == possibleParent.id),
);
}
@override
Widget build(BuildContext context) {
return Column(
children: [
if (!widget.startOpen)
ListTile(
enableFeedback: !widget.startOpen,
title: Text(
widget.mode == AddToClassMode.exchange
? L10n.of(context)!.addToClass
: L10n.of(context)!.addToClassOrExchange,
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.workspaces_outline,
),
),
trailing: !widget.startOpen
? Icon(
isOpen
? Icons.keyboard_arrow_down_outlined
: Icons.keyboard_arrow_right_outlined,
)
: null,
onTap: () => setState(() => isOpen = !isOpen),
),
if (isOpen)
Padding(
padding: const EdgeInsets.fromLTRB(16.0, 0, 16.0, 0),
child: Column(
children: [
if (possibleParents.isEmpty)
ListTile(
title: Text(L10n.of(context)!.noEligibleSpaces),
),
ListView.builder(
shrinkWrap: true,
itemCount: possibleParents.length,
itemBuilder: (BuildContext context, int i) {
final bool canIAddSpaceChildren =
possibleParents[i].canIAddSpaceChild(room) &&
(room?.canIAddSpaceParents ?? true);
return Column(
children: [
Opacity(
opacity: canIAddSpaceChildren ? 1 : 0.5,
child: SwitchListTile.adaptive(
title: possibleParents[i].nameAndRoomTypeIcon(),
activeColor: AppConfig.activeToggleColor,
value: parents
.any((r) => r.id == possibleParents[i].id),
onChanged: (bool add) => canIAddSpaceChildren
? _handleAdd(add, possibleParents[i])
: ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content:
Text(L10n.of(context)!.noPermission),
),
),
),
),
],
);
},
),
],
),
),
],
);
}
}

View file

@ -10,21 +10,20 @@ import 'package:matrix/matrix.dart';
import '../../../widgets/matrix.dart';
import '../../utils/firebase_analytics.dart';
import 'add_class_and_invite.dart';
//PTODO - auto invite students when you add a space and delete the add_class_and_invite.dart file
class AddToSpaceToggles extends StatefulWidget {
final String? roomId;
final bool startOpen;
final String? activeSpaceId;
final AddToClassMode mode;
final bool spaceMode;
const AddToSpaceToggles({
super.key,
this.roomId,
this.startOpen = false,
this.activeSpaceId,
required this.mode,
this.spaceMode = false,
});
@override
@ -42,6 +41,19 @@ class AddToSpaceState extends State<AddToSpaceToggles> {
@override
void initState() {
initialize();
super.initState();
}
@override
void didUpdateWidget(AddToSpaceToggles oldWidget) {
if (oldWidget.roomId != widget.roomId) {
initialize();
}
super.didUpdateWidget(oldWidget);
}
void initialize() {
//if roomId is null, it means this widget is being used in the creation flow
room = widget.roomId != null
? Matrix.of(context).client.getRoomById(widget.roomId!)
@ -54,10 +66,7 @@ class AddToSpaceState extends State<AddToSpaceToggles> {
.client
.rooms
.where(
widget.mode == AddToClassMode.exchange
? (Room r) => r.isPangeaClass && widget.roomId != r.id
: (Room r) =>
(r.isPangeaClass || r.isExchange) && widget.roomId != r.id,
(Room r) => r.isSpace && widget.roomId != r.id,
)
.toList();
@ -96,7 +105,6 @@ class AddToSpaceState extends State<AddToSpaceToggles> {
});
isOpen = widget.startOpen;
super.initState();
}
Future<void> _addSingleSpace(String roomToAddId, Room newParent) async {
@ -144,13 +152,10 @@ class AddToSpaceState extends State<AddToSpaceToggles> {
Widget getAddToSpaceToggleItem(int index) {
final Room possibleParent = possibleParents[index];
final bool canAdd = (room?.isSpace ?? false)
// Room is space
? possibleParent.isRoomAdmin &&
room!.isRoomAdmin &&
possibleParent.canAddAsParentOf(room)
// Room is null or chat
: possibleParent.canAddAsParentOf(room);
final bool canAdd = possibleParent.canAddAsParentOf(
room,
spaceMode: widget.spaceMode,
);
return Opacity(
opacity: canAdd ? 1 : 0.5,
@ -189,24 +194,21 @@ class AddToSpaceState extends State<AddToSpaceToggles> {
@override
Widget build(BuildContext context) {
final String title = widget.mode == AddToClassMode.exchange
? L10n.of(context)!.addToClass
: L10n.of(context)!.addToClassOrExchange;
final String subtitle = widget.mode == AddToClassMode.exchange
? L10n.of(context)!.addToClassDesc
: L10n.of(context)!.addToClassOrExchangeDesc;
return Column(
children: [
ListTile(
title: Text(
title,
L10n.of(context)!.addToSpace,
style: TextStyle(
color: Theme.of(context).colorScheme.secondary,
fontWeight: FontWeight.bold,
),
),
subtitle: Text(subtitle),
subtitle: Text(
widget.spaceMode || (room?.isSpace ?? false)
? L10n.of(context)!.addSpaceToSpaceDesc
: L10n.of(context)!.addChatToSpaceDesc,
),
leading: CircleAvatar(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
foregroundColor: Theme.of(context).textTheme.bodyLarge!.color,
@ -227,13 +229,21 @@ class AddToSpaceState extends State<AddToSpaceToggles> {
? Column(
children: [
SwitchListTile.adaptive(
title: Text(L10n.of(context)!.suggestToChat),
title: Text(
widget.spaceMode || (room?.isSpace ?? false)
? L10n.of(context)!.suggestToSpace
: L10n.of(context)!.suggestToChat,
),
secondary: Icon(
isSuggested
? Icons.visibility_outlined
: Icons.visibility_off_outlined,
),
subtitle: Text(L10n.of(context)!.suggestToChatDesc),
subtitle: Text(
widget.spaceMode || (room?.isSpace ?? false)
? L10n.of(context)!.suggestToSpaceDesc
: L10n.of(context)!.suggestToChatDesc,
),
activeColor: AppConfig.activeToggleColor,
value: isSuggested,
onChanged: (bool add) => setSuggested(add),

View file

@ -1,342 +0,0 @@
// import 'dart:developer';
// import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
// import 'package:flutter/foundation.dart';
// import 'package:flutter/material.dart';
// import 'package:flutter_gen/gen_l10n/l10n.dart';
// import 'package:future_loading_dialog/future_loading_dialog.dart';
// import 'package:matrix/matrix.dart';
// import '../../../utils/matrix_sdk_extensions/matrix_locals.dart';
// import '../../../widgets/avatar.dart';
// import '../../../widgets/matrix.dart';
// import '../../utils/error_handler.dart';
// import '../../utils/firebase_analytics.dart';
// class InviteStudentsFromClass extends StatefulWidget {
// final String? roomId;
// final bool startOpen;
// final Function setParentState;
// const InviteStudentsFromClass({
// Key? key,
// this.roomId,
// this.startOpen = false,
// required this.setParentState,
// }) : super(key: key);
// @override
// InviteStudentsFromClassState createState() => InviteStudentsFromClassState();
// }
// class InviteStudentsFromClassState extends State<InviteStudentsFromClass> {
// late Room? room;
// late List<Room> otherSpaces;
// late bool isOpen;
// final List<String> invitedSpaces = [];
// final List<String> kickedSpaces = [];
// InviteStudentsFromClassState({Key? key, cont});
// @override
// void initState() {
// room = widget.roomId != null
// ? Matrix.of(context).client.getRoomById(widget.roomId!)
// : null;
// otherSpaces = Matrix.of(context)
// .client
// .rooms
// .where((r) => r.isPangeaClass && r.id != widget.roomId)
// .toList();
// isOpen = widget.startOpen;
// super.initState();
// }
// Future<void> inviteSpaceMembers(BuildContext context, Room spaceToInvite) =>
// showFutureLoadingDialog(
// context: context,
// future: () async {
// if (room == null) {
// ErrorHandler.logError(m: 'Room is null in inviteSpaceMembers');
// debugger(when: kDebugMode);
// return;
// }
// final List<List<User>> existingMembers = await Future.wait([
// room!.requestParticipants(),
// spaceToInvite.requestParticipants(),
// ]);
// final List<User> roomMembers = existingMembers[0];
// final List<User> spaceMembers = existingMembers[1];
// final List<Future<void>> inviteFutures = [];
// for (final spaceMember in spaceMembers
// .where((element) => element.id != room!.client.userID)) {
// if (!roomMembers.any((m) =>
// m.id == spaceMember.id && m.membership == Membership.join)) {
// inviteFutures.add(inviteSpaceMember(spaceMember));
// //add to invitedSpaces
// invitedSpaces.add(spaceToInvite.id);
// //if in kickedSpaces, remove
// kickedSpaces.remove(spaceToInvite.id);
// } else {
// debugPrint('User ${spaceMember.id} is already in the room');
// }
// }
// await Future.wait(inviteFutures);
// debugPrint('Invited ${spaceMembers.length} members');
// GoogleAnalytics.inviteClassToExchange(room!.id, spaceToInvite.id);
// // setState(() {
// // widget.setParentState();
// // });
// },
// onError: handleError,
// );
// //function for kicking single student and haandling error
// Future<void> kickSpaceMember(User spaceMember) async {
// try {
// await room!.kick(spaceMember.id);
// debugPrint('Kicked ${spaceMember.id}');
// } catch (e) {
// debugger(when: kDebugMode);
// ErrorHandler.logError(e: e);
// }
// }
// //function for adding single student and haandling error
// Future<void> inviteSpaceMember(User spaceMember) async {
// try {
// await room!.invite(spaceMember.id);
// debugPrint('added ${spaceMember.id}');
// } catch (e) {
// debugger(when: kDebugMode);
// ErrorHandler.logError(e: e);
// }
// }
// Future<void> kickSpaceMembers(BuildContext context, Room spaceToKick) =>
// showFutureLoadingDialog(
// context: context,
// future: () async {
// if (room == null) {
// ErrorHandler.logError(m: 'Room is null in kickSpaceMembers');
// debugger(when: kDebugMode);
// return;
// }
// final List<List<User>> existingMembers = await Future.wait([
// room!.requestParticipants(),
// spaceToKick.requestParticipants(),
// ]);
// final List<User> roomMembers = existingMembers[0];
// final List<User> spaceMembers = existingMembers[1];
// final List<User> toKick = spaceMembers
// .where((element) =>
// element.id != room!.client.userID &&
// roomMembers.any((m) => m.id == element.id))
// .toList();
// //add to kickedSpaces
// kickedSpaces.add(spaceToKick.id);
// //if in invitedSpaces, remove from invitedSpaces
// invitedSpaces.remove(spaceToKick.id);
// final List<Future<void>> kickFutures = [];
// for (final spaceMember in toKick) {
// kickFutures.add(kickSpaceMember(spaceMember));
// }
// await Future.wait(kickFutures);
// GoogleAnalytics.kickClassFromExchange(room!.id, spaceToKick.id);
// setState(() {
// widget.setParentState();
// });
// return;
// },
// onError: handleError,
// );
// String handleError(dynamic exception) {
// ErrorHandler.logError(
// e: exception, m: 'Error inviting or kicking students');
// ScaffoldMessenger.of(context).showSnackBar(
// SnackBar(
// content: Text(exception.toString()),
// ),
// );
// return exception.toString();
// }
// @override
// Widget build(BuildContext context) {
// if (room == null) return Container();
// return Column(
// children: [
// ListTile(
// title: Text(
// L10n.of(context)!.inviteStudentsFromOtherClasses,
// style: TextStyle(
// color: Theme.of(context).colorScheme.secondary,
// fontWeight: FontWeight.bold,
// ),
// ),
// leading: CircleAvatar(
// backgroundColor: Theme.of(context).primaryColor,
// foregroundColor: Colors.white,
// radius: Avatar.defaultSize / 2,
// child: const Icon(Icons.workspaces_outlined),
// ),
// trailing: Icon(
// isOpen
// ? Icons.keyboard_arrow_down_outlined
// : Icons.keyboard_arrow_right_outlined,
// ),
// onTap: () => setState(() => isOpen = !isOpen),
// ),
// if (isOpen)
// Padding(
// padding: const EdgeInsets.fromLTRB(16.0, 0, 16.0, 0),
// child: Column(
// children: [
// if (otherSpaces.isEmpty)
// ListTile(title: Text(L10n.of(context)!.noEligibleSpaces)),
// ListView.builder(
// shrinkWrap: true,
// itemCount: otherSpaces.length,
// itemBuilder: (BuildContext context, int i) {
// final bool canIAddSpaceChildren =
// otherSpaces[i].canIAddSpaceChild(room);
// return Column(
// children: [
// Opacity(
// opacity: canIAddSpaceChildren ? 1 : 0.5,
// child: InviteKickClass(
// room: otherSpaces[i],
// inviteCallback: (Room room) =>
// inviteSpaceMembers(context, otherSpaces[i]),
// kickCallback: (Room room) =>
// kickSpaceMembers(context, otherSpaces[i]),
// controller: this,
// ),
// ),
// ],
// );
// },
// ),
// ],
// ),
// ),
// // END: ed8c6549bwf9
// ],
// );
// }
// }
// //listTile with two buttons - one to invite all students in the class and the other to kick them all out
// // parameters
// // 1. Room
// // 2. invite callback
// // 3. kick callback
// // when the user clicks either button, a dialog pops up asking for confirmation
// // after the dialog is confirmed, the callback is executed
// class InviteKickClass extends StatelessWidget {
// final Room room;
// final Function inviteCallback;
// final Function kickCallback;
// final InviteStudentsFromClassState controller;
// const InviteKickClass({
// Key? key,
// required this.room,
// required this.inviteCallback,
// required this.kickCallback,
// required this.controller,
// }) : super(key: key);
// @override
// Widget build(BuildContext context) {
// return ListTile(
// title: Text(
// room.getLocalizedDisplayname(
// MatrixLocals(L10n.of(context)!),
// ),
// ),
// leading: const SizedBox(
// height: Avatar.defaultSize,
// width: Avatar.defaultSize,
// ),
// trailing: Row(
// mainAxisSize: MainAxisSize.min,
// children: [
// IconButton(
// icon: const Icon(Icons.add),
// isSelected: controller.invitedSpaces.contains(room.id),
// onPressed: () async {
// final bool? result = await showDialog<bool>(
// context: context,
// builder: (BuildContext context) => AlertDialog(
// title: Text(
// L10n.of(context)!.inviteAllStudents,
// style: TextStyle(
// color: Theme.of(context).colorScheme.secondary,
// fontWeight: FontWeight.bold,
// ),
// ),
// content: Text(
// L10n.of(context)!.inviteAllStudentsConfirmation,
// ),
// actions: [
// TextButton(
// onPressed: () => Navigator.of(context).pop(false),
// child: Text(L10n.of(context)!.cancel),
// ),
// TextButton(
// onPressed: () => Navigator.of(context).pop(true),
// child: Text(L10n.of(context)!.ok),
// ),
// ],
// ),
// );
// if (result != null && result) {
// inviteCallback(room);
// }
// },
// ),
// IconButton(
// icon: const Icon(Icons.remove),
// isSelected: controller.kickedSpaces.contains(room.id),
// onPressed: () async {
// final bool? result = await showDialog<bool>(
// context: context,
// builder: (BuildContext context) => AlertDialog(
// title: Text(
// L10n.of(context)!.kickAllStudents,
// style: TextStyle(
// color: Theme.of(context).colorScheme.secondary,
// fontWeight: FontWeight.bold,
// ),
// ),
// content: Text(
// L10n.of(context)!.kickAllStudentsConfirmation,
// ),
// actions: [
// TextButton(
// onPressed: () => Navigator.of(context).pop(false),
// child: Text(L10n.of(context)!.cancel),
// ),
// TextButton(
// onPressed: () => Navigator.of(context).pop(true),
// child: Text(L10n.of(context)!.ok),
// ),
// ],
// ),
// );
// if (result != null && result) {
// kickCallback(room);
// }
// },
// ),
// ],
// ),
// );
// }
// }

View file

@ -21,14 +21,12 @@ class ConversationBotSettings extends StatefulWidget {
final Room? room;
final bool startOpen;
final String? activeSpaceId;
// final ClassSettingsModel? initialSettings;
const ConversationBotSettings({
super.key,
this.room,
this.startOpen = false,
this.activeSpaceId,
// this.initialSettings,
});
@override
@ -58,9 +56,6 @@ class ConversationBotSettingsState extends State<ConversationBotSettings> {
parentSpace = widget.activeSpaceId != null
? Matrix.of(context).client.getRoomById(widget.activeSpaceId!)
: null;
if (parentSpace != null && botOptions.languageLevel == null) {
botOptions.languageLevel = parentSpace?.classSettings?.languageLevel;
}
}
Future<void> updateBotOption(void Function() makeLocalChange) async {

View file

@ -2,7 +2,6 @@ import 'dart:developer';
import 'dart:ui';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/constants/language_keys.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/representation_content_model.dart';
@ -191,19 +190,6 @@ class PangeaRichTextState extends State<PangeaRichText> {
: richText;
}
bool get areLanguagesSet =>
userL2LangCode != null && userL2LangCode != LanguageKeys.unknownLanguage;
String? get userL2LangCode =>
pangeaController.languageController.activeL2Code(
roomID: widget.pangeaMessageEvent.room.id,
);
String? get userL1LangCode =>
pangeaController.languageController.activeL1Code(
roomID: widget.pangeaMessageEvent.room.id,
);
Future<void> onIgnore() async {
debugPrint("PTODO implement onIgnore");
}

View file

@ -61,10 +61,8 @@ class WordDataCardController extends State<WordDataCard> {
@override
void initState() {
if (!mounted) return;
activeL1 =
controller.languageController.activeL1Model(roomID: widget.room.id)!;
activeL2 =
controller.languageController.activeL2Model(roomID: widget.room.id)!;
activeL1 = controller.languageController.activeL1Model()!;
activeL2 = controller.languageController.activeL2Model()!;
if (activeL1 == null || activeL2 == null) {
wordNetError = noLanguages;
definitionError = noLanguages;

View file

@ -1,183 +0,0 @@
import 'dart:developer';
import 'package:fluffychat/pangea/models/class_model.dart';
import 'package:fluffychat/pangea/widgets/space/language_level_dropdown.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:matrix/matrix.dart';
import '../../../widgets/matrix.dart';
import '../../constants/language_keys.dart';
import '../../constants/pangea_event_types.dart';
import '../../controllers/language_list_controller.dart';
import '../../controllers/pangea_controller.dart';
import '../../extensions/pangea_room_extension/pangea_room_extension.dart';
import '../../models/language_model.dart';
import '../../utils/error_handler.dart';
import '../user_settings/p_language_dropdown.dart';
import '../user_settings/p_question_container.dart';
class ClassSettings extends StatefulWidget {
final String? roomId;
final bool startOpen;
final ClassSettingsModel? initialSettings;
const ClassSettings({
super.key,
this.roomId,
this.startOpen = false,
this.initialSettings,
});
@override
ClassSettingsState createState() => ClassSettingsState();
}
class ClassSettingsState extends State<ClassSettings> {
Room? room;
late ClassSettingsModel classSettings;
late bool isOpen;
final PangeaController pangeaController = MatrixState.pangeaController;
final cityController = TextEditingController();
final countryController = TextEditingController();
final schoolController = TextEditingController();
ClassSettingsState({Key? key});
@override
void initState() {
room = widget.roomId != null
? Matrix.of(context).client.getRoomById(widget.roomId!)
: null;
classSettings =
room?.classSettings ?? widget.initialSettings ?? ClassSettingsModel();
isOpen = widget.startOpen;
super.initState();
}
bool get sameLanguages =>
classSettings.targetLanguage == classSettings.dominantLanguage;
LanguageModel getLanguage({required bool isBase, required String? langCode}) {
final LanguageModel backup = isBase
? pangeaController.pLanguageStore.baseOptions.first
: pangeaController.pLanguageStore.targetOptions.first;
if (langCode == null) return backup;
final LanguageModel byCode = PangeaLanguage.byLangCode(langCode);
return byCode.langCode != LanguageKeys.unknownLanguage ? byCode : backup;
}
Future<void> updatePermission(void Function() makeLocalRuleChange) async {
makeLocalRuleChange();
if (room != null) {
await showFutureLoadingDialog(
context: context,
future: () => setClassSettings(room!.id),
);
}
setState(() {});
}
void setTextControllerValues() {
classSettings.city = cityController.text;
classSettings.country = countryController.text;
classSettings.schoolName = schoolController.text;
}
Future<void> setClassSettings(String roomId) async {
try {
setTextControllerValues();
await Matrix.of(context).client.setRoomStateWithKey(
roomId,
PangeaEventTypes.classSettings,
'',
classSettings.toJson(),
);
} catch (err, stack) {
debugger(when: kDebugMode);
ErrorHandler.logError(e: err, s: stack);
}
}
@override
Widget build(BuildContext context) => Column(
children: [
ListTile(
title: Text(
L10n.of(context)!.classSettings,
style: TextStyle(
color: Theme.of(context).colorScheme.secondary,
fontWeight: FontWeight.bold,
),
),
subtitle: Text(L10n.of(context)!.classSettingsDesc),
leading: CircleAvatar(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
foregroundColor: Theme.of(context).textTheme.bodyLarge!.color,
child: const Icon(Icons.language),
),
trailing: Icon(
isOpen
? Icons.keyboard_arrow_down_outlined
: Icons.keyboard_arrow_right_outlined,
),
onTap: () => setState(() => isOpen = !isOpen),
),
if (isOpen)
AnimatedContainer(
duration: const Duration(milliseconds: 300),
height: isOpen ? null : 0,
child: Padding(
padding: const EdgeInsets.fromLTRB(16.0, 0, 16.0, 8.0),
child: Column(
children: [
PQuestionContainer(
title: L10n.of(context)!.selectClassRoomDominantLanguage,
),
PLanguageDropdown(
onChange: (p0) => updatePermission(() {
classSettings.dominantLanguage = p0.langCode;
}),
initialLanguage: getLanguage(
isBase: true,
langCode: classSettings.dominantLanguage,
),
languages: pangeaController.pLanguageStore.baseOptions,
showMultilingual: true,
),
PQuestionContainer(
title: L10n.of(context)!.selectTargetLanguage,
),
PLanguageDropdown(
onChange: (p0) => updatePermission(() {
classSettings.targetLanguage = p0.langCode;
}),
initialLanguage: getLanguage(
isBase: false,
langCode: classSettings.targetLanguage,
),
languages: pangeaController.pLanguageStore.targetOptions,
),
PQuestionContainer(
title: L10n.of(context)!.whatIsYourClassLanguageLevel,
),
LanguageLevelDropdown(
initialLevel: classSettings.languageLevel,
onChanged: (int? newValue) => updatePermission(() {
classSettings.languageLevel = newValue!;
}),
),
],
),
),
),
],
);
}

View file

@ -0,0 +1,184 @@
// import 'dart:developer';
// import 'package:fluffychat/pangea/models/space_model.dart';
// import 'package:fluffychat/pangea/widgets/space/language_level_dropdown.dart';
// import 'package:flutter/foundation.dart';
// import 'package:flutter/material.dart';
// import 'package:flutter_gen/gen_l10n/l10n.dart';
// import 'package:future_loading_dialog/future_loading_dialog.dart';
// import 'package:matrix/matrix.dart';
// import '../../../widgets/matrix.dart';
// import '../../constants/language_keys.dart';
// import '../../constants/pangea_event_types.dart';
// import '../../controllers/language_list_controller.dart';
// import '../../controllers/pangea_controller.dart';
// import '../../extensions/pangea_room_extension/pangea_room_extension.dart';
// import '../../models/language_model.dart';
// import '../../utils/error_handler.dart';
// import '../user_settings/p_language_dropdown.dart';
// import '../user_settings/p_question_container.dart';
// class LanguageSettings extends StatefulWidget {
// final String? roomId;
// final bool startOpen;
// final LanguageSettingsModel? initialSettings;
// const LanguageSettings({
// super.key,
// this.roomId,
// this.startOpen = false,
// this.initialSettings,
// });
// @override
// LanguageSettingsState createState() => LanguageSettingsState();
// }
// class LanguageSettingsState extends State<LanguageSettings> {
// Room? room;
// late LanguageSettingsModel languageSettings;
// late bool isOpen;
// final PangeaController pangeaController = MatrixState.pangeaController;
// final cityController = TextEditingController();
// final countryController = TextEditingController();
// final schoolController = TextEditingController();
// LanguageSettingsState({Key? key});
// @override
// void initState() {
// room = widget.roomId != null
// ? Matrix.of(context).client.getRoomById(widget.roomId!)
// : null;
// languageSettings = room?.languageSettings ??
// widget.initialSettings ??
// LanguageSettingsModel();
// isOpen = widget.startOpen;
// super.initState();
// }
// bool get sameLanguages =>
// languageSettings.targetLanguage == languageSettings.dominantLanguage;
// LanguageModel getLanguage({required bool isBase, required String? langCode}) {
// final LanguageModel backup = isBase
// ? pangeaController.pLanguageStore.baseOptions.first
// : pangeaController.pLanguageStore.targetOptions.first;
// if (langCode == null) return backup;
// final LanguageModel byCode = PangeaLanguage.byLangCode(langCode);
// return byCode.langCode != LanguageKeys.unknownLanguage ? byCode : backup;
// }
// Future<void> updatePermission(void Function() makeLocalRuleChange) async {
// makeLocalRuleChange();
// if (room != null) {
// await showFutureLoadingDialog(
// context: context,
// future: () => setLanguageSettings(room!.id),
// );
// }
// setState(() {});
// }
// void setTextControllerValues() {
// languageSettings.city = cityController.text;
// languageSettings.country = countryController.text;
// languageSettings.schoolName = schoolController.text;
// }
// Future<void> setLanguageSettings(String roomId) async {
// try {
// setTextControllerValues();
// await Matrix.of(context).client.setRoomStateWithKey(
// roomId,
// PangeaEventTypes.languageSettings,
// '',
// languageSettings.toJson(),
// );
// } catch (err, stack) {
// debugger(when: kDebugMode);
// ErrorHandler.logError(e: err, s: stack);
// }
// }
// @override
// Widget build(BuildContext context) => Column(
// children: [
// ListTile(
// title: Text(
// L10n.of(context)!.languageSettings,
// style: TextStyle(
// color: Theme.of(context).colorScheme.secondary,
// fontWeight: FontWeight.bold,
// ),
// ),
// subtitle: Text(L10n.of(context)!.languageSettingsDesc),
// leading: CircleAvatar(
// backgroundColor: Theme.of(context).scaffoldBackgroundColor,
// foregroundColor: Theme.of(context).textTheme.bodyLarge!.color,
// child: const Icon(Icons.language),
// ),
// trailing: Icon(
// isOpen
// ? Icons.keyboard_arrow_down_outlined
// : Icons.keyboard_arrow_right_outlined,
// ),
// onTap: () => setState(() => isOpen = !isOpen),
// ),
// if (isOpen)
// AnimatedContainer(
// duration: const Duration(milliseconds: 300),
// height: isOpen ? null : 0,
// child: Padding(
// padding: const EdgeInsets.fromLTRB(16.0, 0, 16.0, 8.0),
// child: Column(
// children: [
// PQuestionContainer(
// title: L10n.of(context)!.selectSpaceDominantLanguage,
// ),
// PLanguageDropdown(
// onChange: (p0) => updatePermission(() {
// languageSettings.dominantLanguage = p0.langCode;
// }),
// initialLanguage: getLanguage(
// isBase: true,
// langCode: languageSettings.dominantLanguage,
// ),
// languages: pangeaController.pLanguageStore.baseOptions,
// showMultilingual: true,
// ),
// PQuestionContainer(
// title: L10n.of(context)!.selectSpaceTargetLanguage,
// ),
// PLanguageDropdown(
// onChange: (p0) => updatePermission(() {
// languageSettings.targetLanguage = p0.langCode;
// }),
// initialLanguage: getLanguage(
// isBase: false,
// langCode: languageSettings.targetLanguage,
// ),
// languages: pangeaController.pLanguageStore.targetOptions,
// ),
// PQuestionContainer(
// title: L10n.of(context)!.whatIsYourSpaceLanguageLevel,
// ),
// LanguageLevelDropdown(
// initialLevel: languageSettings.languageLevel,
// onChanged: (int? newValue) => updatePermission(() {
// languageSettings.languageLevel = newValue!;
// }),
// ),
// ],
// ),
// ),
// ),
// ],
// );
// }

View file

@ -111,7 +111,7 @@ abstract class ClientManager {
// To make room emotes work
'im.ponies.room_emotes',
// #Pangea
PangeaEventTypes.classSettings,
PangeaEventTypes.languageSettings,
PangeaEventTypes.rules,
PangeaEventTypes.botOptions,
EventTypes.RoomTopic,

View file

@ -0,0 +1,86 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:matrix/matrix.dart';
class VideoRenderer extends StatefulWidget {
final WrappedMediaStream? stream;
final bool mirror;
final RTCVideoViewObjectFit fit;
const VideoRenderer(
this.stream, {
this.mirror = false,
this.fit = RTCVideoViewObjectFit.RTCVideoViewObjectFitContain,
super.key,
});
@override
State<StatefulWidget> createState() => _VideoRendererState();
}
class _VideoRendererState extends State<VideoRenderer> {
RTCVideoRenderer? _renderer;
bool _rendererReady = false;
MediaStream? get mediaStream => widget.stream?.stream;
StreamSubscription? _streamChangeSubscription;
Future<RTCVideoRenderer> _initializeRenderer() async {
_renderer ??= RTCVideoRenderer();
await _renderer!.initialize();
_renderer!.srcObject = mediaStream;
return _renderer!;
}
void disposeRenderer() {
try {
_renderer?.srcObject = null;
_renderer?.dispose();
_renderer = null;
// ignore: empty_catches
} catch (e) {}
}
@override
void initState() {
_streamChangeSubscription =
widget.stream?.onStreamChanged.stream.listen((stream) {
setState(() {
_renderer?.srcObject = stream;
});
});
setupRenderer();
super.initState();
}
Future<void> setupRenderer() async {
await _initializeRenderer();
setState(() => _rendererReady = true);
}
@override
void dispose() {
_streamChangeSubscription?.cancel();
disposeRenderer();
super.dispose();
}
@override
Widget build(BuildContext context) => !_rendererReady
? Container()
: Builder(
key: widget.key,
builder: (ctx) {
return RTCVideoView(
_renderer!,
mirror: widget.mirror,
filterQuality: FilterQuality.medium,
objectFit: widget.fit,
placeholderBuilder: (_) =>
Container(color: Colors.white.withOpacity(0.18)),
);
},
);
}

View file

@ -1,9 +1,7 @@
import 'dart:async';
import 'package:adaptive_dialog/adaptive_dialog.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/utils/download_chat.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
@ -49,13 +47,6 @@ class ChatSettingsPopupMenuState extends State<ChatSettingsPopupMenu> {
@override
Widget build(BuildContext context) {
// #Pangea
final PangeaController pangeaController = MatrixState.pangeaController;
final ClassSettingsModel? classSettings = pangeaController
.matrixState.client
.getRoomById(widget.room.id)
?.firstLanguageSettings;
// Pangea#
notificationChangeSub ??= Matrix.of(context)
.client
.onSync
@ -151,7 +142,6 @@ class ChatSettingsPopupMenuState extends State<ChatSettingsPopupMenu> {
context: context,
future: () => downloadChat(
widget.room,
classSettings!,
DownloadType.txt,
Matrix.of(context).client,
context,
@ -163,7 +153,6 @@ class ChatSettingsPopupMenuState extends State<ChatSettingsPopupMenu> {
context: context,
future: () => downloadChat(
widget.room,
classSettings!,
DownloadType.csv,
Matrix.of(context).client,
context,
@ -175,7 +164,6 @@ class ChatSettingsPopupMenuState extends State<ChatSettingsPopupMenu> {
context: context,
future: () => downloadChat(
widget.room,
classSettings!,
DownloadType.xlsx,
Matrix.of(context).client,
context,
@ -189,6 +177,17 @@ class ChatSettingsPopupMenuState extends State<ChatSettingsPopupMenu> {
}
},
itemBuilder: (BuildContext context) => [
if (widget.displayChatDetails)
PopupMenuItem<ChatPopupMenuActions>(
value: ChatPopupMenuActions.details,
child: Row(
children: [
const Icon(Icons.info_outline_rounded),
const SizedBox(width: 12),
Text(L10n.of(context)!.chatDetails),
],
),
),
// #Pangea
PopupMenuItem<ChatPopupMenuActions>(
value: ChatPopupMenuActions.learningSettings,
@ -201,17 +200,6 @@ class ChatSettingsPopupMenuState extends State<ChatSettingsPopupMenu> {
),
),
// Pangea#
if (widget.displayChatDetails)
PopupMenuItem<ChatPopupMenuActions>(
value: ChatPopupMenuActions.details,
child: Row(
children: [
const Icon(Icons.info_outline_rounded),
const SizedBox(width: 12),
Text(L10n.of(context)!.chatDetails),
],
),
),
if (widget.room.pushRuleState == PushRuleState.notify)
PopupMenuItem<ChatPopupMenuActions>(
value: ChatPopupMenuActions.mute,
@ -270,39 +258,36 @@ class ChatSettingsPopupMenuState extends State<ChatSettingsPopupMenu> {
],
),
),
if (classSettings != null)
PopupMenuItem<ChatPopupMenuActions>(
value: ChatPopupMenuActions.downloadTxt,
child: Row(
children: [
const Icon(Icons.download_outlined),
const SizedBox(width: 12),
Text(L10n.of(context)!.downloadTxtFile),
],
),
PopupMenuItem<ChatPopupMenuActions>(
value: ChatPopupMenuActions.downloadTxt,
child: Row(
children: [
const Icon(Icons.download_outlined),
const SizedBox(width: 12),
Text(L10n.of(context)!.downloadTxtFile),
],
),
if (classSettings != null)
PopupMenuItem<ChatPopupMenuActions>(
value: ChatPopupMenuActions.downloadCsv,
child: Row(
children: [
const Icon(Icons.download_outlined),
const SizedBox(width: 12),
Text(L10n.of(context)!.downloadCSVFile),
],
),
),
PopupMenuItem<ChatPopupMenuActions>(
value: ChatPopupMenuActions.downloadCsv,
child: Row(
children: [
const Icon(Icons.download_outlined),
const SizedBox(width: 12),
Text(L10n.of(context)!.downloadCSVFile),
],
),
if (classSettings != null)
PopupMenuItem<ChatPopupMenuActions>(
value: ChatPopupMenuActions.downloadXlsx,
child: Row(
children: [
const Icon(Icons.download_outlined),
const SizedBox(width: 12),
Text(L10n.of(context)!.downloadXLSXFile),
],
),
),
PopupMenuItem<ChatPopupMenuActions>(
value: ChatPopupMenuActions.downloadXlsx,
child: Row(
children: [
const Icon(Icons.download_outlined),
const SizedBox(width: 12),
Text(L10n.of(context)!.downloadXLSXFile),
],
),
),
// Pangea#
],
),

File diff suppressed because it is too large Load diff

View file

@ -5,10 +5,10 @@ packages:
dependency: transitive
description:
name: _flutterfire_internals
sha256: "3ff770dfff04a67b0863dff205a0936784de1b87a5e99b11c693fc10e66a9ce3"
sha256: "0816f12bbbd9e21f72ea8592b11bce4a628d4e5cb7a81ff9f1eee4f3dc23206e"
url: "https://pub.dev"
source: hosted
version: "1.0.12"
version: "1.3.37"
async:
dependency: transitive
description:
@ -61,50 +61,50 @@ packages:
dependency: "direct main"
description:
name: firebase_core
sha256: c129209ba55f3d4272c89fb4a4994c15bea77fb6de63a82d45fb6bc5c94e4355
sha256: fae4ab4317c2a7afb13d44ef1e3f9f28a630e10016bc5cfe761e8e6a0ed7816a
url: "https://pub.dev"
source: hosted
version: "2.4.1"
version: "3.1.0"
firebase_core_platform_interface:
dependency: transitive
description:
name: firebase_core_platform_interface
sha256: "5fab93f5b354648efa62e7cc829c90efb68c8796eecf87e0888cae2d5f3accd4"
sha256: "1003a5a03a61fc9a22ef49f37cbcb9e46c86313a7b2e7029b9390cf8c6fc32cb"
url: "https://pub.dev"
source: hosted
version: "4.5.2"
version: "5.1.0"
firebase_core_web:
dependency: transitive
description:
name: firebase_core_web
sha256: "18b35ce111b0a4266abf723c825bcf9d4e2519d13638cc7f06f2a8dd960c75bc"
sha256: "6643fe3dbd021e6ccfb751f7882b39df355708afbdeb4130fc50f9305a9d1a3d"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
version: "2.17.2"
firebase_messaging:
dependency: "direct main"
description:
name: firebase_messaging
sha256: dc010a6436333029fba858415fe65887c3fe44d8f6e6ea162bb8d3dd764fbcb6
sha256: "2d0ea2234ce46030eda2e6922611115ce603adc614ebd8c00e7db06a8929efbb"
url: "https://pub.dev"
source: hosted
version: "14.2.1"
version: "15.0.1"
firebase_messaging_platform_interface:
dependency: transitive
description:
name: firebase_messaging_platform_interface
sha256: abda2d766486096eb1c568c7b20aef46180596c8b0708190b929133ff03e0a8d
sha256: c38c27f58cb6a88b8c145018d0567802376549c32a60098a13f3bdf3ddea326f
url: "https://pub.dev"
source: hosted
version: "4.2.10"
version: "4.5.39"
firebase_messaging_web:
dependency: transitive
description:
name: firebase_messaging_web
sha256: "7a0ce957bd2210e8636325152234728874dad039f1c7271ba1be5c752fdc5888"
sha256: "8502849c2f232f7db338c052e045442207a0db82bd03ff14be3c80897dd8c26c"
url: "https://pub.dev"
source: hosted
version: "3.2.11"
version: "3.8.9"
flutter:
dependency: "direct main"
description: flutter
@ -120,46 +120,62 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
js:
leak_tracker:
dependency: transitive
description:
name: js
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
name: leak_tracker
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
url: "https://pub.dev"
source: hosted
version: "0.6.7"
version: "10.0.4"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
url: "https://pub.dev"
source: hosted
version: "3.0.3"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
matcher:
dependency: transitive
description:
name: matcher
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev"
source: hosted
version: "0.12.16"
version: "0.12.16+1"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev"
source: hosted
version: "0.5.0"
version: "0.8.0"
meta:
dependency: transitive
description:
name: meta
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
url: "https://pub.dev"
source: hosted
version: "1.10.0"
version: "1.12.0"
path:
dependency: transitive
description:
name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev"
source: hosted
version: "1.8.3"
version: "1.9.0"
pedantic:
dependency: "direct dev"
description:
@ -225,10 +241,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
url: "https://pub.dev"
source: hosted
version: "0.6.1"
version: "0.7.0"
vector_math:
dependency: transitive
description:
@ -237,14 +253,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
url: "https://pub.dev"
source: hosted
version: "14.2.1"
web:
dependency: transitive
description:
name: web
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
url: "https://pub.dev"
source: hosted
version: "0.3.0"
version: "0.5.1"
sdks:
dart: ">=3.2.0-194.0.dev <4.0.0"
flutter: ">=1.20.0"
dart: ">=3.3.0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"

View file

@ -10,8 +10,8 @@ environment:
flutter: ">=1.20.0"
dependencies:
firebase_core: ^2.4.1
firebase_messaging: ^14.2.1
firebase_core: ^3.1.0
firebase_messaging: ^15.0.1
flutter:
sdk: flutter

View file

@ -13,10 +13,10 @@ packages:
dependency: transitive
description:
name: _flutterfire_internals
sha256: "3dee3db3468c5f4640a4e8aa9c1e22561c298976d8c39ed2fdd456a9a3db26e1"
sha256: "0816f12bbbd9e21f72ea8592b11bce4a628d4e5cb7a81ff9f1eee4f3dc23206e"
url: "https://pub.dev"
source: hosted
version: "1.3.32"
version: "1.3.37"
adaptive_dialog:
dependency: "direct main"
description:
@ -460,74 +460,74 @@ packages:
dependency: "direct main"
description:
name: firebase_analytics
sha256: c56bcc7abc6caacc33e8495bc604a5861d25ce371f1b06476ae226e7cbb21d4c
sha256: "3363045ecc3141673262493eea4bd888c2c627c5e986b8766fa5f4bfafdbcdd8"
url: "https://pub.dev"
source: hosted
version: "10.10.4"
version: "11.0.1"
firebase_analytics_platform_interface:
dependency: transitive
description:
name: firebase_analytics_platform_interface
sha256: e8cdb845820eaa5f1d29de31a72aab8addbbffc6483f1e5123a9d0b611735285
sha256: "5a2c14e18bf6c4423a6462113d85d3dee534a8bdd620391729e9570632d2aec4"
url: "https://pub.dev"
source: hosted
version: "3.10.5"
version: "4.0.1"
firebase_analytics_web:
dependency: transitive
description:
name: firebase_analytics_web
sha256: e2fabebdf16bb99506a1e7e84f5effd6313c90678e6ea1876d301f057483a198
sha256: db4bdb166aae9e5a0ce7791672401db139cd342369a92f7eaf020dbff9df2998
url: "https://pub.dev"
source: hosted
version: "0.5.7+4"
version: "0.5.7+9"
firebase_core:
dependency: "direct main"
description:
name: firebase_core
sha256: "4aef2a23d0f3265545807d68fbc2f76a6b994ca3c778d88453b99325abd63284"
sha256: fae4ab4317c2a7afb13d44ef1e3f9f28a630e10016bc5cfe761e8e6a0ed7816a
url: "https://pub.dev"
source: hosted
version: "2.30.1"
version: "3.1.0"
firebase_core_platform_interface:
dependency: transitive
description:
name: firebase_core_platform_interface
sha256: c437ae5d17e6b5cc7981cf6fd458a5db4d12979905f9aafd1fea930428a9fe63
sha256: "1003a5a03a61fc9a22ef49f37cbcb9e46c86313a7b2e7029b9390cf8c6fc32cb"
url: "https://pub.dev"
source: hosted
version: "5.0.0"
version: "5.1.0"
firebase_core_web:
dependency: transitive
description:
name: firebase_core_web
sha256: "67f2fcc600fc78c2f731c370a3a5e6c87ee862e3a2fba6f951eca6d5dafe5c29"
sha256: "6643fe3dbd021e6ccfb751f7882b39df355708afbdeb4130fc50f9305a9d1a3d"
url: "https://pub.dev"
source: hosted
version: "2.16.0"
version: "2.17.2"
firebase_messaging:
dependency: "direct main"
description:
name: firebase_messaging
sha256: "73a43445a7f8c6e6327f0ec3922b1c99a9f4a0e4896197bfe10a88259f775aad"
sha256: "2d0ea2234ce46030eda2e6922611115ce603adc614ebd8c00e7db06a8929efbb"
url: "https://pub.dev"
source: hosted
version: "14.9.1"
version: "15.0.1"
firebase_messaging_platform_interface:
dependency: transitive
description:
name: firebase_messaging_platform_interface
sha256: "675527aadccb679c9dfd43a4558690427123ac1e99f03eef5bbce9dc216edc91"
sha256: c38c27f58cb6a88b8c145018d0567802376549c32a60098a13f3bdf3ddea326f
url: "https://pub.dev"
source: hosted
version: "4.5.34"
version: "4.5.39"
firebase_messaging_web:
dependency: transitive
description:
name: firebase_messaging_web
sha256: "66deff69307f54fc7a20732b4278af327ae378b86607d5ac877d8f2201fe881e"
sha256: "8502849c2f232f7db338c052e045442207a0db82bd03ff14be3c80897dd8c26c"
url: "https://pub.dev"
source: hosted
version: "3.8.4"
version: "3.8.9"
fixnum:
dependency: transitive
description:

View file

@ -103,9 +103,9 @@ dependencies:
country_picker: ^2.0.25
csv: ^6.0.0
fl_chart: ^0.67.0
firebase_analytics: ^10.10.2
firebase_core: ^2.30.0
firebase_messaging: ^14.8.2
firebase_analytics: ^11.0.1
firebase_core: ^3.1.0
firebase_messaging: ^15.0.1
flutter_dotenv: ^5.1.0
fcm_shared_isolate:
path: pangea_packages/fcm_shared_isolate

6
sentry 2.properties Normal file
View file

@ -0,0 +1,6 @@
upload_debug_symbols=true
upload_source_maps=true
upload_sources=true
wait_for_processing=false
log_level=info
commits=auto