From aae00cd1d633c4f30ec09d96d3c0e324ddd34021 Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Wed, 28 Jan 2026 10:48:40 -0500 Subject: [PATCH 01/19] fix: don't label room as activity room if activityID is null (#5480) --- lib/pangea/activity_sessions/activity_room_extension.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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; From 172f1c96f8e753ce0c33ee225603430712ac30df Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Wed, 28 Jan 2026 11:28:54 -0500 Subject: [PATCH 02/19] chore: onboarding updates (#5485) --- lib/l10n/intl_ar.arb | 12 +- lib/l10n/intl_be.arb | 12 +- lib/l10n/intl_bn.arb | 12 +- lib/l10n/intl_bo.arb | 12 +- lib/l10n/intl_ca.arb | 12 +- lib/l10n/intl_cs.arb | 12 +- lib/l10n/intl_da.arb | 12 +- lib/l10n/intl_de.arb | 12 +- lib/l10n/intl_el.arb | 12 +- lib/l10n/intl_en.arb | 4 +- lib/l10n/intl_eo.arb | 12 +- lib/l10n/intl_es.arb | 12 +- lib/l10n/intl_et.arb | 12 +- lib/l10n/intl_eu.arb | 12 +- lib/l10n/intl_fa.arb | 12 +- lib/l10n/intl_fi.arb | 12 +- lib/l10n/intl_fil.arb | 12 +- lib/l10n/intl_fr.arb | 12 +- lib/l10n/intl_ga.arb | 12 +- lib/l10n/intl_gl.arb | 12 +- lib/l10n/intl_he.arb | 12 +- lib/l10n/intl_hi.arb | 12 +- lib/l10n/intl_hr.arb | 12 +- lib/l10n/intl_hu.arb | 12 +- lib/l10n/intl_ia.arb | 12 +- lib/l10n/intl_id.arb | 12 +- lib/l10n/intl_ie.arb | 12 +- lib/l10n/intl_it.arb | 12 +- lib/l10n/intl_ja.arb | 12 +- lib/l10n/intl_ka.arb | 12 +- lib/l10n/intl_ko.arb | 12 +- lib/l10n/intl_lt.arb | 12 +- lib/l10n/intl_lv.arb | 12 +- lib/l10n/intl_nb.arb | 12 +- lib/l10n/intl_nl.arb | 12 +- lib/l10n/intl_pl.arb | 12 +- lib/l10n/intl_pt.arb | 12 +- lib/l10n/intl_pt_BR.arb | 12 +- lib/l10n/intl_pt_PT.arb | 12 +- lib/l10n/intl_ro.arb | 12 +- lib/l10n/intl_ru.arb | 12 +- lib/l10n/intl_sk.arb | 12 +- lib/l10n/intl_sl.arb | 12 +- lib/l10n/intl_sr.arb | 12 +- lib/l10n/intl_sv.arb | 12 +- lib/l10n/intl_ta.arb | 12 +- lib/l10n/intl_te.arb | 12 +- lib/l10n/intl_th.arb | 12 +- lib/l10n/intl_tr.arb | 12 +- lib/l10n/intl_uk.arb | 12 +- lib/l10n/intl_vi.arb | 12 +- lib/l10n/intl_yue.arb | 12 +- lib/l10n/intl_zh.arb | 12 +- lib/l10n/intl_zh_Hant.arb | 12 +- lib/pages/chat/chat.dart | 23 +- lib/pages/chat/events/html_message.dart | 38 +- lib/pages/chat/events/message.dart | 396 +++++++++--------- .../common/widgets/shimmer_background.dart | 31 +- .../instructions/instructions_enum.dart | 12 +- .../login/pages/language_selection_page.dart | 105 +++-- .../select_mode_buttons.dart | 49 ++- 61 files changed, 931 insertions(+), 363 deletions(-) diff --git a/lib/l10n/intl_ar.arb b/lib/l10n/intl_ar.arb index 202bc80fc..a3e84354e 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 11:22:30.477643", "about": "حول", "@about": { "type": "String", @@ -11151,5 +11151,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "ما اللغة التي تتعلمها؟", + "searchLanguagesHint": "ابحث عن اللغات المستهدفة", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..83396d15e 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 11:22:20.711094", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -12033,5 +12033,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Якую мову вы вывучаеце?", + "searchLanguagesHint": "Пошук мэтавых моў", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..2b83ca456 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 11:22:41.965760", "about": "সম্পর্কে", "@about": { "type": "String", @@ -12038,5 +12038,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "আপনি কোন ভাষা শিখছেন?", + "searchLanguagesHint": "লক্ষ্য ভাষা অনুসন্ধান করুন", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..cf87f482d 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 11:22:39.504934", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -10688,5 +10688,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Kedua bahasa apa yang Anda pelajari?", + "searchLanguagesHint": "Cari bahasa target", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..af000f581 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 11:22:22.285622", "about": "Quant a", "@about": { "type": "String", @@ -10958,5 +10958,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Quina llengua estàs aprenent?", + "searchLanguagesHint": "Cerca llengües objectiu", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..21e52e7d7 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 11:22:18.388059", "about": "O aplikaci", "@about": { "type": "String", @@ -11541,5 +11541,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Jaký jazyk se učíte?", + "searchLanguagesHint": "Hledejte cílové jazyky", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..f9a2cc459 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 11:21:51.175741", "@aboutHomeserver": { "type": "String", "placeholders": { @@ -11995,5 +11995,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Hvilket sprog lærer du?", + "searchLanguagesHint": "Søg efter målsprog", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..2f2d9004d 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 11:22:11.549404", "alwaysUse24HourFormat": "true", "@alwaysUse24HourFormat": { "description": "Set to true to always display time of day in 24 hour format." @@ -10941,5 +10941,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Welche Sprache lernst du?", + "searchLanguagesHint": "Zielsprachen suchen", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..e19d4365f 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 11:22:48.079247", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -11992,5 +11992,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Ποια γλώσσα μαθαίνετε;", + "searchLanguagesHint": "Αναζητήστε γλώσσες στόχου", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..2f4ea4eb1 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -5063,5 +5063,7 @@ "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.", + "onboardingLanguagesTitle": "What language are you learning?", + "searchLanguagesHint": "Search target languages" } diff --git a/lib/l10n/intl_eo.arb b/lib/l10n/intl_eo.arb index e3f7b5447..65fb42eb1 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 11:22:51.689025", "about": "Prio", "@about": { "type": "String", @@ -12023,5 +12023,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Kian lingvon vi lernas?", + "searchLanguagesHint": "Serĉu celajn lingvojn", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..65fffc353 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 11:21:46.446878", "about": "Acerca de", "@about": { "type": "String", @@ -8168,5 +8168,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "¿Qué idioma estás aprendiendo?", + "searchLanguagesHint": "Buscar idiomas objetivo", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..16a9e2348 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 11:22:07.195180", "about": "Rakenduse teave", "@about": { "type": "String", @@ -11205,5 +11205,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Millist keelt sa õpid?", + "searchLanguagesHint": "Otsi sihtkeeli", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..df54f5d95 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 11:22:04.786494", "about": "Honi buruz", "@about": { "type": "String", @@ -10934,5 +10934,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Zer hizkuntza ikasten ari zara?", + "searchLanguagesHint": "Bilatu helburu hizkuntzak", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..81f5df938 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 11:22:43.436826", "repeatPassword": "تکرار رمزعبور", "@repeatPassword": {}, "about": "درباره", @@ -11666,5 +11666,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "شما در حال یادگیری چه زبانی هستید؟", + "searchLanguagesHint": "زبان‌های هدف را جستجو کنید", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..724790477 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 11:21:50.004841", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -11557,5 +11557,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Mitä kieltä opit?", + "searchLanguagesHint": "Etsi kohdekieliä", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..d8ceffa14 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 11:22:27.725706", "@setCustomPermissionLevel": { "type": "String", "placeholders": {} @@ -11910,5 +11910,15 @@ "@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": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index 3c03b9544..95090b0bc 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 11:22:58.035061", "about": "À propos", "@about": { "type": "String", @@ -11258,5 +11258,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Quelle langue apprenez-vous ?", + "searchLanguagesHint": "Recherchez des langues cibles", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..f35eab0df 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 11:22:56.916598", "@customReaction": { "type": "String", "placeholders": {} @@ -10932,5 +10932,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Cén teanga atá á foghlaim agat?", + "searchLanguagesHint": "Cuardaigh teangacha sprioc", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..c61e3297c 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 11:21:48.193565", "about": "Acerca de", "@about": { "type": "String", @@ -10931,5 +10931,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Que idioma estás aprendendo?", + "searchLanguagesHint": "Busca idiomas de destino", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..50eebc387 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 11:22:00.895298", "about": "אודות", "@about": { "type": "String", @@ -11983,5 +11983,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "איזו שפה אתה לומד?", + "searchLanguagesHint": "חפש שפות יעד", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..a34c80ed4 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 11:22:50.465177", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -12019,5 +12019,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "आप कौन सी भाषा सीख रहे हैं?", + "searchLanguagesHint": "लक्षित भाषाएँ खोजें", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..207deed27 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 11:21:59.771425", "about": "Informacije", "@about": { "type": "String", @@ -11306,5 +11306,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Koji jezik učite?", + "searchLanguagesHint": "Pretraži ciljne jezike", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..25c63f49d 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 11:21:52.684213", "about": "Névjegy", "@about": { "type": "String", @@ -10935,5 +10935,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Milyen nyelvet tanulsz?", + "searchLanguagesHint": "Keresd a célnyelveket", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..644f4a47e 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 11:22:02.235990", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -12012,5 +12012,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Kia lingvo vi lernas?", + "searchLanguagesHint": "Serĉu celajn lingvojn", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..c516c332f 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 11:21:53.689466", "setAsCanonicalAlias": "Atur sebagai alias utama", "@setAsCanonicalAlias": { "type": "String", @@ -10925,5 +10925,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Bahasa apa yang Anda pelajari?", + "searchLanguagesHint": "Cari bahasa target", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..6d34d7ad3 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 11:21:58.032557", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -11908,5 +11908,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Cén teanga atá á foghlaim agat?", + "searchLanguagesHint": "Cuardaigh teangacha sprioc", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..cf25a7485 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 11:22:15.909371", "about": "Informazioni", "@about": { "type": "String", @@ -10937,5 +10937,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Quale lingua stai imparando?", + "searchLanguagesHint": "Cerca lingue target", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..c87c21207 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 11:22:49.278909", "about": "このアプリについて", "@about": { "type": "String", @@ -11724,5 +11724,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "どの言語を学んでいますか?", + "searchLanguagesHint": "ターゲット言語を検索", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..7e9018c91 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 11:22:53.904103", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -11964,5 +11964,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "რომელი ენა სწავლობთ?", + "searchLanguagesHint": "ძებნა მიზნობრივი ენების", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..02d46a1a1 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 11:21:45.008877", "about": "소개", "@about": { "type": "String", @@ -11042,5 +11042,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "어떤 언어를 배우고 있나요?", + "searchLanguagesHint": "목표 언어 검색", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..759931a3a 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 11:22:34.341908", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -11739,5 +11739,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Kokią kalbą mokotės?", + "searchLanguagesHint": "Ieškoti tikslo kalbų", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..f6951d6a2 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 11:22:29.356174", "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,15 @@ "@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": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_nb.arb b/lib/l10n/intl_nb.arb index 24eb30e86..8c033bbed 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 11:22:19.521122", "about": "Om", "@about": { "type": "String", @@ -12027,5 +12027,15 @@ "@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": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_nl.arb b/lib/l10n/intl_nl.arb index c9a7bb5db..841565f86 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 11:22:38.374548", "about": "Over ons", "@about": { "type": "String", @@ -10934,5 +10934,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Welke taal ben je aan het leren?", + "searchLanguagesHint": "Zoek doeltalen", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..c84225bcc 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 11:22:45.302604", "about": "O aplikacji", "@about": { "type": "String", @@ -10932,5 +10932,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Jakiego języka się uczysz?", + "searchLanguagesHint": "Szukaj języków docelowych", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..1f483a77e 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 11:22:05.803053", "copiedToClipboard": "Copiada para a área de transferência", "@copiedToClipboard": { "type": "String", @@ -12034,5 +12034,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Qual idioma você está aprendendo?", + "searchLanguagesHint": "Pesquise idiomas-alvo", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..1d5c7929c 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 11:22:03.356659", "about": "Sobre", "@about": { "type": "String", @@ -11292,5 +11292,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Qual idioma você está aprendendo?", + "searchLanguagesHint": "Pesquise idiomas-alvo", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..1917430aa 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 11:22:25.268348", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -11963,5 +11963,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Qual idioma você está aprendendo?", + "searchLanguagesHint": "Pesquise idiomas-alvo", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..040b2eef8 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 11:21:55.514912", "about": "Despre", "@about": { "type": "String", @@ -11669,5 +11669,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Ce limbă înveți?", + "searchLanguagesHint": "Caută limbi țintă", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..e4210d833 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 11:22:52.811123", "about": "О проекте", "@about": { "type": "String", @@ -11042,5 +11042,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Какой язык вы изучаете?", + "searchLanguagesHint": "Поиск целевых языков", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..251bccf24 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 11:21:56.691598", "about": "O aplikácii", "@about": { "type": "String", @@ -12018,5 +12018,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Aký jazyk sa učíte?", + "searchLanguagesHint": "Hľadajte cieľové jazyky", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..af6bf03c8 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 11:22:12.982879", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -12015,5 +12015,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Katero jezika se učiš?", + "searchLanguagesHint": "Išči ciljne jezike", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..de0464130 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 11:22:55.286222", "about": "О програму", "@about": { "type": "String", @@ -12036,5 +12036,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Koji jezik učite?", + "searchLanguagesHint": "Pretraži ciljne jezike", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..727f3bcff 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 11:22:46.768605", "about": "Om", "@about": { "type": "String", @@ -11412,5 +11412,15 @@ "@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": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ta.arb b/lib/l10n/intl_ta.arb index 67a9812e1..019abe378 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 11:22:37.250743", "acceptedTheInvitation": "👍 {username} அழைப்பை ஏற்றுக்கொண்டது", "@acceptedTheInvitation": { "type": "String", @@ -11158,5 +11158,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "நீங்கள் எது மொழி கற்றுக்கொள்கிறீர்கள்?", + "searchLanguagesHint": "இலக்கு மொழிகளை தேடுங்கள்", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..48f1f7b0c 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 11:22:33.163067", "@setCustomPermissionLevel": { "type": "String", "placeholders": {} @@ -12023,5 +12023,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "మీరు ఏ భాష నేర్చుకుంటున్నారు?", + "searchLanguagesHint": "లక్ష్య భాషలను శోధించండి", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..3b408adaa 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 11:22:23.457945", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -11992,5 +11992,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "คุณกำลังเรียนภาษาอะไรอยู่?", + "searchLanguagesHint": "ค้นหาภาษาที่ต้องการ", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..674059505 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 11:22:31.719920", "about": "Hakkında", "@about": { "type": "String", @@ -11156,5 +11156,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Hangi dili öğreniyorsunuz?", + "searchLanguagesHint": "Hedef dilleri arayın", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..035827df0 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 11:22:17.132130", "about": "Про застосунок", "@about": { "type": "String", @@ -10928,5 +10928,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "Яку мову ви вивчаєте?", + "searchLanguagesHint": "Шукати цільові мови", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..a34bac973 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 11:22:35.606622", "about": "Giới thiệu", "@about": { "type": "String", @@ -6504,5 +6504,15 @@ "@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": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_yue.arb b/lib/l10n/intl_yue.arb index b32ac2bfb..851faa12c 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 11:22:14.017616", "@ignoreUser": { "type": "String", "placeholders": {} @@ -12025,5 +12025,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "你正在學習什麼語言?", + "searchLanguagesHint": "搜尋目標語言", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..3b3251ebe 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 11:22:40.631434", "about": "关于", "@about": { "type": "String", @@ -10925,5 +10925,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "你正在学习什么语言?", + "searchLanguagesHint": "搜索目标语言", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..c56b58a75 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 11:22:26.284769", "about": "關於", "@about": { "type": "String", @@ -10932,5 +10932,15 @@ "@newCourseAccess": { "type": "String", "placeholders": {} + }, + "onboardingLanguagesTitle": "你正在學習什麼語言?", + "searchLanguagesHint": "搜尋目標語言", + "@onboardingLanguagesTitle": { + "type": "String", + "placeholders": {} + }, + "@searchLanguagesHint": { + "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..abf42cc51 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,11 @@ 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 (!InstructionsEnum.clickMessage.isToggledOff) { + InstructionsEnum.clickMessage.setToggledOff(true); } - if (event.messageType == MessageTypes.Audio && - !InstructionsEnum.clickAudioMessages.isToggledOff) { - InstructionsEnum.clickAudioMessages.setToggledOff(true); + if (!InstructionsEnum.shimmerNewToken.isToggledOff) { + InstructionsEnum.shimmerNewToken.setToggledOff(true); } if (!kIsWeb) { diff --git a/lib/pages/chat/events/html_message.dart b/lib/pages/chat/events/html_message.dart index 78a5149a5..b6d8da92a 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,10 @@ class HtmlMessage extends StatelessWidget { : false; final isNew = token != null && newTokens.contains(token.text); + final isFirstNewToken = isNew && 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 +506,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/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/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/language_selection_page.dart b/lib/pangea/login/pages/language_selection_page.dart index fad33f3fd..e86b55bbd 100644 --- a/lib/pangea/login/pages/language_selection_page.dart +++ b/lib/pangea/login/pages/language_selection_page.dart @@ -4,6 +4,7 @@ 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/languages/language_model.dart'; import 'package:fluffychat/pangea/languages/language_service.dart'; import 'package:fluffychat/pangea/languages/p_language_store.dart'; @@ -102,7 +103,24 @@ class LanguageSelectionPageState extends State { return Scaffold( appBar: AppBar( - title: Text(L10n.of(context).languages), + title: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 450, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BackButton( + onPressed: Navigator.of(context).pop, + ), + Text(L10n.of(context).onboardingLanguagesTitle), + const SizedBox( + width: 40.0, + ), + ], + ), + ), + automaticallyImplyLeading: false, ), body: SafeArea( child: Center( @@ -121,6 +139,7 @@ class LanguageSelectionPageState extends State { border: OutlineInputBorder( borderRadius: BorderRadius.circular(50), ), + hintText: L10n.of(context).searchLanguagesHint, ), ), Expanded( @@ -153,27 +172,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 +250,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/toolbar/reading_assistance/select_mode_buttons.dart b/lib/pangea/toolbar/reading_assistance/select_mode_buttons.dart index 1a43815e8..8d0f9f201 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,31 @@ 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, + 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, + ), ), ), ), From 1828c368ee7911c14543bdf13955f1f9e50cdf87 Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Wed, 28 Jan 2026 11:56:59 -0500 Subject: [PATCH 03/19] chore: update logic for which bot chats are targeted for bot options update on language update, add retry logic (#5488) --- lib/pangea/bot/utils/bot_room_extension.dart | 44 +++++++++++-- .../utils/bot_client_extension.dart | 64 ++++++++++++------- 2 files changed, 78 insertions(+), 30 deletions(-) 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/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); } } From 0df853a616c45742d439bc5dcfbb70ad73ed75cc Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Wed, 28 Jan 2026 13:19:13 -0500 Subject: [PATCH 04/19] chore: add subtitle to chat with support tile (#5494) --- lib/l10n/intl_ar.arb | 7 ++++++- lib/l10n/intl_be.arb | 7 ++++++- lib/l10n/intl_bn.arb | 7 ++++++- lib/l10n/intl_bo.arb | 7 ++++++- lib/l10n/intl_ca.arb | 7 ++++++- lib/l10n/intl_cs.arb | 7 ++++++- lib/l10n/intl_da.arb | 7 ++++++- lib/l10n/intl_de.arb | 7 ++++++- lib/l10n/intl_el.arb | 7 ++++++- lib/l10n/intl_en.arb | 3 ++- lib/l10n/intl_eo.arb | 7 ++++++- lib/l10n/intl_es.arb | 7 ++++++- lib/l10n/intl_et.arb | 7 ++++++- lib/l10n/intl_eu.arb | 7 ++++++- lib/l10n/intl_fa.arb | 7 ++++++- lib/l10n/intl_fi.arb | 7 ++++++- lib/l10n/intl_fil.arb | 7 ++++++- lib/l10n/intl_fr.arb | 7 ++++++- lib/l10n/intl_ga.arb | 7 ++++++- lib/l10n/intl_gl.arb | 7 ++++++- lib/l10n/intl_he.arb | 7 ++++++- lib/l10n/intl_hi.arb | 7 ++++++- lib/l10n/intl_hr.arb | 7 ++++++- lib/l10n/intl_hu.arb | 7 ++++++- lib/l10n/intl_ia.arb | 7 ++++++- lib/l10n/intl_id.arb | 7 ++++++- lib/l10n/intl_ie.arb | 7 ++++++- lib/l10n/intl_it.arb | 7 ++++++- lib/l10n/intl_ja.arb | 7 ++++++- lib/l10n/intl_ka.arb | 7 ++++++- lib/l10n/intl_ko.arb | 7 ++++++- lib/l10n/intl_lt.arb | 7 ++++++- lib/l10n/intl_lv.arb | 7 ++++++- lib/l10n/intl_nb.arb | 7 ++++++- lib/l10n/intl_nl.arb | 7 ++++++- lib/l10n/intl_pl.arb | 7 ++++++- lib/l10n/intl_pt.arb | 7 ++++++- lib/l10n/intl_pt_BR.arb | 7 ++++++- lib/l10n/intl_pt_PT.arb | 7 ++++++- lib/l10n/intl_ro.arb | 7 ++++++- lib/l10n/intl_ru.arb | 7 ++++++- lib/l10n/intl_sk.arb | 7 ++++++- lib/l10n/intl_sl.arb | 7 ++++++- lib/l10n/intl_sr.arb | 7 ++++++- lib/l10n/intl_sv.arb | 7 ++++++- lib/l10n/intl_ta.arb | 7 ++++++- lib/l10n/intl_te.arb | 7 ++++++- lib/l10n/intl_th.arb | 7 ++++++- lib/l10n/intl_tr.arb | 7 ++++++- lib/l10n/intl_uk.arb | 7 ++++++- lib/l10n/intl_vi.arb | 7 ++++++- lib/l10n/intl_yue.arb | 7 ++++++- lib/l10n/intl_zh.arb | 7 ++++++- lib/l10n/intl_zh_Hant.arb | 7 ++++++- lib/pages/chat_list/chat_list_body.dart | 1 + 55 files changed, 321 insertions(+), 54 deletions(-) diff --git a/lib/l10n/intl_ar.arb b/lib/l10n/intl_ar.arb index a3e84354e..e3efdee57 100644 --- a/lib/l10n/intl_ar.arb +++ b/lib/l10n/intl_ar.arb @@ -1,6 +1,6 @@ { "@@locale": "ar", - "@@last_modified": "2026-01-28 11:22:30.477643", + "@@last_modified": "2026-01-28 13:15:01.672677", "about": "حول", "@about": { "type": "String", @@ -11161,5 +11161,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "أسئلة؟ نحن هنا للمساعدة!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_be.arb b/lib/l10n/intl_be.arb index 83396d15e..5b0786d71 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-28 11:22:20.711094", + "@@last_modified": "2026-01-28 13:14:52.470096", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -12043,5 +12043,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Пытанні? Мы тут, каб дапамагчы!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_bn.arb b/lib/l10n/intl_bn.arb index 2b83ca456..3e25a4bce 100644 --- a/lib/l10n/intl_bn.arb +++ b/lib/l10n/intl_bn.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 11:22:41.965760", + "@@last_modified": "2026-01-28 13:15:10.096744", "about": "সম্পর্কে", "@about": { "type": "String", @@ -12048,5 +12048,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "প্রশ্ন আছে? আমরা সাহায্য করতে এখানে আছি!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_bo.arb b/lib/l10n/intl_bo.arb index cf87f482d..eaf77e36f 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-28 11:22:39.504934", + "@@last_modified": "2026-01-28 13:15:08.342907", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -10698,5 +10698,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Pytania? Jesteśmy tutaj, aby pomóc!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ca.arb b/lib/l10n/intl_ca.arb index af000f581..d33b7a35a 100644 --- a/lib/l10n/intl_ca.arb +++ b/lib/l10n/intl_ca.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 11:22:22.285622", + "@@last_modified": "2026-01-28 13:14:53.381545", "about": "Quant a", "@about": { "type": "String", @@ -10968,5 +10968,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Preguntes? Som aquí per ajudar!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_cs.arb b/lib/l10n/intl_cs.arb index 21e52e7d7..a778a593b 100644 --- a/lib/l10n/intl_cs.arb +++ b/lib/l10n/intl_cs.arb @@ -1,6 +1,6 @@ { "@@locale": "cs", - "@@last_modified": "2026-01-28 11:22:18.388059", + "@@last_modified": "2026-01-28 13:14:50.685204", "about": "O aplikaci", "@about": { "type": "String", @@ -11551,5 +11551,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Otázky? Jsme tu, abychom pomohli!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_da.arb b/lib/l10n/intl_da.arb index f9a2cc459..75f564f36 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-28 11:21:51.175741", + "@@last_modified": "2026-01-28 13:14:34.655684", "@aboutHomeserver": { "type": "String", "placeholders": { @@ -12005,5 +12005,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Spørgsmål? Vi er her for at hjælpe!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_de.arb b/lib/l10n/intl_de.arb index 2f2d9004d..62b1a1393 100644 --- a/lib/l10n/intl_de.arb +++ b/lib/l10n/intl_de.arb @@ -1,6 +1,6 @@ { "@@locale": "de", - "@@last_modified": "2026-01-28 11:22:11.549404", + "@@last_modified": "2026-01-28 13:14:46.402247", "alwaysUse24HourFormat": "true", "@alwaysUse24HourFormat": { "description": "Set to true to always display time of day in 24 hour format." @@ -10951,5 +10951,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Fragen? Wir sind hier, um zu helfen!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_el.arb b/lib/l10n/intl_el.arb index e19d4365f..c365a9f70 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-28 11:22:48.079247", + "@@last_modified": "2026-01-28 13:15:14.168967", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -12002,5 +12002,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Ερωτήσεις; Είμαστε εδώ για να βοηθήσουμε!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 2f4ea4eb1..555c2fff0 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -5065,5 +5065,6 @@ "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.", "onboardingLanguagesTitle": "What language are you learning?", - "searchLanguagesHint": "Search target languages" + "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 65fb42eb1..2ef9a5f71 100644 --- a/lib/l10n/intl_eo.arb +++ b/lib/l10n/intl_eo.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 11:22:51.689025", + "@@last_modified": "2026-01-28 13:15:16.669813", "about": "Prio", "@about": { "type": "String", @@ -12033,5 +12033,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Demandoj? Ni ĉi tie por helpi!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_es.arb b/lib/l10n/intl_es.arb index 65fffc353..d22d67cb4 100644 --- a/lib/l10n/intl_es.arb +++ b/lib/l10n/intl_es.arb @@ -1,6 +1,6 @@ { "@@locale": "es", - "@@last_modified": "2026-01-28 11:21:46.446878", + "@@last_modified": "2026-01-28 13:14:29.244829", "about": "Acerca de", "@about": { "type": "String", @@ -8178,5 +8178,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "¿Preguntas? ¡Estamos aquí para ayudar!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_et.arb b/lib/l10n/intl_et.arb index 16a9e2348..7671f3d1e 100644 --- a/lib/l10n/intl_et.arb +++ b/lib/l10n/intl_et.arb @@ -1,6 +1,6 @@ { "@@locale": "et", - "@@last_modified": "2026-01-28 11:22:07.195180", + "@@last_modified": "2026-01-28 13:14:45.496673", "about": "Rakenduse teave", "@about": { "type": "String", @@ -11215,5 +11215,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Küsimused? Me oleme siin, et aidata!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_eu.arb b/lib/l10n/intl_eu.arb index df54f5d95..93ec5f6ad 100644 --- a/lib/l10n/intl_eu.arb +++ b/lib/l10n/intl_eu.arb @@ -1,6 +1,6 @@ { "@@locale": "eu", - "@@last_modified": "2026-01-28 11:22:04.786494", + "@@last_modified": "2026-01-28 13:14:43.760155", "about": "Honi buruz", "@about": { "type": "String", @@ -10944,5 +10944,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Galderak? Hemen gaude laguntzeko!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_fa.arb b/lib/l10n/intl_fa.arb index 81f5df938..ca1d84e7b 100644 --- a/lib/l10n/intl_fa.arb +++ b/lib/l10n/intl_fa.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 11:22:43.436826", + "@@last_modified": "2026-01-28 13:15:11.202215", "repeatPassword": "تکرار رمزعبور", "@repeatPassword": {}, "about": "درباره", @@ -11676,5 +11676,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "سوالات؟ ما اینجا هستیم تا کمک کنیم!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_fi.arb b/lib/l10n/intl_fi.arb index 724790477..d7bd8e914 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-28 11:21:50.004841", + "@@last_modified": "2026-01-28 13:14:33.454529", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -11567,5 +11567,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Kysymyksiä? Olemme täällä auttamassa!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_fil.arb b/lib/l10n/intl_fil.arb index d8ceffa14..47c7dcdf4 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-28 11:22:27.725706", + "@@last_modified": "2026-01-28 13:14:59.720543", "@setCustomPermissionLevel": { "type": "String", "placeholders": {} @@ -11920,5 +11920,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "May mga tanong? Nandito kami para tumulong!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index 95090b0bc..efc6efc65 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -1,6 +1,6 @@ { "@@locale": "fr", - "@@last_modified": "2026-01-28 11:22:58.035061", + "@@last_modified": "2026-01-28 13:15:23.868439", "about": "À propos", "@about": { "type": "String", @@ -11268,5 +11268,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Des questions ? Nous sommes là pour vous aider !", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ga.arb b/lib/l10n/intl_ga.arb index f35eab0df..66084053c 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-28 11:22:56.916598", + "@@last_modified": "2026-01-28 13:15:22.976612", "@customReaction": { "type": "String", "placeholders": {} @@ -10942,5 +10942,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Ceisteanna? Táimid anseo chun cabhrú!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_gl.arb b/lib/l10n/intl_gl.arb index c61e3297c..9a3a458d8 100644 --- a/lib/l10n/intl_gl.arb +++ b/lib/l10n/intl_gl.arb @@ -1,6 +1,6 @@ { "@@locale": "gl", - "@@last_modified": "2026-01-28 11:21:48.193565", + "@@last_modified": "2026-01-28 13:14:30.543364", "about": "Acerca de", "@about": { "type": "String", @@ -10941,5 +10941,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "¿Preguntas? Estamos aquí para axudar!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_he.arb b/lib/l10n/intl_he.arb index 50eebc387..e248347da 100644 --- a/lib/l10n/intl_he.arb +++ b/lib/l10n/intl_he.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 11:22:00.895298", + "@@last_modified": "2026-01-28 13:14:40.798594", "about": "אודות", "@about": { "type": "String", @@ -11993,5 +11993,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "שאלות? אנחנו כאן כדי לעזור!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_hi.arb b/lib/l10n/intl_hi.arb index a34c80ed4..129ea1ddb 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-28 11:22:50.465177", + "@@last_modified": "2026-01-28 13:15:15.802387", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -12029,5 +12029,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "प्रश्न? हम आपकी मदद के लिए यहाँ हैं!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_hr.arb b/lib/l10n/intl_hr.arb index 207deed27..7222fea23 100644 --- a/lib/l10n/intl_hr.arb +++ b/lib/l10n/intl_hr.arb @@ -1,6 +1,6 @@ { "@@locale": "hr", - "@@last_modified": "2026-01-28 11:21:59.771425", + "@@last_modified": "2026-01-28 13:14:39.679670", "about": "Informacije", "@about": { "type": "String", @@ -11316,5 +11316,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Imate pitanja? Tu smo da pomognemo!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_hu.arb b/lib/l10n/intl_hu.arb index 25c63f49d..834f260d0 100644 --- a/lib/l10n/intl_hu.arb +++ b/lib/l10n/intl_hu.arb @@ -1,6 +1,6 @@ { "@@locale": "hu", - "@@last_modified": "2026-01-28 11:21:52.684213", + "@@last_modified": "2026-01-28 13:14:35.577945", "about": "Névjegy", "@about": { "type": "String", @@ -10945,5 +10945,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Kérdése van? Itt vagyunk, hogy segítsünk!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ia.arb b/lib/l10n/intl_ia.arb index 644f4a47e..7a226c9f7 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-28 11:22:02.235990", + "@@last_modified": "2026-01-28 13:14:41.526022", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -12022,5 +12022,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Kwestyon? Nou la pou ede!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_id.arb b/lib/l10n/intl_id.arb index c516c332f..75cff5031 100644 --- a/lib/l10n/intl_id.arb +++ b/lib/l10n/intl_id.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 11:21:53.689466", + "@@last_modified": "2026-01-28 13:14:36.407775", "setAsCanonicalAlias": "Atur sebagai alias utama", "@setAsCanonicalAlias": { "type": "String", @@ -10935,5 +10935,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Pertanyaan? Kami di sini untuk membantu!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ie.arb b/lib/l10n/intl_ie.arb index 6d34d7ad3..86470c46a 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-28 11:21:58.032557", + "@@last_modified": "2026-01-28 13:14:38.933486", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -11918,5 +11918,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Ceisteanna? Táimid anseo chun cabhrú!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_it.arb b/lib/l10n/intl_it.arb index cf25a7485..ca4b2f648 100644 --- a/lib/l10n/intl_it.arb +++ b/lib/l10n/intl_it.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 11:22:15.909371", + "@@last_modified": "2026-01-28 13:14:48.927123", "about": "Informazioni", "@about": { "type": "String", @@ -10947,5 +10947,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Domande? Siamo qui per aiutarti!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ja.arb b/lib/l10n/intl_ja.arb index c87c21207..cc7e1fe9f 100644 --- a/lib/l10n/intl_ja.arb +++ b/lib/l10n/intl_ja.arb @@ -1,6 +1,6 @@ { "@@locale": "ja", - "@@last_modified": "2026-01-28 11:22:49.278909", + "@@last_modified": "2026-01-28 13:15:15.028390", "about": "このアプリについて", "@about": { "type": "String", @@ -11734,5 +11734,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "質問がありますか?私たちはお手伝いします!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ka.arb b/lib/l10n/intl_ka.arb index 7e9018c91..f664b26e4 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-28 11:22:53.904103", + "@@last_modified": "2026-01-28 13:15:18.434906", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -11974,5 +11974,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "კითხვები? ჩვენ აქ ვართ, რომ დაგეხმაროთ!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ko.arb b/lib/l10n/intl_ko.arb index 02d46a1a1..6bcd37c03 100644 --- a/lib/l10n/intl_ko.arb +++ b/lib/l10n/intl_ko.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 11:21:45.008877", + "@@last_modified": "2026-01-28 13:14:27.883089", "about": "소개", "@about": { "type": "String", @@ -11052,5 +11052,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "질문이 있으신가요? 저희가 도와드리겠습니다!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_lt.arb b/lib/l10n/intl_lt.arb index 759931a3a..5cf3a13e8 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-28 11:22:34.341908", + "@@last_modified": "2026-01-28 13:15:04.627463", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -11749,5 +11749,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Klausimai? Mes čia, kad padėtume!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_lv.arb b/lib/l10n/intl_lv.arb index f6951d6a2..799bfdc12 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-28 11:22:29.356174", + "@@last_modified": "2026-01-28 13:15:00.855502", "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", @@ -10930,5 +10930,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Jautājumi? Mēs esam šeit, lai palīdzētu!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_nb.arb b/lib/l10n/intl_nb.arb index 8c033bbed..bcc1051ee 100644 --- a/lib/l10n/intl_nb.arb +++ b/lib/l10n/intl_nb.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 11:22:19.521122", + "@@last_modified": "2026-01-28 13:14:51.538756", "about": "Om", "@about": { "type": "String", @@ -12037,5 +12037,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Spørsmål? Vi er her for å hjelpe!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_nl.arb b/lib/l10n/intl_nl.arb index 841565f86..b00f43158 100644 --- a/lib/l10n/intl_nl.arb +++ b/lib/l10n/intl_nl.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 11:22:38.374548", + "@@last_modified": "2026-01-28 13:15:07.411214", "about": "Over ons", "@about": { "type": "String", @@ -10944,5 +10944,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Vragen? We zijn hier om te helpen!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_pl.arb b/lib/l10n/intl_pl.arb index c84225bcc..db1073500 100644 --- a/lib/l10n/intl_pl.arb +++ b/lib/l10n/intl_pl.arb @@ -1,6 +1,6 @@ { "@@locale": "pl", - "@@last_modified": "2026-01-28 11:22:45.302604", + "@@last_modified": "2026-01-28 13:15:12.000878", "about": "O aplikacji", "@about": { "type": "String", @@ -10942,5 +10942,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Pytania? Jesteśmy tutaj, aby pomóc!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_pt.arb b/lib/l10n/intl_pt.arb index 1f483a77e..d380811d8 100644 --- a/lib/l10n/intl_pt.arb +++ b/lib/l10n/intl_pt.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 11:22:05.803053", + "@@last_modified": "2026-01-28 13:14:44.551739", "copiedToClipboard": "Copiada para a área de transferência", "@copiedToClipboard": { "type": "String", @@ -12044,5 +12044,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Dúvidas? Estamos aqui para ajudar!", + "@supportSubtitle": { + "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 1d5c7929c..1791e719f 100644 --- a/lib/l10n/intl_pt_BR.arb +++ b/lib/l10n/intl_pt_BR.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 11:22:03.356659", + "@@last_modified": "2026-01-28 13:14:42.316685", "about": "Sobre", "@about": { "type": "String", @@ -11302,5 +11302,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Dúvidas? Estamos aqui para ajudar!", + "@supportSubtitle": { + "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 1917430aa..6d83c3f65 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-28 11:22:25.268348", + "@@last_modified": "2026-01-28 13:14:55.919420", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -11973,5 +11973,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Dúvidas? Estamos aqui para ajudar!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ro.arb b/lib/l10n/intl_ro.arb index 040b2eef8..5400b576b 100644 --- a/lib/l10n/intl_ro.arb +++ b/lib/l10n/intl_ro.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 11:21:55.514912", + "@@last_modified": "2026-01-28 13:14:37.290571", "about": "Despre", "@about": { "type": "String", @@ -11679,5 +11679,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Întrebări? Suntem aici să ajutăm!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index e4210d833..f43e24c4f 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -1,6 +1,6 @@ { "@@locale": "ru", - "@@last_modified": "2026-01-28 11:22:52.811123", + "@@last_modified": "2026-01-28 13:15:17.544325", "about": "О проекте", "@about": { "type": "String", @@ -11052,5 +11052,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Вопросы? Мы здесь, чтобы помочь!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_sk.arb b/lib/l10n/intl_sk.arb index 251bccf24..636d87a2c 100644 --- a/lib/l10n/intl_sk.arb +++ b/lib/l10n/intl_sk.arb @@ -1,6 +1,6 @@ { "@@locale": "sk", - "@@last_modified": "2026-01-28 11:21:56.691598", + "@@last_modified": "2026-01-28 13:14:38.125638", "about": "O aplikácii", "@about": { "type": "String", @@ -12028,5 +12028,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Otázky? Sme tu, aby sme pomohli!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_sl.arb b/lib/l10n/intl_sl.arb index af6bf03c8..e8cc227e5 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-28 11:22:12.982879", + "@@last_modified": "2026-01-28 13:14:47.252956", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -12025,5 +12025,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Vprašanja? Tu smo, da pomagamo!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_sr.arb b/lib/l10n/intl_sr.arb index de0464130..39f7eca2c 100644 --- a/lib/l10n/intl_sr.arb +++ b/lib/l10n/intl_sr.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 11:22:55.286222", + "@@last_modified": "2026-01-28 13:15:21.956772", "about": "О програму", "@about": { "type": "String", @@ -12046,5 +12046,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Pitanja? Tu smo da pomognemo!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_sv.arb b/lib/l10n/intl_sv.arb index 727f3bcff..2ce1414b0 100644 --- a/lib/l10n/intl_sv.arb +++ b/lib/l10n/intl_sv.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 11:22:46.768605", + "@@last_modified": "2026-01-28 13:15:13.142667", "about": "Om", "@about": { "type": "String", @@ -11422,5 +11422,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Frågor? Vi är här för att hjälpa till!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ta.arb b/lib/l10n/intl_ta.arb index 019abe378..25759505d 100644 --- a/lib/l10n/intl_ta.arb +++ b/lib/l10n/intl_ta.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 11:22:37.250743", + "@@last_modified": "2026-01-28 13:15:06.531947", "acceptedTheInvitation": "👍 {username} அழைப்பை ஏற்றுக்கொண்டது", "@acceptedTheInvitation": { "type": "String", @@ -11168,5 +11168,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "கேள்விகள்? நாங்கள் உதவ இங்கே இருக்கிறோம்!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_te.arb b/lib/l10n/intl_te.arb index 48f1f7b0c..de5e3b5e4 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-28 11:22:33.163067", + "@@last_modified": "2026-01-28 13:15:03.545787", "@setCustomPermissionLevel": { "type": "String", "placeholders": {} @@ -12033,5 +12033,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "ప్రశ్నలు? మేము మీకు సహాయం చేయడానికి ఇక్కడ ఉన్నాము!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_th.arb b/lib/l10n/intl_th.arb index 3b408adaa..93eb27946 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-28 11:22:23.457945", + "@@last_modified": "2026-01-28 13:14:54.268890", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -12002,5 +12002,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "มีคำถามไหม? เราพร้อมที่จะช่วยเหลือ!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_tr.arb b/lib/l10n/intl_tr.arb index 674059505..91c0cb26a 100644 --- a/lib/l10n/intl_tr.arb +++ b/lib/l10n/intl_tr.arb @@ -1,6 +1,6 @@ { "@@locale": "tr", - "@@last_modified": "2026-01-28 11:22:31.719920", + "@@last_modified": "2026-01-28 13:15:02.596810", "about": "Hakkında", "@about": { "type": "String", @@ -11166,5 +11166,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Sorular mı? Yardımcı olmaya buradayız!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_uk.arb b/lib/l10n/intl_uk.arb index 035827df0..2ec6786db 100644 --- a/lib/l10n/intl_uk.arb +++ b/lib/l10n/intl_uk.arb @@ -1,6 +1,6 @@ { "@@locale": "uk", - "@@last_modified": "2026-01-28 11:22:17.132130", + "@@last_modified": "2026-01-28 13:14:49.802739", "about": "Про застосунок", "@about": { "type": "String", @@ -10938,5 +10938,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Питання? Ми тут, щоб допомогти!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_vi.arb b/lib/l10n/intl_vi.arb index a34bac973..2e3f6c370 100644 --- a/lib/l10n/intl_vi.arb +++ b/lib/l10n/intl_vi.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 11:22:35.606622", + "@@last_modified": "2026-01-28 13:15:05.563934", "about": "Giới thiệu", "@about": { "type": "String", @@ -6514,5 +6514,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "Câu hỏi? Chúng tôi ở đây để giúp đỡ!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_yue.arb b/lib/l10n/intl_yue.arb index 851faa12c..660f86c39 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-28 11:22:14.017616", + "@@last_modified": "2026-01-28 13:14:48.149157", "@ignoreUser": { "type": "String", "placeholders": {} @@ -12035,5 +12035,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "有問題嗎?我們在這裡幫助你!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb index 3b3251ebe..c712caf8a 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -1,6 +1,6 @@ { "@@locale": "zh", - "@@last_modified": "2026-01-28 11:22:40.631434", + "@@last_modified": "2026-01-28 13:15:09.153321", "about": "关于", "@about": { "type": "String", @@ -10935,5 +10935,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "有问题吗?我们在这里帮助您!", + "@supportSubtitle": { + "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 c56b58a75..d59728531 100644 --- a/lib/l10n/intl_zh_Hant.arb +++ b/lib/l10n/intl_zh_Hant.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 11:22:26.284769", + "@@last_modified": "2026-01-28 13:14:56.825997", "about": "關於", "@about": { "type": "String", @@ -10942,5 +10942,10 @@ "@searchLanguagesHint": { "type": "String", "placeholders": {} + }, + "supportSubtitle": "有問題嗎?我們在這裡幫助您!", + "@supportSubtitle": { + "type": "String", + "placeholders": {} } } \ No newline at end of file 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, From 95e0f8052f442e96a962d0612be34e031f7e6a81 Mon Sep 17 00:00:00 2001 From: Kelrap <99418823+Kelrap@users.noreply.github.com> Date: Wed, 28 Jan 2026 13:19:40 -0500 Subject: [PATCH 05/19] Use vocab symbol for newly collected words (#5489) --- lib/pangea/toolbar/reading_assistance/new_word_overlay.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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, ), From 53364007b7a35dddad30c26fe8470b59aae1d7a2 Mon Sep 17 00:00:00 2001 From: Kelrap <99418823+Kelrap@users.noreply.github.com> Date: Wed, 28 Jan 2026 13:28:11 -0500 Subject: [PATCH 06/19] Show different course plan page if 500 error is detected (#5478) * Show different course plan page if 500 error is detected * translations --------- Co-authored-by: ggurdin --- lib/l10n/intl_ar.arb | 7 ++++++- lib/l10n/intl_be.arb | 7 ++++++- lib/l10n/intl_bn.arb | 7 ++++++- lib/l10n/intl_bo.arb | 7 ++++++- lib/l10n/intl_ca.arb | 7 ++++++- lib/l10n/intl_cs.arb | 7 ++++++- lib/l10n/intl_da.arb | 7 ++++++- lib/l10n/intl_de.arb | 7 ++++++- lib/l10n/intl_el.arb | 7 ++++++- lib/l10n/intl_en.arb | 1 + lib/l10n/intl_eo.arb | 7 ++++++- lib/l10n/intl_es.arb | 7 ++++++- lib/l10n/intl_et.arb | 7 ++++++- lib/l10n/intl_eu.arb | 7 ++++++- lib/l10n/intl_fa.arb | 7 ++++++- lib/l10n/intl_fi.arb | 7 ++++++- lib/l10n/intl_fil.arb | 7 ++++++- lib/l10n/intl_fr.arb | 7 ++++++- lib/l10n/intl_ga.arb | 7 ++++++- lib/l10n/intl_gl.arb | 7 ++++++- lib/l10n/intl_he.arb | 7 ++++++- lib/l10n/intl_hi.arb | 7 ++++++- lib/l10n/intl_hr.arb | 7 ++++++- lib/l10n/intl_hu.arb | 7 ++++++- lib/l10n/intl_ia.arb | 7 ++++++- lib/l10n/intl_id.arb | 7 ++++++- lib/l10n/intl_ie.arb | 7 ++++++- lib/l10n/intl_it.arb | 7 ++++++- lib/l10n/intl_ja.arb | 7 ++++++- lib/l10n/intl_ka.arb | 7 ++++++- lib/l10n/intl_ko.arb | 7 ++++++- lib/l10n/intl_lt.arb | 7 ++++++- lib/l10n/intl_lv.arb | 7 ++++++- lib/l10n/intl_nb.arb | 7 ++++++- lib/l10n/intl_nl.arb | 7 ++++++- lib/l10n/intl_pl.arb | 7 ++++++- lib/l10n/intl_pt.arb | 7 ++++++- lib/l10n/intl_pt_BR.arb | 7 ++++++- lib/l10n/intl_pt_PT.arb | 7 ++++++- lib/l10n/intl_ro.arb | 7 ++++++- lib/l10n/intl_ru.arb | 7 ++++++- lib/l10n/intl_sk.arb | 7 ++++++- lib/l10n/intl_sl.arb | 7 ++++++- lib/l10n/intl_sr.arb | 7 ++++++- lib/l10n/intl_sv.arb | 7 ++++++- lib/l10n/intl_ta.arb | 7 ++++++- lib/l10n/intl_te.arb | 7 ++++++- lib/l10n/intl_th.arb | 7 ++++++- lib/l10n/intl_tr.arb | 7 ++++++- lib/l10n/intl_uk.arb | 7 ++++++- lib/l10n/intl_vi.arb | 7 ++++++- lib/l10n/intl_yue.arb | 7 ++++++- lib/l10n/intl_zh.arb | 7 ++++++- lib/l10n/intl_zh_Hant.arb | 7 ++++++- lib/pangea/course_settings/course_settings.dart | 12 ++++++++++++ 55 files changed, 331 insertions(+), 53 deletions(-) diff --git a/lib/l10n/intl_ar.arb b/lib/l10n/intl_ar.arb index e3efdee57..79de6dbcb 100644 --- a/lib/l10n/intl_ar.arb +++ b/lib/l10n/intl_ar.arb @@ -1,6 +1,6 @@ { "@@locale": "ar", - "@@last_modified": "2026-01-28 13:15:01.672677", + "@@last_modified": "2026-01-28 13:26:35.542116", "about": "حول", "@about": { "type": "String", @@ -11166,5 +11166,10 @@ "@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 5b0786d71..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-28 13:14:52.470096", + "@@last_modified": "2026-01-28 13:26:22.828870", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -12048,5 +12048,10 @@ "@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 3e25a4bce..8f60d1e0b 100644 --- a/lib/l10n/intl_bn.arb +++ b/lib/l10n/intl_bn.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 13:15:10.096744", + "@@last_modified": "2026-01-28 13:26:47.712647", "about": "সম্পর্কে", "@about": { "type": "String", @@ -12053,5 +12053,10 @@ "@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 eaf77e36f..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-28 13:15:08.342907", + "@@last_modified": "2026-01-28 13:26:44.570789", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -10703,5 +10703,10 @@ "@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 d33b7a35a..32c6fa6c5 100644 --- a/lib/l10n/intl_ca.arb +++ b/lib/l10n/intl_ca.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 13:14:53.381545", + "@@last_modified": "2026-01-28 13:26:23.872618", "about": "Quant a", "@about": { "type": "String", @@ -10973,5 +10973,10 @@ "@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 a778a593b..94fb2d6fd 100644 --- a/lib/l10n/intl_cs.arb +++ b/lib/l10n/intl_cs.arb @@ -1,6 +1,6 @@ { "@@locale": "cs", - "@@last_modified": "2026-01-28 13:14:50.685204", + "@@last_modified": "2026-01-28 13:26:20.564591", "about": "O aplikaci", "@about": { "type": "String", @@ -11556,5 +11556,10 @@ "@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 75f564f36..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-28 13:14:34.655684", + "@@last_modified": "2026-01-28 13:25:58.575899", "@aboutHomeserver": { "type": "String", "placeholders": { @@ -12010,5 +12010,10 @@ "@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 62b1a1393..d09f5edac 100644 --- a/lib/l10n/intl_de.arb +++ b/lib/l10n/intl_de.arb @@ -1,6 +1,6 @@ { "@@locale": "de", - "@@last_modified": "2026-01-28 13:14:46.402247", + "@@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." @@ -10956,5 +10956,10 @@ "@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 c365a9f70..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-28 13:15:14.168967", + "@@last_modified": "2026-01-28 13:26:53.057151", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -12007,5 +12007,10 @@ "@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 555c2fff0..e8064ea1e 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -5064,6 +5064,7 @@ "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.", + "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 2ef9a5f71..09bb94838 100644 --- a/lib/l10n/intl_eo.arb +++ b/lib/l10n/intl_eo.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 13:15:16.669813", + "@@last_modified": "2026-01-28 13:26:57.219657", "about": "Prio", "@about": { "type": "String", @@ -12038,5 +12038,10 @@ "@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 d22d67cb4..d55ec2891 100644 --- a/lib/l10n/intl_es.arb +++ b/lib/l10n/intl_es.arb @@ -1,6 +1,6 @@ { "@@locale": "es", - "@@last_modified": "2026-01-28 13:14:29.244829", + "@@last_modified": "2026-01-28 13:25:54.826808", "about": "Acerca de", "@about": { "type": "String", @@ -8183,5 +8183,10 @@ "@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 7671f3d1e..8c8809c5a 100644 --- a/lib/l10n/intl_et.arb +++ b/lib/l10n/intl_et.arb @@ -1,6 +1,6 @@ { "@@locale": "et", - "@@last_modified": "2026-01-28 13:14:45.496673", + "@@last_modified": "2026-01-28 13:26:13.431455", "about": "Rakenduse teave", "@about": { "type": "String", @@ -11220,5 +11220,10 @@ "@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 93ec5f6ad..3a569f1af 100644 --- a/lib/l10n/intl_eu.arb +++ b/lib/l10n/intl_eu.arb @@ -1,6 +1,6 @@ { "@@locale": "eu", - "@@last_modified": "2026-01-28 13:14:43.760155", + "@@last_modified": "2026-01-28 13:26:10.864458", "about": "Honi buruz", "@about": { "type": "String", @@ -10949,5 +10949,10 @@ "@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 ca1d84e7b..a9a59f38c 100644 --- a/lib/l10n/intl_fa.arb +++ b/lib/l10n/intl_fa.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 13:15:11.202215", + "@@last_modified": "2026-01-28 13:26:49.168597", "repeatPassword": "تکرار رمزعبور", "@repeatPassword": {}, "about": "درباره", @@ -11681,5 +11681,10 @@ "@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 d7bd8e914..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-28 13:14:33.454529", + "@@last_modified": "2026-01-28 13:25:57.438673", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -11572,5 +11572,10 @@ "@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 47c7dcdf4..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-28 13:14:59.720543", + "@@last_modified": "2026-01-28 13:26:32.216257", "@setCustomPermissionLevel": { "type": "String", "placeholders": {} @@ -11925,5 +11925,10 @@ "@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 efc6efc65..22902c22d 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -1,6 +1,6 @@ { "@@locale": "fr", - "@@last_modified": "2026-01-28 13:15:23.868439", + "@@last_modified": "2026-01-28 13:27:03.910294", "about": "À propos", "@about": { "type": "String", @@ -11273,5 +11273,10 @@ "@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 66084053c..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-28 13:15:22.976612", + "@@last_modified": "2026-01-28 13:27:02.604512", "@customReaction": { "type": "String", "placeholders": {} @@ -10947,5 +10947,10 @@ "@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 9a3a458d8..f1aed4b67 100644 --- a/lib/l10n/intl_gl.arb +++ b/lib/l10n/intl_gl.arb @@ -1,6 +1,6 @@ { "@@locale": "gl", - "@@last_modified": "2026-01-28 13:14:30.543364", + "@@last_modified": "2026-01-28 13:25:56.077589", "about": "Acerca de", "@about": { "type": "String", @@ -10946,5 +10946,10 @@ "@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 e248347da..d75f1d839 100644 --- a/lib/l10n/intl_he.arb +++ b/lib/l10n/intl_he.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 13:14:40.798594", + "@@last_modified": "2026-01-28 13:26:06.755080", "about": "אודות", "@about": { "type": "String", @@ -11998,5 +11998,10 @@ "@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 129ea1ddb..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-28 13:15:15.802387", + "@@last_modified": "2026-01-28 13:26:55.548010", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -12034,5 +12034,10 @@ "@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 7222fea23..f0a63cb08 100644 --- a/lib/l10n/intl_hr.arb +++ b/lib/l10n/intl_hr.arb @@ -1,6 +1,6 @@ { "@@locale": "hr", - "@@last_modified": "2026-01-28 13:14:39.679670", + "@@last_modified": "2026-01-28 13:26:05.722028", "about": "Informacije", "@about": { "type": "String", @@ -11321,5 +11321,10 @@ "@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 834f260d0..d21418bcf 100644 --- a/lib/l10n/intl_hu.arb +++ b/lib/l10n/intl_hu.arb @@ -1,6 +1,6 @@ { "@@locale": "hu", - "@@last_modified": "2026-01-28 13:14:35.577945", + "@@last_modified": "2026-01-28 13:25:59.737331", "about": "Névjegy", "@about": { "type": "String", @@ -10950,5 +10950,10 @@ "@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 7a226c9f7..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-28 13:14:41.526022", + "@@last_modified": "2026-01-28 13:26:08.333302", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -12027,5 +12027,10 @@ "@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 75cff5031..afffc9180 100644 --- a/lib/l10n/intl_id.arb +++ b/lib/l10n/intl_id.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 13:14:36.407775", + "@@last_modified": "2026-01-28 13:26:00.772325", "setAsCanonicalAlias": "Atur sebagai alias utama", "@setAsCanonicalAlias": { "type": "String", @@ -10940,5 +10940,10 @@ "@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 86470c46a..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-28 13:14:38.933486", + "@@last_modified": "2026-01-28 13:26:04.525215", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -11923,5 +11923,10 @@ "@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 ca4b2f648..5c3e594ee 100644 --- a/lib/l10n/intl_it.arb +++ b/lib/l10n/intl_it.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 13:14:48.927123", + "@@last_modified": "2026-01-28 13:26:18.238331", "about": "Informazioni", "@about": { "type": "String", @@ -10952,5 +10952,10 @@ "@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 cc7e1fe9f..5728a47ad 100644 --- a/lib/l10n/intl_ja.arb +++ b/lib/l10n/intl_ja.arb @@ -1,6 +1,6 @@ { "@@locale": "ja", - "@@last_modified": "2026-01-28 13:15:15.028390", + "@@last_modified": "2026-01-28 13:26:54.166864", "about": "このアプリについて", "@about": { "type": "String", @@ -11739,5 +11739,10 @@ "@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 f664b26e4..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-28 13:15:18.434906", + "@@last_modified": "2026-01-28 13:27:00.130109", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -11979,5 +11979,10 @@ "@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 6bcd37c03..c07d9a90d 100644 --- a/lib/l10n/intl_ko.arb +++ b/lib/l10n/intl_ko.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 13:14:27.883089", + "@@last_modified": "2026-01-28 13:25:53.869320", "about": "소개", "@about": { "type": "String", @@ -11057,5 +11057,10 @@ "@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 5cf3a13e8..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-28 13:15:04.627463", + "@@last_modified": "2026-01-28 13:26:39.413825", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -11754,5 +11754,10 @@ "@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 799bfdc12..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-28 13:15:00.855502", + "@@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", @@ -10935,5 +10935,10 @@ "@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 bcc1051ee..05c19a9c4 100644 --- a/lib/l10n/intl_nb.arb +++ b/lib/l10n/intl_nb.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 13:14:51.538756", + "@@last_modified": "2026-01-28 13:26:21.535919", "about": "Om", "@about": { "type": "String", @@ -12042,5 +12042,10 @@ "@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 b00f43158..902a72a70 100644 --- a/lib/l10n/intl_nl.arb +++ b/lib/l10n/intl_nl.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 13:15:07.411214", + "@@last_modified": "2026-01-28 13:26:43.074504", "about": "Over ons", "@about": { "type": "String", @@ -10949,5 +10949,10 @@ "@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 db1073500..d7353e601 100644 --- a/lib/l10n/intl_pl.arb +++ b/lib/l10n/intl_pl.arb @@ -1,6 +1,6 @@ { "@@locale": "pl", - "@@last_modified": "2026-01-28 13:15:12.000878", + "@@last_modified": "2026-01-28 13:26:50.545730", "about": "O aplikacji", "@about": { "type": "String", @@ -10947,5 +10947,10 @@ "@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 d380811d8..61e8795dd 100644 --- a/lib/l10n/intl_pt.arb +++ b/lib/l10n/intl_pt.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 13:14:44.551739", + "@@last_modified": "2026-01-28 13:26:11.994676", "copiedToClipboard": "Copiada para a área de transferência", "@copiedToClipboard": { "type": "String", @@ -12049,5 +12049,10 @@ "@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 1791e719f..29d204099 100644 --- a/lib/l10n/intl_pt_BR.arb +++ b/lib/l10n/intl_pt_BR.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 13:14:42.316685", + "@@last_modified": "2026-01-28 13:26:09.473589", "about": "Sobre", "@about": { "type": "String", @@ -11307,5 +11307,10 @@ "@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 6d83c3f65..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-28 13:14:55.919420", + "@@last_modified": "2026-01-28 13:26:26.329625", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -11978,5 +11978,10 @@ "@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 5400b576b..8ab62b620 100644 --- a/lib/l10n/intl_ro.arb +++ b/lib/l10n/intl_ro.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 13:14:37.290571", + "@@last_modified": "2026-01-28 13:26:01.958335", "about": "Despre", "@about": { "type": "String", @@ -11684,5 +11684,10 @@ "@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 f43e24c4f..470f329fd 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -1,6 +1,6 @@ { "@@locale": "ru", - "@@last_modified": "2026-01-28 13:15:17.544325", + "@@last_modified": "2026-01-28 13:26:58.889837", "about": "О проекте", "@about": { "type": "String", @@ -11057,5 +11057,10 @@ "@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 636d87a2c..39202ddde 100644 --- a/lib/l10n/intl_sk.arb +++ b/lib/l10n/intl_sk.arb @@ -1,6 +1,6 @@ { "@@locale": "sk", - "@@last_modified": "2026-01-28 13:14:38.125638", + "@@last_modified": "2026-01-28 13:26:03.157059", "about": "O aplikácii", "@about": { "type": "String", @@ -12033,5 +12033,10 @@ "@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 e8cc227e5..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-28 13:14:47.252956", + "@@last_modified": "2026-01-28 13:26:15.797667", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -12030,5 +12030,10 @@ "@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 39f7eca2c..d666cef35 100644 --- a/lib/l10n/intl_sr.arb +++ b/lib/l10n/intl_sr.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 13:15:21.956772", + "@@last_modified": "2026-01-28 13:27:01.338972", "about": "О програму", "@about": { "type": "String", @@ -12051,5 +12051,10 @@ "@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 2ce1414b0..356a01900 100644 --- a/lib/l10n/intl_sv.arb +++ b/lib/l10n/intl_sv.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 13:15:13.142667", + "@@last_modified": "2026-01-28 13:26:51.814505", "about": "Om", "@about": { "type": "String", @@ -11427,5 +11427,10 @@ "@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 25759505d..af6a8649c 100644 --- a/lib/l10n/intl_ta.arb +++ b/lib/l10n/intl_ta.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 13:15:06.531947", + "@@last_modified": "2026-01-28 13:26:42.100203", "acceptedTheInvitation": "👍 {username} அழைப்பை ஏற்றுக்கொண்டது", "@acceptedTheInvitation": { "type": "String", @@ -11173,5 +11173,10 @@ "@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 de5e3b5e4..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-28 13:15:03.545787", + "@@last_modified": "2026-01-28 13:26:38.102435", "@setCustomPermissionLevel": { "type": "String", "placeholders": {} @@ -12038,5 +12038,10 @@ "@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 93eb27946..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-28 13:14:54.268890", + "@@last_modified": "2026-01-28 13:26:25.241346", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -12007,5 +12007,10 @@ "@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 91c0cb26a..480a0e9c5 100644 --- a/lib/l10n/intl_tr.arb +++ b/lib/l10n/intl_tr.arb @@ -1,6 +1,6 @@ { "@@locale": "tr", - "@@last_modified": "2026-01-28 13:15:02.596810", + "@@last_modified": "2026-01-28 13:26:36.845268", "about": "Hakkında", "@about": { "type": "String", @@ -11171,5 +11171,10 @@ "@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 2ec6786db..c13815b3e 100644 --- a/lib/l10n/intl_uk.arb +++ b/lib/l10n/intl_uk.arb @@ -1,6 +1,6 @@ { "@@locale": "uk", - "@@last_modified": "2026-01-28 13:14:49.802739", + "@@last_modified": "2026-01-28 13:26:19.427065", "about": "Про застосунок", "@about": { "type": "String", @@ -10943,5 +10943,10 @@ "@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 2e3f6c370..3fc2e61d1 100644 --- a/lib/l10n/intl_vi.arb +++ b/lib/l10n/intl_vi.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 13:15:05.563934", + "@@last_modified": "2026-01-28 13:26:40.659203", "about": "Giới thiệu", "@about": { "type": "String", @@ -6519,5 +6519,10 @@ "@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 660f86c39..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-28 13:14:48.149157", + "@@last_modified": "2026-01-28 13:26:16.825978", "@ignoreUser": { "type": "String", "placeholders": {} @@ -12040,5 +12040,10 @@ "@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 c712caf8a..c488b0d36 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -1,6 +1,6 @@ { "@@locale": "zh", - "@@last_modified": "2026-01-28 13:15:09.153321", + "@@last_modified": "2026-01-28 13:26:45.843538", "about": "关于", "@about": { "type": "String", @@ -10940,5 +10940,10 @@ "@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 d59728531..45bd99c5c 100644 --- a/lib/l10n/intl_zh_Hant.arb +++ b/lib/l10n/intl_zh_Hant.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-01-28 13:14:56.825997", + "@@last_modified": "2026-01-28 13:26:27.558260", "about": "關於", "@about": { "type": "String", @@ -10947,5 +10947,10 @@ "@supportSubtitle": { "type": "String", "placeholders": {} + }, + "courseLoadingError": "發生了一些問題,我們正在努力修復。稍後再檢查。", + "@courseLoadingError": { + "type": "String", + "placeholders": {} } } \ No newline at end of file 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, From bbda3b646ba4f7b6ad22abb66a7418b513be8642 Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Wed, 28 Jan 2026 13:51:27 -0500 Subject: [PATCH 07/19] chore: In user search, append needed decorators (#5495) --- .../new_private_chat/new_private_chat.dart | 8 ++++++-- .../pages/pangea_invitation_selection.dart | 13 ++----------- lib/pangea/user/user_search_extension.dart | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+), 13 deletions(-) create mode 100644 lib/pangea/user/user_search_extension.dart 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/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/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); + } +} From 4daee5a6dee071d45c6ea8fe8eed1dae9bb16358 Mon Sep 17 00:00:00 2001 From: Kelrap <99418823+Kelrap@users.noreply.github.com> Date: Wed, 28 Jan 2026 14:33:56 -0500 Subject: [PATCH 08/19] Move login/signup back buttons closer to center of screen (#5496) --- .../space_code_onboarding_view.dart | 22 +++++++++++++++---- .../login/pages/login_options_view.dart | 19 ++++++++++++++-- lib/pangea/login/pages/pangea_login_view.dart | 19 ++++++++++++++-- lib/pangea/login/pages/signup_view.dart | 18 ++++++++++++++- .../login/pages/signup_with_email_view.dart | 20 ++++++++++++++++- 5 files changed, 88 insertions(+), 10 deletions(-) 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/login/pages/login_options_view.dart b/lib/pangea/login/pages/login_options_view.dart index 5ef1bc512..2a35de65b 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).loginToAccount), + const SizedBox( + width: 40.0, + ), + ], + ), ), + automaticallyImplyLeading: false, ), body: SafeArea( child: Center( 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..22fdceda3 100644 --- a/lib/pangea/login/pages/signup_view.dart +++ b/lib/pangea/login/pages/signup_view.dart @@ -27,7 +27,23 @@ 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, + ), + 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( From c455c37f23d9b5cb1b5fc48b7be38bf4e0506598 Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Wed, 28 Jan 2026 14:56:08 -0500 Subject: [PATCH 09/19] fix: better message offset defaults (#5497) --- .../layout/message_selection_positioner.dart | 38 ++++++++++++------- 1 file changed, 24 insertions(+), 14 deletions(-) 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; From 8c7d64c0cc0e3cb395fb5e4292e6eea310cc6401 Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Wed, 28 Jan 2026 15:31:58 -0500 Subject: [PATCH 10/19] chore: more onboarding tweaks (#5499) --- lib/pages/chat/chat.dart | 3 --- lib/pages/chat/events/html_message.dart | 4 +++- .../common/widgets/shrinkable_text.dart | 3 +++ .../login/pages/language_selection_page.dart | 19 +++++++++++++++---- lib/pangea/toolbar/token_rendering_mixin.dart | 5 +++++ 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index abf42cc51..53a791d19 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -2050,9 +2050,6 @@ class ChatController extends State if (!InstructionsEnum.clickMessage.isToggledOff) { InstructionsEnum.clickMessage.setToggledOff(true); } - if (!InstructionsEnum.shimmerNewToken.isToggledOff) { - InstructionsEnum.shimmerNewToken.setToggledOff(true); - } if (!kIsWeb) { HapticFeedback.mediumImpact(); diff --git a/lib/pages/chat/events/html_message.dart b/lib/pages/chat/events/html_message.dart index b6d8da92a..d09a7b7dd 100644 --- a/lib/pages/chat/events/html_message.dart +++ b/lib/pages/chat/events/html_message.dart @@ -448,7 +448,9 @@ class HtmlMessage extends StatelessWidget { : false; final isNew = token != null && newTokens.contains(token.text); - final isFirstNewToken = isNew && newTokens.first == token.text; + final isFirstNewToken = isNew && + controller.buttonEventID == event.eventId && + newTokens.first == token.text; final showShimmer = !InstructionsEnum.shimmerNewToken.isToggledOff && isFirstNewToken; 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/login/pages/language_selection_page.dart b/lib/pangea/login/pages/language_selection_page.dart index e86b55bbd..939af0d98 100644 --- a/lib/pangea/login/pages/language_selection_page.dart +++ b/lib/pangea/login/pages/language_selection_page.dart @@ -5,6 +5,7 @@ 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'; @@ -105,15 +106,25 @@ class LanguageSelectionPageState extends State { appBar: AppBar( title: ConstrainedBox( constraints: const BoxConstraints( - maxWidth: 450, + maxWidth: 500, ), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + spacing: 12.0, children: [ BackButton( onPressed: Navigator.of(context).pop, ), - Text(L10n.of(context).onboardingLanguagesTitle), + Expanded( + child: LayoutBuilder( + builder: (context, constraints) { + return ShrinkableText( + text: L10n.of(context).onboardingLanguagesTitle, + maxWidth: constraints.maxWidth, + alignment: Alignment.center, + ); + }, + ), + ), const SizedBox( width: 40.0, ), @@ -127,7 +138,7 @@ class LanguageSelectionPageState extends State { child: Container( padding: const EdgeInsets.all(20.0), constraints: const BoxConstraints( - maxWidth: 450, + maxWidth: 500, ), child: Column( spacing: 24.0, 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, From 597387def425911f4a394834b1b6b3d99544da2e Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Wed, 28 Jan 2026 16:13:50 -0500 Subject: [PATCH 11/19] chore: update room summary model (#5502) --- .../activity_session_start_page.dart | 14 +++- .../activity_sessions_start_view.dart | 4 +- .../utils/room_summary_extension.dart | 71 ++++++++++++------- .../course_chats/course_chats_page.dart | 8 ++- .../activity_summaries_provider.dart | 16 +++-- 5 files changed, 74 insertions(+), 39 deletions(-) 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/chat_settings/utils/room_summary_extension.dart b/lib/pangea/chat_settings/utils/room_summary_extension.dart index fd72f3669..bcb2bed1b 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'; @@ -52,25 +53,21 @@ 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 JoinRules? joinRule; + final Map? powerLevels; final Map membershipSummary; RoomSummaryResponse({ - required this.activityPlan, - required this.activityRoles, required this.membershipSummary, + this.activityPlan, + this.activityRoles, + this.joinRule, + this.powerLevels, }); Membership? getMembershipForUserId(String userId) { @@ -83,32 +80,52 @@ 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 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); + } + return RoomSummaryResponse( - activityPlan: ActivityPlanModel.fromJson( - json[PangeaEventTypes.activityPlan]?["default"]?["content"] ?? {}, - ), - activityRoles: ActivityRolesModel.fromJson( - json[PangeaEventTypes.activityRole]?["default"]?["content"] ?? {}, - ), + activityPlan: plan, + activityRoles: roles, + powerLevels: powerLevels, + joinRule: joinRule, membershipSummary: Map.from( json['membership_summary'] ?? {}, ), ); } - - Map toJson() { - return { - PangeaEventTypes.activityPlan: activityPlan.toJson(), - PangeaEventTypes.activityRole: activityRoles.toJson(), - 'membership_summary': membershipSummary, - }; - } } diff --git a/lib/pangea/course_chats/course_chats_page.dart b/lib/pangea/course_chats/course_chats_page.dart index b01f8d04c..525852251 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; } 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(); } From 7c8820754cd446cdead6a55dfc30ad25e4c1c394 Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Wed, 28 Jan 2026 16:53:17 -0500 Subject: [PATCH 12/19] fix: Don't shimmer disabled translation button (#5505) --- lib/pangea/toolbar/reading_assistance/select_mode_buttons.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pangea/toolbar/reading_assistance/select_mode_buttons.dart b/lib/pangea/toolbar/reading_assistance/select_mode_buttons.dart index 8d0f9f201..227a8781e 100644 --- a/lib/pangea/toolbar/reading_assistance/select_mode_buttons.dart +++ b/lib/pangea/toolbar/reading_assistance/select_mode_buttons.dart @@ -431,7 +431,8 @@ class SelectModeButtonsState extends State { ShimmerBackground( enabled: !InstructionsEnum .shimmerTranslation.isToggledOff && - mode == SelectMode.translate, + mode == SelectMode.translate && + enabled, borderRadius: BorderRadius.circular(100), child: AnimatedContainer( duration: FluffyThemes.animationDuration, From 2fdbce0c6ddf845e9260906e45d565174dc7a1b7 Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Thu, 29 Jan 2026 10:03:34 -0500 Subject: [PATCH 13/19] feat: initial updates to public course preview page (#5453) * feat: initial updates to public course preview page * chore: account for join rules and power levels in RoomSummaryResponse * load room preview in course preview page * seperate public course preview page from selected course page * display course admins * Add avatar URL and display name to room summary. Get courseID from room summary * don't leave page on knock --- lib/config/routes.dart | 10 +- .../utils/room_summary_extension.dart | 60 +++ .../public_course_preview.dart | 188 +++++++++ .../public_course_preview_view.dart | 388 ++++++++++++++++++ .../course_creation/selected_course_page.dart | 79 +--- .../course_creation/selected_course_view.dart | 130 +++--- .../courses/course_plan_builder.dart | 8 +- lib/pangea/login/pages/find_course_page.dart | 18 +- 8 files changed, 740 insertions(+), 141 deletions(-) create mode 100644 lib/pangea/course_creation/public_course_preview.dart create mode 100644 lib/pangea/course_creation/public_course_preview_view.dart 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/pangea/chat_settings/utils/room_summary_extension.dart b/lib/pangea/chat_settings/utils/room_summary_extension.dart index bcb2bed1b..4315a11a7 100644 --- a/lib/pangea/chat_settings/utils/room_summary_extension.dart +++ b/lib/pangea/chat_settings/utils/room_summary_extension.dart @@ -8,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 { @@ -58,18 +60,35 @@ class RoomSummariesResponse { class RoomSummaryResponse { 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.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; @@ -103,6 +122,20 @@ class RoomSummaryResponse { 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; @@ -118,14 +151,41 @@ class RoomSummaryResponse { .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: plan, activityRoles: roles, + activitySummary: summary, + coursePlan: coursePlan, powerLevels: powerLevels, joinRule: joinRule, membershipSummary: Map.from( json['membership_summary'] ?? {}, ), + displayName: displayName, + avatarUrl: avatarUrl, ); } + + Map toJson() { + return { + '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/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/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/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( From dc71d1caebfde864a8edc0963b0b60eb934ea754 Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Thu, 29 Jan 2026 10:20:56 -0500 Subject: [PATCH 14/19] fix: on IT closed, only replace source text if IT manually dismissed to prevent race condition with accepted continuance stream for single-span translation (#5510) --- lib/pangea/choreographer/choreographer.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/pangea/choreographer/choreographer.dart b/lib/pangea/choreographer/choreographer.dart index 0a5e11d2c..31d869862 100644 --- a/lib/pangea/choreographer/choreographer.dart +++ b/lib/pangea/choreographer/choreographer.dart @@ -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, From 5d6825ad30a0cbbdc7ef75cdcae8a43a0918a42a Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Thu, 29 Jan 2026 10:30:08 -0500 Subject: [PATCH 15/19] fix: reset IT progress on send and on edit (#5511) --- lib/pangea/choreographer/choreographer.dart | 2 +- lib/pangea/choreographer/it/it_controller.dart | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/pangea/choreographer/choreographer.dart b/lib/pangea/choreographer/choreographer.dart index 31d869862..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); 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(); } From 3dddf1d8bd42d8daeb5f2dd12e6a425a132bc65f Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Thu, 29 Jan 2026 10:41:52 -0500 Subject: [PATCH 16/19] chore: show close button on error snackbar (#5512) --- lib/pangea/course_chats/course_chats_page.dart | 5 ++++- lib/utils/error_reporter.dart | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/pangea/course_chats/course_chats_page.dart b/lib/pangea/course_chats/course_chats_page.dart index 525852251..a26eeb56d 100644 --- a/lib/pangea/course_chats/course_chats_page.dart +++ b/lib/pangea/course_chats/course_chats_page.dart @@ -237,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/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) { From 85a2b9efe9e85f0a53955d043f59b816830ead2d Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Thu, 29 Jan 2026 12:45:08 -0500 Subject: [PATCH 17/19] fix: make analytics practice view scrollable, fix heights of top elements to prevent jumping around (#5513) --- .../analytics_misc/example_message_util.dart | 103 +++- .../analytics_practice_view.dart | 487 +++++++++--------- .../choice_cards/game_choice_card.dart | 2 - 3 files changed, 331 insertions(+), 261 deletions(-) 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 36ebddc35..72058e7c4 100644 --- a/lib/pangea/analytics_practice/analytics_practice_view.dart +++ b/lib/pangea/analytics_practice/analytics_practice_view.dart @@ -112,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, + ); + }, + ), ], ); } @@ -234,32 +202,42 @@ 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( - 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( + activity: activity, + ), + const SizedBox(height: 12), + _GrammarErrorTranslationButton( + key: ValueKey( + '${activity.eventID}_${activity.errorOffset}_${activity.errorLength}', + ), + translation: activity.translation, + ), + ], ), - const SizedBox(height: 12), - _GrammarErrorTranslationButton( - key: ValueKey( - '${activity.eventID}_${activity.errorOffset}_${activity.errorLength}', - ), - translation: activity.translation, - ), - ], - ), - _ => const SizedBox(), - }, + _ => const SizedBox(), + }, + ), + ), ), - _ => _ExampleMessageWidget( - controller.getExampleMessage(target!.target), + _ => SizedBox( + height: 100.0, + child: Center( + child: _ExampleMessageWidget( + controller.getExampleMessage(target!.target), + ), + ), ), }, ); @@ -320,6 +298,51 @@ class _ErrorBlankWidget extends StatelessWidget { final errorOffset = activity.errorOffset; final errorLength = 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 Container( padding: const EdgeInsets.symmetric( horizontal: 12, @@ -339,8 +362,8 @@ class _ErrorBlankWidget extends StatelessWidget { fontSize: 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, @@ -351,10 +374,88 @@ class _ErrorBlankWidget extends StatelessWidget { ), ), ), - if (errorOffset + errorLength < text.length) - TextSpan( - text: - text.characters.skip(errorOffset + errorLength).toString(), + if (after.isNotEmpty) TextSpan(text: after), + if (trimmedAfter) const TextSpan(text: '…'), + ], + ), + ), + ); + } +} + +class _GrammarErrorTranslationButton extends StatefulWidget { + final String translation; + + const _GrammarErrorTranslationButton({ + super.key, + required this.translation, + }); + + @override + State<_GrammarErrorTranslationButton> createState() => + _GrammarErrorTranslationButtonState(); +} + +class _GrammarErrorTranslationButtonState + extends State<_GrammarErrorTranslationButton> { + bool _showTranslation = false; + + void _toggleTranslation() { + if (_showTranslation) { + setState(() { + _showTranslation = false; + }); + } else { + setState(() { + _showTranslation = true; + }); + } + } + + @override + Widget build(BuildContext context) { + return Center( + child: GestureDetector( + onTap: _toggleTranslation, + child: Row( + mainAxisSize: MainAxisSize.min, + spacing: 8.0, + children: [ + if (_showTranslation) + Flexible( + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + decoration: BoxDecoration( + color: Color.alphaBlend( + Colors.white.withAlpha(180), + ThemeData.dark().colorScheme.primary, + ), + borderRadius: BorderRadius.circular(16), + ), + child: Text( + widget.translation, + style: TextStyle( + color: Theme.of(context).colorScheme.onPrimaryFixed, + fontSize: + AppConfig.fontSizeFactor * AppConfig.messageFontSize, + ), + ), + ), + ) + else + ElevatedButton( + onPressed: _toggleTranslation, + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + padding: const EdgeInsets.all(8), + ), + child: const Icon( + Icons.lightbulb_outline, + size: 20, + ), ), ], ), @@ -398,43 +499,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(), ); }, ), @@ -553,85 +640,3 @@ class _ChoiceCard extends StatelessWidget { } } } - -class _GrammarErrorTranslationButton extends StatefulWidget { - final String translation; - - const _GrammarErrorTranslationButton({ - super.key, - required this.translation, - }); - - @override - State<_GrammarErrorTranslationButton> createState() => - _GrammarErrorTranslationButtonState(); -} - -class _GrammarErrorTranslationButtonState - extends State<_GrammarErrorTranslationButton> { - bool _showTranslation = false; - - void _toggleTranslation() { - if (_showTranslation) { - setState(() { - _showTranslation = false; - }); - } else { - setState(() { - _showTranslation = true; - }); - } - } - - @override - Widget build(BuildContext context) { - return Center( - child: GestureDetector( - onTap: _toggleTranslation, - child: Row( - mainAxisSize: MainAxisSize.min, - spacing: 8.0, - children: [ - if (_showTranslation) - Flexible( - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 8, - ), - decoration: BoxDecoration( - color: Color.alphaBlend( - Colors.white.withAlpha(180), - ThemeData.dark().colorScheme.primary, - ), - borderRadius: BorderRadius.circular(16), - ), - child: Text( - widget.translation, - style: TextStyle( - color: Theme.of(context).colorScheme.onPrimaryFixed, - fontSize: - AppConfig.fontSizeFactor * AppConfig.messageFontSize, - ), - textAlign: TextAlign.center, - ), - ), - ), - if (!_showTranslation) - ElevatedButton( - onPressed: _toggleTranslation, - style: ElevatedButton.styleFrom( - shape: const CircleBorder(), - padding: const EdgeInsets.all(8), - ), - child: const Icon( - Icons.lightbulb_outline, - size: 20, - ), - ), - ], - ), - ), - ); - } -} 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, From ed6c2ff5ad824dbb8d8b29fd4281b7ca1dc3c48d Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Thu, 29 Jan 2026 12:54:58 -0500 Subject: [PATCH 18/19] fix: save activities to analytics room for corresponding language (#5514) --- .../activity_finished_status_message.dart | 10 +++++++++- .../analytics_data/analytics_update_service.dart | 8 +++++--- 2 files changed, 14 insertions(+), 4 deletions(-) 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/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 { From 33826b02fe1e94f60fdb85c5bca69db56b76aa0a Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Thu, 29 Jan 2026 14:11:20 -0500 Subject: [PATCH 19/19] chore: make login and signup views more consistent (#5518) --- lib/pangea/login/pages/login_options_view.dart | 9 ++++++++- lib/pangea/login/pages/signup_view.dart | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/pangea/login/pages/login_options_view.dart b/lib/pangea/login/pages/login_options_view.dart index 2a35de65b..c6a73b9e4 100644 --- a/lib/pangea/login/pages/login_options_view.dart +++ b/lib/pangea/login/pages/login_options_view.dart @@ -31,7 +31,7 @@ class LoginOptionsView extends StatelessWidget { BackButton( onPressed: Navigator.of(context).pop, ), - Text(L10n.of(context).loginToAccount), + Text(L10n.of(context).login), const SizedBox( width: 40.0, ), @@ -51,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/signup_view.dart b/lib/pangea/login/pages/signup_view.dart index 22fdceda3..6b79ee241 100644 --- a/lib/pangea/login/pages/signup_view.dart +++ b/lib/pangea/login/pages/signup_view.dart @@ -36,6 +36,7 @@ class SignupPageView extends StatelessWidget { BackButton( onPressed: Navigator.of(context).pop, ), + Text(L10n.of(context).signUp), const SizedBox( width: 40.0, ),