Merge branch 'main' into 5244-grammar-practice-ui-updates
This commit is contained in:
commit
be9ef801a9
94 changed files with 1183 additions and 745 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "ar",
|
||||
"@@last_modified": "2026-01-20 12:31:24.671375",
|
||||
"@@last_modified": "2026-01-21 13:54:18.388293",
|
||||
"about": "حول",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11107,5 +11107,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "صوت بوت بانجيا",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "تم إرسال طلبك إلى إدارة الدورة! سيتم السماح لك بالدخول إذا وافقوا.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1911,7 +1911,7 @@
|
|||
"playWithAI": "Пакуль гуляйце з ШІ",
|
||||
"courseStartDesc": "Pangea Bot гатовы да працы ў любы час!\n\n...але навучанне лепш з сябрамі!",
|
||||
"@@locale": "be",
|
||||
"@@last_modified": "2026-01-20 12:31:06.570011",
|
||||
"@@last_modified": "2026-01-21 13:54:07.402936",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -11989,5 +11989,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Голас Pangea Bot",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Ваш запыт быў адпраўлены адміністрацыі курса! Вы будзеце дапушчаны, калі яны зацвердзяць.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-20 12:31:49.464406",
|
||||
"@@last_modified": "2026-01-21 13:54:31.328626",
|
||||
"about": "সম্পর্কে",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11994,5 +11994,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "প্যাঙ্গিয়া বটের কণ্ঠ",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "আপনার অনুরোধ কোর্স প্রশাসকের কাছে পাঠানো হয়েছে! তারা অনুমোদন করলে আপনাকে প্রবেশ করতে দেওয়া হবে।",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -4279,7 +4279,7 @@
|
|||
"joinPublicTrip": "མི་ཚེས་ལ་ལོག་འབད།",
|
||||
"startOwnTrip": "ངེད་རང་གི་ལོག་ལ་སྦྱོར་བཅོས།",
|
||||
"@@locale": "bo",
|
||||
"@@last_modified": "2026-01-20 12:31:44.969872",
|
||||
"@@last_modified": "2026-01-21 13:54:28.767499",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -10644,5 +10644,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Pangea Bot voz",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Yor requst has been sent to course admin! Yu'll be let in if dey approve.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-20 12:31:09.126351",
|
||||
"@@last_modified": "2026-01-21 13:54:08.647836",
|
||||
"about": "Quant a",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -10914,5 +10914,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Veu del bot Pangea",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "La teva sol·licitud s'ha enviat a l'administrador del curs! Et deixaran entrar si ho aproven.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "cs",
|
||||
"@@last_modified": "2026-01-20 12:31:00.323945",
|
||||
"@@last_modified": "2026-01-21 13:54:04.800051",
|
||||
"about": "O aplikaci",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11497,5 +11497,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Hlas Pangea Bota",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Vaše žádost byla odeslána administrátorovi kurzu! Budete vpuštěni, pokud ji schválí.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1930,7 +1930,7 @@
|
|||
"playWithAI": "Leg med AI for nu",
|
||||
"courseStartDesc": "Pangea Bot er klar til at starte når som helst!\n\n...men læring er bedre med venner!",
|
||||
"@@locale": "da",
|
||||
"@@last_modified": "2026-01-20 12:30:10.761209",
|
||||
"@@last_modified": "2026-01-21 13:53:34.885532",
|
||||
"@aboutHomeserver": {
|
||||
"type": "String",
|
||||
"placeholders": {
|
||||
|
|
@ -11951,5 +11951,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Pangea Bot stemme",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Din anmodning er sendt til kursusadministratoren! Du vil blive lukket ind, hvis de godkender.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "de",
|
||||
"@@last_modified": "2026-01-20 12:30:47.434524",
|
||||
"@@last_modified": "2026-01-21 13:53:56.595777",
|
||||
"alwaysUse24HourFormat": "true",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"description": "Set to true to always display time of day in 24 hour format."
|
||||
|
|
@ -10897,5 +10897,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Pangea Bot Stimme",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Ihre Anfrage wurde an den Kursadministrator gesendet! Sie werden eingelassen, wenn sie zustimmen.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -4456,7 +4456,7 @@
|
|||
"playWithAI": "Παίξτε με την Τεχνητή Νοημοσύνη προς το παρόν",
|
||||
"courseStartDesc": "Ο Pangea Bot είναι έτοιμος να ξεκινήσει οποιαδήποτε στιγμή!\n\n...αλλά η μάθηση είναι καλύτερη με φίλους!",
|
||||
"@@locale": "el",
|
||||
"@@last_modified": "2026-01-20 12:31:59.503296",
|
||||
"@@last_modified": "2026-01-21 13:54:37.783088",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -11948,5 +11948,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Φωνή Pangea Bot",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Το αίτημά σας έχει σταλεί στον διαχειριστή του μαθήματος! Θα σας επιτρέψουν να μπείτε αν το εγκρίνουν.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -5056,5 +5056,7 @@
|
|||
"constructUseIncGEDesc": "Incorrect grammar error practice",
|
||||
"fillInBlank": "Fill in the blank with the correct choice",
|
||||
"learn": "Learn",
|
||||
"languageUpdated": "Target language updated!"
|
||||
"languageUpdated": "Target language updated!",
|
||||
"voiceDropdownTitle": "Pangea Bot voice",
|
||||
"knockDesc": "Your request has been sent to course admin! You'll be let in if they approve."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-20 12:32:07.490560",
|
||||
"@@last_modified": "2026-01-21 13:54:42.193114",
|
||||
"about": "Prio",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11979,5 +11979,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Voĉo de Pangea Bot",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Via peto estis sendita al la kursa administranto! Vi estos enirita se ili aprobas.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "es",
|
||||
"@@last_modified": "2026-01-20 12:29:59.898184",
|
||||
"@@last_modified": "2026-01-21 13:53:29.857658",
|
||||
"about": "Acerca de",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -8124,5 +8124,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Voz del bot Pangea",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "¡Tu solicitud ha sido enviada al administrador del curso! Te dejarán entrar si la aprueban.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "et",
|
||||
"@@last_modified": "2026-01-20 12:30:45.162738",
|
||||
"@@last_modified": "2026-01-21 13:53:55.586614",
|
||||
"about": "Rakenduse teave",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11161,5 +11161,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Pangea Boti hääl",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Teie taotlus on saadetud kursuse administraatorile! Teid lastakse sisse, kui nad heaks kiidavad.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "eu",
|
||||
"@@last_modified": "2026-01-20 12:30:40.144354",
|
||||
"@@last_modified": "2026-01-21 13:53:53.122879",
|
||||
"about": "Honi buruz",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -10890,5 +10890,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Pangea Bot ahotsa",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Zure eskaera ikastaroaren administratzaileari bidali zaio! Onartzen badute, sartuko zara.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-20 12:31:52.231221",
|
||||
"@@last_modified": "2026-01-21 13:54:33.096668",
|
||||
"repeatPassword": "تکرار رمزعبور",
|
||||
"@repeatPassword": {},
|
||||
"about": "درباره",
|
||||
|
|
@ -11622,5 +11622,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "صدای ربات پانژیا",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "درخواست شما به مدیر دوره ارسال شده است! اگر آنها تأیید کنند، شما وارد خواهید شد.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -4009,7 +4009,7 @@
|
|||
"playWithAI": "Leiki tekoälyn kanssa nyt",
|
||||
"courseStartDesc": "Pangea Bot on valmis milloin tahansa!\n\n...mutta oppiminen on parempaa ystävien kanssa!",
|
||||
"@@locale": "fi",
|
||||
"@@last_modified": "2026-01-20 12:30:08.099637",
|
||||
"@@last_modified": "2026-01-21 13:53:33.564588",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -11513,5 +11513,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Pangea Botin ääni",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Pyyntösi on lähetetty kurssin ylläpitäjälle! Sinut päästetään sisään, jos he hyväksyvät sen.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -2787,7 +2787,7 @@
|
|||
"selectAll": "Piliin lahat",
|
||||
"deselectAll": "Huwag piliin lahat",
|
||||
"@@locale": "fil",
|
||||
"@@last_modified": "2026-01-20 12:31:19.884620",
|
||||
"@@last_modified": "2026-01-21 13:54:14.817261",
|
||||
"@setCustomPermissionLevel": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -11866,5 +11866,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Boses ng Pangea Bot",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Ang iyong kahilingan ay naipadala sa admin ng kurso! Papayagan ka nilang pumasok kung sila ay mag-aapruba.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "fr",
|
||||
"@@last_modified": "2026-01-20 12:32:20.398562",
|
||||
"@@last_modified": "2026-01-21 13:54:48.318824",
|
||||
"about": "À propos",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11214,5 +11214,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Voix du bot Pangea",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Votre demande a été envoyée à l'administrateur du cours ! Vous serez admis s'ils approuvent.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -4517,7 +4517,7 @@
|
|||
"playWithAI": "Imir le AI faoi láthair",
|
||||
"courseStartDesc": "Tá Bot Pangea réidh chun dul am ar bith!\n\n...ach is fearr foghlaim le cairde!",
|
||||
"@@locale": "ga",
|
||||
"@@last_modified": "2026-01-20 12:32:18.033824",
|
||||
"@@last_modified": "2026-01-21 13:54:46.978792",
|
||||
"@customReaction": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -10888,5 +10888,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "guth Pangea Bot",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Tá do hiarratas curtha chuig an riarachán cúrsa! Cuirfear isteach thú má cheadaíonn siad é.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "gl",
|
||||
"@@last_modified": "2026-01-20 12:30:05.234280",
|
||||
"@@last_modified": "2026-01-21 13:53:31.619299",
|
||||
"about": "Acerca de",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -10887,5 +10887,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Voz do bot Pangea",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "A túa solicitude foi enviada ao administrador do curso! Serás admitido se a aproban.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-20 12:30:31.884050",
|
||||
"@@last_modified": "2026-01-21 13:53:47.592162",
|
||||
"about": "אודות",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11939,5 +11939,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "קול של פנגיאה בוט",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "הבקשה שלך נשלחה למנהל הקורס! תורשה להיכנס אם הם יאשרו.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -4483,7 +4483,7 @@
|
|||
"playWithAI": "अभी के लिए एआई के साथ खेलें",
|
||||
"courseStartDesc": "पैंजिया बॉट कभी भी जाने के लिए तैयार है!\n\n...लेकिन दोस्तों के साथ सीखना बेहतर है!",
|
||||
"@@locale": "hi",
|
||||
"@@last_modified": "2026-01-20 12:32:05.240015",
|
||||
"@@last_modified": "2026-01-21 13:54:40.850433",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -11975,5 +11975,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "पैंगिया बॉट की आवाज़",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "आपका अनुरोध पाठ्यक्रम प्रशासन को भेज दिया गया है! यदि वे स्वीकृत करते हैं, तो आपको अंदर जाने दिया जाएगा।",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "hr",
|
||||
"@@last_modified": "2026-01-20 12:30:29.823787",
|
||||
"@@last_modified": "2026-01-21 13:53:46.524141",
|
||||
"about": "Informacije",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11262,5 +11262,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Pangea Bot glas",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Vaš zahtjev je poslan administratoru tečaja! Bit ćete primljeni ako odobre.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "hu",
|
||||
"@@last_modified": "2026-01-20 12:30:13.762355",
|
||||
"@@last_modified": "2026-01-21 13:53:36.742109",
|
||||
"about": "Névjegy",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -10891,5 +10891,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Pangea Bot hang",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "A kérésed el lett küldve a kurzus adminisztrátorának! Be fogsz engedni, ha jóváhagyják.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1958,7 +1958,7 @@
|
|||
"playWithAI": "Joca con le IA pro ora",
|
||||
"courseStartDesc": "Pangea Bot es preste a comenzar a qualunque momento!\n\n...ma apprender es melior con amicos!",
|
||||
"@@locale": "ia",
|
||||
"@@last_modified": "2026-01-20 12:30:35.012898",
|
||||
"@@last_modified": "2026-01-21 13:53:49.028067",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -11968,5 +11968,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Voix du bot Pangea",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Tua peticio est missa ad administratorem cursuum! Te admittent si illi approbant.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-20 12:30:15.788809",
|
||||
"@@last_modified": "2026-01-21 13:53:37.899898",
|
||||
"setAsCanonicalAlias": "Atur sebagai alias utama",
|
||||
"@setAsCanonicalAlias": {
|
||||
"type": "String",
|
||||
|
|
@ -10881,5 +10881,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Suara Pangea Bot",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Permintaan Anda telah dikirim ke admin kursus! Anda akan diizinkan masuk jika mereka menyetujuinya.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -4372,7 +4372,7 @@
|
|||
"playWithAI": "Joca con AI pro ora",
|
||||
"courseStartDesc": "Pangea Bot es preste a partir a qualunque momento!\n\n...ma apprender es melior con amicos!",
|
||||
"@@locale": "ie",
|
||||
"@@last_modified": "2026-01-20 12:30:26.700297",
|
||||
"@@last_modified": "2026-01-21 13:53:45.416677",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -11864,5 +11864,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Pangea Bot guth",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Tua iarrtas a chaidh a chur gu rianachd a' chùrsa! Thèid thu a leigeil a-steach ma tha iad a' freagairt gu math.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-20 12:30:55.791406",
|
||||
"@@last_modified": "2026-01-21 13:54:01.568848",
|
||||
"about": "Informazioni",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -10893,5 +10893,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Voce del bot Pangea",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "La tua richiesta è stata inviata all'amministratore del corso! Sarai ammesso se approvano.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "ja",
|
||||
"@@last_modified": "2026-01-20 12:32:02.883097",
|
||||
"@@last_modified": "2026-01-21 13:54:39.359536",
|
||||
"about": "このアプリについて",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11680,5 +11680,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "パンゲアボットの声",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "あなたのリクエストはコース管理者に送信されました! 彼らが承認すれば、入ることができます。",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -2594,7 +2594,7 @@
|
|||
"playWithAI": "ამ დროისთვის ითამაშეთ AI-თან",
|
||||
"courseStartDesc": "Pangea Bot მზადაა ნებისმიერ დროს გასასვლელად!\n\n...მაგრამ სწავლა უკეთესია მეგობრებთან ერთად!",
|
||||
"@@locale": "ka",
|
||||
"@@last_modified": "2026-01-20 12:32:12.611848",
|
||||
"@@last_modified": "2026-01-21 13:54:44.486907",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -11920,5 +11920,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "პანჯეა ბოტის ხმა",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "თქვენი მოთხოვნა გაგზავნილია კურსის ადმინისტრატორთან! თქვენ შეგიშვებენ, თუ ისინი დაამტკიცებენ.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-20 12:29:56.840054",
|
||||
"@@last_modified": "2026-01-21 13:53:28.417406",
|
||||
"about": "소개",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -10998,5 +10998,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "판게아 봇 음성",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "귀하의 요청이 과정 관리자에게 전송되었습니다! 그들이 승인하면 들어갈 수 있습니다.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -3861,7 +3861,7 @@
|
|||
"playWithAI": "Žaiskite su dirbtiniu intelektu dabar",
|
||||
"courseStartDesc": "Pangea botas pasiruošęs bet kada pradėti!\n\n...bet mokymasis yra geresnis su draugais!",
|
||||
"@@locale": "lt",
|
||||
"@@last_modified": "2026-01-20 12:31:36.164653",
|
||||
"@@last_modified": "2026-01-21 13:54:22.729769",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -11695,5 +11695,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Pangea Bot balsas",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Jūsų prašymas buvo išsiųstas kurso administratoriui! Būsite įleistas, jei jie patvirtins.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -4482,7 +4482,7 @@
|
|||
"playWithAI": "Tagad spēlējiet ar AI",
|
||||
"courseStartDesc": "Pangea bots ir gatavs jebkurā laikā!\n\n...bet mācīties ir labāk ar draugiem!",
|
||||
"@@locale": "lv",
|
||||
"@@last_modified": "2026-01-20 12:31:22.453166",
|
||||
"@@last_modified": "2026-01-21 13:54:16.883169",
|
||||
"analyticsInactiveTitle": "Pieprasījumi neaktīviem lietotājiem nevar tikt nosūtīti",
|
||||
"analyticsInactiveDesc": "Neaktīvi lietotāji, kuri nav pieteikušies kopš šīs funkcijas ieviešanas, neredzēs jūsu pieprasījumu.\n\nPieprasījuma poga parādīsies, kad viņi atgriezīsies. Jūs varat atkārtoti nosūtīt pieprasījumu vēlāk, noklikšķinot uz pieprasījuma pogas viņu vārdā, kad tā būs pieejama.",
|
||||
"accessRequestedTitle": "Pieprasījums piekļūt analītikai",
|
||||
|
|
@ -10876,5 +10876,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Pangea Bot balss",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Jūsu pieprasījums ir nosūtīts kursa administratoram! Jūs tiksiet iekšā, ja viņi apstiprinās.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-20 12:31:02.821968",
|
||||
"@@last_modified": "2026-01-21 13:54:06.232198",
|
||||
"about": "Om",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11983,5 +11983,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Pangea Bot-stemme",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Din forespørsel har blitt sendt til kursadministratoren! Du vil bli sluppet inn hvis de godkjenner.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-20 12:31:42.507524",
|
||||
"@@last_modified": "2026-01-21 13:54:27.649909",
|
||||
"about": "Over ons",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -10890,5 +10890,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Pangea Bot stem",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Je verzoek is verzonden naar de cursusbeheerder! Je wordt toegelaten als ze goedkeuren.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "pl",
|
||||
"@@last_modified": "2026-01-20 12:31:54.796841",
|
||||
"@@last_modified": "2026-01-21 13:54:34.451816",
|
||||
"about": "O aplikacji",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -10888,5 +10888,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Głos bota Pangea",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Twoja prośba została wysłana do administratora kursu! Zostaniesz wpuszczony, jeśli ją zatwierdzą.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-20 12:30:42.601068",
|
||||
"@@last_modified": "2026-01-21 13:53:54.148542",
|
||||
"copiedToClipboard": "Copiada para a área de transferência",
|
||||
"@copiedToClipboard": {
|
||||
"type": "String",
|
||||
|
|
@ -11990,5 +11990,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Voz do Bot Pangea",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Sua solicitação foi enviada ao administrador do curso! Você será admitido se eles aprovarem.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-20 12:30:37.380939",
|
||||
"@@last_modified": "2026-01-21 13:53:51.587209",
|
||||
"about": "Sobre",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11248,5 +11248,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Voz do Bot Pangea",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Sua solicitação foi enviada ao administrador do curso! Você será admitido se eles aprovarem.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -3331,7 +3331,7 @@
|
|||
"selectAll": "Selecionar tudo",
|
||||
"deselectAll": "Desmarcar tudo",
|
||||
"@@locale": "pt_PT",
|
||||
"@@last_modified": "2026-01-20 12:31:13.609297",
|
||||
"@@last_modified": "2026-01-21 13:54:11.801274",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -11919,5 +11919,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Voz do Bot Pangea",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Sua solicitação foi enviada ao administrador do curso! Você será admitido se eles aprovarem.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-20 12:30:21.408747",
|
||||
"@@last_modified": "2026-01-21 13:53:41.456181",
|
||||
"about": "Despre",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11625,5 +11625,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Vocea Pangea Bot",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Cererea ta a fost trimisă administratorului cursului! Vei fi lăsat să intri dacă ei aprobă.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "ru",
|
||||
"@@last_modified": "2026-01-20 12:32:09.850624",
|
||||
"@@last_modified": "2026-01-21 13:54:43.229000",
|
||||
"about": "О проекте",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -10995,5 +10995,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Голос бота Pangea",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Ваш запрос отправлен администратору курса! Вы будете допущены, если они одобрят.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "sk",
|
||||
"@@last_modified": "2026-01-20 12:30:24.498564",
|
||||
"@@last_modified": "2026-01-21 13:53:43.706457",
|
||||
"about": "O aplikácii",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11974,5 +11974,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Hlas Pangea Bota",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Vaša žiadosť bola odoslaná administrátorovi kurzu! Budete vpustený, ak ju schvália.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -2464,7 +2464,7 @@
|
|||
"playWithAI": "Za zdaj igrajte z AI-jem",
|
||||
"courseStartDesc": "Pangea Bot je pripravljen kadarkoli!\n\n...ampak je bolje učiti se s prijatelji!",
|
||||
"@@locale": "sl",
|
||||
"@@last_modified": "2026-01-20 12:30:51.399294",
|
||||
"@@last_modified": "2026-01-21 13:53:58.241980",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -11971,5 +11971,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Glas Pangea Bota",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Vaša zahteva je bila poslana skrbniku tečaja! Vstopili boste, če jo odobrijo.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-20 12:32:14.799697",
|
||||
"@@last_modified": "2026-01-21 13:54:45.561732",
|
||||
"about": "О програму",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11992,5 +11992,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Glas Pangea Bota",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Vaš zahtev je poslat administratoru kursa! Bićete primljeni ako odobre.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-20 12:31:57.066428",
|
||||
"@@last_modified": "2026-01-21 13:54:35.931933",
|
||||
"about": "Om",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11368,5 +11368,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Pangea Bot röst",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Din begäran har skickats till kursadministratören! Du kommer att släppas in om de godkänner.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-20 12:31:40.562260",
|
||||
"@@last_modified": "2026-01-21 13:54:25.999605",
|
||||
"acceptedTheInvitation": "👍 {username} அழைப்பை ஏற்றுக்கொண்டது",
|
||||
"@acceptedTheInvitation": {
|
||||
"type": "String",
|
||||
|
|
@ -11114,5 +11114,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "பாஙேஆ பாட்டின் குரல்",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "உங்கள் கோரிக்கை பாடம் நிர்வாகிக்கு அனுப்பப்பட்டுள்ளது! அவர்கள் ஒப்புதலளித்தால் நீங்கள் உள்ளே அனுமதிக்கப்படுவீர்கள்.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1920,7 +1920,7 @@
|
|||
"playWithAI": "ఇప్పుడే AI తో ఆడండి",
|
||||
"courseStartDesc": "పాంజియా బాట్ ఎప్పుడైనా సిద్ధంగా ఉంటుంది!\n\n...కానీ స్నేహితులతో నేర్చుకోవడం మెరుగైనది!",
|
||||
"@@locale": "te",
|
||||
"@@last_modified": "2026-01-20 12:31:32.903548",
|
||||
"@@last_modified": "2026-01-21 13:54:20.897642",
|
||||
"@setCustomPermissionLevel": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -11979,5 +11979,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "పాంజియా బాట్ శబ్దం",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "మీ అభ్యర్థన కోర్సు నిర్వాహకుడికి పంపబడింది! వారు ఆమోదిస్తే, మీరు లోపలికి రానున్నారు.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -4456,7 +4456,7 @@
|
|||
"playWithAI": "เล่นกับ AI ชั่วคราว",
|
||||
"courseStartDesc": "Pangea Bot พร้อมที่จะเริ่มต้นได้ทุกเมื่อ!\n\n...แต่การเรียนรู้ดีกว่ากับเพื่อน!",
|
||||
"@@locale": "th",
|
||||
"@@last_modified": "2026-01-20 12:31:11.891533",
|
||||
"@@last_modified": "2026-01-21 13:54:10.758435",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -11948,5 +11948,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "เสียงของ Pangea Bot",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "คำขอของคุณได้ถูกส่งไปยังผู้ดูแลหลักสูตรแล้ว! คุณจะได้รับอนุญาตให้เข้าหากพวกเขาอนุมัติ.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "tr",
|
||||
"@@last_modified": "2026-01-20 12:31:28.469826",
|
||||
"@@last_modified": "2026-01-21 13:54:19.467039",
|
||||
"about": "Hakkında",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -11112,5 +11112,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Pangea Bot sesi",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Talebiniz kurs yöneticisine gönderildi! Onaylarlarsa içeri alınacaksınız.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "uk",
|
||||
"@@last_modified": "2026-01-20 12:30:57.869011",
|
||||
"@@last_modified": "2026-01-21 13:54:03.403456",
|
||||
"about": "Про застосунок",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -10884,5 +10884,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Голос Pangea Bot",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Ваш запит надіслано адміністратору курсу! Ви будете допущені, якщо вони схвалять.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-20 12:31:38.101874",
|
||||
"@@last_modified": "2026-01-21 13:54:24.252269",
|
||||
"about": "Giới thiệu",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -6460,5 +6460,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Giọng nói của Pangea Bot",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "Yêu cầu của bạn đã được gửi đến quản trị viên khóa học! Bạn sẽ được cho vào nếu họ chấp thuận.",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1856,7 +1856,7 @@
|
|||
"selectAll": "全選",
|
||||
"deselectAll": "取消全選",
|
||||
"@@locale": "yue",
|
||||
"@@last_modified": "2026-01-20 12:30:53.854617",
|
||||
"@@last_modified": "2026-01-21 13:53:59.885092",
|
||||
"@ignoreUser": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -11981,5 +11981,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Pangea Bot 聲音",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "你的請求已經發送給課程管理員!如果他們批准,你將被允許進入。",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "zh",
|
||||
"@@last_modified": "2026-01-20 12:31:47.017242",
|
||||
"@@last_modified": "2026-01-21 13:54:29.681537",
|
||||
"about": "关于",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -10881,5 +10881,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "潘吉亚机器人声音",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "您的请求已发送给课程管理员!如果他们批准,您将被允许进入。",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2026-01-20 12:31:16.140892",
|
||||
"@@last_modified": "2026-01-21 13:54:13.165623",
|
||||
"about": "關於",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -10888,5 +10888,15 @@
|
|||
"@languageUpdated": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"voiceDropdownTitle": "Pangea Bot 語音",
|
||||
"@voiceDropdownTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"knockDesc": "您的請求已發送給課程管理員!如果他們批准,您將被允許進入。",
|
||||
"@knockDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -496,6 +496,8 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
if (botAudioEvent == null) return;
|
||||
|
||||
final matrix = Matrix.of(context);
|
||||
if (matrix.voiceMessageEventId.value != null) return;
|
||||
|
||||
matrix.voiceMessageEventId.value = botAudioEvent.eventId;
|
||||
matrix.audioPlayer?.dispose();
|
||||
matrix.audioPlayer = AudioPlayer();
|
||||
|
|
@ -2009,6 +2011,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
|
||||
bool showMessageShimmer(Event event) {
|
||||
if (event.type != EventTypes.Message) return false;
|
||||
if (!(event.eventId == buttonEventID)) return false;
|
||||
if (event.messageType == MessageTypes.Text) {
|
||||
return !InstructionsEnum.clickTextMessages.isToggledOff;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,6 @@ import 'package:fluffychat/pages/chat/events/room_creation_state_event.dart';
|
|||
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_session_chat/activity_roles_event_widget.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_summary_widget.dart';
|
||||
import 'package:fluffychat/pangea/bot/utils/bot_name.dart';
|
||||
import 'package:fluffychat/pangea/bot/widgets/bot_settings_language_icon.dart';
|
||||
import 'package:fluffychat/pangea/chat/extensions/custom_room_display_extension.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/pressable_button.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/shimmer_background.dart';
|
||||
|
|
@ -477,18 +475,6 @@ class Message extends StatelessWidget {
|
|||
presenceBackgroundColor: wallpaperMode
|
||||
? Colors.transparent
|
||||
: null,
|
||||
// #Pangea
|
||||
miniIcon:
|
||||
user.id == BotName.byEnvironment
|
||||
? BotSettingsLanguageIcon(
|
||||
user: user,
|
||||
)
|
||||
: null,
|
||||
presenceOffset:
|
||||
user.id == BotName.byEnvironment
|
||||
? const Offset(0, 0)
|
||||
: null,
|
||||
// Pangea#
|
||||
);
|
||||
},
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/setting_keys.dart';
|
||||
import 'package:fluffychat/pangea/user/style_settings_repo.dart';
|
||||
import 'package:fluffychat/utils/account_config.dart';
|
||||
import 'package:fluffychat/utils/file_selector.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
|
|
@ -157,10 +157,16 @@ class SettingsStyleController extends State<SettingsStyle> {
|
|||
|
||||
void changeFontSizeFactor(double d) {
|
||||
setState(() => AppConfig.fontSizeFactor = d);
|
||||
Matrix.of(context).store.setString(
|
||||
SettingKeys.fontSizeFactor,
|
||||
AppConfig.fontSizeFactor.toString(),
|
||||
);
|
||||
// #Pangea
|
||||
// Matrix.of(context).store.setString(
|
||||
// SettingKeys.fontSizeFactor,
|
||||
// AppConfig.fontSizeFactor.toString(),
|
||||
// );
|
||||
StyleSettingsRepo.setFontSizeFactor(
|
||||
Matrix.of(context).client.userID!,
|
||||
AppConfig.fontSizeFactor,
|
||||
);
|
||||
// Pangea#
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ import 'package:shimmer/shimmer.dart';
|
|||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/bot/utils/bot_name.dart';
|
||||
import 'package:fluffychat/pangea/bot/widgets/bot_settings_language_icon.dart';
|
||||
import 'package:fluffychat/utils/string_color.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:fluffychat/widgets/hover_builder.dart';
|
||||
|
|
@ -70,14 +68,6 @@ class ActivityParticipantIndicator extends StatelessWidget {
|
|||
name: userId!.localpart,
|
||||
size: 60.0,
|
||||
userId: userId,
|
||||
miniIcon:
|
||||
room != null && userId == BotName.byEnvironment
|
||||
? BotSettingsLanguageIcon(user: user!)
|
||||
: null,
|
||||
presenceOffset:
|
||||
room != null && userId == BotName.byEnvironment
|
||||
? const Offset(0, 0)
|
||||
: null,
|
||||
)
|
||||
: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
|
|
|
|||
|
|
@ -106,27 +106,17 @@ class _VocabChipsState extends State<_VocabChips> with TokenRenderingMixin {
|
|||
OverlayUtil.showPositionedCard(
|
||||
overlayKey: target,
|
||||
context: context,
|
||||
cardToShow: StatefulBuilder(
|
||||
builder: (context, setState) => WordZoomWidget(
|
||||
token: PangeaTokenText(
|
||||
content: v.lemma,
|
||||
length: v.lemma.characters.length,
|
||||
offset: 0,
|
||||
),
|
||||
construct: ConstructIdentifier(
|
||||
lemma: v.lemma,
|
||||
type: ConstructTypeEnum.vocab,
|
||||
category: v.pos,
|
||||
),
|
||||
langCode: widget.langCode,
|
||||
onClose: () {
|
||||
MatrixState.pAnyState.closeOverlay(target);
|
||||
setState(() => _selectedVocab = null);
|
||||
},
|
||||
onDismissNewWordOverlay: () {
|
||||
if (mounted) setState(() {});
|
||||
},
|
||||
),
|
||||
cardToShow: _WordCardWrapper(
|
||||
v: v,
|
||||
langCode: widget.langCode,
|
||||
target: target,
|
||||
onClose: () {
|
||||
if (mounted) {
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) => setState(() => _selectedVocab = null),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
transformTargetId: target,
|
||||
closePrevOverlay: false,
|
||||
|
|
@ -204,3 +194,52 @@ class _VocabChipsState extends State<_VocabChips> with TokenRenderingMixin {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _WordCardWrapper extends StatefulWidget {
|
||||
final Vocab v;
|
||||
final String langCode;
|
||||
final String target;
|
||||
final VoidCallback onClose;
|
||||
|
||||
const _WordCardWrapper({
|
||||
required this.v,
|
||||
required this.langCode,
|
||||
required this.target,
|
||||
required this.onClose,
|
||||
});
|
||||
|
||||
@override
|
||||
State<_WordCardWrapper> createState() => _WordCardWrapperState();
|
||||
}
|
||||
|
||||
class _WordCardWrapperState extends State<_WordCardWrapper> {
|
||||
@override
|
||||
void dispose() {
|
||||
widget.onClose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return WordZoomWidget(
|
||||
token: PangeaTokenText(
|
||||
content: widget.v.lemma,
|
||||
length: widget.v.lemma.characters.length,
|
||||
offset: 0,
|
||||
),
|
||||
construct: ConstructIdentifier(
|
||||
lemma: widget.v.lemma,
|
||||
type: ConstructTypeEnum.vocab,
|
||||
category: widget.v.pos,
|
||||
),
|
||||
langCode: widget.langCode,
|
||||
onClose: () {
|
||||
MatrixState.pAnyState.closeOverlay(widget.target);
|
||||
widget.onClose();
|
||||
},
|
||||
onDismissNewWordOverlay: () {
|
||||
if (mounted) setState(() {});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,7 +97,6 @@ class SpaceAnalyticsSummaryModel {
|
|||
Set<ConstructIdentifier> blockedConstructs,
|
||||
int numCompletedActivities,
|
||||
) {
|
||||
int totalXP = 0;
|
||||
int numWordsTyped = 0;
|
||||
int numChoicesCorrect = 0;
|
||||
int numChoicesIncorrect = 0;
|
||||
|
|
@ -114,7 +113,9 @@ class SpaceAnalyticsSummaryModel {
|
|||
mergeTable.addConstructsByUses(e.content.uses, blockedConstructs);
|
||||
|
||||
for (final use in e.content.uses) {
|
||||
totalXP += use.xp;
|
||||
final id = use.identifier;
|
||||
if (blockedConstructs.contains(id)) continue;
|
||||
|
||||
allUses.add(use);
|
||||
|
||||
if (use.useType.summaryEnumType ==
|
||||
|
|
@ -132,8 +133,7 @@ class SpaceAnalyticsSummaryModel {
|
|||
sentEventIds.add(use.metadata.eventId!);
|
||||
}
|
||||
|
||||
final id = use.identifier;
|
||||
final existing = id.type == ConstructTypeEnum.vocab
|
||||
final existing = use.identifier.type == ConstructTypeEnum.vocab
|
||||
? aggregatedVocab[id]
|
||||
: aggregatedMorph[id];
|
||||
|
||||
|
|
@ -189,6 +189,10 @@ class SpaceAnalyticsSummaryModel {
|
|||
}
|
||||
}
|
||||
|
||||
final totalXP = cleanedVocab.values
|
||||
.fold<int>(0, (sum, entry) => sum + entry.points) +
|
||||
cleanedMorph.values.fold<int>(0, (sum, entry) => sum + entry.points);
|
||||
|
||||
final level = DerivedAnalyticsDataModel.calculateLevelWithXp(totalXP);
|
||||
final uniqueVocabCount = cleanedVocab.length;
|
||||
final uniqueMorphCount = cleanedMorph.length;
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ class SessionLoader extends AsyncLoader<AnalyticsPracticeSessionModel> {
|
|||
}
|
||||
|
||||
class AnalyticsPractice extends StatefulWidget {
|
||||
static bool bypassExitConfirmation = false;
|
||||
static bool bypassExitConfirmation = true;
|
||||
|
||||
final ConstructTypeEnum type;
|
||||
const AnalyticsPractice({
|
||||
|
|
@ -201,18 +201,18 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
|
|||
String choiceTargetId(String choiceId) =>
|
||||
'${widget.type.name}-choice-card-${choiceId.replaceAll(' ', '_')}';
|
||||
|
||||
void _resetActivityState() {
|
||||
void _clearState() {
|
||||
activityState.value = const AsyncState.loading();
|
||||
activityTarget.value = null;
|
||||
selectedMorphChoice.value = null;
|
||||
}
|
||||
|
||||
void _resetSessionState() {
|
||||
enableChoicesNotifier.value = true;
|
||||
progressNotifier.value = 0.0;
|
||||
_queue.clear();
|
||||
_choiceTexts.clear();
|
||||
_choiceEmojis.clear();
|
||||
activityState.value = const AsyncState.idle();
|
||||
|
||||
AnalyticsPractice.bypassExitConfirmation = true;
|
||||
}
|
||||
|
||||
void updateElapsedTime(int seconds) {
|
||||
|
|
@ -239,8 +239,7 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
|
|||
|
||||
Future<void> _onLanguageUpdate() async {
|
||||
try {
|
||||
_resetActivityState();
|
||||
_resetSessionState();
|
||||
_clearState();
|
||||
await _analyticsService
|
||||
.updateDispatcher.constructUpdateStream.stream.first
|
||||
.timeout(const Duration(seconds: 10));
|
||||
|
|
@ -257,14 +256,14 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
|
|||
Future<void> _startSession() async {
|
||||
await _waitForAnalytics();
|
||||
await _sessionLoader.load();
|
||||
if (_sessionLoader.isError) return;
|
||||
|
||||
progressNotifier.value = _sessionLoader.value!.progress;
|
||||
await _continueSession();
|
||||
}
|
||||
|
||||
Future<void> reloadSession() async {
|
||||
_resetActivityState();
|
||||
_resetSessionState();
|
||||
|
||||
_clearState();
|
||||
_sessionLoader.reset();
|
||||
await _startSession();
|
||||
}
|
||||
|
|
@ -324,8 +323,10 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
|
|||
|
||||
final activity = await nextActivityCompleter.completer.future;
|
||||
activityState.value = AsyncState.loaded(activity);
|
||||
AnalyticsPractice.bypassExitConfirmation = false;
|
||||
}
|
||||
} catch (e) {
|
||||
AnalyticsPractice.bypassExitConfirmation = true;
|
||||
activityState.value = AsyncState.error(e);
|
||||
} finally {
|
||||
_continuing = false;
|
||||
|
|
@ -349,7 +350,9 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
|
|||
if (!mounted) return;
|
||||
|
||||
activityState.value = AsyncState.loaded(res);
|
||||
AnalyticsPractice.bypassExitConfirmation = false;
|
||||
} catch (e) {
|
||||
AnalyticsPractice.bypassExitConfirmation = true;
|
||||
if (!mounted) return;
|
||||
activityState.value = AsyncState.error(e);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
|
|||
import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/analytics_practice/analytics_practice_constants.dart';
|
||||
import 'package:fluffychat/pangea/analytics_practice/analytics_practice_session_model.dart';
|
||||
import 'package:fluffychat/pangea/common/network/requests.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
|
||||
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
|
|
@ -15,10 +16,17 @@ import 'package:fluffychat/pangea/practice_activities/message_activity_request.d
|
|||
import 'package:fluffychat/pangea/practice_activities/practice_target.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class InsufficientDataException implements Exception {}
|
||||
|
||||
class AnalyticsPracticeSessionRepo {
|
||||
static Future<AnalyticsPracticeSessionModel> get(
|
||||
ConstructTypeEnum type,
|
||||
) async {
|
||||
if (MatrixState.pangeaController.subscriptionController.isSubscribed ==
|
||||
false) {
|
||||
throw UnsubscribedException();
|
||||
}
|
||||
|
||||
final r = Random();
|
||||
final activityTypes = ActivityTypeEnum.analyticsPracticeTypes(type);
|
||||
|
||||
|
|
@ -67,6 +75,10 @@ class AnalyticsPracticeSessionRepo {
|
|||
}
|
||||
}
|
||||
|
||||
if (targets.isEmpty) {
|
||||
throw InsufficientDataException();
|
||||
}
|
||||
|
||||
final session = AnalyticsPracticeSessionModel(
|
||||
userL1: MatrixState.pangeaController.userController.userL1!.langCode,
|
||||
userL2: MatrixState.pangeaController.userController.userL2!.langCode,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import 'package:fluffychat/pangea/instructions/instructions_inline_tooltip.dart'
|
|||
import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_widget.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/practice_activity_model.dart';
|
||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
@ -84,7 +85,9 @@ class AnalyticsPracticeView extends StatelessWidget {
|
|||
builder: (context, state, __) {
|
||||
return switch (state) {
|
||||
AsyncError<AnalyticsPracticeSessionModel>(:final error) =>
|
||||
ErrorIndicator(message: error.toString()),
|
||||
ErrorIndicator(
|
||||
message: error.toLocalizedString(context),
|
||||
),
|
||||
AsyncLoaded<AnalyticsPracticeSessionModel>(:final value) =>
|
||||
value.isComplete
|
||||
? CompletedActivitySessionView(state.value, controller)
|
||||
|
|
|
|||
|
|
@ -147,6 +147,7 @@ class LearningProgressIndicators extends StatelessWidget {
|
|||
.colorScheme
|
||||
.primary,
|
||||
),
|
||||
textScaler: TextScaler.noScaling,
|
||||
),
|
||||
if (userL1 != null && userL2 != null)
|
||||
const Icon(Icons.chevron_right_outlined),
|
||||
|
|
@ -162,6 +163,7 @@ class LearningProgressIndicators extends StatelessWidget {
|
|||
.colorScheme
|
||||
.primary,
|
||||
),
|
||||
textScaler: TextScaler.noScaling,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ class AnimatedFloatingNumberState extends State<AnimatedFloatingNumber>
|
|||
Text(
|
||||
widget.number.toString(),
|
||||
style: indicatorStyle,
|
||||
textScaler: TextScaler.noScaling,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,194 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/bot/utils/bot_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/models/bot_options_model.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/widgets/language_level_dropdown.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/dropdown_text_button.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_model.dart';
|
||||
import 'package:fluffychat/pangea/languages/p_language_store.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/language_level_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/p_language_dropdown.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class BotChatSettingsDialog extends StatefulWidget {
|
||||
final Room room;
|
||||
|
||||
const BotChatSettingsDialog({
|
||||
required this.room,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
BotChatSettingsDialogState createState() => BotChatSettingsDialogState();
|
||||
}
|
||||
|
||||
class BotChatSettingsDialogState extends State<BotChatSettingsDialog> {
|
||||
LanguageModel? _selectedLang;
|
||||
LanguageLevelTypeEnum? _selectedLevel;
|
||||
String? _selectedVoice;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final botSettings = widget.room.botOptions;
|
||||
final activityPlan = _isActivity ? widget.room.activityPlan : null;
|
||||
|
||||
_selectedLevel = activityPlan?.req.cefrLevel ?? botSettings?.languageLevel;
|
||||
_selectedVoice = botSettings?.targetVoice;
|
||||
final lang =
|
||||
activityPlan?.req.targetLanguage ?? botSettings?.targetLanguage;
|
||||
if (lang != null) {
|
||||
_selectedLang = PLanguageStore.byLangCode(lang);
|
||||
}
|
||||
super.initState();
|
||||
}
|
||||
|
||||
bool get _isActivity => widget.room.isActivitySession;
|
||||
|
||||
Future<void> _setLanguage(LanguageModel? lang) async {
|
||||
setState(() {
|
||||
_selectedLang = lang;
|
||||
_selectedVoice = null;
|
||||
});
|
||||
|
||||
final model = widget.room.botOptions ?? BotOptionsModel();
|
||||
model.targetLanguage = lang?.langCode;
|
||||
model.targetVoice = null;
|
||||
|
||||
try {
|
||||
await widget.room.setBotOptions(model);
|
||||
} catch (e, s) {
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
data: {
|
||||
'roomId': widget.room.id,
|
||||
'langCode': lang?.langCode,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _setLevel(LanguageLevelTypeEnum? level) async {
|
||||
if (level == null) return;
|
||||
|
||||
setState(() => _selectedLevel = level);
|
||||
final model = widget.room.botOptions ?? BotOptionsModel();
|
||||
model.languageLevel = level;
|
||||
try {
|
||||
await widget.room.setBotOptions(model);
|
||||
} catch (e, s) {
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
data: {
|
||||
'roomId': widget.room.id,
|
||||
'level': level.name,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _setVoice(String? voice) async {
|
||||
setState(() => _selectedVoice = voice);
|
||||
final model = widget.room.botOptions ?? BotOptionsModel();
|
||||
model.targetVoice = voice;
|
||||
try {
|
||||
await widget.room.setBotOptions(model);
|
||||
} catch (e, s) {
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
data: {
|
||||
'roomId': widget.room.id,
|
||||
'voice': voice,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Column(
|
||||
spacing: 12.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
if (widget.room.isActivitySession)
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.all(0.0),
|
||||
minLeadingWidth: 12.0,
|
||||
leading: Icon(
|
||||
Icons.info_outline,
|
||||
size: 12.0,
|
||||
color: Theme.of(context).disabledColor,
|
||||
),
|
||||
title: Text(
|
||||
L10n.of(context).activitySettingsOverrideWarning,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).disabledColor,
|
||||
fontSize: 12.0,
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
const SizedBox(),
|
||||
PLanguageDropdown(
|
||||
onChange: _setLanguage,
|
||||
initialLanguage: _selectedLang,
|
||||
languages:
|
||||
MatrixState.pangeaController.pLanguageStore.targetOptions,
|
||||
isL2List: true,
|
||||
decorationText: L10n.of(context).targetLanguage,
|
||||
enabled: !widget.room.isActivitySession,
|
||||
),
|
||||
LanguageLevelDropdown(
|
||||
initialLevel: _selectedLevel,
|
||||
onChanged: _setLevel,
|
||||
enabled: !widget.room.isActivitySession,
|
||||
width: 300,
|
||||
maxHeight: 300,
|
||||
),
|
||||
DropdownButtonFormField2<String>(
|
||||
customButton: _selectedVoice != null
|
||||
? CustomDropdownTextButton(text: _selectedVoice!)
|
||||
: null,
|
||||
menuItemStyleData: const MenuItemStyleData(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 8.0,
|
||||
horizontal: 16.0,
|
||||
),
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
labelText: L10n.of(context).voice,
|
||||
),
|
||||
isExpanded: true,
|
||||
dropdownStyleData: DropdownStyleData(
|
||||
maxHeight: 250,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(14.0),
|
||||
),
|
||||
),
|
||||
items: (_selectedLang?.voices ?? <String>[]).map((voice) {
|
||||
return DropdownMenuItem(
|
||||
value: voice,
|
||||
child: Text(voice),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: _setVoice,
|
||||
value: _selectedVoice,
|
||||
),
|
||||
const SizedBox(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/bot/utils/bot_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/widgets/member_actions_popup_menu_button.dart';
|
||||
|
||||
class BotSettingsLanguageIcon extends StatelessWidget {
|
||||
final User user;
|
||||
|
||||
const BotSettingsLanguageIcon({
|
||||
super.key,
|
||||
required this.user,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final room = user.room;
|
||||
String? langCode = room.botOptions?.targetLanguage;
|
||||
if (room.isActivitySession && room.activityPlan != null) {
|
||||
langCode = room.activityPlan!.req.targetLanguage;
|
||||
}
|
||||
if (langCode == null) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
langCode = langCode.split('-').first;
|
||||
return InkWell(
|
||||
borderRadius: BorderRadius.circular(32.0),
|
||||
onTap: room.isRoomAdmin
|
||||
? () => showMemberActionsPopupMenu(
|
||||
context: context,
|
||||
user: user,
|
||||
room: room,
|
||||
)
|
||||
: null,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16.0),
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
child: Text(
|
||||
langCode,
|
||||
style: TextStyle(
|
||||
fontSize: 10.0,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -8,23 +8,23 @@ import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
|||
import 'package:fluffychat/pangea/learning_settings/language_level_type_enum.dart';
|
||||
|
||||
class BotOptionsModel {
|
||||
LanguageLevelTypeEnum languageLevel;
|
||||
String topic;
|
||||
List<String> keywords;
|
||||
bool safetyModeration;
|
||||
String mode;
|
||||
String? discussionTopic;
|
||||
String? discussionKeywords;
|
||||
bool? discussionTriggerReactionEnabled;
|
||||
String? discussionTriggerReactionKey;
|
||||
String? customSystemPrompt;
|
||||
bool? customTriggerReactionEnabled;
|
||||
String? customTriggerReactionKey;
|
||||
String? textAdventureGameMasterInstructions;
|
||||
String? targetLanguage;
|
||||
String? targetVoice;
|
||||
final LanguageLevelTypeEnum languageLevel;
|
||||
final String topic;
|
||||
final List<String> keywords;
|
||||
final bool safetyModeration;
|
||||
final String mode;
|
||||
final String? discussionTopic;
|
||||
final String? discussionKeywords;
|
||||
final bool? discussionTriggerReactionEnabled;
|
||||
final String? discussionTriggerReactionKey;
|
||||
final String? customSystemPrompt;
|
||||
final bool? customTriggerReactionEnabled;
|
||||
final String? customTriggerReactionKey;
|
||||
final String? textAdventureGameMasterInstructions;
|
||||
final String? targetLanguage;
|
||||
final String? targetVoice;
|
||||
|
||||
BotOptionsModel({
|
||||
const BotOptionsModel({
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// General Bot Options
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -133,50 +133,45 @@ class BotOptionsModel {
|
|||
}
|
||||
}
|
||||
|
||||
//TODO: define enum with all possible values
|
||||
updateBotOption(String key, dynamic value) {
|
||||
switch (key) {
|
||||
case ModelKey.languageLevel:
|
||||
languageLevel = value;
|
||||
break;
|
||||
case ModelKey.safetyModeration:
|
||||
safetyModeration = value;
|
||||
break;
|
||||
case ModelKey.mode:
|
||||
mode = value;
|
||||
break;
|
||||
case ModelKey.discussionTopic:
|
||||
discussionTopic = value;
|
||||
break;
|
||||
case ModelKey.discussionKeywords:
|
||||
discussionKeywords = value;
|
||||
break;
|
||||
case ModelKey.discussionTriggerReactionEnabled:
|
||||
discussionTriggerReactionEnabled = value;
|
||||
break;
|
||||
case ModelKey.discussionTriggerReactionKey:
|
||||
discussionTriggerReactionKey = value;
|
||||
break;
|
||||
case ModelKey.customSystemPrompt:
|
||||
customSystemPrompt = value;
|
||||
break;
|
||||
case ModelKey.customTriggerReactionEnabled:
|
||||
customTriggerReactionEnabled = value;
|
||||
break;
|
||||
case ModelKey.customTriggerReactionKey:
|
||||
customTriggerReactionKey = value;
|
||||
break;
|
||||
case ModelKey.textAdventureGameMasterInstructions:
|
||||
textAdventureGameMasterInstructions = value;
|
||||
break;
|
||||
case ModelKey.targetLanguage:
|
||||
targetLanguage = value;
|
||||
break;
|
||||
case ModelKey.targetVoice:
|
||||
targetVoice = value;
|
||||
break;
|
||||
default:
|
||||
throw Exception('Invalid key for bot options - $key');
|
||||
}
|
||||
BotOptionsModel copyWith({
|
||||
LanguageLevelTypeEnum? languageLevel,
|
||||
String? topic,
|
||||
List<String>? keywords,
|
||||
bool? safetyModeration,
|
||||
String? mode,
|
||||
String? discussionTopic,
|
||||
String? discussionKeywords,
|
||||
bool? discussionTriggerReactionEnabled,
|
||||
String? discussionTriggerReactionKey,
|
||||
String? customSystemPrompt,
|
||||
bool? customTriggerReactionEnabled,
|
||||
String? customTriggerReactionKey,
|
||||
String? textAdventureGameMasterInstructions,
|
||||
String? targetLanguage,
|
||||
String? targetVoice,
|
||||
}) {
|
||||
return BotOptionsModel(
|
||||
languageLevel: languageLevel ?? this.languageLevel,
|
||||
topic: topic ?? this.topic,
|
||||
keywords: keywords ?? this.keywords,
|
||||
safetyModeration: safetyModeration ?? this.safetyModeration,
|
||||
mode: mode ?? this.mode,
|
||||
discussionTopic: discussionTopic ?? this.discussionTopic,
|
||||
discussionKeywords: discussionKeywords ?? this.discussionKeywords,
|
||||
discussionTriggerReactionEnabled: discussionTriggerReactionEnabled ??
|
||||
this.discussionTriggerReactionEnabled,
|
||||
discussionTriggerReactionKey:
|
||||
discussionTriggerReactionKey ?? this.discussionTriggerReactionKey,
|
||||
customSystemPrompt: customSystemPrompt ?? this.customSystemPrompt,
|
||||
customTriggerReactionEnabled:
|
||||
customTriggerReactionEnabled ?? this.customTriggerReactionEnabled,
|
||||
customTriggerReactionKey:
|
||||
customTriggerReactionKey ?? this.customTriggerReactionKey,
|
||||
textAdventureGameMasterInstructions:
|
||||
textAdventureGameMasterInstructions ??
|
||||
this.textAdventureGameMasterInstructions,
|
||||
targetLanguage: targetLanguage ?? this.targetLanguage,
|
||||
targetVoice: targetVoice ?? this.targetVoice,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,23 +7,19 @@ import 'package:fluffychat/pangea/chat/constants/default_power_level.dart';
|
|||
import 'package:fluffychat/pangea/chat_settings/constants/bot_mode.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/models/bot_options_model.dart';
|
||||
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
|
||||
import 'package:fluffychat/pangea/user/user_model.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
extension BotClientExtension on Client {
|
||||
bool get hasBotDM => rooms.any((r) => r.isBotDM);
|
||||
Room? get botDM => rooms.firstWhereOrNull((r) => r.isBotDM);
|
||||
|
||||
Room? get botDM => rooms.firstWhereOrNull(
|
||||
(room) {
|
||||
if (room.isDirectChat &&
|
||||
room.directChatMatrixID == BotName.byEnvironment) {
|
||||
return true;
|
||||
}
|
||||
if (room.botOptions?.mode == BotMode.directChat) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
);
|
||||
// All 2-member rooms with the bot
|
||||
List<Room> get targetBotChats => rooms.where((r) {
|
||||
if (r.isBotDM) return true;
|
||||
if (r.summary.mJoinedMemberCount != 2) return false;
|
||||
return r.getParticipants().any((u) => u.id == BotName.byEnvironment);
|
||||
}).toList();
|
||||
|
||||
Future<String> startChatWithBot() => startDirectChat(
|
||||
BotName.byEnvironment,
|
||||
|
|
@ -45,27 +41,31 @@ extension BotClientExtension on Client {
|
|||
],
|
||||
);
|
||||
|
||||
Future<void> updateBotOptions() async {
|
||||
if (!isLogged() || botDM == null) return;
|
||||
Future<void> updateBotOptions(UserSettings userSettings) async {
|
||||
final rooms = targetBotChats;
|
||||
if (rooms.isEmpty) return;
|
||||
|
||||
final targetLanguage =
|
||||
MatrixState.pangeaController.userController.userL2?.langCode;
|
||||
final cefrLevel = MatrixState
|
||||
.pangeaController.userController.profile.userSettings.cefrLevel;
|
||||
final updateBotOptions = botDM!.botOptions ?? BotOptionsModel();
|
||||
final futures = <Future>[];
|
||||
for (final room in rooms) {
|
||||
final botOptions = room.botOptions ?? const BotOptionsModel();
|
||||
final targetLanguage = userSettings.targetLanguage;
|
||||
final languageLevel = userSettings.cefrLevel;
|
||||
final voice = userSettings.voice;
|
||||
|
||||
if (updateBotOptions.targetLanguage == targetLanguage &&
|
||||
updateBotOptions.languageLevel == cefrLevel) {
|
||||
return;
|
||||
if (botOptions.targetLanguage == targetLanguage &&
|
||||
botOptions.languageLevel == languageLevel &&
|
||||
botOptions.targetVoice == voice) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final updated = botOptions.copyWith(
|
||||
targetLanguage: targetLanguage,
|
||||
languageLevel: languageLevel,
|
||||
targetVoice: voice,
|
||||
);
|
||||
futures.add(room.setBotOptions(updated));
|
||||
}
|
||||
|
||||
if (targetLanguage != null &&
|
||||
updateBotOptions.targetLanguage != targetLanguage) {
|
||||
updateBotOptions.targetVoice = null;
|
||||
}
|
||||
|
||||
updateBotOptions.targetLanguage = targetLanguage;
|
||||
updateBotOptions.languageLevel = cefrLevel;
|
||||
await botDM!.setBotOptions(updateBotOptions);
|
||||
await Future.wait(futures);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,6 +109,7 @@ class ModelKey {
|
|||
|
||||
static const String transcription = "transcription";
|
||||
static const String botTranscription = 'bot_transcription';
|
||||
static const String voice = "voice";
|
||||
|
||||
// bot options
|
||||
static const String languageLevel = "difficulty";
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import 'package:fluffychat/pangea/languages/p_language_store.dart';
|
|||
import 'package:fluffychat/pangea/subscription/controllers/subscription_controller.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart';
|
||||
import 'package:fluffychat/pangea/user/pangea_push_rules_extension.dart';
|
||||
import 'package:fluffychat/pangea/user/style_settings_repo.dart';
|
||||
import 'package:fluffychat/pangea/user/user_controller.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import '../utils/firebase_analytics.dart';
|
||||
|
|
@ -55,7 +56,7 @@ class PangeaController {
|
|||
TtsController.setAvailableLanguages();
|
||||
}
|
||||
|
||||
void _onLogin(BuildContext context) {
|
||||
void _onLogin(BuildContext context, String? userID) {
|
||||
initControllers();
|
||||
_registerSubscriptions();
|
||||
|
||||
|
|
@ -64,6 +65,10 @@ class PangeaController {
|
|||
Provider.of<LocaleProvider>(context, listen: false).setLocale(l1);
|
||||
});
|
||||
subscriptionController.reinitialize();
|
||||
|
||||
StyleSettingsRepo.fontSizeFactor(userID!).then((factor) {
|
||||
AppConfig.fontSizeFactor = factor;
|
||||
});
|
||||
}
|
||||
|
||||
void _onLogout(BuildContext context) {
|
||||
|
|
@ -91,7 +96,7 @@ class PangeaController {
|
|||
_onLogout(context);
|
||||
break;
|
||||
case LoginState.loggedIn:
|
||||
_onLogin(context);
|
||||
_onLogin(context, userID);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -112,8 +117,9 @@ class PangeaController {
|
|||
userController.languageStream.stream.listen(_onLanguageUpdate);
|
||||
|
||||
_settingsSubscription?.cancel();
|
||||
_settingsSubscription = userController.settingsUpdateStream.stream
|
||||
.listen((_) => matrixState.client.updateBotOptions());
|
||||
_settingsSubscription = userController.settingsUpdateStream.stream.listen(
|
||||
(update) => matrixState.client.updateBotOptions(update.userSettings),
|
||||
);
|
||||
|
||||
_joinSpaceSubscription?.cancel();
|
||||
_joinSpaceSubscription ??= matrixState.client.onSync.stream
|
||||
|
|
@ -122,9 +128,7 @@ class PangeaController {
|
|||
}
|
||||
|
||||
Future<void> _clearCache({List<String> exclude = const []}) async {
|
||||
final List<Future<void>> futures = [
|
||||
matrixState.store.setString(SettingKeys.fontSizeFactor, ''),
|
||||
];
|
||||
final List<Future<void>> futures = [];
|
||||
for (final key in _storageKeys) {
|
||||
if (exclude.contains(key)) continue;
|
||||
futures.add(GetStorage(key).erase());
|
||||
|
|
@ -142,7 +146,6 @@ class PangeaController {
|
|||
);
|
||||
}
|
||||
|
||||
AppConfig.fontSizeFactor = 1.0;
|
||||
await Future.wait(futures);
|
||||
}
|
||||
|
||||
|
|
@ -174,7 +177,7 @@ class PangeaController {
|
|||
}
|
||||
|
||||
_clearCache(exclude: exclude);
|
||||
matrixState.client.updateBotOptions();
|
||||
matrixState.client.updateBotOptions(userController.profile.userSettings);
|
||||
}
|
||||
|
||||
static final List<String> _storageKeys = [
|
||||
|
|
|
|||
|
|
@ -57,7 +57,9 @@ class CourseChatsController extends State<CourseChats>
|
|||
|
||||
@override
|
||||
void initState() {
|
||||
loadHierarchy(reload: true);
|
||||
loadHierarchy(reload: true).then(
|
||||
(_) => _joinDefaultChats(),
|
||||
);
|
||||
|
||||
// Listen for changes to the activeSpace's hierarchy,
|
||||
// and reload the hierarchy when they come through
|
||||
|
|
@ -212,7 +214,6 @@ class CourseChatsController extends State<CourseChats>
|
|||
|
||||
try {
|
||||
await _loadHierarchy(activeSpace: room, reload: reload);
|
||||
if (mounted) await _joinDefaultChats();
|
||||
if (mounted) {
|
||||
final futures = [
|
||||
loadRoomSummaries(
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ class SelectedCourseController extends State<SelectedCourse>
|
|||
await showOkAlertDialog(
|
||||
context: context,
|
||||
title: L10n.of(context).youHaveKnocked,
|
||||
message: L10n.of(context).pleaseWaitUntilInvited,
|
||||
message: L10n.of(context).knockDesc,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -234,40 +234,43 @@ class SelectedCourseView extends StatelessWidget {
|
|||
spacing: 8.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
spacing: 12.0,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.edit,
|
||||
size: mediumIconSize,
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
L10n.of(context).editCourseLater,
|
||||
style: const TextStyle(
|
||||
fontSize: descFontSize,
|
||||
if (controller.widget.mode !=
|
||||
SelectedCourseMode.join) ...[
|
||||
Row(
|
||||
spacing: 12.0,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.edit,
|
||||
size: mediumIconSize,
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
L10n.of(context).editCourseLater,
|
||||
style: const TextStyle(
|
||||
fontSize: descFontSize,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
spacing: 12.0,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.shield,
|
||||
size: mediumIconSize,
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
L10n.of(context).newCourseAccess,
|
||||
style: const TextStyle(
|
||||
fontSize: descFontSize,
|
||||
],
|
||||
),
|
||||
Row(
|
||||
spacing: 12.0,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.shield,
|
||||
size: mediumIconSize,
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
L10n.of(context).newCourseAccess,
|
||||
style: const TextStyle(
|
||||
fontSize: descFontSize,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: ElevatedButton(
|
||||
|
|
|
|||
|
|
@ -294,16 +294,18 @@ class PangeaMessageEvent {
|
|||
RepresentationEvent? representationByLanguage(
|
||||
String langCode, {
|
||||
bool Function(RepresentationEvent)? filter,
|
||||
}) {
|
||||
representations.firstWhereOrNull(
|
||||
(element) =>
|
||||
element.langCode.split("-")[0] == langCode.split("-")[0] &&
|
||||
(filter?.call(element) ?? true),
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}) =>
|
||||
representations.firstWhereOrNull(
|
||||
(element) =>
|
||||
element.langCode.split("-")[0] == langCode.split("-")[0] &&
|
||||
(filter?.call(element) ?? true),
|
||||
);
|
||||
|
||||
Event? getTextToSpeechLocal(String langCode, String text) {
|
||||
Event? getTextToSpeechLocal(
|
||||
String langCode,
|
||||
String text,
|
||||
String? voice,
|
||||
) {
|
||||
for (final audio in allAudio) {
|
||||
final dataMap = audio.content.tryGetMap(ModelKey.transcription);
|
||||
if (dataMap == null || !dataMap.containsKey(ModelKey.tokens)) continue;
|
||||
|
|
@ -313,7 +315,9 @@ class PangeaMessageEvent {
|
|||
dataMap as dynamic,
|
||||
);
|
||||
|
||||
if (audioData.langCode == langCode && audioData.text == text) {
|
||||
if (audioData.langCode == langCode &&
|
||||
audioData.text == text &&
|
||||
audioData.voice == voice) {
|
||||
return audio;
|
||||
}
|
||||
} catch (e, s) {
|
||||
|
|
@ -368,7 +372,7 @@ class PangeaMessageEvent {
|
|||
String langCode,
|
||||
String? voice,
|
||||
) async {
|
||||
final local = getTextToSpeechLocal(langCode, messageDisplayText);
|
||||
final local = getTextToSpeechLocal(langCode, messageDisplayText, voice);
|
||||
if (local != null) {
|
||||
final file = await local.getPangeaAudioFile();
|
||||
if (file != null) return file;
|
||||
|
|
@ -424,7 +428,7 @@ class PangeaMessageEvent {
|
|||
'waveform': response.waveform,
|
||||
},
|
||||
ModelKey.transcription: response
|
||||
.toPangeaAudioEventData(rep?.text ?? body, langCode)
|
||||
.toPangeaAudioEventData(rep?.text ?? body, langCode, voice)
|
||||
.toJson(),
|
||||
},
|
||||
).then((eventId) async {
|
||||
|
|
|
|||
|
|
@ -44,7 +44,6 @@ class PSettingsSwitchListTileState
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SwitchListTile.adaptive(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
value: currentValue,
|
||||
title: Text(widget.title),
|
||||
activeThumbColor: AppConfig.activeToggleColor,
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ class SettingsLearningController extends State<SettingsLearning> {
|
|||
PangeaController pangeaController = MatrixState.pangeaController;
|
||||
late Profile _profile;
|
||||
|
||||
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
|
||||
String? languageMatchError;
|
||||
|
||||
final ScrollController scrollController = ScrollController();
|
||||
|
|
@ -110,18 +109,16 @@ class SettingsLearningController extends State<SettingsLearning> {
|
|||
updateToolSetting(ToolSetting.enableTTS, false);
|
||||
}
|
||||
|
||||
if (formKey.currentState!.validate()) {
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () async => pangeaController.userController
|
||||
.updateProfile(
|
||||
(_) => _profile,
|
||||
waitForDataInSync: true,
|
||||
)
|
||||
.timeout(const Duration(seconds: 15)),
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () async => pangeaController.userController
|
||||
.updateProfile(
|
||||
(_) => _profile,
|
||||
waitForDataInSync: true,
|
||||
)
|
||||
.timeout(const Duration(seconds: 15)),
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
Future<void> resetInstructionTooltips() async {
|
||||
|
|
@ -153,11 +150,12 @@ class SettingsLearningController extends State<SettingsLearning> {
|
|||
LanguageModel? sourceLanguage,
|
||||
LanguageModel? targetLanguage,
|
||||
}) async {
|
||||
if (sourceLanguage != null) {
|
||||
if (sourceLanguage != null && sourceLanguage != selectedSourceLanguage) {
|
||||
_profile.userSettings.sourceLanguage = sourceLanguage.langCode;
|
||||
}
|
||||
if (targetLanguage != null) {
|
||||
if (targetLanguage != null && targetLanguage != selectedTargetLanguage) {
|
||||
_profile.userSettings.targetLanguage = targetLanguage.langCode;
|
||||
_profile.userSettings.voice = null;
|
||||
if (!_profile.toolSettings.enableTTS && isTTSSupported) {
|
||||
updateToolSetting(ToolSetting.enableTTS, true);
|
||||
}
|
||||
|
|
@ -181,6 +179,11 @@ class SettingsLearningController extends State<SettingsLearning> {
|
|||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
void setVoice(String? voice) {
|
||||
_profile.userSettings.voice = voice;
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
void changeCountry(Country? country) {
|
||||
_profile.userSettings.country = country?.name;
|
||||
if (mounted) setState(() {});
|
||||
|
|
@ -343,6 +346,8 @@ class SettingsLearningController extends State<SettingsLearning> {
|
|||
|
||||
LanguageLevelTypeEnum get cefrLevel => _profile.userSettings.cefrLevel;
|
||||
|
||||
String? get selectedVoice => _profile.userSettings.voice;
|
||||
|
||||
Country? get country =>
|
||||
CountryService().findByName(_profile.userSettings.country);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/widgets/language_level_dropdown.dart';
|
||||
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
|
||||
|
|
@ -14,6 +13,7 @@ import 'package:fluffychat/pangea/learning_settings/p_language_dropdown.dart';
|
|||
import 'package:fluffychat/pangea/learning_settings/p_settings_switch_list_tile.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/settings_learning.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/tool_settings_enum.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/voice_dropdown.dart';
|
||||
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
|
|
@ -45,21 +45,23 @@ class SettingsLearningView extends StatelessWidget {
|
|||
)
|
||||
: null,
|
||||
),
|
||||
body: Form(
|
||||
key: controller.formKey,
|
||||
child: ListTileTheme(
|
||||
iconColor: Theme.of(context).textTheme.bodyLarge!.color,
|
||||
child: MaxWidthBody(
|
||||
withScrolling: false,
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
controller: controller.scrollController,
|
||||
body: ListTileTheme(
|
||||
iconColor: Theme.of(context).textTheme.bodyLarge!.color,
|
||||
child: MaxWidthBody(
|
||||
withScrolling: false,
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
controller: controller.scrollController,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 32.0),
|
||||
child: Column(
|
||||
spacing: 16.0,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Column(
|
||||
spacing: 16.0,
|
||||
children: [
|
||||
|
|
@ -99,171 +101,112 @@ class SettingsLearningView extends StatelessWidget {
|
|||
.colorScheme
|
||||
.surfaceContainerHigh,
|
||||
),
|
||||
AnimatedSize(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
child: controller.userL1?.langCodeShort ==
|
||||
controller.userL2?.langCodeShort
|
||||
? Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
if (controller.userL1?.langCodeShort ==
|
||||
controller.userL2?.langCodeShort)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
),
|
||||
child: Row(
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.info_outlined,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.error,
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
L10n.of(context)
|
||||
.noIdenticalLanguages,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.error,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.info_outlined,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.error,
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
L10n.of(context)
|
||||
.noIdenticalLanguages,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.error,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
CountryPickerDropdown(controller),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
LanguageLevelDropdown(
|
||||
initialLevel: controller.cefrLevel,
|
||||
onChanged: controller.setCefrLevel,
|
||||
),
|
||||
VoiceDropdown(
|
||||
value: controller.selectedVoice,
|
||||
language: controller.selectedTargetLanguage,
|
||||
onChanged: controller.setVoice,
|
||||
),
|
||||
CountryPickerDropdown(controller),
|
||||
GenderDropdown(
|
||||
initialGender: controller.gender,
|
||||
onChanged: controller.setGender,
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Colors.white54,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
children: [
|
||||
ProfileSettingsSwitchListTile.adaptive(
|
||||
defaultValue:
|
||||
controller.getToolSetting(
|
||||
ToolSetting.autoIGC,
|
||||
),
|
||||
title: ToolSetting.autoIGC
|
||||
.toolName(context),
|
||||
subtitle: ToolSetting.autoIGC
|
||||
.toolDescription(context),
|
||||
onChange: (bool value) =>
|
||||
controller.updateToolSetting(
|
||||
ToolSetting.autoIGC,
|
||||
value,
|
||||
),
|
||||
enabled: true,
|
||||
),
|
||||
ProfileSettingsSwitchListTile.adaptive(
|
||||
defaultValue:
|
||||
controller.getToolSetting(
|
||||
ToolSetting.enableAutocorrect,
|
||||
),
|
||||
title: ToolSetting.enableAutocorrect
|
||||
.toolName(context),
|
||||
subtitle: ToolSetting
|
||||
.enableAutocorrect
|
||||
.toolDescription(context),
|
||||
onChange: (bool value) {
|
||||
controller.updateToolSetting(
|
||||
ToolSetting.enableAutocorrect,
|
||||
value,
|
||||
);
|
||||
if (value) {
|
||||
controller
|
||||
.showKeyboardSettingsDialog();
|
||||
}
|
||||
},
|
||||
enabled: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
for (final toolSetting
|
||||
in ToolSetting.values.where(
|
||||
(tool) =>
|
||||
tool.isAvailableSetting &&
|
||||
tool != ToolSetting.autoIGC &&
|
||||
tool != ToolSetting.enableAutocorrect,
|
||||
))
|
||||
Column(
|
||||
children: [
|
||||
ProfileSettingsSwitchListTile.adaptive(
|
||||
defaultValue: controller
|
||||
.getToolSetting(toolSetting),
|
||||
title: toolSetting.toolName(context),
|
||||
subtitle: toolSetting ==
|
||||
ToolSetting.enableTTS &&
|
||||
!controller.isTTSSupported
|
||||
? null
|
||||
: toolSetting
|
||||
.toolDescription(context),
|
||||
onChange: (bool value) =>
|
||||
controller.updateToolSetting(
|
||||
toolSetting,
|
||||
value,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SwitchListTile.adaptive(
|
||||
value: controller.publicProfile,
|
||||
onChanged: controller.setPublicProfile,
|
||||
title: Text(
|
||||
L10n.of(context).publicProfileTitle,
|
||||
),
|
||||
subtitle: Text(
|
||||
L10n.of(context).publicProfileDesc,
|
||||
),
|
||||
activeThumbColor:
|
||||
AppConfig.activeToggleColor,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
ResetInstructionsListTile(
|
||||
controller: controller,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
...ToolSetting.values
|
||||
.where(
|
||||
(tool) => tool.isAvailableSetting,
|
||||
)
|
||||
.map(
|
||||
(toolSetting) => _ProfileSwitchTile(
|
||||
value:
|
||||
controller.getToolSetting(toolSetting),
|
||||
setting: toolSetting,
|
||||
onChanged: (v) {
|
||||
controller.updateToolSetting(
|
||||
toolSetting,
|
||||
v,
|
||||
);
|
||||
if (v &&
|
||||
toolSetting ==
|
||||
ToolSetting.enableTTS) {
|
||||
controller.showKeyboardSettingsDialog();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
SwitchListTile.adaptive(
|
||||
value: controller.publicProfile,
|
||||
onChanged: controller.setPublicProfile,
|
||||
title: Text(
|
||||
L10n.of(context).publicProfileTitle,
|
||||
),
|
||||
subtitle: Text(
|
||||
L10n.of(context).publicProfileDesc,
|
||||
),
|
||||
activeThumbColor: AppConfig.activeToggleColor,
|
||||
),
|
||||
ResetInstructionsListTile(
|
||||
controller: controller,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: controller.haveSettingsBeenChanged
|
||||
? controller.submit
|
||||
: null,
|
||||
child: Text(L10n.of(context).saveChanges),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: controller.haveSettingsBeenChanged
|
||||
? controller.submit
|
||||
: null,
|
||||
child: Text(L10n.of(context).saveChanges),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (!controller.widget.isDialog) return dialogContent;
|
||||
|
||||
return FullWidthDialog(
|
||||
dialogContent: dialogContent,
|
||||
maxWidth: 600,
|
||||
|
|
@ -273,3 +216,25 @@ class SettingsLearningView extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ProfileSwitchTile extends StatelessWidget {
|
||||
final bool value;
|
||||
final ToolSetting setting;
|
||||
final Function(bool) onChanged;
|
||||
|
||||
const _ProfileSwitchTile({
|
||||
required this.value,
|
||||
required this.setting,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ProfileSettingsSwitchListTile.adaptive(
|
||||
defaultValue: value,
|
||||
title: setting.toolName(context),
|
||||
subtitle: setting.toolDescription(context),
|
||||
onChange: onChanged,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
53
lib/pangea/learning_settings/voice_dropdown.dart
Normal file
53
lib/pangea/learning_settings/voice_dropdown.dart
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/dropdown_text_button.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_model.dart';
|
||||
|
||||
class VoiceDropdown extends StatelessWidget {
|
||||
final String? value;
|
||||
final LanguageModel? language;
|
||||
final Function(String?) onChanged;
|
||||
|
||||
const VoiceDropdown({
|
||||
super.key,
|
||||
this.value,
|
||||
this.language,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DropdownButtonFormField2<String>(
|
||||
customButton:
|
||||
value != null ? CustomDropdownTextButton(text: value!) : null,
|
||||
menuItemStyleData: const MenuItemStyleData(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 8.0,
|
||||
horizontal: 16.0,
|
||||
),
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
labelText: L10n.of(context).voiceDropdownTitle,
|
||||
),
|
||||
isExpanded: true,
|
||||
dropdownStyleData: DropdownStyleData(
|
||||
maxHeight: 250,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(14.0),
|
||||
),
|
||||
),
|
||||
items: (language?.voices ?? <String>[]).map((voice) {
|
||||
return DropdownMenuItem(
|
||||
value: voice,
|
||||
child: Text(voice),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: onChanged,
|
||||
value: value,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,6 @@ import 'package:fluffychat/l10n/l10n.dart';
|
|||
import 'package:fluffychat/pangea/analytics_misc/client_analytics_extension.dart';
|
||||
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
|
||||
import 'package:fluffychat/pangea/space_analytics/space_analytics_requested_dialog.dart';
|
||||
import 'package:fluffychat/utils/stream_extension.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
|
||||
class AnalyticsRequestIndicator extends StatefulWidget {
|
||||
|
|
@ -60,18 +59,38 @@ class AnalyticsRequestIndicatorState extends State<AnalyticsRequestIndicator> {
|
|||
);
|
||||
await Future.wait(futures);
|
||||
|
||||
final analyicsRoomIds = analyticsRooms.map((r) => r.id).toSet();
|
||||
final analyticsRoomIds = analyticsRooms.map((r) => r.id).toSet();
|
||||
_analyticsRoomSub?.cancel();
|
||||
_analyticsRoomSub = widget.room.client.onRoomState.stream
|
||||
.where(
|
||||
(event) =>
|
||||
analyicsRoomIds.contains(event.roomId) &&
|
||||
event.state.type == EventTypes.RoomMember,
|
||||
)
|
||||
.rateLimit(const Duration(seconds: 1))
|
||||
.listen((_) => setState(() {}));
|
||||
_analyticsRoomSub = widget.room.client.onSync.stream.listen((update) async {
|
||||
final joined = update.rooms?.join?.entries
|
||||
.where((e) => analyticsRoomIds.contains(e.key));
|
||||
|
||||
if (mounted) setState(() {});
|
||||
if (joined == null || joined.isEmpty) return;
|
||||
final Set<String> updatedRoomIds = {};
|
||||
for (final entry in joined) {
|
||||
final memberEvents = entry.value.timeline?.events?.where(
|
||||
(e) => e.type == EventTypes.RoomMember,
|
||||
);
|
||||
if (memberEvents != null && memberEvents.isNotEmpty) {
|
||||
updatedRoomIds.add(entry.key);
|
||||
}
|
||||
}
|
||||
|
||||
if (updatedRoomIds.isEmpty) return;
|
||||
for (final roomId in updatedRoomIds) {
|
||||
final room = widget.room.client.getRoomById(roomId);
|
||||
if (room == null) continue;
|
||||
await room.requestParticipants(
|
||||
[Membership.join, Membership.invite, Membership.knock],
|
||||
false,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Map<User, List<Room>> get _knockingAdmins {
|
||||
|
|
|
|||
|
|
@ -94,4 +94,8 @@ class AnalyticsRequestsRepo {
|
|||
final key = _storageKey(userId, language);
|
||||
await _requestStorage.remove(key);
|
||||
}
|
||||
|
||||
static Future<void> clear() async {
|
||||
await _requestStorage.erase();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -189,6 +189,7 @@ class SpaceAnalyticsState extends State<SpaceAnalytics> {
|
|||
|
||||
Future<void> refresh() async {
|
||||
if (room == null || !room!.isSpace || selectedLanguage == null) return;
|
||||
await AnalyticsRequestsRepo.clear();
|
||||
|
||||
setState(() {
|
||||
downloads = Map.fromEntries(
|
||||
|
|
|
|||
|
|
@ -41,11 +41,16 @@ class TextToSpeechResponseModel {
|
|||
"tts_tokens": List<dynamic>.from(ttsTokens.map((x) => x.toJson())),
|
||||
};
|
||||
|
||||
PangeaAudioEventData toPangeaAudioEventData(String text, String langCode) {
|
||||
PangeaAudioEventData toPangeaAudioEventData(
|
||||
String text,
|
||||
String langCode,
|
||||
String? voice,
|
||||
) {
|
||||
return PangeaAudioEventData(
|
||||
text: text,
|
||||
langCode: langCode,
|
||||
tokens: ttsTokens,
|
||||
voice: voice,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -91,11 +96,13 @@ class PangeaAudioEventData {
|
|||
final String text;
|
||||
final String langCode;
|
||||
final List<TTSToken> tokens;
|
||||
final String? voice;
|
||||
|
||||
PangeaAudioEventData({
|
||||
required this.text,
|
||||
required this.langCode,
|
||||
required this.tokens,
|
||||
this.voice,
|
||||
});
|
||||
|
||||
factory PangeaAudioEventData.fromJson(dynamic json) => PangeaAudioEventData(
|
||||
|
|
@ -106,6 +113,7 @@ class PangeaAudioEventData {
|
|||
.map((x) => TTSToken.fromJson(x))
|
||||
.toList(),
|
||||
),
|
||||
voice: json[ModelKey.voice] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
|
|
@ -113,5 +121,6 @@ class PangeaAudioEventData {
|
|||
ModelKey.langCode: langCode,
|
||||
ModelKey.tokens:
|
||||
List<Map<String, dynamic>>.from(tokens.map((x) => x.toJson())),
|
||||
if (voice != null) ModelKey.voice: voice,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,9 +14,6 @@ import 'package:fluffychat/pangea/common/utils/async_state.dart';
|
|||
import 'package:fluffychat/pangea/common/widgets/error_indicator.dart';
|
||||
import 'package:fluffychat/pangea/events/extensions/pangea_event_extension.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_model.dart';
|
||||
import 'package:fluffychat/pangea/languages/p_language_store.dart';
|
||||
import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_widget.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/layout/reading_assistance_mode_enum.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/message_selection_overlay.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/reading_assistance/select_mode_buttons.dart';
|
||||
|
|
@ -500,19 +497,19 @@ class _MessageBubbleTranscription extends StatelessWidget {
|
|||
onClick: onTokenSelected,
|
||||
isSelected: isTokenSelected,
|
||||
),
|
||||
if (MatrixState
|
||||
.pangeaController.userController.showTranscription)
|
||||
PhoneticTranscriptionWidget(
|
||||
text: transcription.transcript.text,
|
||||
textLanguage: PLanguageStore.byLangCode(
|
||||
transcription.langCode,
|
||||
) ??
|
||||
LanguageModel.unknown,
|
||||
style: style,
|
||||
iconColor: style.color,
|
||||
onTranscriptionFetched: () =>
|
||||
controller.contentChangedStream.add(true),
|
||||
),
|
||||
// if (MatrixState
|
||||
// .pangeaController.userController.showTranscription)
|
||||
// PhoneticTranscriptionWidget(
|
||||
// text: transcription.transcript.text,
|
||||
// textLanguage: PLanguageStore.byLangCode(
|
||||
// transcription.langCode,
|
||||
// ) ??
|
||||
// LanguageModel.unknown,
|
||||
// style: style,
|
||||
// iconColor: style.color,
|
||||
// onTranscriptionFetched: () =>
|
||||
// controller.contentChangedStream.add(true),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ import 'package:matrix/matrix.dart';
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pages/chat/events/audio_player.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/text_loading_shimmer.dart';
|
||||
import 'package:fluffychat/pangea/bot/utils/bot_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/text_to_speech_response_model.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class MessageAudioCard extends StatefulWidget {
|
||||
final PangeaMessageEvent messageEvent;
|
||||
|
|
@ -44,7 +44,7 @@ class MessageAudioCardState extends State<MessageAudioCard> {
|
|||
try {
|
||||
audioFile = await widget.messageEvent.requestTextToSpeech(
|
||||
widget.messageEvent.messageDisplayLangCode,
|
||||
widget.messageEvent.room.botOptions?.targetVoice,
|
||||
MatrixState.pangeaController.userController.voice,
|
||||
);
|
||||
debugPrint("audio file is now: $audioFile. setting starts and ends...");
|
||||
if (mounted) setState(() => _isLoading = false);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import 'package:matrix/matrix.dart';
|
|||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/analytics_misc/lemma_emoji_setter_mixin.dart';
|
||||
import 'package:fluffychat/pangea/bot/utils/bot_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/async_state.dart';
|
||||
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart';
|
||||
|
|
@ -58,7 +57,7 @@ class _AudioLoader extends AsyncLoader<(PangeaAudioFile, File?)> {
|
|||
Future<(PangeaAudioFile, File?)> fetch() async {
|
||||
final audioBytes = await messageEvent.requestTextToSpeech(
|
||||
messageEvent.messageDisplayLangCode,
|
||||
messageEvent.room.botOptions?.targetVoice,
|
||||
MatrixState.pangeaController.userController.voice,
|
||||
);
|
||||
|
||||
File? audioFile;
|
||||
|
|
@ -91,8 +90,6 @@ class SelectModeController with LemmaEmojiSetter {
|
|||
|
||||
ValueNotifier<SelectMode?> selectedMode = ValueNotifier<SelectMode?>(null);
|
||||
|
||||
final StreamController contentChangedStream = StreamController.broadcast();
|
||||
|
||||
// Sometimes the same token is clicked twice. Setting it to the same value
|
||||
// won't trigger the notifier, so use the bool for force it to trigger.
|
||||
ValueNotifier<(PangeaTokenText?, bool)> playTokenNotifier =
|
||||
|
|
@ -105,7 +102,6 @@ class SelectModeController with LemmaEmojiSetter {
|
|||
_translationLoader.dispose();
|
||||
_sttTranslationLoader.dispose();
|
||||
_audioLoader.dispose();
|
||||
contentChangedStream.close();
|
||||
}
|
||||
|
||||
static List<SelectMode> get _textModes => [
|
||||
|
|
|
|||
40
lib/pangea/user/style_settings_repo.dart
Normal file
40
lib/pangea/user/style_settings_repo.dart
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import 'package:get_storage/get_storage.dart';
|
||||
|
||||
class _StyleSettings {
|
||||
final double fontSizeFactor;
|
||||
|
||||
const _StyleSettings({
|
||||
this.fontSizeFactor = 1.0,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'fontSizeFactor': fontSizeFactor,
|
||||
};
|
||||
}
|
||||
|
||||
factory _StyleSettings.fromJson(Map<String, dynamic> json) {
|
||||
return _StyleSettings(
|
||||
fontSizeFactor: (json['fontSizeFactor'] as num?)?.toDouble() ?? 1.0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class StyleSettingsRepo {
|
||||
static final GetStorage _storage = GetStorage("style_settings");
|
||||
|
||||
static Future<double> fontSizeFactor(String userId) async {
|
||||
await GetStorage.init("style_settings");
|
||||
final json =
|
||||
_storage.read<Map<String, dynamic>>('${userId}_style_settings');
|
||||
final settings =
|
||||
json != null ? _StyleSettings.fromJson(json) : const _StyleSettings();
|
||||
return settings.fontSizeFactor;
|
||||
}
|
||||
|
||||
static Future<void> setFontSizeFactor(String userId, double factor) async {
|
||||
await GetStorage.init("style_settings");
|
||||
final settings = _StyleSettings(fontSizeFactor: factor);
|
||||
await _storage.write('${userId}_style_settings', settings.toJson());
|
||||
}
|
||||
}
|
||||
|
|
@ -431,6 +431,8 @@ class UserController {
|
|||
: langModel;
|
||||
}
|
||||
|
||||
String? get voice => profile.userSettings.voice;
|
||||
|
||||
bool get languagesSet =>
|
||||
userL1Code != null &&
|
||||
userL2Code != null &&
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ class UserSettings {
|
|||
GenderEnum gender;
|
||||
String? country;
|
||||
LanguageLevelTypeEnum cefrLevel;
|
||||
String? voice;
|
||||
|
||||
UserSettings({
|
||||
this.dateOfBirth,
|
||||
|
|
@ -29,6 +30,7 @@ class UserSettings {
|
|||
this.gender = GenderEnum.unselected,
|
||||
this.country,
|
||||
this.cefrLevel = LanguageLevelTypeEnum.a1,
|
||||
this.voice,
|
||||
});
|
||||
|
||||
factory UserSettings.fromJson(Map<String, dynamic> json) => UserSettings(
|
||||
|
|
@ -52,6 +54,7 @@ class UserSettings {
|
|||
json[ModelKey.cefrLevel],
|
||||
)
|
||||
: LanguageLevelTypeEnum.a1,
|
||||
voice: json[ModelKey.voice],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
|
|
@ -64,6 +67,7 @@ class UserSettings {
|
|||
data[ModelKey.userGender] = gender.string;
|
||||
data[ModelKey.userCountry] = country;
|
||||
data[ModelKey.cefrLevel] = cefrLevel.string;
|
||||
data[ModelKey.voice] = voice;
|
||||
return data;
|
||||
}
|
||||
|
||||
|
|
@ -123,6 +127,7 @@ class UserSettings {
|
|||
gender: gender,
|
||||
country: country,
|
||||
cefrLevel: cefrLevel,
|
||||
voice: voice,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -138,7 +143,8 @@ class UserSettings {
|
|||
other.sourceLanguage == sourceLanguage &&
|
||||
other.gender == gender &&
|
||||
other.country == country &&
|
||||
other.cefrLevel == cefrLevel;
|
||||
other.cefrLevel == cefrLevel &&
|
||||
other.voice == voice;
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -151,6 +157,7 @@ class UserSettings {
|
|||
gender.hashCode,
|
||||
country.hashCode,
|
||||
cefrLevel.hashCode,
|
||||
voice.hashCode,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import 'package:matrix/encryption.dart';
|
|||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/analytics_practice/analytics_practice_session_repo.dart';
|
||||
import 'package:fluffychat/pangea/common/network/requests.dart';
|
||||
import 'package:fluffychat/utils/other_party_can_receive.dart';
|
||||
import 'uia_request_manager.dart';
|
||||
|
|
@ -34,6 +35,10 @@ extension LocalizedExceptionExtension on Object {
|
|||
if (this is UnsubscribedException) {
|
||||
return L10n.of(context).unsubscribedResponseError;
|
||||
}
|
||||
|
||||
if (this is InsufficientDataException) {
|
||||
return L10n.of(context).notEnoughToPractice;
|
||||
}
|
||||
// Pangea#
|
||||
if (this is FileTooBigMatrixException) {
|
||||
final exception = this as FileTooBigMatrixException;
|
||||
|
|
|
|||
|
|
@ -64,7 +64,8 @@ extension IsStateExtension on Event {
|
|||
bool get isVisibleInPangeaGui {
|
||||
if (!room.showActivityChatUI) {
|
||||
return type != EventTypes.RoomMember ||
|
||||
roomMemberChangeType != RoomMemberChangeType.avatar;
|
||||
(roomMemberChangeType != RoomMemberChangeType.avatar &&
|
||||
roomMemberChangeType != RoomMemberChangeType.other);
|
||||
}
|
||||
|
||||
return type != EventTypes.RoomMember;
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ class Avatar extends StatelessWidget {
|
|||
|
||||
final double? presenceSize;
|
||||
final Offset? presenceOffset;
|
||||
final Widget? miniIcon;
|
||||
// Pangea#
|
||||
|
||||
const Avatar({
|
||||
|
|
@ -48,7 +47,6 @@ class Avatar extends StatelessWidget {
|
|||
this.userId,
|
||||
this.presenceSize,
|
||||
this.presenceOffset,
|
||||
this.miniIcon,
|
||||
// Pangea#
|
||||
super.key,
|
||||
});
|
||||
|
|
@ -140,13 +138,7 @@ class Avatar extends StatelessWidget {
|
|||
),
|
||||
// #Pangea
|
||||
// if (presenceUserId != null)
|
||||
if (miniIcon != null)
|
||||
Positioned(
|
||||
bottom: presenceOffset?.dy ?? -3,
|
||||
right: presenceOffset?.dx ?? -3,
|
||||
child: miniIcon!,
|
||||
)
|
||||
else if (presenceUserId != null && size >= 32.0 && showPresence)
|
||||
if (presenceUserId != null && size >= 32.0 && showPresence)
|
||||
// Pangea#
|
||||
PresenceBuilder(
|
||||
client: client,
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import 'package:fluffychat/pangea/common/utils/any_state_holder.dart';
|
|||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/join_codes/space_code_controller.dart';
|
||||
import 'package:fluffychat/pangea/languages/locale_provider.dart';
|
||||
import 'package:fluffychat/pangea/user/style_settings_repo.dart';
|
||||
import 'package:fluffychat/utils/client_manager.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_file_extension.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
|
|
@ -553,9 +554,16 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
|
|||
}
|
||||
|
||||
void initSettings() {
|
||||
AppConfig.fontSizeFactor =
|
||||
double.tryParse(store.getString(SettingKeys.fontSizeFactor) ?? '') ??
|
||||
AppConfig.fontSizeFactor;
|
||||
// #Pangea
|
||||
// AppConfig.fontSizeFactor =
|
||||
// double.tryParse(store.getString(SettingKeys.fontSizeFactor) ?? '') ??
|
||||
// AppConfig.fontSizeFactor;
|
||||
if (client.isLogged()) {
|
||||
StyleSettingsRepo.fontSizeFactor(client.userID!).then((factor) {
|
||||
AppConfig.fontSizeFactor = factor;
|
||||
});
|
||||
}
|
||||
// Pangea#
|
||||
|
||||
AppConfig.renderHtml =
|
||||
store.getBool(SettingKeys.renderHtml) ?? AppConfig.renderHtml;
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ import 'package:matrix/matrix.dart';
|
|||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/level_display_name.dart';
|
||||
import 'package:fluffychat/pangea/bot/utils/bot_name.dart';
|
||||
import 'package:fluffychat/pangea/bot/widgets/bot_chat_settings_dialog.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:fluffychat/widgets/permission_slider_dialog.dart';
|
||||
import 'adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
|
|
@ -108,15 +106,15 @@ void showMemberActionsPopupMenu({
|
|||
],
|
||||
),
|
||||
),
|
||||
if (user.id == BotName.byEnvironment && room != null && room.isRoomAdmin)
|
||||
PopupMenuItem(
|
||||
enabled: false,
|
||||
padding: const EdgeInsets.only(
|
||||
left: 12.0,
|
||||
right: 12.0,
|
||||
),
|
||||
child: BotChatSettingsDialog(room: room),
|
||||
),
|
||||
// if (user.id == BotName.byEnvironment && room != null && room.isRoomAdmin)
|
||||
// PopupMenuItem(
|
||||
// enabled: false,
|
||||
// padding: const EdgeInsets.only(
|
||||
// left: 12.0,
|
||||
// right: 12.0,
|
||||
// ),
|
||||
// child: BotChatSettingsDialog(room: room),
|
||||
// ),
|
||||
const PopupMenuDivider(),
|
||||
// #Pangea
|
||||
if (user.room.client.userID != user.id)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue