diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 96300a979..350610368 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -5,7 +5,6 @@ import 'package:flutter/material.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:go_router/go_router.dart'; -import 'package:matrix/matrix_api_lite/generated/model.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; @@ -48,6 +47,7 @@ import 'package:fluffychat/pangea/chat_settings/pages/pangea_invitation_selectio import 'package:fluffychat/pangea/common/utils/p_vguard.dart'; import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; import 'package:fluffychat/pangea/course_creation/course_invite_page.dart'; +import 'package:fluffychat/pangea/course_creation/public_course_preview.dart'; import 'package:fluffychat/pangea/course_creation/selected_course_page.dart'; import 'package:fluffychat/pangea/join_codes/join_with_link_page.dart'; import 'package:fluffychat/pangea/learning_settings/settings_learning.dart'; @@ -407,15 +407,13 @@ abstract class AppRoutes { ], ), GoRoute( - path: ':courseid', + path: ':courseroomid', pageBuilder: (context, state) { return defaultPageBuilder( context, state, - SelectedCourse( - state.pathParameters['courseid']!, - SelectedCourseMode.join, - roomChunk: state.extra as PublicRoomsChunk?, + PublicCoursePreview( + roomID: state.pathParameters['courseroomid']!, ), ); }, diff --git a/lib/l10n/intl_ar.arb b/lib/l10n/intl_ar.arb index 202bc80fc..79de6dbcb 100644 --- a/lib/l10n/intl_ar.arb +++ b/lib/l10n/intl_ar.arb @@ -1,6 +1,6 @@ { "@@locale": "ar", - "@@last_modified": "2026-01-27 14:02:59.727958", + "@@last_modified": "2026-01-28 13:26:35.542116", "about": "حول", "@about": { "type": "String", @@ -11151,5 +11151,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "ما اللغة التي تتعلمها؟", + "searchLanguagesHint": "ابحث عن اللغات المستهدفة", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "أسئلة؟ نحن هنا للمساعدة!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "حدث خطأ ما، ونحن نعمل بجد على إصلاحه. تحقق مرة أخرى لاحقًا.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_be.arb b/lib/l10n/intl_be.arb index b46955cc2..75bef97b7 100644 --- a/lib/l10n/intl_be.arb +++ b/lib/l10n/intl_be.arb @@ -1910,7 +1910,7 @@ "playWithAI": "Пакуль гуляйце з ШІ", "courseStartDesc": "Pangea Bot гатовы да працы ў любы час!\n\n...але навучанне лепш з сябрамі!", "@@locale": "be", - "@@last_modified": "2026-01-27 14:02:50.270963", + "@@last_modified": "2026-01-28 13:26:22.828870", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -12033,5 +12033,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Якую мову вы вывучаеце?", + "searchLanguagesHint": "Пошук мэтавых моў", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Пытанні? Мы тут, каб дапамагчы!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Што-то пайшло не так, і мы актыўна працуем над выпраўленнем. Праверце пазней.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_bn.arb b/lib/l10n/intl_bn.arb index 91b4a1d22..8f60d1e0b 100644 --- a/lib/l10n/intl_bn.arb +++ b/lib/l10n/intl_bn.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-27 14:03:11.965364", + "@@last_modified": "2026-01-28 13:26:47.712647", "about": "সম্পর্কে", "@about": { "type": "String", @@ -12038,5 +12038,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "আপনি কোন ভাষা শিখছেন?", + "searchLanguagesHint": "লক্ষ্য ভাষা অনুসন্ধান করুন", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "প্রশ্ন আছে? আমরা সাহায্য করতে এখানে আছি!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "কিছু ভুল হয়েছে, এবং আমরা এটি ঠিক করতে কঠোর পরিশ্রম করছি। পরে আবার চেক করুন।", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_bo.arb b/lib/l10n/intl_bo.arb index 62ea8c50f..943d56029 100644 --- a/lib/l10n/intl_bo.arb +++ b/lib/l10n/intl_bo.arb @@ -4278,7 +4278,7 @@ "joinPublicTrip": "མི་ཚེས་ལ་ལོག་འབད།", "startOwnTrip": "ངེད་རང་གི་ལོག་ལ་སྦྱོར་བཅོས།", "@@locale": "bo", - "@@last_modified": "2026-01-27 14:03:09.952724", + "@@last_modified": "2026-01-28 13:26:44.570789", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -10688,5 +10688,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Kedua bahasa apa yang Anda pelajari?", + "searchLanguagesHint": "Cari bahasa target", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Pytania? Jesteśmy tutaj, aby pomóc!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Nǐng bǐng wǒng, yǐng wǒng bǐng wǒng. Cǐng bǐng yǐng bǐng.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ca.arb b/lib/l10n/intl_ca.arb index 704212bae..32c6fa6c5 100644 --- a/lib/l10n/intl_ca.arb +++ b/lib/l10n/intl_ca.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-27 14:02:51.719519", + "@@last_modified": "2026-01-28 13:26:23.872618", "about": "Quant a", "@about": { "type": "String", @@ -10958,5 +10958,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Quina llengua estàs aprenent?", + "searchLanguagesHint": "Cerca llengües objectiu", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Preguntes? Som aquí per ajudar!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Alguna cosa ha anat malament, i estem treballant dur per solucionar-ho. Comprova-ho més tard.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_cs.arb b/lib/l10n/intl_cs.arb index 5406b0317..94fb2d6fd 100644 --- a/lib/l10n/intl_cs.arb +++ b/lib/l10n/intl_cs.arb @@ -1,6 +1,6 @@ { "@@locale": "cs", - "@@last_modified": "2026-01-27 14:02:46.567796", + "@@last_modified": "2026-01-28 13:26:20.564591", "about": "O aplikaci", "@about": { "type": "String", @@ -11541,5 +11541,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Jaký jazyk se učíte?", + "searchLanguagesHint": "Hledejte cílové jazyky", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Otázky? Jsme tu, abychom pomohli!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Něco se pokazilo a my na tom tvrdě pracujeme. Zkontrolujte to prosím později.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_da.arb b/lib/l10n/intl_da.arb index 3be98520e..51ca228a6 100644 --- a/lib/l10n/intl_da.arb +++ b/lib/l10n/intl_da.arb @@ -1929,7 +1929,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-27 14:02:21.118853", + "@@last_modified": "2026-01-28 13:25:58.575899", "@aboutHomeserver": { "type": "String", "placeholders": { @@ -11995,5 +11995,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Hvilket sprog lærer du?", + "searchLanguagesHint": "Søg efter målsprog", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Spørgsmål? Vi er her for at hjælpe!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Noget gik galt, og vi arbejder hårdt på at løse det. Tjek igen senere.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_de.arb b/lib/l10n/intl_de.arb index 90363750d..d09f5edac 100644 --- a/lib/l10n/intl_de.arb +++ b/lib/l10n/intl_de.arb @@ -1,6 +1,6 @@ { "@@locale": "de", - "@@last_modified": "2026-01-27 14:02:39.850908", + "@@last_modified": "2026-01-28 13:26:14.577124", "alwaysUse24HourFormat": "true", "@alwaysUse24HourFormat": { "description": "Set to true to always display time of day in 24 hour format." @@ -10941,5 +10941,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Welche Sprache lernst du?", + "searchLanguagesHint": "Zielsprachen suchen", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Fragen? Wir sind hier, um zu helfen!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Etwas ist schiefgelaufen, und wir arbeiten hart daran, es zu beheben. Überprüfen Sie es später erneut.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_el.arb b/lib/l10n/intl_el.arb index d7dada299..a752153e0 100644 --- a/lib/l10n/intl_el.arb +++ b/lib/l10n/intl_el.arb @@ -4455,7 +4455,7 @@ "playWithAI": "Παίξτε με την Τεχνητή Νοημοσύνη προς το παρόν", "courseStartDesc": "Ο Pangea Bot είναι έτοιμος να ξεκινήσει οποιαδήποτε στιγμή!\n\n...αλλά η μάθηση είναι καλύτερη με φίλους!", "@@locale": "el", - "@@last_modified": "2026-01-27 14:03:17.212773", + "@@last_modified": "2026-01-28 13:26:53.057151", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -11992,5 +11992,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Ποια γλώσσα μαθαίνετε;", + "searchLanguagesHint": "Αναζητήστε γλώσσες στόχου", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Ερωτήσεις; Είμαστε εδώ για να βοηθήσουμε!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Κάτι πήγε στραβά και εργαζόμαστε σκληρά για να το διορθώσουμε. Έλεγξε ξανά αργότερα.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 95daec80c..e8064ea1e 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -5063,5 +5063,9 @@ "publicInviteDescSpace": "Search for users to invite them to this space.", "useActivityImageAsChatBackground": "Use activity image as chat background", "chatWithSupport": "Chat with Support", - "newCourseAccess": "By default, courses are publicly searchable and require admin approval to join. You can edit these settings at any time." + "newCourseAccess": "By default, courses are publicly searchable and require admin approval to join. You can edit these settings at any time.", + "courseLoadingError": "Something went wrong, and we're hard at work fixing it. Check again later.", + "onboardingLanguagesTitle": "What language are you learning?", + "searchLanguagesHint": "Search target languages", + "supportSubtitle": "Questions? We're here to help!" } diff --git a/lib/l10n/intl_eo.arb b/lib/l10n/intl_eo.arb index e3f7b5447..09bb94838 100644 --- a/lib/l10n/intl_eo.arb +++ b/lib/l10n/intl_eo.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-27 14:03:21.507072", + "@@last_modified": "2026-01-28 13:26:57.219657", "about": "Prio", "@about": { "type": "String", @@ -12023,5 +12023,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Kian lingvon vi lernas?", + "searchLanguagesHint": "Serĉu celajn lingvojn", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Demandoj? Ni ĉi tie por helpi!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Io malĝuste okazis, kaj ni diligente laboras por ripari ĝin. Kontrolu denove poste.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_es.arb b/lib/l10n/intl_es.arb index 6a6c206d8..d55ec2891 100644 --- a/lib/l10n/intl_es.arb +++ b/lib/l10n/intl_es.arb @@ -1,6 +1,6 @@ { "@@locale": "es", - "@@last_modified": "2026-01-27 14:02:15.353643", + "@@last_modified": "2026-01-28 13:25:54.826808", "about": "Acerca de", "@about": { "type": "String", @@ -8168,5 +8168,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "¿Qué idioma estás aprendiendo?", + "searchLanguagesHint": "Buscar idiomas objetivo", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "¿Preguntas? ¡Estamos aquí para ayudar!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Algo salió mal y estamos trabajando arduamente para solucionarlo. Revisa de nuevo más tarde.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_et.arb b/lib/l10n/intl_et.arb index 604646e87..8c8809c5a 100644 --- a/lib/l10n/intl_et.arb +++ b/lib/l10n/intl_et.arb @@ -1,6 +1,6 @@ { "@@locale": "et", - "@@last_modified": "2026-01-27 14:02:38.644251", + "@@last_modified": "2026-01-28 13:26:13.431455", "about": "Rakenduse teave", "@about": { "type": "String", @@ -11205,5 +11205,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Millist keelt sa õpid?", + "searchLanguagesHint": "Otsi sihtkeeli", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Küsimused? Me oleme siin, et aidata!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Midagi läks valesti ja me teeme kõvasti tööd, et see parandada. Kontrolli hiljem uuesti.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_eu.arb b/lib/l10n/intl_eu.arb index 41dd14b4e..3a569f1af 100644 --- a/lib/l10n/intl_eu.arb +++ b/lib/l10n/intl_eu.arb @@ -1,6 +1,6 @@ { "@@locale": "eu", - "@@last_modified": "2026-01-27 14:02:35.628448", + "@@last_modified": "2026-01-28 13:26:10.864458", "about": "Honi buruz", "@about": { "type": "String", @@ -10934,5 +10934,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Zer hizkuntza ikasten ari zara?", + "searchLanguagesHint": "Bilatu helburu hizkuntzak", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Galderak? Hemen gaude laguntzeko!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Zerbait oker joan da, eta horren konponketan lan gogorra egiten ari gara. Begiratu berriro geroago.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_fa.arb b/lib/l10n/intl_fa.arb index 2cce2de65..a9a59f38c 100644 --- a/lib/l10n/intl_fa.arb +++ b/lib/l10n/intl_fa.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-27 14:03:13.031797", + "@@last_modified": "2026-01-28 13:26:49.168597", "repeatPassword": "تکرار رمزعبور", "@repeatPassword": {}, "about": "درباره", @@ -11666,5 +11666,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "شما در حال یادگیری چه زبانی هستید؟", + "searchLanguagesHint": "زبان‌های هدف را جستجو کنید", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "سوالات؟ ما اینجا هستیم تا کمک کنیم!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "مشکلی پیش آمده و ما در حال تلاش برای رفع آن هستیم. بعداً دوباره بررسی کنید.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_fi.arb b/lib/l10n/intl_fi.arb index 61b955468..d17f6b29b 100644 --- a/lib/l10n/intl_fi.arb +++ b/lib/l10n/intl_fi.arb @@ -4008,7 +4008,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-27 14:02:18.192045", + "@@last_modified": "2026-01-28 13:25:57.438673", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -11557,5 +11557,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Mitä kieltä opit?", + "searchLanguagesHint": "Etsi kohdekieliä", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Kysymyksiä? Olemme täällä auttamassa!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Jotain meni pieleen, ja teemme kovasti töitä sen korjaamiseksi. Tarkista myöhemmin uudelleen.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_fil.arb b/lib/l10n/intl_fil.arb index e89792ee7..284988b9b 100644 --- a/lib/l10n/intl_fil.arb +++ b/lib/l10n/intl_fil.arb @@ -2786,7 +2786,7 @@ "selectAll": "Piliin lahat", "deselectAll": "Huwag piliin lahat", "@@locale": "fil", - "@@last_modified": "2026-01-27 14:02:56.958154", + "@@last_modified": "2026-01-28 13:26:32.216257", "@setCustomPermissionLevel": { "type": "String", "placeholders": {} @@ -11910,5 +11910,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Anong wika ang iyong pinag-aaralan?", + "searchLanguagesHint": "Maghanap ng mga target na wika", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "May mga tanong? Nandito kami para tumulong!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "May nangyaring mali, at abala kami sa pag-aayos nito. Suriin muli mamaya.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index 3c03b9544..22902c22d 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -1,6 +1,6 @@ { "@@locale": "fr", - "@@last_modified": "2026-01-27 14:03:27.361514", + "@@last_modified": "2026-01-28 13:27:03.910294", "about": "À propos", "@about": { "type": "String", @@ -11258,5 +11258,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Quelle langue apprenez-vous ?", + "searchLanguagesHint": "Recherchez des langues cibles", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Des questions ? Nous sommes là pour vous aider !", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Quelque chose a mal tourné, et nous travaillons dur pour le réparer. Vérifiez à nouveau plus tard.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ga.arb b/lib/l10n/intl_ga.arb index 90ed04ae4..3bbdb7ddf 100644 --- a/lib/l10n/intl_ga.arb +++ b/lib/l10n/intl_ga.arb @@ -4516,7 +4516,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-27 14:03:26.279031", + "@@last_modified": "2026-01-28 13:27:02.604512", "@customReaction": { "type": "String", "placeholders": {} @@ -10932,5 +10932,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Cén teanga atá á foghlaim agat?", + "searchLanguagesHint": "Cuardaigh teangacha sprioc", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Ceisteanna? Táimid anseo chun cabhrú!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Tharla rud éigin mícheart, agus táimid ag obair go dian chun é a shocrú. Seiceáil arís níos déanaí.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_gl.arb b/lib/l10n/intl_gl.arb index 20e29ea7b..f1aed4b67 100644 --- a/lib/l10n/intl_gl.arb +++ b/lib/l10n/intl_gl.arb @@ -1,6 +1,6 @@ { "@@locale": "gl", - "@@last_modified": "2026-01-27 14:02:17.041137", + "@@last_modified": "2026-01-28 13:25:56.077589", "about": "Acerca de", "@about": { "type": "String", @@ -10931,5 +10931,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Que idioma estás aprendendo?", + "searchLanguagesHint": "Busca idiomas de destino", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "¿Preguntas? Estamos aquí para axudar!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Algo saíu mal e estamos traballando duro para solucionalo. Comproba de novo máis tarde.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_he.arb b/lib/l10n/intl_he.arb index 202aac760..d75f1d839 100644 --- a/lib/l10n/intl_he.arb +++ b/lib/l10n/intl_he.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-27 14:02:31.662677", + "@@last_modified": "2026-01-28 13:26:06.755080", "about": "אודות", "@about": { "type": "String", @@ -11983,5 +11983,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "איזו שפה אתה לומד?", + "searchLanguagesHint": "חפש שפות יעד", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "שאלות? אנחנו כאן כדי לעזור!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "משהו השתבש, ואנחנו עובדים קשה על תיקון זה. בדוק שוב מאוחר יותר.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_hi.arb b/lib/l10n/intl_hi.arb index 602f6ae31..84368b8da 100644 --- a/lib/l10n/intl_hi.arb +++ b/lib/l10n/intl_hi.arb @@ -4482,7 +4482,7 @@ "playWithAI": "अभी के लिए एआई के साथ खेलें", "courseStartDesc": "पैंजिया बॉट कभी भी जाने के लिए तैयार है!\n\n...लेकिन दोस्तों के साथ सीखना बेहतर है!", "@@locale": "hi", - "@@last_modified": "2026-01-27 14:03:20.292018", + "@@last_modified": "2026-01-28 13:26:55.548010", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -12019,5 +12019,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "आप कौन सी भाषा सीख रहे हैं?", + "searchLanguagesHint": "लक्षित भाषाएँ खोजें", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "प्रश्न? हम आपकी मदद के लिए यहाँ हैं!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "कुछ गलत हो गया है, और हम इसे ठीक करने में कड़ी मेहनत कर रहे हैं। बाद में फिर से जांचें।", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_hr.arb b/lib/l10n/intl_hr.arb index 86992f1f6..f0a63cb08 100644 --- a/lib/l10n/intl_hr.arb +++ b/lib/l10n/intl_hr.arb @@ -1,6 +1,6 @@ { "@@locale": "hr", - "@@last_modified": "2026-01-27 14:02:30.204264", + "@@last_modified": "2026-01-28 13:26:05.722028", "about": "Informacije", "@about": { "type": "String", @@ -11306,5 +11306,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Koji jezik učite?", + "searchLanguagesHint": "Pretraži ciljne jezike", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Imate pitanja? Tu smo da pomognemo!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Nešto je pošlo po zlu i marljivo radimo na rješavanju problema. Provjerite ponovo kasnije.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_hu.arb b/lib/l10n/intl_hu.arb index d9917ddb8..d21418bcf 100644 --- a/lib/l10n/intl_hu.arb +++ b/lib/l10n/intl_hu.arb @@ -1,6 +1,6 @@ { "@@locale": "hu", - "@@last_modified": "2026-01-27 14:02:22.464338", + "@@last_modified": "2026-01-28 13:25:59.737331", "about": "Névjegy", "@about": { "type": "String", @@ -10935,5 +10935,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Milyen nyelvet tanulsz?", + "searchLanguagesHint": "Keresd a célnyelveket", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Kérdése van? Itt vagyunk, hogy segítsünk!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Valami hiba történt, és keményen dolgozunk a javításon. Kérlek, nézd meg később.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ia.arb b/lib/l10n/intl_ia.arb index d582dabf6..9dabec835 100644 --- a/lib/l10n/intl_ia.arb +++ b/lib/l10n/intl_ia.arb @@ -1957,7 +1957,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-27 14:02:33.390978", + "@@last_modified": "2026-01-28 13:26:08.333302", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -12012,5 +12012,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Kia lingvo vi lernas?", + "searchLanguagesHint": "Serĉu celajn lingvojn", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Kwestyon? Nou la pou ede!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "N'ayen a fau, e n'ayen a t'awen a t'awen a t'awen. T'awen a t'awen a t'awen.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_id.arb b/lib/l10n/intl_id.arb index 1e54ccac8..afffc9180 100644 --- a/lib/l10n/intl_id.arb +++ b/lib/l10n/intl_id.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-27 14:02:23.466673", + "@@last_modified": "2026-01-28 13:26:00.772325", "setAsCanonicalAlias": "Atur sebagai alias utama", "@setAsCanonicalAlias": { "type": "String", @@ -10925,5 +10925,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Bahasa apa yang Anda pelajari?", + "searchLanguagesHint": "Cari bahasa target", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Pertanyaan? Kami di sini untuk membantu!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Ada yang tidak beres, dan kami sedang bekerja keras untuk memperbaikinya. Periksa lagi nanti.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ie.arb b/lib/l10n/intl_ie.arb index 8fa61b062..3c8ed322a 100644 --- a/lib/l10n/intl_ie.arb +++ b/lib/l10n/intl_ie.arb @@ -4371,7 +4371,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-27 14:02:28.999815", + "@@last_modified": "2026-01-28 13:26:04.525215", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -11908,5 +11908,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Cén teanga atá á foghlaim agat?", + "searchLanguagesHint": "Cuardaigh teangacha sprioc", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Ceisteanna? Táimid anseo chun cabhrú!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Níl aon rud ag dul i gceart, agus táimid ag obair go dian chun é a shocrú. Seiceáil arís níos déanaí.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_it.arb b/lib/l10n/intl_it.arb index 141411532..5c3e594ee 100644 --- a/lib/l10n/intl_it.arb +++ b/lib/l10n/intl_it.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-27 14:02:43.998266", + "@@last_modified": "2026-01-28 13:26:18.238331", "about": "Informazioni", "@about": { "type": "String", @@ -10937,5 +10937,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Quale lingua stai imparando?", + "searchLanguagesHint": "Cerca lingue target", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Domande? Siamo qui per aiutarti!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Qualcosa è andato storto e stiamo lavorando duramente per risolverlo. Controlla di nuovo più tardi.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ja.arb b/lib/l10n/intl_ja.arb index c779c5280..5728a47ad 100644 --- a/lib/l10n/intl_ja.arb +++ b/lib/l10n/intl_ja.arb @@ -1,6 +1,6 @@ { "@@locale": "ja", - "@@last_modified": "2026-01-27 14:03:18.964652", + "@@last_modified": "2026-01-28 13:26:54.166864", "about": "このアプリについて", "@about": { "type": "String", @@ -11724,5 +11724,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "どの言語を学んでいますか?", + "searchLanguagesHint": "ターゲット言語を検索", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "質問がありますか?私たちはお手伝いします!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "何かがうまくいかなかったため、私たちは修正作業に取り組んでいます。後で再度確認してください。", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ka.arb b/lib/l10n/intl_ka.arb index 8f6867c70..5a03fc058 100644 --- a/lib/l10n/intl_ka.arb +++ b/lib/l10n/intl_ka.arb @@ -2593,7 +2593,7 @@ "playWithAI": "ამ დროისთვის ითამაშეთ AI-თან", "courseStartDesc": "Pangea Bot მზადაა ნებისმიერ დროს გასასვლელად!\n\n...მაგრამ სწავლა უკეთესია მეგობრებთან ერთად!", "@@locale": "ka", - "@@last_modified": "2026-01-27 14:03:23.702338", + "@@last_modified": "2026-01-28 13:27:00.130109", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -11964,5 +11964,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "რომელი ენა სწავლობთ?", + "searchLanguagesHint": "ძებნა მიზნობრივი ენების", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "კითხვები? ჩვენ აქ ვართ, რომ დაგეხმაროთ!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "რამე არასწორად მოხდა, და ჩვენ აქტიურად ვმუშაობთ ამის გამოსასწორებლად. შეამოწმეთ მოგვიანებით.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ko.arb b/lib/l10n/intl_ko.arb index d81b9484e..c07d9a90d 100644 --- a/lib/l10n/intl_ko.arb +++ b/lib/l10n/intl_ko.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-27 14:02:13.792128", + "@@last_modified": "2026-01-28 13:25:53.869320", "about": "소개", "@about": { "type": "String", @@ -11042,5 +11042,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "어떤 언어를 배우고 있나요?", + "searchLanguagesHint": "목표 언어 검색", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "질문이 있으신가요? 저희가 도와드리겠습니다!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "문제가 발생했으며, 우리는 이를 해결하기 위해 열심히 작업하고 있습니다. 나중에 다시 확인해 주세요.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_lt.arb b/lib/l10n/intl_lt.arb index 0b8f743c9..245586145 100644 --- a/lib/l10n/intl_lt.arb +++ b/lib/l10n/intl_lt.arb @@ -3860,7 +3860,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-27 14:03:05.189233", + "@@last_modified": "2026-01-28 13:26:39.413825", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -11739,5 +11739,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Kokią kalbą mokotės?", + "searchLanguagesHint": "Ieškoti tikslo kalbų", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Klausimai? Mes čia, kad padėtume!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Kažkas nepavyko, ir mes sunkiai dirbame, kad tai išspręstume. Patikrinkite vėliau.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_lv.arb b/lib/l10n/intl_lv.arb index 42cec020d..6cc205fba 100644 --- a/lib/l10n/intl_lv.arb +++ b/lib/l10n/intl_lv.arb @@ -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-27 14:02:58.530525", + "@@last_modified": "2026-01-28 13:26:33.690801", "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", @@ -10920,5 +10920,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Kuru valodu tu mācies?", + "searchLanguagesHint": "Meklēt mērķa valodas", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Jautājumi? Mēs esam šeit, lai palīdzētu!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Kaut kas nogāja greizi, un mēs smagi strādājam, lai to labotu. Pārbaudiet vēlāk.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_nb.arb b/lib/l10n/intl_nb.arb index 24eb30e86..05c19a9c4 100644 --- a/lib/l10n/intl_nb.arb +++ b/lib/l10n/intl_nb.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-27 14:02:48.767207", + "@@last_modified": "2026-01-28 13:26:21.535919", "about": "Om", "@about": { "type": "String", @@ -12027,5 +12027,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Hvilket språk lærer du?", + "searchLanguagesHint": "Søk etter målspråk", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Spørsmål? Vi er her for å hjelpe!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Noe gikk galt, og vi jobber hardt med å fikse det. Sjekk igjen senere.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_nl.arb b/lib/l10n/intl_nl.arb index c9a7bb5db..902a72a70 100644 --- a/lib/l10n/intl_nl.arb +++ b/lib/l10n/intl_nl.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-27 14:03:08.964846", + "@@last_modified": "2026-01-28 13:26:43.074504", "about": "Over ons", "@about": { "type": "String", @@ -10934,5 +10934,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Welke taal ben je aan het leren?", + "searchLanguagesHint": "Zoek doeltalen", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Vragen? We zijn hier om te helpen!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Er is iets misgegaan en we zijn hard aan het werk om het op te lossen. Kijk later nog eens.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_pl.arb b/lib/l10n/intl_pl.arb index cd734186f..d7353e601 100644 --- a/lib/l10n/intl_pl.arb +++ b/lib/l10n/intl_pl.arb @@ -1,6 +1,6 @@ { "@@locale": "pl", - "@@last_modified": "2026-01-27 14:03:14.463461", + "@@last_modified": "2026-01-28 13:26:50.545730", "about": "O aplikacji", "@about": { "type": "String", @@ -10932,5 +10932,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Jakiego języka się uczysz?", + "searchLanguagesHint": "Szukaj języków docelowych", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Pytania? Jesteśmy tutaj, aby pomóc!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Coś poszło nie tak, a my ciężko pracujemy nad naprawą. Sprawdź ponownie później.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_pt.arb b/lib/l10n/intl_pt.arb index 5cb94166f..61e8795dd 100644 --- a/lib/l10n/intl_pt.arb +++ b/lib/l10n/intl_pt.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-27 14:02:37.702228", + "@@last_modified": "2026-01-28 13:26:11.994676", "copiedToClipboard": "Copiada para a área de transferência", "@copiedToClipboard": { "type": "String", @@ -12034,5 +12034,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Qual idioma você está aprendendo?", + "searchLanguagesHint": "Pesquise idiomas-alvo", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Dúvidas? Estamos aqui para ajudar!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Algo deu errado, e estamos trabalhando arduamente para corrigir isso. Verifique novamente mais tarde.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_pt_BR.arb b/lib/l10n/intl_pt_BR.arb index d24701241..29d204099 100644 --- a/lib/l10n/intl_pt_BR.arb +++ b/lib/l10n/intl_pt_BR.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-27 14:02:34.316481", + "@@last_modified": "2026-01-28 13:26:09.473589", "about": "Sobre", "@about": { "type": "String", @@ -11292,5 +11292,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Qual idioma você está aprendendo?", + "searchLanguagesHint": "Pesquise idiomas-alvo", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Dúvidas? Estamos aqui para ajudar!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Algo deu errado, e estamos trabalhando duro para consertar. Verifique novamente mais tarde.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_pt_PT.arb b/lib/l10n/intl_pt_PT.arb index e26a982b2..3c53e2fbc 100644 --- a/lib/l10n/intl_pt_PT.arb +++ b/lib/l10n/intl_pt_PT.arb @@ -3330,7 +3330,7 @@ "selectAll": "Selecionar tudo", "deselectAll": "Desmarcar tudo", "@@locale": "pt_PT", - "@@last_modified": "2026-01-27 14:02:54.292550", + "@@last_modified": "2026-01-28 13:26:26.329625", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -11963,5 +11963,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Qual idioma você está aprendendo?", + "searchLanguagesHint": "Pesquise idiomas-alvo", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Dúvidas? Estamos aqui para ajudar!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Algo deu errado, e estamos trabalhando arduamente para corrigir isso. Verifique novamente mais tarde.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ro.arb b/lib/l10n/intl_ro.arb index 56b30d355..8ab62b620 100644 --- a/lib/l10n/intl_ro.arb +++ b/lib/l10n/intl_ro.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-27 14:02:25.277330", + "@@last_modified": "2026-01-28 13:26:01.958335", "about": "Despre", "@about": { "type": "String", @@ -11669,5 +11669,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Ce limbă înveți?", + "searchLanguagesHint": "Caută limbi țintă", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Întrebări? Suntem aici să ajutăm!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Ceva a mers prost și lucrăm din greu pentru a remedia problema. Verifică din nou mai târziu.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index ecf7836b4..470f329fd 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -1,6 +1,6 @@ { "@@locale": "ru", - "@@last_modified": "2026-01-27 14:03:22.446889", + "@@last_modified": "2026-01-28 13:26:58.889837", "about": "О проекте", "@about": { "type": "String", @@ -11042,5 +11042,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Какой язык вы изучаете?", + "searchLanguagesHint": "Поиск целевых языков", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Вопросы? Мы здесь, чтобы помочь!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Что-то пошло не так, и мы усердно работаем над исправлением. Проверьте позже.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_sk.arb b/lib/l10n/intl_sk.arb index c504483f2..39202ddde 100644 --- a/lib/l10n/intl_sk.arb +++ b/lib/l10n/intl_sk.arb @@ -1,6 +1,6 @@ { "@@locale": "sk", - "@@last_modified": "2026-01-27 14:02:27.134602", + "@@last_modified": "2026-01-28 13:26:03.157059", "about": "O aplikácii", "@about": { "type": "String", @@ -12018,5 +12018,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Aký jazyk sa učíte?", + "searchLanguagesHint": "Hľadajte cieľové jazyky", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Otázky? Sme tu, aby sme pomohli!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Niečo sa pokazilo a my na tom tvrdo pracujeme, aby sme to opravili. Skontrolujte to neskôr.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_sl.arb b/lib/l10n/intl_sl.arb index 005e74f65..f63713041 100644 --- a/lib/l10n/intl_sl.arb +++ b/lib/l10n/intl_sl.arb @@ -2463,7 +2463,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-27 14:02:41.198883", + "@@last_modified": "2026-01-28 13:26:15.797667", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -12015,5 +12015,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Katero jezika se učiš?", + "searchLanguagesHint": "Išči ciljne jezike", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Vprašanja? Tu smo, da pomagamo!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Nekaj je šlo narobe in trdo delamo na tem, da to popravimo. Preverite znova kasneje.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_sr.arb b/lib/l10n/intl_sr.arb index b7ee563f9..d666cef35 100644 --- a/lib/l10n/intl_sr.arb +++ b/lib/l10n/intl_sr.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-27 14:03:24.807506", + "@@last_modified": "2026-01-28 13:27:01.338972", "about": "О програму", "@about": { "type": "String", @@ -12036,5 +12036,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Koji jezik učite?", + "searchLanguagesHint": "Pretraži ciljne jezike", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Pitanja? Tu smo da pomognemo!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Nešto je pošlo po zlu, i mi marljivo radimo na rešenju. Proverite ponovo kasnije.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_sv.arb b/lib/l10n/intl_sv.arb index 3c152a834..356a01900 100644 --- a/lib/l10n/intl_sv.arb +++ b/lib/l10n/intl_sv.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-27 14:03:15.575050", + "@@last_modified": "2026-01-28 13:26:51.814505", "about": "Om", "@about": { "type": "String", @@ -11412,5 +11412,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Vilket språk lär du dig?", + "searchLanguagesHint": "Sök efter målspråk", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Frågor? Vi är här för att hjälpa till!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Något gick fel, och vi arbetar hårt för att åtgärda det. Kolla igen senare.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ta.arb b/lib/l10n/intl_ta.arb index 67a9812e1..af6a8649c 100644 --- a/lib/l10n/intl_ta.arb +++ b/lib/l10n/intl_ta.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-27 14:03:07.633079", + "@@last_modified": "2026-01-28 13:26:42.100203", "acceptedTheInvitation": "👍 {username} அழைப்பை ஏற்றுக்கொண்டது", "@acceptedTheInvitation": { "type": "String", @@ -11158,5 +11158,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "நீங்கள் எது மொழி கற்றுக்கொள்கிறீர்கள்?", + "searchLanguagesHint": "இலக்கு மொழிகளை தேடுங்கள்", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "கேள்விகள்? நாங்கள் உதவ இங்கே இருக்கிறோம்!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "எதோ தவறு ஏற்பட்டது, அதை சரிசெய்ய நாங்கள் கடுமையாக வேலை செய்கிறோம். பின்னர் மீண்டும் சரிபார்க்கவும்.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_te.arb b/lib/l10n/intl_te.arb index e57e4694d..abc4086be 100644 --- a/lib/l10n/intl_te.arb +++ b/lib/l10n/intl_te.arb @@ -1919,7 +1919,7 @@ "playWithAI": "ఇప్పుడే AI తో ఆడండి", "courseStartDesc": "పాంజియా బాట్ ఎప్పుడైనా సిద్ధంగా ఉంటుంది!\n\n...కానీ స్నేహితులతో నేర్చుకోవడం మెరుగైనది!", "@@locale": "te", - "@@last_modified": "2026-01-27 14:03:02.156703", + "@@last_modified": "2026-01-28 13:26:38.102435", "@setCustomPermissionLevel": { "type": "String", "placeholders": {} @@ -12023,5 +12023,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "మీరు ఏ భాష నేర్చుకుంటున్నారు?", + "searchLanguagesHint": "లక్ష్య భాషలను శోధించండి", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "ప్రశ్నలు? మేము మీకు సహాయం చేయడానికి ఇక్కడ ఉన్నాము!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "ఏదో తప్పు జరిగింది, మరియు మేము దీన్ని సరిదిద్దడానికి కష్టపడుతున్నాము. తర్వాత మళ్లీ తనిఖీ చేయండి.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_th.arb b/lib/l10n/intl_th.arb index 7cd2bc089..5946ef20f 100644 --- a/lib/l10n/intl_th.arb +++ b/lib/l10n/intl_th.arb @@ -4455,7 +4455,7 @@ "playWithAI": "เล่นกับ AI ชั่วคราว", "courseStartDesc": "Pangea Bot พร้อมที่จะเริ่มต้นได้ทุกเมื่อ!\n\n...แต่การเรียนรู้ดีกว่ากับเพื่อน!", "@@locale": "th", - "@@last_modified": "2026-01-27 14:02:53.265962", + "@@last_modified": "2026-01-28 13:26:25.241346", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -11992,5 +11992,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "คุณกำลังเรียนภาษาอะไรอยู่?", + "searchLanguagesHint": "ค้นหาภาษาที่ต้องการ", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "มีคำถามไหม? เราพร้อมที่จะช่วยเหลือ!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "มีบางอย่างผิดพลาด และเรากำลังทำงานอย่างหนักเพื่อแก้ไข ตรวจสอบอีกครั้งในภายหลัง.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_tr.arb b/lib/l10n/intl_tr.arb index a9e48865c..480a0e9c5 100644 --- a/lib/l10n/intl_tr.arb +++ b/lib/l10n/intl_tr.arb @@ -1,6 +1,6 @@ { "@@locale": "tr", - "@@last_modified": "2026-01-27 14:03:00.927147", + "@@last_modified": "2026-01-28 13:26:36.845268", "about": "Hakkında", "@about": { "type": "String", @@ -11156,5 +11156,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Hangi dili öğreniyorsunuz?", + "searchLanguagesHint": "Hedef dilleri arayın", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Sorular mı? Yardımcı olmaya buradayız!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Bir şeyler yanlış gitti ve biz bunu düzeltmek için yoğun bir şekilde çalışıyoruz. Lütfen daha sonra tekrar kontrol edin.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_uk.arb b/lib/l10n/intl_uk.arb index c9dec7183..c13815b3e 100644 --- a/lib/l10n/intl_uk.arb +++ b/lib/l10n/intl_uk.arb @@ -1,6 +1,6 @@ { "@@locale": "uk", - "@@last_modified": "2026-01-27 14:02:45.151936", + "@@last_modified": "2026-01-28 13:26:19.427065", "about": "Про застосунок", "@about": { "type": "String", @@ -10928,5 +10928,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Яку мову ви вивчаєте?", + "searchLanguagesHint": "Шукати цільові мови", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Питання? Ми тут, щоб допомогти!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Щось пішло не так, і ми наполегливо працюємо над виправленням. Перевірте пізніше.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_vi.arb b/lib/l10n/intl_vi.arb index 09877a61a..3fc2e61d1 100644 --- a/lib/l10n/intl_vi.arb +++ b/lib/l10n/intl_vi.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-27 14:03:06.468185", + "@@last_modified": "2026-01-28 13:26:40.659203", "about": "Giới thiệu", "@about": { "type": "String", @@ -6504,5 +6504,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Bạn đang học ngôn ngữ nào?", + "searchLanguagesHint": "Tìm kiếm ngôn ngữ mục tiêu", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "Câu hỏi? Chúng tôi ở đây để giúp đỡ!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "Đã xảy ra sự cố, và chúng tôi đang nỗ lực khắc phục. Vui lòng kiểm tra lại sau.", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_yue.arb b/lib/l10n/intl_yue.arb index b32ac2bfb..fe82df0cf 100644 --- a/lib/l10n/intl_yue.arb +++ b/lib/l10n/intl_yue.arb @@ -1855,7 +1855,7 @@ "selectAll": "全選", "deselectAll": "取消全選", "@@locale": "yue", - "@@last_modified": "2026-01-27 14:02:42.486712", + "@@last_modified": "2026-01-28 13:26:16.825978", "@ignoreUser": { "type": "String", "placeholders": {} @@ -12025,5 +12025,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "你正在學習什麼語言?", + "searchLanguagesHint": "搜尋目標語言", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "有問題嗎?我們在這裡幫助你!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "發生了一些問題,我們正在努力修復。稍後再檢查。", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb index 2ebdcb6bc..c488b0d36 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -1,6 +1,6 @@ { "@@locale": "zh", - "@@last_modified": "2026-01-27 14:03:10.872296", + "@@last_modified": "2026-01-28 13:26:45.843538", "about": "关于", "@about": { "type": "String", @@ -10925,5 +10925,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "你正在学习什么语言?", + "searchLanguagesHint": "搜索目标语言", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "有问题吗?我们在这里帮助您!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "出现了一些问题,我们正在努力修复。请稍后再检查。", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_zh_Hant.arb b/lib/l10n/intl_zh_Hant.arb index c8658333c..45bd99c5c 100644 --- a/lib/l10n/intl_zh_Hant.arb +++ b/lib/l10n/intl_zh_Hant.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-27 14:02:55.518251", + "@@last_modified": "2026-01-28 13:26:27.558260", "about": "關於", "@about": { "type": "String", @@ -10932,5 +10932,25 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "你正在學習什麼語言?", + "searchLanguagesHint": "搜尋目標語言", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "type": "String", + "placeholders": {} + }, + "supportSubtitle": "有問題嗎?我們在這裡幫助您!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} + }, + "courseLoadingError": "發生了一些問題,我們正在努力修復。稍後再檢查。", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 175bd9275..53a791d19 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -2015,18 +2015,6 @@ class ChatController extends State bool get _isToolbarOpen => MatrixState.pAnyState.isOverlayOpen(RegExp(r'^message_toolbar_overlay$')); - 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; - } - if (event.messageType == MessageTypes.Audio) { - return !InstructionsEnum.clickAudioMessages.isToggledOff; - } - return false; - } - void showToolbar( Event event, { PangeaMessageEvent? pangeaMessageEvent, @@ -2059,14 +2047,8 @@ class ChatController extends State ); // you've clicked a message so lets turn this off - InstructionsEnum.clickMessage.setToggledOff(true); - if (event.messageType == MessageTypes.Text && - !InstructionsEnum.clickTextMessages.isToggledOff) { - InstructionsEnum.clickTextMessages.setToggledOff(true); - } - if (event.messageType == MessageTypes.Audio && - !InstructionsEnum.clickAudioMessages.isToggledOff) { - InstructionsEnum.clickAudioMessages.setToggledOff(true); + if (!InstructionsEnum.clickMessage.isToggledOff) { + InstructionsEnum.clickMessage.setToggledOff(true); } if (!kIsWeb) { diff --git a/lib/pages/chat/events/html_message.dart b/lib/pages/chat/events/html_message.dart index 78a5149a5..d09a7b7dd 100644 --- a/lib/pages/chat/events/html_message.dart +++ b/lib/pages/chat/events/html_message.dart @@ -11,8 +11,10 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pages/chat/chat.dart'; +import 'package:fluffychat/pangea/common/widgets/shimmer_background.dart'; import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; +import 'package:fluffychat/pangea/instructions/instructions_enum.dart'; import 'package:fluffychat/pangea/toolbar/layout/reading_assistance_mode_enum.dart'; import 'package:fluffychat/pangea/toolbar/message_practice/message_practice_mode_enum.dart'; import 'package:fluffychat/pangea/toolbar/message_practice/token_practice_button.dart'; @@ -446,6 +448,12 @@ class HtmlMessage extends StatelessWidget { : false; final isNew = token != null && newTokens.contains(token.text); + final isFirstNewToken = isNew && + controller.buttonEventID == event.eventId && + newTokens.first == token.text; + final showShimmer = + !InstructionsEnum.shimmerNewToken.isToggledOff && isFirstNewToken; + final tokenWidth = renderer.tokenTextWidthForContainer( node.text, Theme.of(context).colorScheme.primary.withAlpha(200), @@ -500,19 +508,25 @@ class HtmlMessage extends StatelessWidget { : null, child: HoverBuilder( builder: (context, hovered) { - return UnderlineText( - text: node.text.trim(), - style: existingStyle, - linkStyle: linkStyle, - textDirection: pangeaMessageEvent?.textDirection, - underlineColor: TokenRenderingUtil.underlineColor( - underlineColor, - selected: selected, - highlighted: highlighted, - isNew: isNew, - practiceMode: readingAssistanceMode == - ReadingAssistanceMode.practiceMode, - hovered: hovered, + return ShimmerBackground( + enabled: showShimmer, + borderRadius: BorderRadius.circular(4.0), + child: UnderlineText( + text: node.text.trim(), + style: existingStyle, + linkStyle: linkStyle, + textDirection: + pangeaMessageEvent?.textDirection, + underlineColor: + TokenRenderingUtil.underlineColor( + underlineColor, + selected: selected, + highlighted: highlighted, + isNew: isNew, + practiceMode: readingAssistanceMode == + ReadingAssistanceMode.practiceMode, + hovered: hovered, + ), ), ); }, diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart index 12ec81504..d4ee1d5c1 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -17,7 +17,6 @@ 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'; import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/utils/date_time_extension.dart'; @@ -603,228 +602,223 @@ class Message extends StatelessWidget { child: ValueListenableBuilder( valueListenable: controller .depressMessageButton, - // #Pangea - child: ShimmerBackground( - enabled: controller - .showMessageShimmer( - event, + + child: Container( + decoration: BoxDecoration( + color: noBubble + ? Colors.transparent + : color, + borderRadius: + borderRadius, ), - // Pangea# - child: Container( - decoration: - BoxDecoration( - color: noBubble - ? Colors - .transparent - : color, - borderRadius: - borderRadius, - ), - clipBehavior: - Clip.antiAlias, - // #Pangea - child: - CompositedTransformTarget( - link: MatrixState + clipBehavior: + Clip.antiAlias, + // #Pangea + child: + CompositedTransformTarget( + link: MatrixState + .pAnyState + .layerLinkAndKey( + event.eventId, + ) + .link, + // child: BubbleBackground( + // colors: colors, + // ignore: noBubble || !ownMessage, + // scrollController: scrollController, + // Pangea# + child: Container( + // #Pangea + key: MatrixState .pAnyState .layerLinkAndKey( event.eventId, ) - .link, - // child: BubbleBackground( - // colors: colors, - // ignore: noBubble || !ownMessage, - // scrollController: scrollController, + .key, // Pangea# - child: Container( - // #Pangea - key: MatrixState - .pAnyState - .layerLinkAndKey( - event.eventId, - ) - .key, - // Pangea# - decoration: - BoxDecoration( - borderRadius: - BorderRadius - .circular( - AppConfig - .borderRadius, - ), + decoration: + BoxDecoration( + borderRadius: + BorderRadius + .circular( + AppConfig + .borderRadius, ), - constraints: - const BoxConstraints( - maxWidth: FluffyThemes - .columnWidth * - 1.5, - ), - child: Column( - mainAxisSize: - MainAxisSize - .min, - crossAxisAlignment: - CrossAxisAlignment - .start, - children: [ - if ({ - RelationshipTypes - .reply, - RelationshipTypes - .thread, - }.contains( - event - .relationshipType, - )) - FutureBuilder< - Event?>( - future: event - .getReplyEvent( - timeline, - ), - builder: ( - BuildContext - context, - snapshot, - ) { - final replyEvent = snapshot - .hasData - ? snapshot - .data! - : Event( - eventId: event.relationshipEventId!, - content: { - 'msgtype': 'm.text', - 'body': '...', - }, - // #Pangea - // senderId: event - // .senderId, - senderId: "", - // Pangea# - type: 'm.room.message', - room: event.room, - status: EventStatus.sent, - originServerTs: DateTime.now(), - ); - return Padding( - padding: - const EdgeInsets.only( - left: - 16, - right: - 16, - top: - 8, - ), + ), + constraints: + const BoxConstraints( + maxWidth: FluffyThemes + .columnWidth * + 1.5, + ), + child: Column( + mainAxisSize: + MainAxisSize + .min, + crossAxisAlignment: + CrossAxisAlignment + .start, + children: [ + if ({ + RelationshipTypes + .reply, + RelationshipTypes + .thread, + }.contains( + event + .relationshipType, + )) + FutureBuilder< + Event?>( + future: event + .getReplyEvent( + timeline, + ), + builder: ( + BuildContext + context, + snapshot, + ) { + final replyEvent = snapshot + .hasData + ? snapshot + .data! + : Event( + eventId: + event.relationshipEventId!, + content: { + 'msgtype': 'm.text', + 'body': '...', + }, + // #Pangea + // senderId: event + // .senderId, + senderId: + "", + // Pangea# + type: + 'm.room.message', + room: + event.room, + status: + EventStatus.sent, + originServerTs: + DateTime.now(), + ); + return Padding( + padding: + const EdgeInsets + .only( + left: + 16, + right: + 16, + top: 8, + ), + child: + Material( + color: Colors + .transparent, + borderRadius: + ReplyContent.borderRadius, child: - Material( - color: - Colors.transparent, + InkWell( borderRadius: ReplyContent.borderRadius, + onTap: () => + scrollToEventId( + replyEvent.eventId, + ), child: - InkWell( - borderRadius: - ReplyContent.borderRadius, - onTap: () => - scrollToEventId( - replyEvent.eventId, - ), + AbsorbPointer( child: - AbsorbPointer( - child: ReplyContent( - replyEvent, - ownMessage: ownMessage, - timeline: timeline, - ), + ReplyContent( + replyEvent, + ownMessage: ownMessage, + timeline: timeline, ), ), ), - ); - }, - ), - MessageContent( - displayEvent, - textColor: - textColor, - linkColor: - linkColor, - onInfoTab: - onInfoTab, - borderRadius: - borderRadius, - timeline: - timeline, - selected: - selected, - // #Pangea - pangeaMessageEvent: - pangeaMessageEvent, - controller: - controller, - nextEvent: - nextEvent, - prevEvent: - previousEvent, - // Pangea# + ), + ); + }, ), - if (event - .hasAggregatedEvents( - timeline, - RelationshipTypes - .edit, - )) - Padding( - padding: - const EdgeInsets - .only( - bottom: - 8.0, - left: - 16.0, - right: - 16.0, - ), - child: Row( - mainAxisSize: - MainAxisSize - .min, - spacing: - 4.0, - children: [ - Icon( - Icons - .edit_outlined, + MessageContent( + displayEvent, + textColor: + textColor, + linkColor: + linkColor, + onInfoTab: + onInfoTab, + borderRadius: + borderRadius, + timeline: + timeline, + selected: + selected, + // #Pangea + pangeaMessageEvent: + pangeaMessageEvent, + controller: + controller, + nextEvent: + nextEvent, + prevEvent: + previousEvent, + // Pangea# + ), + if (event + .hasAggregatedEvents( + timeline, + RelationshipTypes + .edit, + )) + Padding( + padding: + const EdgeInsets + .only( + bottom: 8.0, + left: 16.0, + right: 16.0, + ), + child: Row( + mainAxisSize: + MainAxisSize + .min, + spacing: + 4.0, + children: [ + Icon( + Icons + .edit_outlined, + color: textColor + .withAlpha( + 164, + ), + size: + 14, + ), + Text( + displayEvent + .originServerTs + .localizedTimeShort( + context, + ), + style: + TextStyle( color: textColor.withAlpha( 164, ), - size: - 14, + fontSize: + 11, ), - Text( - displayEvent - .originServerTs - .localizedTimeShort( - context, - ), - style: - TextStyle( - color: - textColor.withAlpha( - 164, - ), - fontSize: - 11, - ), - ), - ], - ), + ), + ], ), - ], - ), + ), + ], ), ), ), diff --git a/lib/pages/chat_list/chat_list_body.dart b/lib/pages/chat_list/chat_list_body.dart index aa81b55d4..7e998777c 100644 --- a/lib/pages/chat_list/chat_list_body.dart +++ b/lib/pages/chat_list/chat_list_body.dart @@ -380,6 +380,7 @@ class ChatListViewBody extends StatelessWidget { .setToggledOff(true), ), title: Text(L10n.of(context).chatWithSupport), + subtitle: Text(L10n.of(context).supportSubtitle), onTap: () async { await showFutureLoadingDialog( context: context, diff --git a/lib/pages/new_private_chat/new_private_chat.dart b/lib/pages/new_private_chat/new_private_chat.dart index 543031c9f..45f94a302 100644 --- a/lib/pages/new_private_chat/new_private_chat.dart +++ b/lib/pages/new_private_chat/new_private_chat.dart @@ -9,6 +9,7 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pages/new_private_chat/new_private_chat_view.dart'; import 'package:fluffychat/pages/new_private_chat/qr_scanner_modal.dart'; +import 'package:fluffychat/pangea/user/user_search_extension.dart'; import 'package:fluffychat/utils/adaptive_bottom_sheet.dart'; import 'package:fluffychat/utils/fluffy_share.dart'; import 'package:fluffychat/utils/platform_infos.dart'; @@ -52,8 +53,11 @@ class NewPrivateChatController extends State { } Future> _searchUser(String searchTerm) async { - final result = - await Matrix.of(context).client.searchUserDirectory(searchTerm); + // #Pangea + // final result = + // await Matrix.of(context).client.searchUserDirectory(searchTerm); + final result = await Matrix.of(context).client.searchUser(searchTerm); + // Pangea# final profiles = result.results; if (searchTerm.isValidMatrixId && diff --git a/lib/pages/onboarding/space_code_onboarding_view.dart b/lib/pages/onboarding/space_code_onboarding_view.dart index 37dc648f9..7fe4dcd89 100644 --- a/lib/pages/onboarding/space_code_onboarding_view.dart +++ b/lib/pages/onboarding/space_code_onboarding_view.dart @@ -19,12 +19,26 @@ class SpaceCodeOnboardingView extends StatelessWidget { Widget build(BuildContext context) { return PangeaLoginScaffold( customAppBar: AppBar( - leading: BackButton( - onPressed: () => pLogoutAction( - context, - bypassWarning: true, + title: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 450, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BackButton( + onPressed: () => pLogoutAction( + context, + bypassWarning: true, + ), + ), + const SizedBox( + width: 40.0, + ), + ], ), ), + automaticallyImplyLeading: false, ), showAppName: false, mainAssetUrl: controller.profile?.avatarUrl, diff --git a/lib/pangea/activity_sessions/activity_room_extension.dart b/lib/pangea/activity_sessions/activity_room_extension.dart index 19bd114b9..e48a8cfd5 100644 --- a/lib/pangea/activity_sessions/activity_room_extension.dart +++ b/lib/pangea/activity_sessions/activity_room_extension.dart @@ -353,7 +353,8 @@ extension ActivityRoomExtension on Room { bool get isActivitySession => (roomType?.startsWith(PangeaRoomTypes.activitySession) == true || activityPlan != null) && - activityPlan?.isDeprecatedModel == false; + activityPlan?.isDeprecatedModel == false && + activityPlan?.activityId != null; String? get activityId { if (!isActivitySession) return null; diff --git a/lib/pangea/activity_sessions/activity_session_chat/activity_finished_status_message.dart b/lib/pangea/activity_sessions/activity_session_chat/activity_finished_status_message.dart index 971fa359f..dbd0b0578 100644 --- a/lib/pangea/activity_sessions/activity_session_chat/activity_finished_status_message.dart +++ b/lib/pangea/activity_sessions/activity_session_chat/activity_finished_status_message.dart @@ -9,6 +9,7 @@ import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart import 'package:fluffychat/pangea/activity_summary/activity_summary_model.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/common/widgets/error_indicator.dart'; +import 'package:fluffychat/pangea/languages/p_language_store.dart'; import 'package:fluffychat/widgets/matrix.dart'; class ActivityFinishedStatusMessage extends StatelessWidget { @@ -28,10 +29,17 @@ class ActivityFinishedStatusMessage extends StatelessWidget { Future _archiveToAnalytics() async { try { + final activityPlan = controller.room.activityPlan; + if (activityPlan == null) { + throw Exception("No activity plan found for room"); + } + + final lang = activityPlan.req.targetLanguage.split("-").first; + final langModel = PLanguageStore.byLangCode(lang)!; await controller.room.archiveActivity(); await MatrixState .pangeaController.matrixState.analyticsDataService.updateService - .sendActivityAnalytics(controller.room.id); + .sendActivityAnalytics(controller.room.id, langModel); } catch (e, s) { ErrorHandler.logError( e: e, diff --git a/lib/pangea/activity_sessions/activity_session_start/activity_session_start_page.dart b/lib/pangea/activity_sessions/activity_session_start/activity_session_start_page.dart index 95a3366e2..ecfaeae14 100644 --- a/lib/pangea/activity_sessions/activity_session_start/activity_session_start_page.dart +++ b/lib/pangea/activity_sessions/activity_session_start/activity_session_start_page.dart @@ -14,6 +14,7 @@ import 'package:fluffychat/pangea/activity_sessions/activity_session_start/activ import 'package:fluffychat/pangea/activity_sessions/activity_session_start/bot_join_error_dialog.dart'; import 'package:fluffychat/pangea/bot/utils/bot_name.dart'; import 'package:fluffychat/pangea/chat_settings/utils/room_summary_extension.dart'; +import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/course_plans/course_activities/activity_summaries_provider.dart'; import 'package:fluffychat/pangea/course_plans/course_activities/course_activity_repo.dart'; import 'package:fluffychat/pangea/course_plans/course_activities/course_activity_translation_request.dart'; @@ -191,7 +192,7 @@ class ActivitySessionStartController extends State final availableRoles = activity!.roles; final assignedRoles = activityRoom?.assignedRoles ?? - roomSummaries?[widget.roomId]?.activityRoles.roles ?? + roomSummaries?[widget.roomId]?.activityRoles?.roles ?? {}; final unassignedIds = availableRoles.keys .where((id) => !assignedRoles.containsKey(id)) @@ -293,8 +294,17 @@ class ActivitySessionStartController extends State ); } await Future.wait(futures); - } catch (e) { + } catch (e, s) { error = e; + ErrorHandler.logError( + e: e, + s: s, + data: { + "activityId": widget.activityId, + "roomId": widget.roomId, + "parentId": widget.parentId, + }, + ); } finally { if (mounted) { setState(() => loading = false); diff --git a/lib/pangea/activity_sessions/activity_session_start/activity_sessions_start_view.dart b/lib/pangea/activity_sessions/activity_session_start/activity_sessions_start_view.dart index 788a94f21..eb11a47fa 100644 --- a/lib/pangea/activity_sessions/activity_session_start/activity_sessions_start_view.dart +++ b/lib/pangea/activity_sessions/activity_session_start/activity_sessions_start_view.dart @@ -518,7 +518,7 @@ class _ActivityStatuses extends StatelessWidget { // room (like the bot). Otherwise, show only joined users with roles Map activityRoles = status == ActivitySummaryStatus.completed - ? e.value.activityRoles.roles + ? (e.value.activityRoles?.roles ?? {}) : e.value.joinedUsersWithRoles; // If the user is in the activity room and it's not completed, use the room's @@ -530,7 +530,7 @@ class _ActivityStatuses extends StatelessWidget { return ListTile( title: OpenRolesIndicator( - roles: activityPlan.roles.values + roles: (activityPlan?.roles.values ?? []) .sorted((a, b) => a.id.compareTo(b.id)) .toList(), assignedRoles: activityRoles.values.toList(), diff --git a/lib/pangea/analytics_data/analytics_update_service.dart b/lib/pangea/analytics_data/analytics_update_service.dart index d2bfd164e..e0cd2d66c 100644 --- a/lib/pangea/analytics_data/analytics_update_service.dart +++ b/lib/pangea/analytics_data/analytics_update_service.dart @@ -128,12 +128,14 @@ class AnalyticsUpdateService { await future; } - Future sendActivityAnalytics(String roomId) async { - final analyticsRoom = await _getAnalyticsRoom(); + Future sendActivityAnalytics(String roomId, LanguageModel lang) async { + final analyticsRoom = await _getAnalyticsRoom(l2Override: lang); if (analyticsRoom == null) return; await analyticsRoom.addActivityRoomId(roomId); - dataService.updateDispatcher.sendActivityAnalyticsUpdate(roomId); + if (lang.langCodeShort == _l2?.langCodeShort) { + dataService.updateDispatcher.sendActivityAnalyticsUpdate(roomId); + } } Future blockConstruct(ConstructIdentifier constructId) async { diff --git a/lib/pangea/analytics_misc/example_message_util.dart b/lib/pangea/analytics_misc/example_message_util.dart index bbbc3b76a..f9c5713aa 100644 --- a/lib/pangea/analytics_misc/example_message_util.dart +++ b/lib/pangea/analytics_misc/example_message_util.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:collection/collection.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/pangea/analytics_misc/client_analytics_extension.dart'; @@ -50,42 +49,110 @@ class ExampleMessageUtil { String? form, PangeaMessageEvent messageEvent, ) { - PangeaToken? token; String? text; + List? tokens; + int targetTokenIndex = -1; if (messageEvent.isAudioMessage) { final stt = messageEvent.getSpeechToTextLocal(); if (stt == null) return null; - final tokens = stt.transcript.sttTokens.map((t) => t.token).toList(); - token = tokens.firstWhereOrNull( - (token) => token.text.content == form, - ); + + tokens = stt.transcript.sttTokens.map((t) => t.token).toList(); + targetTokenIndex = tokens.indexWhere((t) => t.text.content == form); text = stt.transcript.text; } else { - final tokens = messageEvent.messageDisplayRepresentation?.tokens; + tokens = messageEvent.messageDisplayRepresentation?.tokens; if (tokens == null || tokens.isEmpty) return null; - token = tokens.firstWhereOrNull( - (token) => token.text.content == form, - ); + + targetTokenIndex = tokens.indexWhere((t) => t.text.content == form); text = messageEvent.messageDisplayText; } - if (token == null) return null; + if (targetTokenIndex == -1) { + return null; + } - final before = text.characters.take(token.text.offset).toString(); - final after = text.characters - .skip(token.text.offset + token.text.content.characters.length) + final targetToken = tokens[targetTokenIndex]; + + const maxContextChars = 100; + + final targetStart = targetToken.text.offset; + final targetEnd = targetStart + targetToken.text.content.characters.length; + + final totalChars = text.characters.length; + + final beforeAvailable = targetStart; + final afterAvailable = totalChars - targetEnd; + + // ---------- Dynamic budget split ---------- + int beforeBudget = maxContextChars ~/ 2; + int afterBudget = maxContextChars - beforeBudget; + + if (beforeAvailable < beforeBudget) { + afterBudget += beforeBudget - beforeAvailable; + beforeBudget = beforeAvailable; + } else if (afterAvailable < afterBudget) { + beforeBudget += afterBudget - afterAvailable; + afterBudget = afterAvailable; + } + + // ---------- BEFORE ---------- + int beforeStartOffset = 0; + bool trimmedBefore = false; + + if (beforeAvailable > beforeBudget) { + final desiredStart = targetStart - beforeBudget; + + for (int i = 0; i < targetTokenIndex; i++) { + final token = tokens[i]; + final tokenEnd = + token.text.offset + token.text.content.characters.length; + + if (tokenEnd > desiredStart) { + beforeStartOffset = token.text.offset; + trimmedBefore = true; + break; + } + } + } + + final before = text.characters + .skip(beforeStartOffset) + .take(targetStart - beforeStartOffset) .toString(); + // ---------- AFTER ---------- + int afterEndOffset = totalChars; + bool trimmedAfter = false; + + if (afterAvailable > afterBudget) { + final desiredEnd = targetEnd + afterBudget; + + for (int i = targetTokenIndex + 1; i < tokens.length; i++) { + final token = tokens[i]; + if (token.text.offset >= desiredEnd) { + afterEndOffset = token.text.offset; + trimmedAfter = true; + break; + } + } + } + + final after = text.characters + .skip(targetEnd) + .take(afterEndOffset - targetEnd) + .toString() + .trimRight(); + return [ + if (trimmedBefore) const TextSpan(text: '… '), TextSpan(text: before), TextSpan( - text: token.text.content, - style: const TextStyle( - fontWeight: FontWeight.bold, - ), + text: targetToken.text.content, + style: const TextStyle(fontWeight: FontWeight.bold), ), TextSpan(text: after), + if (trimmedAfter) const TextSpan(text: '…'), ]; } } diff --git a/lib/pangea/analytics_practice/analytics_practice_view.dart b/lib/pangea/analytics_practice/analytics_practice_view.dart index 2724a191b..2fd5c9af5 100644 --- a/lib/pangea/analytics_practice/analytics_practice_view.dart +++ b/lib/pangea/analytics_practice/analytics_practice_view.dart @@ -1,5 +1,3 @@ -import 'package:flutter/material.dart'; - import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/l10n/l10n.dart'; @@ -25,6 +23,7 @@ import 'package:fluffychat/pangea/practice_activities/practice_activity_model.da 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'; class AnalyticsPracticeView extends StatelessWidget { final AnalyticsPracticeState controller; @@ -113,110 +112,78 @@ class _AnalyticsActivityView extends StatelessWidget { @override Widget build(BuildContext context) { - return Column( + final isColumnMode = FluffyThemes.isColumnMode(context); + TextStyle? titleStyle = isColumnMode + ? Theme.of(context).textTheme.titleLarge + : Theme.of(context).textTheme.titleMedium; + titleStyle = titleStyle?.copyWith(fontWeight: FontWeight.bold); + + return ListView( children: [ //per-activity instructions, add switch statement once there are more types const InstructionsInlineTooltip( instructionsEnum: InstructionsEnum.selectMeaning, padding: EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 24.0, + vertical: 8.0, ), ), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - ValueListenableBuilder( - valueListenable: controller.activityTarget, - builder: (context, target, __) => target != null - ? Padding( - padding: const EdgeInsets.only( - left: 16.0, - right: 16.0, - top: 16.0, + SizedBox( + height: 75.0, + child: ValueListenableBuilder( + valueListenable: controller.activityTarget, + builder: (context, target, __) => target != null + ? Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + target.promptText(context), + textAlign: TextAlign.center, + style: titleStyle, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + if (controller.widget.type == ConstructTypeEnum.vocab) + PhoneticTranscriptionWidget( + text: + target.target.tokens.first.vocabConstructID.lemma, + textLanguage: MatrixState + .pangeaController.userController.userL2!, + style: const TextStyle(fontSize: 14.0), ), - child: Column( - spacing: 12.0, - children: [ - Text( - target.promptText(context), - textAlign: TextAlign.center, - style: FluffyThemes.isColumnMode(context) - ? Theme.of(context) - .textTheme - .titleLarge - ?.copyWith( - fontWeight: FontWeight.bold, - ) - : Theme.of(context) - .textTheme - .titleMedium - ?.copyWith( - fontWeight: FontWeight.bold, - ), - ), - if (controller.widget.type == - ConstructTypeEnum.vocab) - PhoneticTranscriptionWidget( - text: target - .target.tokens.first.vocabConstructID.lemma, - textLanguage: MatrixState - .pangeaController.userController.userL2!, - style: const TextStyle(fontSize: 14.0), - ), - ], - ), - ) - : const SizedBox.shrink(), - ), - Flexible( - fit: FlexFit.loose, - child: SingleChildScrollView( - child: _AnalyticsPracticeCenterContent( - controller: controller, - ), - ), - ), - Expanded( - child: _ActivityChoicesWidget(controller), - ), - //reserve space for grammar category morph meaning to avoid shifting, but only in those questions - AnimatedBuilder( - animation: Listenable.merge([ - controller.activityState, - controller.selectedMorphChoice, - ]), - builder: (context, _) { - final activityState = controller.activityState.value; - final selectedChoice = controller.selectedMorphChoice.value; - - final isGrammarCategory = activityState - is AsyncLoaded && - activityState.value.activityType == - ActivityTypeEnum.grammarCategory; - - if (!isGrammarCategory) { - return const SizedBox.shrink(); - } - - return SizedBox( - height: 125.0, - child: selectedChoice == null - ? const SizedBox.shrink() - : SingleChildScrollView( - child: MorphMeaningWidget( - feature: selectedChoice.feature, - tag: selectedChoice.tag, - blankErrorFeedback: true, - ), - ), - ); - }, - ), - ], + ], + ) + : const SizedBox.shrink(), ), ), + const SizedBox(height: 16.0), + Center( + child: _AnalyticsPracticeCenterContent(controller: controller), + ), + const SizedBox(height: 16.0), + _ActivityChoicesWidget(controller), + const SizedBox(height: 16.0), + ListenableBuilder( + listenable: Listenable.merge([ + controller.activityState, + controller.selectedMorphChoice, + ]), + builder: (context, _) { + final activityState = controller.activityState.value; + final selectedChoice = controller.selectedMorphChoice.value; + + if (activityState + is! AsyncLoaded || + selectedChoice == null) { + return const SizedBox.shrink(); + } + + return MorphMeaningWidget( + feature: selectedChoice.feature, + tag: selectedChoice.tag, + blankErrorFeedback: true, + ); + }, + ), ], ); } @@ -235,29 +202,39 @@ class _AnalyticsPracticeCenterContent extends StatelessWidget { valueListenable: controller.activityTarget, builder: (context, target, __) => switch (target?.target.activityType) { null => const SizedBox(), - ActivityTypeEnum.grammarError => ValueListenableBuilder( - valueListenable: controller.activityState, - builder: (context, state, __) => switch (state) { - AsyncLoaded( - value: final GrammarErrorPracticeActivityModel activity - ) => - Column( - mainAxisSize: MainAxisSize.min, - children: [ - _ErrorBlankWidget( - key: ValueKey( - '${activity.eventID}_${activity.errorOffset}_${activity.errorLength}', - ), - activity: activity, + ActivityTypeEnum.grammarError => SizedBox( + height: 160.0, + child: SingleChildScrollView( + child: ValueListenableBuilder( + valueListenable: controller.activityState, + builder: (context, state, __) => switch (state) { + AsyncLoaded( + value: final GrammarErrorPracticeActivityModel activity + ) => + Column( + mainAxisSize: MainAxisSize.min, + children: [ + _ErrorBlankWidget( + key: ValueKey( + '${activity.eventID}_${activity.errorOffset}_${activity.errorLength}', + ), + activity: activity, + ), + const SizedBox(height: 12), + ], ), - const SizedBox(height: 12), - ], - ), - _ => const SizedBox(), - }, + _ => const SizedBox(), + }, + ), + ), ), - _ => _ExampleMessageWidget( - controller.getExampleMessage(target!.target), + _ => SizedBox( + height: 100.0, + child: Center( + child: _ExampleMessageWidget( + controller.getExampleMessage(target!.target), + ), + ), ), }, ); @@ -333,6 +310,51 @@ class _ErrorBlankWidgetState extends State<_ErrorBlankWidget> { final errorOffset = widget.activity.errorOffset; final errorLength = widget.activity.errorLength; + const maxContextChars = 50; + + final chars = text.characters; + final totalLength = chars.length; + + // ---------- BEFORE ---------- + int beforeStart = 0; + bool trimmedBefore = false; + + if (errorOffset > maxContextChars) { + int desiredStart = errorOffset - maxContextChars; + + // Snap left to nearest whitespace to avoid cutting words + while (desiredStart > 0 && chars.elementAt(desiredStart) != ' ') { + desiredStart--; + } + + beforeStart = desiredStart; + trimmedBefore = true; + } + + final before = + chars.skip(beforeStart).take(errorOffset - beforeStart).toString(); + + // ---------- AFTER ---------- + int afterEnd = totalLength; + bool trimmedAfter = false; + + final errorEnd = errorOffset + errorLength; + final afterChars = totalLength - errorEnd; + + if (afterChars > maxContextChars) { + int desiredEnd = errorEnd + maxContextChars; + + // Snap right to nearest whitespace + while (desiredEnd < totalLength && chars.elementAt(desiredEnd) != ' ') { + desiredEnd++; + } + + afterEnd = desiredEnd; + trimmedAfter = true; + } + + final after = chars.skip(errorEnd).take(afterEnd - errorEnd).toString(); + return Column( children: [ Container( @@ -357,10 +379,8 @@ class _ErrorBlankWidgetState extends State<_ErrorBlankWidget> { AppConfig.fontSizeFactor * AppConfig.messageFontSize, ), children: [ - if (errorOffset > 0) - TextSpan( - text: text.characters.take(errorOffset).toString(), - ), + if (trimmedBefore) const TextSpan(text: '…'), + if (before.isNotEmpty) TextSpan(text: before), WidgetSpan( child: Container( height: 4.0, @@ -371,12 +391,8 @@ class _ErrorBlankWidgetState extends State<_ErrorBlankWidget> { ), ), ), - if (errorOffset + errorLength < text.length) - TextSpan( - text: text.characters - .skip(errorOffset + errorLength) - .toString(), - ), + if (after.isNotEmpty) TextSpan(text: after), + if (trimmedAfter) const TextSpan(text: '…'), ], ), ), @@ -464,43 +480,29 @@ class _ActivityChoicesWidget extends StatelessWidget { ], ), AsyncLoaded(:final value) => - LayoutBuilder( - builder: (context, constraints) { + ValueListenableBuilder( + valueListenable: controller.enableChoicesNotifier, + builder: (context, enabled, __) { final choices = controller.filteredChoices(value); - final constrainedHeight = - constraints.maxHeight.clamp(0.0, 400.0); - final cardHeight = (constrainedHeight / (choices.length + 1)) - .clamp(50.0, 80.0); - - return ValueListenableBuilder( - valueListenable: controller.enableChoicesNotifier, - builder: (context, enabled, __) => Column( - children: [ - Expanded( - child: Column( - spacing: 4.0, - mainAxisAlignment: MainAxisAlignment.center, - children: choices - .map( - (choice) => _ChoiceCard( - activity: value, - targetId: controller - .choiceTargetId(choice.choiceId), - choiceId: choice.choiceId, - onPressed: () => controller.onSelectChoice( - choice.choiceId, - ), - cardHeight: cardHeight, - choiceText: choice.choiceText, - choiceEmoji: choice.choiceEmoji, - enabled: enabled, - ), - ) - .toList(), + return Column( + spacing: 8.0, + mainAxisAlignment: MainAxisAlignment.center, + children: choices + .map( + (choice) => _ChoiceCard( + activity: value, + targetId: controller.choiceTargetId(choice.choiceId), + choiceId: choice.choiceId, + onPressed: () => controller.onSelectChoice( + choice.choiceId, + ), + cardHeight: 60.0, + choiceText: choice.choiceText, + choiceEmoji: choice.choiceEmoji, + enabled: enabled, ), - ), - ], - ), + ) + .toList(), ); }, ), diff --git a/lib/pangea/analytics_practice/choice_cards/game_choice_card.dart b/lib/pangea/analytics_practice/choice_cards/game_choice_card.dart index 58c5095a1..88ee9b7f9 100644 --- a/lib/pangea/analytics_practice/choice_cards/game_choice_card.dart +++ b/lib/pangea/analytics_practice/choice_cards/game_choice_card.dart @@ -149,8 +149,6 @@ class _CardContainer extends StatelessWidget { Widget build(BuildContext context) { return Container( height: height, - margin: const EdgeInsets.symmetric(vertical: 6), - padding: const EdgeInsets.symmetric(horizontal: 16), alignment: Alignment.center, decoration: BoxDecoration( color: baseColor, diff --git a/lib/pangea/bot/utils/bot_room_extension.dart b/lib/pangea/bot/utils/bot_room_extension.dart index b658235ed..830ca6a84 100644 --- a/lib/pangea/bot/utils/bot_room_extension.dart +++ b/lib/pangea/bot/utils/bot_room_extension.dart @@ -3,6 +3,7 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/pangea/bot/utils/bot_name.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/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; extension BotRoomExtension on Room { @@ -23,11 +24,40 @@ extension BotRoomExtension on Room { return BotOptionsModel.fromJson(stateEvent.content); } - Future setBotOptions(BotOptionsModel options) => - client.setRoomStateWithKey( - id, - PangeaEventTypes.botOptions, - '', - options.toJson(), - ); + Future setBotOptions(BotOptionsModel options) async { + const maxRetries = 3; + Duration retryDelay = const Duration(seconds: 5); + + for (int attempt = 1; attempt <= maxRetries; attempt++) { + try { + if (attempt > 1) { + await Future.delayed(retryDelay); + retryDelay *= 2; + } + + await client.setRoomStateWithKey( + id, + PangeaEventTypes.botOptions, + '', + options.toJson(), + ); + + return; + } catch (e, s) { + ErrorHandler.logError( + e: e, + s: s, + data: { + 'roomId': id, + 'options': options.toJson(), + 'attempt': attempt, + }, + ); + + if (attempt == maxRetries) { + rethrow; + } + } + } + } } diff --git a/lib/pangea/chat_settings/pages/pangea_invitation_selection.dart b/lib/pangea/chat_settings/pages/pangea_invitation_selection.dart index e7d5e5d92..bdf7e050c 100644 --- a/lib/pangea/chat_settings/pages/pangea_invitation_selection.dart +++ b/lib/pangea/chat_settings/pages/pangea_invitation_selection.dart @@ -9,10 +9,10 @@ import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart'; import 'package:fluffychat/pangea/bot/utils/bot_name.dart'; import 'package:fluffychat/pangea/chat_settings/pages/pangea_invitation_selection_view.dart'; -import 'package:fluffychat/pangea/common/config/environment.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/extensions/join_rule_extension.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; +import 'package:fluffychat/pangea/user/user_search_extension.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/widgets/future_loading_dialog.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -337,20 +337,11 @@ class PangeaInvitationSelectionController setState(() => foundProfiles = []); } - String pangeaSearchText = text; - if (!pangeaSearchText.startsWith("@")) { - pangeaSearchText = "@$pangeaSearchText"; - } - if (!pangeaSearchText.contains(":")) { - pangeaSearchText = "$pangeaSearchText:${Environment.homeServer}"; - } - setState(() => loading = true); final matrix = Matrix.of(context); SearchUserDirectoryResponse response; try { - response = - await matrix.client.searchUserDirectory(pangeaSearchText, limit: 100); + response = await matrix.client.searchUser(text, limit: 100); } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text((e).toLocalizedString(context))), diff --git a/lib/pangea/chat_settings/utils/bot_client_extension.dart b/lib/pangea/chat_settings/utils/bot_client_extension.dart index 5021d19ed..69c9b84cc 100644 --- a/lib/pangea/chat_settings/utils/bot_client_extension.dart +++ b/lib/pangea/chat_settings/utils/bot_client_extension.dart @@ -1,11 +1,13 @@ import 'package:collection/collection.dart'; import 'package:matrix/matrix.dart'; +import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart'; import 'package:fluffychat/pangea/bot/utils/bot_name.dart'; import 'package:fluffychat/pangea/bot/utils/bot_room_extension.dart'; 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/common/utils/error_handler.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'; @@ -15,10 +17,15 @@ extension BotClientExtension on Client { Room? get botDM => rooms.firstWhereOrNull((r) => r.isBotDM); // All 2-member rooms with the bot - List 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); + List get _targetBotChats => rooms.where((r) { + return + // bot settings exist + r.botOptions != null && + // there is no activity plan + r.activityPlan == null && + // it's just the bot and one other user in the room + r.summary.mJoinedMemberCount == 2 && + r.getParticipants().any((u) => u.id == BotName.byEnvironment); }).toList(); Future startChatWithBot() => startDirectChat( @@ -42,30 +49,41 @@ extension BotClientExtension on Client { ); Future updateBotOptions(UserSettings userSettings) async { - final rooms = targetBotChats; - if (rooms.isEmpty) return; + final targetBotRooms = [..._targetBotChats]; + if (targetBotRooms.isEmpty) return; - final futures = []; - for (final room in rooms) { - final botOptions = room.botOptions ?? const BotOptionsModel(); - final targetLanguage = userSettings.targetLanguage; - final languageLevel = userSettings.cefrLevel; - final voice = userSettings.voice; + try { + final futures = []; + for (final targetBotRoom in targetBotRooms) { + final botOptions = targetBotRoom.botOptions ?? const BotOptionsModel(); + final targetLanguage = userSettings.targetLanguage; + final languageLevel = userSettings.cefrLevel; + final voice = userSettings.voice; - if (botOptions.targetLanguage == targetLanguage && - botOptions.languageLevel == languageLevel && - botOptions.targetVoice == voice) { - continue; + if (botOptions.targetLanguage == targetLanguage && + botOptions.languageLevel == languageLevel && + botOptions.targetVoice == voice) { + continue; + } + + final updated = botOptions.copyWith( + targetLanguage: targetLanguage, + languageLevel: languageLevel, + targetVoice: voice, + ); + futures.add(targetBotRoom.setBotOptions(updated)); } - final updated = botOptions.copyWith( - targetLanguage: targetLanguage, - languageLevel: languageLevel, - targetVoice: voice, + await Future.wait(futures); + } catch (e, s) { + ErrorHandler.logError( + e: e, + s: s, + data: { + 'userSettings': userSettings.toJson(), + 'targetBotRooms': targetBotRooms.map((r) => r.id).toList(), + }, ); - futures.add(room.setBotOptions(updated)); } - - await Future.wait(futures); } } diff --git a/lib/pangea/chat_settings/utils/room_summary_extension.dart b/lib/pangea/chat_settings/utils/room_summary_extension.dart index fd72f3669..4315a11a7 100644 --- a/lib/pangea/chat_settings/utils/room_summary_extension.dart +++ b/lib/pangea/chat_settings/utils/room_summary_extension.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:collection/collection.dart'; import 'package:http/http.dart' hide Client; import 'package:matrix/matrix.dart'; import 'package:matrix/matrix_api_lite/generated/api.dart'; @@ -7,6 +8,8 @@ import 'package:matrix/matrix_api_lite/generated/api.dart'; import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart'; import 'package:fluffychat/pangea/activity_sessions/activity_role_model.dart'; import 'package:fluffychat/pangea/activity_sessions/activity_roles_model.dart'; +import 'package:fluffychat/pangea/activity_summary/activity_summary_model.dart'; +import 'package:fluffychat/pangea/course_plans/courses/course_plan_event.dart'; import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; extension RoomSummaryExtension on Api { @@ -52,27 +55,40 @@ class RoomSummariesResponse { }); return RoomSummariesResponse(summaries: summaries); } - - Map toJson() { - final json = {}; - summaries.forEach((key, value) { - json[key] = value.toJson(); - }); - return json; - } } class RoomSummaryResponse { - final ActivityPlanModel activityPlan; - final ActivityRolesModel activityRoles; + final ActivityPlanModel? activityPlan; + final ActivityRolesModel? activityRoles; + final ActivitySummaryModel? activitySummary; + final CoursePlanEvent? coursePlan; + + final JoinRules? joinRule; + final Map? powerLevels; final Map membershipSummary; + final String? displayName; + final String? avatarUrl; RoomSummaryResponse({ - required this.activityPlan, - required this.activityRoles, required this.membershipSummary, + this.activityPlan, + this.activityRoles, + this.activitySummary, + this.coursePlan, + this.joinRule, + this.powerLevels, + this.displayName, + this.avatarUrl, }); + List get adminUserIDs { + if (powerLevels == null) return []; + return powerLevels!.entries + .where((entry) => entry.value >= 100) + .map((entry) => entry.key) + .toList(); + } + Membership? getMembershipForUserId(String userId) { final membershipString = membershipSummary[userId]; if (membershipString == null) return null; @@ -83,32 +99,93 @@ class RoomSummaryResponse { } Map get joinedUsersWithRoles { + if (activityRoles == null) return {}; return Map.fromEntries( - activityRoles.roles.entries.where( + activityRoles!.roles.entries.where( (role) => getMembershipForUserId(role.value.userId) == Membership.join, ), ); } factory RoomSummaryResponse.fromJson(Map json) { + final planEntry = + json[PangeaEventTypes.activityPlan]?["default"]?["content"]; + ActivityPlanModel? plan; + if (planEntry != null && planEntry is Map) { + plan = ActivityPlanModel.fromJson(planEntry); + } + + final rolesEntry = + json[PangeaEventTypes.activityRole]?["default"]?["content"]; + ActivityRolesModel? roles; + if (rolesEntry != null && rolesEntry is Map) { + roles = ActivityRolesModel.fromJson(rolesEntry); + } + + final summaryEntry = + json[PangeaEventTypes.activitySummary]?["default"]?["content"]; + ActivitySummaryModel? summary; + if (summaryEntry != null && summaryEntry is Map) { + summary = ActivitySummaryModel.fromJson(summaryEntry); + } + + final coursePlanEntry = + json[PangeaEventTypes.coursePlan]?["default"]?["content"]; + CoursePlanEvent? coursePlan; + if (coursePlanEntry != null && coursePlanEntry is Map) { + coursePlan = CoursePlanEvent.fromJson(coursePlanEntry); + } + + final powerLevelsEntry = + json[EventTypes.RoomPowerLevels]?['default']?['content']?['users']; + Map? powerLevels; + if (powerLevelsEntry != null) { + powerLevels = Map.from(powerLevelsEntry); + } + + final joinRulesString = + json[EventTypes.RoomJoinRules]?['default']?['content']?['join_rule']; + JoinRules? joinRule; + if (joinRulesString != null && joinRulesString is String) { + joinRule = JoinRules.values + .singleWhereOrNull((element) => element.text == joinRulesString); + } + + final displayName = + json[EventTypes.RoomName]?['default']?['content']?['name'] as String?; + + String? avatarUrl = + json[EventTypes.RoomAvatar]?['default']?['content']?['url'] as String?; + if (avatarUrl != null && Uri.tryParse(avatarUrl) == null) { + avatarUrl = null; + } + return RoomSummaryResponse( - activityPlan: ActivityPlanModel.fromJson( - json[PangeaEventTypes.activityPlan]?["default"]?["content"] ?? {}, - ), - activityRoles: ActivityRolesModel.fromJson( - json[PangeaEventTypes.activityRole]?["default"]?["content"] ?? {}, - ), + activityPlan: plan, + activityRoles: roles, + activitySummary: summary, + coursePlan: coursePlan, + powerLevels: powerLevels, + joinRule: joinRule, membershipSummary: Map.from( json['membership_summary'] ?? {}, ), + displayName: displayName, + avatarUrl: avatarUrl, ); } Map toJson() { return { - PangeaEventTypes.activityPlan: activityPlan.toJson(), - PangeaEventTypes.activityRole: activityRoles.toJson(), - 'membership_summary': membershipSummary, + 'activityPlan': activityPlan?.toJson(), + 'activityRoles': activityRoles?.toJson(), + 'activitySummary': activitySummary?.toJson(), + 'coursePlan': coursePlan?.toJson(), + 'joinRule': joinRule?.text, + 'powerLevels': powerLevels, + 'membershipSummary': membershipSummary, + 'displayName': displayName, + 'avatarUrl': avatarUrl, }; } } diff --git a/lib/pangea/choreographer/choreographer.dart b/lib/pangea/choreographer/choreographer.dart index 0a5e11d2c..169f5edeb 100644 --- a/lib/pangea/choreographer/choreographer.dart +++ b/lib/pangea/choreographer/choreographer.dart @@ -131,7 +131,7 @@ class Choreographer extends ChangeNotifier { _choreoRecord = null; itController.closeIT(); itController.clearSourceText(); - itController.clearDissmissed(); + itController.clearSession(); igcController.clear(); _resetDebounceTimer(); _setChoreoMode(ChoreoModeEnum.igc); @@ -372,7 +372,9 @@ class Choreographer extends ChangeNotifier { } void _onCloseIT() { - if (currentText.isEmpty && itController.sourceText.value != null) { + if (itController.dismissed && + currentText.isEmpty && + itController.sourceText.value != null) { textController.setSystemText( itController.sourceText.value!, EditTypeEnum.itDismissed, diff --git a/lib/pangea/choreographer/it/it_controller.dart b/lib/pangea/choreographer/it/it_controller.dart index 0cb65b37e..34efef30d 100644 --- a/lib/pangea/choreographer/it/it_controller.dart +++ b/lib/pangea/choreographer/it/it_controller.dart @@ -65,8 +65,9 @@ class ITController { _sourceText.value = null; } - void clearDissmissed() { + void clearSession() { dismissed = false; + _progress.value = 0.0; } void dispose() { @@ -105,6 +106,7 @@ class ITController { _queue.clear(); _currentITStep.value = null; _goldRouteTracker = null; + _progress.value = 0.0; _sourceText.value = text; setEditingSourceText(false); _continueIT(); @@ -152,7 +154,6 @@ class ITController { ) + 1) / _goldRouteTracker!.continuances.length; - debugPrint("Progress updated to $progress"); _progress.value = progress; _continueIT(); } diff --git a/lib/pangea/common/widgets/shimmer_background.dart b/lib/pangea/common/widgets/shimmer_background.dart index 86b7cd49e..e3f5b83c7 100644 --- a/lib/pangea/common/widgets/shimmer_background.dart +++ b/lib/pangea/common/widgets/shimmer_background.dart @@ -22,30 +22,33 @@ class ShimmerBackground extends StatelessWidget { @override Widget build(BuildContext context) { + if (!enabled) { + return child; + } + final borderRadius = this.borderRadius ?? BorderRadius.circular(AppConfig.borderRadius); return Stack( children: [ child, - if (enabled) - Positioned.fill( - child: IgnorePointer( - child: ClipRRect( - borderRadius: borderRadius, - child: Shimmer.fromColors( - baseColor: baseColor ?? shimmerColor.withValues(alpha: 0.1), - highlightColor: shimmerColor.withValues(alpha: 0.6), - direction: ShimmerDirection.ltr, - child: Container( - decoration: BoxDecoration( - color: shimmerColor.withValues(alpha: 0.3), - borderRadius: borderRadius, - ), + Positioned.fill( + child: IgnorePointer( + child: ClipRRect( + borderRadius: borderRadius, + child: Shimmer.fromColors( + baseColor: baseColor ?? shimmerColor.withValues(alpha: 0.1), + highlightColor: shimmerColor.withValues(alpha: 0.6), + direction: ShimmerDirection.ltr, + child: Container( + decoration: BoxDecoration( + color: shimmerColor.withValues(alpha: 0.3), + borderRadius: borderRadius, ), ), ), ), ), + ), ], ); } diff --git a/lib/pangea/common/widgets/shrinkable_text.dart b/lib/pangea/common/widgets/shrinkable_text.dart index 18968ec48..8a3177ed2 100644 --- a/lib/pangea/common/widgets/shrinkable_text.dart +++ b/lib/pangea/common/widgets/shrinkable_text.dart @@ -4,11 +4,13 @@ class ShrinkableText extends StatelessWidget { final String text; final double maxWidth; final TextStyle? style; + final Alignment? alignment; const ShrinkableText({ super.key, required this.text, required this.maxWidth, + this.alignment, this.style, }); @@ -18,6 +20,7 @@ class ShrinkableText extends StatelessWidget { builder: (context, constraints) { return Container( constraints: BoxConstraints(maxWidth: maxWidth), + alignment: alignment, child: FittedBox( fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, diff --git a/lib/pangea/course_chats/course_chats_page.dart b/lib/pangea/course_chats/course_chats_page.dart index b01f8d04c..a26eeb56d 100644 --- a/lib/pangea/course_chats/course_chats_page.dart +++ b/lib/pangea/course_chats/course_chats_page.dart @@ -133,9 +133,13 @@ class CourseChatsController extends State } final activity = summary.activityPlan; + final roles = summary.activityRoles; final users = summary.joinedUsersWithRoles; - if (users.isEmpty || !validIDs.contains(activity.activityId)) { + if (activity == null || + roles == null || + users.isEmpty || + !validIDs.contains(activity.activityId)) { continue; } @@ -148,7 +152,7 @@ class CourseChatsController extends State // It's possible for users to finish an activity and then for some of the // users to leave, but if the activity was archived by anyone, that means // it was full at some point. - if (summary.activityRoles.roles.values.any((role) => role.isArchived)) { + if (roles.roles.values.any((role) => role.isArchived)) { continue; } @@ -233,7 +237,10 @@ class CourseChatsController extends State Logs().w('Unable to load hierarchy', e, s); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(e.toLocalizedString(context))), + SnackBar( + content: Text(e.toLocalizedString(context)), + showCloseIcon: true, + ), ); } } finally { diff --git a/lib/pangea/course_creation/public_course_preview.dart b/lib/pangea/course_creation/public_course_preview.dart new file mode 100644 index 000000000..15b5f3424 --- /dev/null +++ b/lib/pangea/course_creation/public_course_preview.dart @@ -0,0 +1,188 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +import 'package:go_router/go_router.dart'; +import 'package:matrix/matrix.dart'; + +import 'package:fluffychat/l10n/l10n.dart'; +import 'package:fluffychat/pangea/chat_settings/utils/room_summary_extension.dart'; +import 'package:fluffychat/pangea/common/utils/error_handler.dart'; +import 'package:fluffychat/pangea/course_creation/public_course_preview_view.dart'; +import 'package:fluffychat/pangea/course_plans/course_activities/activity_summaries_provider.dart'; +import 'package:fluffychat/pangea/course_plans/courses/course_plan_builder.dart'; +import 'package:fluffychat/pangea/join_codes/space_code_controller.dart'; +import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart'; +import 'package:fluffychat/widgets/future_loading_dialog.dart'; +import 'package:fluffychat/widgets/matrix.dart'; + +class PublicCoursePreview extends StatefulWidget { + final String? roomID; + + const PublicCoursePreview({ + super.key, + required this.roomID, + }); + + @override + PublicCoursePreviewController createState() => + PublicCoursePreviewController(); +} + +class PublicCoursePreviewController extends State + with CoursePlanProvider, ActivitySummariesProvider { + RoomSummaryResponse? roomSummary; + Object? roomSummaryError; + bool loadingRoomSummary = false; + + @override + initState() { + super.initState(); + _loadSummary(); + } + + @override + void didUpdateWidget(covariant PublicCoursePreview oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.roomID != oldWidget.roomID) { + _loadSummary(); + } + } + + bool get loading => loadingCourse || loadingRoomSummary; + bool get hasError => + (courseError != null || (!loadingCourse && course == null)) || + (roomSummaryError != null || + (!loadingRoomSummary && roomSummary == null)); + + Future _loadSummary() async { + try { + if (widget.roomID == null) { + throw Exception("roomID is required"); + } + + setState(() { + loadingRoomSummary = true; + roomSummaryError = null; + }); + + await loadRoomSummaries([widget.roomID!]); + if (roomSummaries == null || !roomSummaries!.containsKey(widget.roomID)) { + throw Exception("Room summary not found"); + } + + roomSummary = roomSummaries![widget.roomID]; + } catch (e, s) { + roomSummaryError = e; + loadingCourse = false; + + ErrorHandler.logError( + e: e, + s: s, + data: {'roomID': widget.roomID, 'roomSummary': roomSummary?.toJson()}, + ); + } finally { + if (mounted) { + setState(() { + loadingRoomSummary = false; + }); + } + } + + if (roomSummary?.coursePlan != null) { + await loadCourse(roomSummary!.coursePlan!.uuid).then((_) => loadTopics()); + } else { + ErrorHandler.logError( + e: Exception("No course plan found in room summary"), + data: {'roomID': widget.roomID, 'roomSummary': roomSummary?.toJson()}, + ); + if (mounted) { + setState(() { + roomSummaryError = Exception("No course plan found in room summary"); + loadingCourse = false; + }); + } + } + } + + Future joinWithCode(String code) async { + if (code.isEmpty) { + return; + } + + final roomId = await SpaceCodeController.joinSpaceWithCode( + context, + code, + ); + + if (roomId != null) { + final room = Matrix.of(context).client.getRoomById(roomId); + room?.isSpace ?? true + ? context.go('/rooms/spaces/$roomId/details') + : context.go('/rooms/$roomId'); + } + } + + Future joinCourse() async { + if (widget.roomID == null) { + throw Exception("roomID is required"); + } + + final roomID = widget.roomID; + + final client = Matrix.of(context).client; + final r = client.getRoomById(roomID!); + if (r != null && r.membership == Membership.join) { + if (mounted) { + context.go("/rooms/spaces/${r.id}/details"); + } + return; + } + + final knock = roomSummary?.joinRule == JoinRules.knock; + final resp = await showFutureLoadingDialog( + context: context, + future: () async { + String roomId; + try { + roomId = knock + ? await client.knockRoom(widget.roomID!) + : await client.joinRoom(widget.roomID!); + } catch (e, s) { + ErrorHandler.logError( + e: e, + s: s, + data: {'roomID': widget.roomID}, + ); + rethrow; + } + + Room? room = client.getRoomById(roomId); + if (!knock && room?.membership != Membership.join) { + await client.waitForRoomInSync(roomId, join: true); + room = client.getRoomById(roomId); + } + + if (knock) return; + if (room == null) { + ErrorHandler.logError( + e: Exception("Failed to load joined room in public course preview"), + data: {'roomID': widget.roomID}, + ); + throw Exception("Failed to join room"); + } + context.go("/rooms/spaces/$roomId/details"); + }, + ); + + if (!knock || resp.isError) return; + await showOkAlertDialog( + context: context, + title: L10n.of(context).youHaveKnocked, + message: L10n.of(context).knockDesc, + ); + } + + @override + Widget build(BuildContext context) => PublicCoursePreviewView(this); +} diff --git a/lib/pangea/course_creation/public_course_preview_view.dart b/lib/pangea/course_creation/public_course_preview_view.dart new file mode 100644 index 000000000..213560f07 --- /dev/null +++ b/lib/pangea/course_creation/public_course_preview_view.dart @@ -0,0 +1,388 @@ +import 'package:flutter/material.dart'; + +import 'package:matrix/matrix.dart'; + +import 'package:fluffychat/l10n/l10n.dart'; +import 'package:fluffychat/pangea/chat_settings/utils/room_summary_extension.dart'; +import 'package:fluffychat/pangea/common/widgets/error_indicator.dart'; +import 'package:fluffychat/pangea/common/widgets/url_image_widget.dart'; +import 'package:fluffychat/pangea/course_creation/course_info_chip_widget.dart'; +import 'package:fluffychat/pangea/course_creation/public_course_preview.dart'; +import 'package:fluffychat/pangea/course_plans/map_clipper.dart'; +import 'package:fluffychat/pangea/course_settings/pin_clipper.dart'; +import 'package:fluffychat/widgets/adaptive_dialogs/user_dialog.dart'; +import 'package:fluffychat/widgets/avatar.dart'; +import 'package:fluffychat/widgets/matrix.dart'; + +class PublicCoursePreviewView extends StatelessWidget { + final PublicCoursePreviewController controller; + const PublicCoursePreviewView( + this.controller, { + super.key, + }); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + const double titleFontSize = 16.0; + const double descFontSize = 12.0; + + const double largeIconSize = 24.0; + const double smallIconSize = 12.0; + + return Scaffold( + appBar: AppBar( + title: Text(L10n.of(context).joinWithClassCode), + ), + body: SafeArea( + child: Container( + alignment: Alignment.topCenter, + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 500.0), + child: Builder( + builder: (context) { + if (controller.loading) { + return const Center( + child: CircularProgressIndicator.adaptive(), + ); + } + + if (controller.hasError) { + return Center( + child: ErrorIndicator( + message: L10n.of(context).oopsSomethingWentWrong, + ), + ); + } + + final course = controller.course!; + final summary = controller.roomSummary!; + + Uri? avatarUrl = course.imageUrl; + if (summary.avatarUrl != null) { + avatarUrl = Uri.tryParse(summary.avatarUrl!); + } + + final displayname = summary.displayName ?? course.title; + + return Column( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.only( + top: 12.0, + left: 12.0, + right: 12.0, + ), + child: ListView.builder( + itemCount: course.topicIds.length + 2, + itemBuilder: (context, index) { + if (index == 0) { + return Column( + spacing: 8.0, + children: [ + ClipPath( + clipper: MapClipper(), + child: ImageByUrl( + imageUrl: avatarUrl, + width: 100.0, + borderRadius: BorderRadius.circular(0.0), + replacement: Avatar( + name: displayname, + size: 100.0, + borderRadius: BorderRadius.circular( + 0.0, + ), + ), + ), + ), + Text( + displayname, + style: const TextStyle( + fontSize: titleFontSize, + ), + ), + if (summary.adminUserIDs.isNotEmpty) + _CourseAdminDisplay(summary), + Text( + course.description, + style: const TextStyle( + fontSize: descFontSize, + ), + ), + Row( + spacing: 8.0, + mainAxisSize: MainAxisSize.min, + children: [ + CourseInfoChips( + course.uuid, + fontSize: descFontSize, + iconSize: smallIconSize, + ), + CourseInfoChip( + icon: Icons.person, + text: + L10n.of(context).countParticipants( + summary.membershipSummary.length, + ), + fontSize: descFontSize, + iconSize: smallIconSize, + ), + ], + ), + Padding( + padding: const EdgeInsets.only( + top: 4.0, + bottom: 8.0, + ), + child: Row( + spacing: 4.0, + children: [ + const Icon( + Icons.map, + size: largeIconSize, + ), + Text( + L10n.of(context).coursePlan, + style: const TextStyle( + fontSize: titleFontSize, + ), + ), + ], + ), + ), + ], + ); + } + + index--; + + if (index >= course.topicIds.length) { + return const SizedBox(height: 12.0); + } + + final topicId = course.topicIds[index]; + final topic = course.loadedTopics[topicId]; + + if (topic == null) { + return const SizedBox(); + } + + return Padding( + padding: const EdgeInsets.symmetric( + vertical: 4.0, + ), + child: Row( + spacing: 8.0, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ClipPath( + clipper: PinClipper(), + child: ImageByUrl( + imageUrl: topic.imageUrl, + width: 45.0, + replacement: Container( + width: 45.0, + height: 45.0, + decoration: BoxDecoration( + color: theme.colorScheme.secondary, + ), + ), + ), + ), + Flexible( + child: Column( + spacing: 4.0, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + topic.title, + style: const TextStyle( + fontSize: titleFontSize, + ), + ), + Text( + topic.description, + style: const TextStyle( + fontSize: descFontSize, + ), + ), + Padding( + padding: const EdgeInsetsGeometry + .symmetric( + vertical: 2.0, + ), + child: Row( + spacing: 8.0, + mainAxisSize: MainAxisSize.min, + children: [ + if (topic.location != null) + CourseInfoChip( + icon: Icons.location_on, + text: topic.location!, + fontSize: descFontSize, + iconSize: smallIconSize, + ), + ], + ), + ), + ], + ), + ), + ], + ), + ); + }, + ), + ), + ), + Container( + decoration: BoxDecoration( + color: theme.colorScheme.surface, + border: Border( + top: BorderSide( + color: theme.dividerColor, + width: 1.0, + ), + ), + ), + padding: const EdgeInsets.all(12.0), + child: Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Column( + spacing: 8.0, + children: [ + if (summary.joinRule == JoinRules.knock) ...[ + TextField( + decoration: InputDecoration( + hintText: L10n.of(context).enterCodeToJoin, + ), + onSubmitted: controller.joinWithCode, + ), + Row( + spacing: 8.0, + children: [ + const Expanded( + child: Divider(), + ), + Text(L10n.of(context).or), + const Expanded( + child: Divider(), + ), + ], + ), + ], + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: + theme.colorScheme.primaryContainer, + foregroundColor: + theme.colorScheme.onPrimaryContainer, + ), + onPressed: controller.joinCourse, + child: Row( + spacing: 8.0, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.map_outlined), + Text( + summary.joinRule == JoinRules.knock + ? L10n.of(context).knock + : L10n.of(context).join, + style: const TextStyle( + fontSize: titleFontSize, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ], + ); + }, + ), + ), + ), + ), + ); + } +} + +class _CourseAdminDisplay extends StatelessWidget { + final RoomSummaryResponse summary; + const _CourseAdminDisplay(this.summary); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Wrap( + alignment: WrapAlignment.center, + spacing: 12.0, + runSpacing: 12.0, + children: [ + ...summary.adminUserIDs.map((adminId) { + return FutureBuilder( + future: Matrix.of(context).client.getProfileFromUserId( + adminId, + ), + builder: (context, snapshot) { + final profile = snapshot.data; + final displayName = + profile?.displayName ?? adminId.localpart ?? adminId; + return InkWell( + onTap: profile != null + ? () => UserDialog.show( + context: context, + profile: profile, + ) + : null, + child: Container( + decoration: BoxDecoration( + color: theme.colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(18.0), + ), + padding: const EdgeInsets.all(4.0), + child: Opacity( + opacity: 0.5, + child: Row( + spacing: 4.0, + mainAxisSize: MainAxisSize.min, + children: [ + Avatar( + size: 18.0, + mxContent: profile?.avatarUrl, + name: displayName, + userId: adminId, + ), + ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 80.0, + ), + child: Text( + displayName, + style: TextStyle( + fontSize: 12.0, + color: theme.colorScheme.onPrimaryContainer, + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ), + ], + ), + ), + ), + ); + }, + ); + }), + ], + ); + } +} diff --git a/lib/pangea/course_creation/selected_course_page.dart b/lib/pangea/course_creation/selected_course_page.dart index 075398f58..86468890f 100644 --- a/lib/pangea/course_creation/selected_course_page.dart +++ b/lib/pangea/course_creation/selected_course_page.dart @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:matrix/matrix.dart' as sdk; -import 'package:matrix/matrix.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/course_creation/selected_course_view.dart'; @@ -12,11 +11,11 @@ import 'package:fluffychat/pangea/course_plans/courses/course_plan_builder.dart' import 'package:fluffychat/pangea/course_plans/courses/course_plan_model.dart'; import 'package:fluffychat/pangea/course_plans/courses/course_plan_room_extension.dart'; import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; +import 'package:fluffychat/pangea/join_codes/space_code_controller.dart'; import 'package:fluffychat/pangea/spaces/client_spaces_extension.dart'; -import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart'; import 'package:fluffychat/widgets/matrix.dart'; -enum SelectedCourseMode { launch, addToSpace, join } +enum SelectedCourseMode { launch, addToSpace } class SelectedCourse extends StatefulWidget { final String courseId; @@ -26,15 +25,11 @@ class SelectedCourse extends StatefulWidget { /// In join mode, the ID of the space to join that already has this course. final String? spaceId; - /// In join mode, the room info for the space that already has this course. - final PublicRoomsChunk? roomChunk; - const SelectedCourse( this.courseId, this.mode, { super.key, this.spaceId, - this.roomChunk, }); @override @@ -63,8 +58,6 @@ class SelectedCourseController extends State return L10n.of(context).newCourse; case SelectedCourseMode.addToSpace: return L10n.of(context).addCoursePlan; - case SelectedCourseMode.join: - return L10n.of(context).joinWithClassCode; } } @@ -74,10 +67,24 @@ class SelectedCourseController extends State return L10n.of(context).createCourse; case SelectedCourseMode.addToSpace: return L10n.of(context).addCoursePlan; - case SelectedCourseMode.join: - return widget.roomChunk?.joinRule == JoinRules.knock.name - ? L10n.of(context).knock - : L10n.of(context).join; + } + } + + Future joinWithCode(String code) async { + if (code.isEmpty) { + return; + } + + final roomId = await SpaceCodeController.joinSpaceWithCode( + context, + code, + ); + + if (roomId != null) { + final room = Matrix.of(context).client.getRoomById(roomId); + room?.isSpace ?? true + ? context.go('/rooms/spaces/$roomId/details') + : context.go('/rooms/$roomId'); } } @@ -87,8 +94,6 @@ class SelectedCourseController extends State return launchCourse(widget.courseId, course); case SelectedCourseMode.addToSpace: return addCourseToSpace(course); - case SelectedCourseMode.join: - return joinCourse(); } } @@ -149,50 +154,6 @@ class SelectedCourseController extends State context.go("/rooms/spaces/${space.id}/details?tab=course"); } - Future joinCourse() async { - if (widget.roomChunk == null) { - throw Exception("Room chunk is null"); - } - - final client = Matrix.of(context).client; - final r = client.getRoomById(widget.roomChunk!.roomId); - if (r != null && r.membership == Membership.join) { - if (mounted) { - context.go("/rooms/spaces/${r.id}/details"); - } - return; - } - - final knock = widget.roomChunk!.joinRule == JoinRules.knock.name; - final roomId = widget.roomChunk != null && knock - ? await client.knockRoom(widget.roomChunk!.roomId) - : await client.joinRoom(widget.roomChunk!.roomId); - - Room? room = client.getRoomById(roomId); - if (!knock && room?.membership != Membership.join) { - await client.waitForRoomInSync(roomId, join: true); - room = client.getRoomById(roomId); - } - - if (knock) { - Navigator.of(context).pop(); - await showOkAlertDialog( - context: context, - title: L10n.of(context).youHaveKnocked, - message: L10n.of(context).knockDesc, - ); - return; - } - - if (room == null) { - throw Exception("Failed to join room"); - } - - if (mounted) { - context.go("/rooms/spaces/$roomId/details"); - } - } - @override Widget build(BuildContext context) => SelectedCourseView(this); } diff --git a/lib/pangea/course_creation/selected_course_view.dart b/lib/pangea/course_creation/selected_course_view.dart index dbafe7ac7..29d57d970 100644 --- a/lib/pangea/course_creation/selected_course_view.dart +++ b/lib/pangea/course_creation/selected_course_view.dart @@ -60,13 +60,7 @@ class SelectedCourseView extends StatelessWidget { child: ListView.builder( itemCount: course.topicIds.length + 2, itemBuilder: (context, index) { - String displayname = course.title; - final roomChunk = controller.widget.roomChunk; - if (roomChunk != null) { - displayname = roomChunk.name ?? - roomChunk.canonicalAlias ?? - L10n.of(context).emptyChat; - } + final String displayname = course.title; if (index == 0) { return Column( @@ -75,9 +69,7 @@ class SelectedCourseView extends StatelessWidget { ClipPath( clipper: MapClipper(), child: ImageByUrl( - imageUrl: controller.widget - .roomChunk?.avatarUrl ?? - course.imageUrl, + imageUrl: course.imageUrl, width: 100.0, borderRadius: BorderRadius.circular(0.0), @@ -233,70 +225,74 @@ class SelectedCourseView extends StatelessWidget { spacing: 8.0, mainAxisSize: MainAxisSize.min, children: [ - 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.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( - style: ElevatedButton.styleFrom( - backgroundColor: - theme.colorScheme.primaryContainer, - foregroundColor: - theme.colorScheme.onPrimaryContainer, - ), - onPressed: () => showFutureLoadingDialog( - context: context, - future: () => controller.submit(course), - ), - child: Row( - spacing: 8.0, - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - const Icon(Icons.map_outlined), - Text( - controller.buttonText, - style: const TextStyle( - fontSize: titleFontSize, - ), + child: Column( + spacing: 8.0, + children: [ + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: theme + .colorScheme.primaryContainer, + foregroundColor: theme + .colorScheme.onPrimaryContainer, ), - ], - ), + onPressed: () => + showFutureLoadingDialog( + context: context, + future: () => + controller.submit(course), + ), + child: Row( + spacing: 8.0, + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + const Icon(Icons.map_outlined), + Text( + controller.buttonText, + style: const TextStyle( + fontSize: titleFontSize, + ), + ), + ], + ), + ), + ], ), ), ], diff --git a/lib/pangea/course_plans/course_activities/activity_summaries_provider.dart b/lib/pangea/course_plans/course_activities/activity_summaries_provider.dart index 6b87e1f75..daf7be1a1 100644 --- a/lib/pangea/course_plans/course_activities/activity_summaries_provider.dart +++ b/lib/pangea/course_plans/course_activities/activity_summaries_provider.dart @@ -48,6 +48,7 @@ mixin ActivitySummariesProvider on State { final activityPlan = roomSummary.activityPlan; final assignedRoles = roomSummary.joinedUsersWithRoles; + if (activityPlan == null) return false; return activityPlan.roles.length - assignedRoles.length <= 0; } @@ -56,6 +57,7 @@ mixin ActivitySummariesProvider on State { if (roomSummary == null) return false; final activityRoles = roomSummary.activityRoles; + if (activityRoles == null) return false; final roles = activityRoles.roles.values.where( (r) => r.userId != BotName.byEnvironment, ); @@ -76,7 +78,7 @@ mixin ActivitySummariesProvider on State { Map activitySessions(String activityId) => Map.fromEntries( roomSummaries?.entries - .where((v) => v.value.activityPlan.activityId == activityId) ?? + .where((v) => v.value.activityPlan?.activityId == activityId) ?? [], ); @@ -115,7 +117,7 @@ mixin ActivitySummariesProvider on State { final summary = entry.value; final roomId = entry.key; - if (summary.activityPlan.activityId != activityId) { + if (summary.activityPlan?.activityId != activityId) { continue; } @@ -132,11 +134,13 @@ mixin ActivitySummariesProvider on State { if (roomSummaries == null || roomSummaries!.isEmpty) return {}; return roomSummaries!.values .where( - (entry) => entry.activityRoles.roles.values.any( - (v) => v.userId == userID && v.isArchived, - ), + (entry) => + entry.activityRoles != null && + entry.activityRoles!.roles.values.any( + (v) => v.userId == userID && v.isArchived, + ), ) - .map((e) => e.activityPlan.activityId) + .map((e) => e.activityPlan?.activityId) .whereType() .toSet(); } diff --git a/lib/pangea/course_plans/courses/course_plan_builder.dart b/lib/pangea/course_plans/courses/course_plan_builder.dart index 2f8733e63..3f4310328 100644 --- a/lib/pangea/course_plans/courses/course_plan_builder.dart +++ b/lib/pangea/course_plans/courses/course_plan_builder.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:get_storage/get_storage.dart'; +import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/course_plans/courses/course_plan_model.dart'; import 'package:fluffychat/pangea/course_plans/courses/get_localized_courses_request.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -47,7 +48,12 @@ mixin CoursePlanProvider on State { ), ); await course!.fetchMediaUrls(); - } catch (e) { + } catch (e, s) { + ErrorHandler.logError( + e: e, + s: s, + data: {'courseId': courseId}, + ); courseError = e; } finally { if (mounted) setState(() => loadingCourse = false); diff --git a/lib/pangea/course_settings/course_settings.dart b/lib/pangea/course_settings/course_settings.dart index 5edd6122c..fb081c5b5 100644 --- a/lib/pangea/course_settings/course_settings.dart +++ b/lib/pangea/course_settings/course_settings.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; +import 'package:http/http.dart'; import 'package:matrix/matrix.dart'; import 'package:shimmer/shimmer.dart'; @@ -54,6 +55,17 @@ class CourseSettings extends StatelessWidget { } if (controller.course == null || controller.courseError != null) { + if (controller.courseError is Response && + (controller.courseError as Response).statusCode == 500) { + return Center( + child: Text( + L10n.of(context).courseLoadingError, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyLarge, + ), + ); + } + return room.canChangeStateEvent(PangeaEventTypes.coursePlan) ? Column( spacing: 50.0, diff --git a/lib/pangea/instructions/instructions_enum.dart b/lib/pangea/instructions/instructions_enum.dart index a86c9d1a8..a3cecfcdc 100644 --- a/lib/pangea/instructions/instructions_enum.dart +++ b/lib/pangea/instructions/instructions_enum.dart @@ -32,9 +32,9 @@ enum InstructionsEnum { setLemmaEmoji, disableLanguageTools, selectMeaning, - clickTextMessages, - clickAudioMessages, dismissSupportChat, + shimmerNewToken, + shimmerTranslation, } extension InstructionsEnumExtension on InstructionsEnum { @@ -66,9 +66,9 @@ extension InstructionsEnumExtension on InstructionsEnum { case InstructionsEnum.noSavedActivitiesYet: case InstructionsEnum.setLemmaEmoji: case InstructionsEnum.disableLanguageTools: - case InstructionsEnum.clickTextMessages: - case InstructionsEnum.clickAudioMessages: case InstructionsEnum.dismissSupportChat: + case InstructionsEnum.shimmerNewToken: + case InstructionsEnum.shimmerTranslation: ErrorHandler.logError( e: Exception("No title for this instruction"), m: 'InstructionsEnumExtension.title', @@ -130,9 +130,9 @@ extension InstructionsEnumExtension on InstructionsEnum { case InstructionsEnum.noSavedActivitiesYet: return l10n.noSavedActivitiesYet; case InstructionsEnum.setLemmaEmoji: - case InstructionsEnum.clickTextMessages: - case InstructionsEnum.clickAudioMessages: case InstructionsEnum.dismissSupportChat: + case InstructionsEnum.shimmerNewToken: + case InstructionsEnum.shimmerTranslation: return ""; case InstructionsEnum.disableLanguageTools: return l10n.disableLanguageToolsDesc; diff --git a/lib/pangea/login/pages/find_course_page.dart b/lib/pangea/login/pages/find_course_page.dart index 01dd38532..4f0eda0a4 100644 --- a/lib/pangea/login/pages/find_course_page.dart +++ b/lib/pangea/login/pages/find_course_page.dart @@ -391,6 +391,14 @@ class _PublicCourseTile extends StatelessWidget { this.course, }); + void _navigateToCoursePage( + BuildContext context, + ) { + context.go( + '/rooms/course/${Uri.encodeComponent(chunk.room.roomId)}', + ); + } + @override Widget build(BuildContext context) { final theme = Theme.of(context); @@ -411,10 +419,7 @@ class _PublicCourseTile extends StatelessWidget { child: Material( type: MaterialType.transparency, child: InkWell( - onTap: () => context.go( - '/rooms/course/$courseId', - extra: space, - ), + onTap: () => _navigateToCoursePage(context), borderRadius: BorderRadius.circular(12.0), child: Container( padding: const EdgeInsets.all(12.0), @@ -490,10 +495,7 @@ class _PublicCourseTile extends StatelessWidget { const SizedBox(height: 12.0), HoverBuilder( builder: (context, hovered) => ElevatedButton( - onPressed: () => context.go( - '/rooms/course/$courseId', - extra: space, - ), + onPressed: () => _navigateToCoursePage(context), style: ElevatedButton.styleFrom( backgroundColor: theme.colorScheme.primaryContainer.withAlpha( diff --git a/lib/pangea/login/pages/language_selection_page.dart b/lib/pangea/login/pages/language_selection_page.dart index fad33f3fd..939af0d98 100644 --- a/lib/pangea/login/pages/language_selection_page.dart +++ b/lib/pangea/login/pages/language_selection_page.dart @@ -4,6 +4,8 @@ import 'package:go_router/go_router.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/l10n/l10n.dart'; +import 'package:fluffychat/pangea/common/widgets/shimmer_background.dart'; +import 'package:fluffychat/pangea/common/widgets/shrinkable_text.dart'; import 'package:fluffychat/pangea/languages/language_model.dart'; import 'package:fluffychat/pangea/languages/language_service.dart'; import 'package:fluffychat/pangea/languages/p_language_store.dart'; @@ -102,14 +104,41 @@ class LanguageSelectionPageState extends State { return Scaffold( appBar: AppBar( - title: Text(L10n.of(context).languages), + title: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 500, + ), + child: Row( + spacing: 12.0, + children: [ + BackButton( + onPressed: Navigator.of(context).pop, + ), + Expanded( + child: LayoutBuilder( + builder: (context, constraints) { + return ShrinkableText( + text: L10n.of(context).onboardingLanguagesTitle, + maxWidth: constraints.maxWidth, + alignment: Alignment.center, + ); + }, + ), + ), + const SizedBox( + width: 40.0, + ), + ], + ), + ), + automaticallyImplyLeading: false, ), body: SafeArea( child: Center( child: Container( padding: const EdgeInsets.all(20.0), constraints: const BoxConstraints( - maxWidth: 450, + maxWidth: 500, ), child: Column( spacing: 24.0, @@ -121,6 +150,7 @@ class LanguageSelectionPageState extends State { border: OutlineInputBorder( borderRadius: BorderRadius.circular(50), ), + hintText: L10n.of(context).searchLanguagesHint, ), ), Expanded( @@ -153,27 +183,38 @@ class LanguageSelectionPageState extends State { ), ) .map( - (l) => FilterChip( - selected: _selectedLanguage == l, - backgroundColor: - _selectedLanguage == l - ? theme.colorScheme.primary - : theme.colorScheme.surface, - padding: const EdgeInsets.symmetric( - horizontal: 8.0, - vertical: 4.0, + (l) => ShimmerBackground( + enabled: _selectedLanguage == null, + borderRadius: const BorderRadius.all( + Radius.circular(16.0), ), - label: Text( - l.getDisplayName(context), - style: isColumnMode - ? theme.textTheme.bodyLarge - : theme.textTheme.bodyMedium, + child: FilterChip( + selected: _selectedLanguage == l, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(16.0), + ), + ), + backgroundColor: + _selectedLanguage == l + ? theme.colorScheme.primary + : theme.colorScheme.surface, + padding: const EdgeInsets.symmetric( + horizontal: 8.0, + vertical: 4.0, + ), + label: Text( + l.getDisplayName(context), + style: isColumnMode + ? theme.textTheme.bodyLarge + : theme.textTheme.bodyMedium, + ), + onSelected: (selected) { + _setSelectedLanguage( + selected ? l : null, + ); + }, ), - onSelected: (selected) { - _setSelectedLanguage( - selected ? l : null, - ); - }, ), ) .toList(), @@ -220,23 +261,24 @@ class LanguageSelectionPageState extends State { ) : const SizedBox(), ), - Text( - L10n.of(context).chooseLanguage, - style: theme.textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.bold, - ), - ), - ElevatedButton( - onPressed: _selectedLanguage != null ? _submit : null, - style: ElevatedButton.styleFrom( - backgroundColor: theme.colorScheme.primaryContainer, - foregroundColor: theme.colorScheme.onPrimaryContainer, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text(L10n.of(context).letsGo), - ], + ShimmerBackground( + enabled: _selectedLanguage != null, + borderRadius: BorderRadius.circular(24.0), + child: ElevatedButton( + onPressed: _selectedLanguage != null ? _submit : null, + style: ElevatedButton.styleFrom( + backgroundColor: theme.colorScheme.primaryContainer, + foregroundColor: theme.colorScheme.onPrimaryContainer, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(24.0), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(L10n.of(context).letsGo), + ], + ), ), ), ], diff --git a/lib/pangea/login/pages/login_options_view.dart b/lib/pangea/login/pages/login_options_view.dart index 5ef1bc512..c6a73b9e4 100644 --- a/lib/pangea/login/pages/login_options_view.dart +++ b/lib/pangea/login/pages/login_options_view.dart @@ -21,9 +21,24 @@ class LoginOptionsView extends StatelessWidget { final theme = Theme.of(context); return Scaffold( appBar: AppBar( - title: Text( - L10n.of(context).loginToAccount, + title: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 450, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BackButton( + onPressed: Navigator.of(context).pop, + ), + Text(L10n.of(context).login), + const SizedBox( + width: 40.0, + ), + ], + ), ), + automaticallyImplyLeading: false, ), body: SafeArea( child: Center( @@ -36,6 +51,13 @@ class LoginOptionsView extends StatelessWidget { spacing: 16.0, mainAxisAlignment: MainAxisAlignment.end, children: [ + Text( + L10n.of(context).loginToAccount, + textAlign: TextAlign.center, + style: theme.textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, + ), + ), const PangeaSsoButton( provider: SSOProvider.apple, title: "Apple", diff --git a/lib/pangea/login/pages/pangea_login_view.dart b/lib/pangea/login/pages/pangea_login_view.dart index 45c3bdfc3..8690136aa 100644 --- a/lib/pangea/login/pages/pangea_login_view.dart +++ b/lib/pangea/login/pages/pangea_login_view.dart @@ -15,9 +15,24 @@ class PasswordLoginView extends StatelessWidget { key: controller.formKey, child: Scaffold( appBar: AppBar( - title: Text( - L10n.of(context).loginWithEmail, + title: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 450, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BackButton( + onPressed: Navigator.of(context).pop, + ), + Text(L10n.of(context).loginWithEmail), + const SizedBox( + width: 40.0, + ), + ], + ), ), + automaticallyImplyLeading: false, ), body: SafeArea( child: Center( diff --git a/lib/pangea/login/pages/signup_view.dart b/lib/pangea/login/pages/signup_view.dart index fdac63894..6b79ee241 100644 --- a/lib/pangea/login/pages/signup_view.dart +++ b/lib/pangea/login/pages/signup_view.dart @@ -27,7 +27,24 @@ class SignupPageView extends StatelessWidget { return Form( key: controller.formKey, child: Scaffold( - appBar: AppBar(), + appBar: AppBar( + title: SizedBox( + width: 450, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BackButton( + onPressed: Navigator.of(context).pop, + ), + Text(L10n.of(context).signUp), + const SizedBox( + width: 40.0, + ), + ], + ), + ), + automaticallyImplyLeading: false, + ), body: SafeArea( child: Center( child: ConstrainedBox( diff --git a/lib/pangea/login/pages/signup_with_email_view.dart b/lib/pangea/login/pages/signup_with_email_view.dart index 59da3b129..840e43812 100644 --- a/lib/pangea/login/pages/signup_with_email_view.dart +++ b/lib/pangea/login/pages/signup_with_email_view.dart @@ -15,7 +15,25 @@ class SignupWithEmailView extends StatelessWidget { return Form( key: controller.formKey, child: Scaffold( - appBar: AppBar(), + appBar: AppBar( + title: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 450, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BackButton( + onPressed: Navigator.of(context).pop, + ), + const SizedBox( + width: 40.0, + ), + ], + ), + ), + automaticallyImplyLeading: false, + ), body: SafeArea( child: Center( child: ConstrainedBox( diff --git a/lib/pangea/toolbar/layout/message_selection_positioner.dart b/lib/pangea/toolbar/layout/message_selection_positioner.dart index 477a3b5f8..c4bf54b8c 100644 --- a/lib/pangea/toolbar/layout/message_selection_positioner.dart +++ b/lib/pangea/toolbar/layout/message_selection_positioner.dart @@ -125,9 +125,6 @@ class MessageSelectionPositionerState extends State final Duration transitionAnimationDuration = const Duration(milliseconds: 300); - final Offset _defaultMessageOffset = - const Offset(Avatar.defaultSize + 16 + 8, 300); - double get _horizontalPadding => FluffyThemes.isColumnMode(context) ? 8.0 : 0.0; @@ -232,14 +229,14 @@ class MessageSelectionPositionerState extends State null, ); - Offset get _originalMessageOffset { + Offset? get _originalMessageOffset { if (_messageRenderBox == null || !_messageRenderBox!.hasSize) { - return _defaultMessageOffset; + return null; } return _runWithLogging( () => _messageRenderBox?.localToGlobal(Offset.zero), "Error getting message offset", - _defaultMessageOffset, + null, ); } @@ -267,27 +264,36 @@ class MessageSelectionPositionerState extends State double? get messageLeftOffset { if (ownMessage) return null; + final offset = _originalMessageOffset; + if (offset == null) { + return Avatar.defaultSize + 16; + } + if (isRtl) { - return _originalMessageOffset.dx - - (showDetails ? FluffyThemes.columnWidth : 0); + return offset.dx - (showDetails ? FluffyThemes.columnWidth : 0); } if (ownMessage) return null; - return max(_originalMessageOffset.dx - columnWidth, 0); + return max(offset.dx - columnWidth, 0); } double? get messageRightOffset { if (mediaQuery == null || !ownMessage) return null; + final offset = _originalMessageOffset; + if (offset == null) { + return 8.0; + } + if (isRtl) { return mediaQuery!.size.width - columnWidth - - _originalMessageOffset.dx - + offset.dx - originalMessageSize.width; } return mediaQuery!.size.width - - _originalMessageOffset.dx - + offset.dx - originalMessageSize.width - (showDetails ? FluffyThemes.columnWidth : 0); } @@ -344,7 +350,10 @@ class MessageSelectionPositionerState extends State bool get _hasFooterOverflow { if (_screenHeight == null) return false; - final bottomOffset = _originalMessageOffset.dy + + final offset = _originalMessageOffset; + if (offset == null) return false; + + final bottomOffset = offset.dy + originalMessageSize.height + _reactionsHeight + AppConfig.toolbarMenuHeight + @@ -357,6 +366,8 @@ class MessageSelectionPositionerState extends State double get spaceBelowContent { if (shouldScroll) return 0; if (_hasFooterOverflow) return 0; + final offset = _originalMessageOffset; + if (offset == null) return 300; final messageHeight = originalMessageSize.height; final originalContentHeight = @@ -364,8 +375,7 @@ class MessageSelectionPositionerState extends State final screenHeight = mediaQuery!.size.height - mediaQuery!.padding.bottom; - double boxHeight = - screenHeight - _originalMessageOffset.dy - originalContentHeight; + double boxHeight = screenHeight - offset.dy - originalContentHeight; final neededSpace = boxHeight + _fullContentHeight + mediaQuery!.padding.top + 4.0; diff --git a/lib/pangea/toolbar/reading_assistance/new_word_overlay.dart b/lib/pangea/toolbar/reading_assistance/new_word_overlay.dart index 1bbef81fe..49e64163c 100644 --- a/lib/pangea/toolbar/reading_assistance/new_word_overlay.dart +++ b/lib/pangea/toolbar/reading_assistance/new_word_overlay.dart @@ -162,7 +162,7 @@ class NewVocabBubble extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Icon( - Symbols.toys_and_games, + Symbols.dictionary, color: theme.colorScheme.primary, size: 24, ), diff --git a/lib/pangea/toolbar/reading_assistance/select_mode_buttons.dart b/lib/pangea/toolbar/reading_assistance/select_mode_buttons.dart index 1a43815e8..227a8781e 100644 --- a/lib/pangea/toolbar/reading_assistance/select_mode_buttons.dart +++ b/lib/pangea/toolbar/reading_assistance/select_mode_buttons.dart @@ -15,9 +15,11 @@ import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pages/chat/events/audio_player.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/common/widgets/pressable_button.dart'; +import 'package:fluffychat/pangea/common/widgets/shimmer_background.dart'; import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/events/extensions/pangea_event_extension.dart'; import 'package:fluffychat/pangea/events/utils/report_message.dart'; +import 'package:fluffychat/pangea/instructions/instructions_enum.dart'; import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart'; import 'package:fluffychat/pangea/toolbar/message_practice/message_audio_card.dart'; import 'package:fluffychat/pangea/toolbar/message_selection_overlay.dart'; @@ -214,6 +216,9 @@ class SelectModeButtonsState extends State { } if (updatedMode == SelectMode.translate) { + if (!InstructionsEnum.shimmerTranslation.isToggledOff) { + InstructionsEnum.shimmerTranslation.setToggledOff(true); + } await controller.fetchTranslation(); } @@ -423,25 +428,32 @@ class SelectModeButtonsState extends State { colorFactor: theme.brightness == Brightness.light ? 0.55 : 0.3, builder: (context, depressed, shadowColor) => - AnimatedContainer( - duration: FluffyThemes.animationDuration, - height: buttonSize, - width: buttonSize, - decoration: BoxDecoration( - color: depressed - ? shadowColor - : theme.colorScheme.primaryContainer, - shape: BoxShape.circle, - ), - child: ValueListenableBuilder( - valueListenable: _isPlayingNotifier, - builder: (context, playing, __) => - _SelectModeButtonIcon( - mode: mode, - loading: controller.isLoading && - mode == selectedMode, - playing: mode == SelectMode.audio && playing, - color: theme.colorScheme.onPrimaryContainer, + ShimmerBackground( + enabled: !InstructionsEnum + .shimmerTranslation.isToggledOff && + mode == SelectMode.translate && + enabled, + borderRadius: BorderRadius.circular(100), + child: AnimatedContainer( + duration: FluffyThemes.animationDuration, + height: buttonSize, + width: buttonSize, + decoration: BoxDecoration( + color: depressed + ? shadowColor + : theme.colorScheme.primaryContainer, + shape: BoxShape.circle, + ), + child: ValueListenableBuilder( + valueListenable: _isPlayingNotifier, + builder: (context, playing, __) => + _SelectModeButtonIcon( + mode: mode, + loading: controller.isLoading && + mode == selectedMode, + playing: mode == SelectMode.audio && playing, + color: theme.colorScheme.onPrimaryContainer, + ), ), ), ), diff --git a/lib/pangea/toolbar/token_rendering_mixin.dart b/lib/pangea/toolbar/token_rendering_mixin.dart index 082a4e8f8..81da0912e 100644 --- a/lib/pangea/toolbar/token_rendering_mixin.dart +++ b/lib/pangea/toolbar/token_rendering_mixin.dart @@ -3,6 +3,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_misc/constructs_model.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; +import 'package:fluffychat/pangea/instructions/instructions_enum.dart'; import 'package:fluffychat/pangea/toolbar/reading_assistance/tokens_util.dart'; mixin TokenRenderingMixin { @@ -15,6 +16,10 @@ mixin TokenRenderingMixin { String? eventId, }) async { TokensUtil.collectToken(cacheKey, token.text); + if (!InstructionsEnum.shimmerNewToken.isToggledOff) { + InstructionsEnum.shimmerNewToken.setToggledOff(true); + } + final constructs = [ OneConstructUse( useType: ConstructUseTypeEnum.click, diff --git a/lib/pangea/user/user_search_extension.dart b/lib/pangea/user/user_search_extension.dart new file mode 100644 index 000000000..871b72101 --- /dev/null +++ b/lib/pangea/user/user_search_extension.dart @@ -0,0 +1,19 @@ +import 'package:matrix/matrix.dart'; + +import 'package:fluffychat/pangea/common/config/environment.dart'; + +extension UserSearchExtension on Client { + Future searchUser( + String search, { + int? limit, + }) async { + String searchText = search; + if (!searchText.startsWith("@")) { + searchText = "@$searchText"; + } + if (!searchText.contains(":")) { + searchText = "$searchText:${Environment.homeServer}"; + } + return searchUserDirectory(searchText, limit: limit); + } +} diff --git a/lib/utils/error_reporter.dart b/lib/utils/error_reporter.dart index adddefd1b..d529292da 100644 --- a/lib/utils/error_reporter.dart +++ b/lib/utils/error_reporter.dart @@ -22,6 +22,9 @@ class ErrorReporter { content: Text( l10n.oopsSomethingWentWrong, // Use the non-null L10n instance to get the error message ), + // #Pangea + showCloseIcon: true, + // Pangea# ), ); } catch (err) {