From 6f32aab48b80bc8da8045349f6ea7b214efe7990 Mon Sep 17 00:00:00 2001 From: wcjord <32568597+wcjord@users.noreply.github.com> Date: Fri, 27 Feb 2026 12:29:50 -0500 Subject: [PATCH] fix: show "knock accepted" push notification body instead of "You have been invited" (#5823) (#5835) * fix: show knock-accepted notification body when invite follows a knock (#5823) When Synapse accepts a knock it sends an m.room.member invite event. The client was displaying the generic 'You have been invited by X' push notification body because the invite event alone doesn't carry prev_content on the push path. Use KnockTracker (already used for auto-join) to detect knock-accepted invites and show a dedicated 'Your join request was accepted!' notification body instead. - Add knockAccepted string to intl_en.arb - Extract condition into isKnockAcceptedInvite() pure util for testability - Expose KnockTracker.getKnockedRoomIds() publicly (was _getKnockedRoomIds) - Override notification body in push_helper.dart (background) and local_notifications_extension.dart (foreground/web) - Unit tests for all isKnockAcceptedInvite() branches (9 tests) * formatting * fix up pangea comments * fix: avoid race condition with knocked room account data updates and local push notification content * translations --------- Co-authored-by: ggurdin --- lib/l10n/intl_ar.arb | 22 ++- lib/l10n/intl_be.arb | 22 ++- lib/l10n/intl_bn.arb | 22 ++- lib/l10n/intl_bo.arb | 22 ++- lib/l10n/intl_ca.arb | 22 ++- lib/l10n/intl_cs.arb | 22 ++- lib/l10n/intl_da.arb | 22 ++- lib/l10n/intl_de.arb | 22 ++- lib/l10n/intl_el.arb | 22 ++- lib/l10n/intl_en.arb | 2 + lib/l10n/intl_eo.arb | 22 ++- lib/l10n/intl_es.arb | 22 ++- lib/l10n/intl_et.arb | 22 ++- lib/l10n/intl_eu.arb | 22 ++- lib/l10n/intl_fa.arb | 22 ++- lib/l10n/intl_fi.arb | 22 ++- lib/l10n/intl_fil.arb | 22 ++- lib/l10n/intl_fr.arb | 22 ++- lib/l10n/intl_ga.arb | 22 ++- lib/l10n/intl_gl.arb | 22 ++- lib/l10n/intl_he.arb | 22 ++- lib/l10n/intl_hi.arb | 22 ++- lib/l10n/intl_hr.arb | 22 ++- lib/l10n/intl_hu.arb | 22 ++- lib/l10n/intl_ia.arb | 22 ++- lib/l10n/intl_id.arb | 22 ++- lib/l10n/intl_ie.arb | 22 ++- lib/l10n/intl_it.arb | 22 ++- lib/l10n/intl_ja.arb | 22 ++- lib/l10n/intl_ka.arb | 22 ++- lib/l10n/intl_ko.arb | 22 ++- lib/l10n/intl_lt.arb | 22 ++- lib/l10n/intl_lv.arb | 22 ++- lib/l10n/intl_nb.arb | 22 ++- lib/l10n/intl_nl.arb | 22 ++- lib/l10n/intl_pl.arb | 22 ++- lib/l10n/intl_pt.arb | 22 ++- lib/l10n/intl_pt_BR.arb | 22 ++- lib/l10n/intl_pt_PT.arb | 22 ++- lib/l10n/intl_ro.arb | 22 ++- lib/l10n/intl_ru.arb | 22 ++- lib/l10n/intl_sk.arb | 22 ++- lib/l10n/intl_sl.arb | 22 ++- lib/l10n/intl_sr.arb | 22 ++- lib/l10n/intl_sv.arb | 22 ++- lib/l10n/intl_ta.arb | 22 ++- lib/l10n/intl_te.arb | 22 ++- lib/l10n/intl_th.arb | 22 ++- lib/l10n/intl_tr.arb | 22 ++- lib/l10n/intl_uk.arb | 22 ++- lib/l10n/intl_uz.arb | 22 ++- lib/l10n/intl_vi.arb | 22 ++- lib/l10n/intl_yue.arb | 22 ++- lib/l10n/intl_zh.arb | 22 ++- lib/l10n/intl_zh_Hant.arb | 22 ++- lib/pages/chat_list/chat_list.dart | 93 ++++------- .../events/constants/pangea_event_types.dart | 2 + .../join_codes/knock_notification_utils.dart | 36 ++++ .../join_codes/knock_room_extension.dart | 78 ++++++++- lib/pangea/join_codes/knock_tracker.dart | 45 ----- .../join_codes/knocked_rooms_model.dart | 84 ++++++++++ .../notification_background_handler.dart | 20 ++- lib/utils/push_helper.dart | 24 ++- .../local_notifications_extension.dart | 35 ++-- .../pangea/knock_notification_utils_test.dart | 155 ++++++++++++++++++ 65 files changed, 1580 insertions(+), 182 deletions(-) create mode 100644 lib/pangea/join_codes/knock_notification_utils.dart delete mode 100644 lib/pangea/join_codes/knock_tracker.dart create mode 100644 lib/pangea/join_codes/knocked_rooms_model.dart create mode 100644 test/pangea/knock_notification_utils_test.dart diff --git a/lib/l10n/intl_ar.arb b/lib/l10n/intl_ar.arb index ef501a0a5..7100110f2 100644 --- a/lib/l10n/intl_ar.arb +++ b/lib/l10n/intl_ar.arb @@ -1,6 +1,6 @@ { "@@locale": "ar", - "@@last_modified": "2026-02-24 14:59:40.158437", + "@@last_modified": "2026-02-27 12:23:01.172808", "about": "حول", "@about": { "type": "String", @@ -11650,5 +11650,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "تم قبول طلب انضمامك! يمكنك الآن دخول الدورة.", + "sessionFull": "لقد فات الأوان! هذه النشاط ممتلئ.", + "returnToCourse": "العودة إلى الدورة", + "returnHome": "العودة إلى الصفحة الرئيسية", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_be.arb b/lib/l10n/intl_be.arb index ec2fac457..44f9f9e13 100644 --- a/lib/l10n/intl_be.arb +++ b/lib/l10n/intl_be.arb @@ -4619,7 +4619,7 @@ "playWithAI": "Пакуль гуляйце з ШІ", "courseStartDesc": "Pangea Bot гатовы да працы ў любы час!\n\n...але навучанне лепш з сябрамі!", "@@locale": "be", - "@@last_modified": "2026-02-24 14:59:33.504013", + "@@last_modified": "2026-02-27 12:22:44.541648", "@ignore": { "type": "String", "placeholders": {} @@ -11319,5 +11319,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Ваш запыт на ўдзел быў прыняты! Цяпер вы можаце ўвайсці ў курс.", + "sessionFull": "Занадта позна! Гэтая актыўнасць запоўнена.", + "returnToCourse": "Вярнуцца да курса", + "returnHome": "Вярнуцца дадому", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_bn.arb b/lib/l10n/intl_bn.arb index 3b28dd69e..8aa05f3d8 100644 --- a/lib/l10n/intl_bn.arb +++ b/lib/l10n/intl_bn.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-02-24 14:59:47.679831", + "@@last_modified": "2026-02-27 12:23:20.380952", "about": "সম্পর্কে", "@about": { "type": "String", @@ -12044,5 +12044,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "আপনার যোগদানের অনুরোধ গৃহীত হয়েছে! আপনি এখন কোর্সে প্রবেশ করতে পারেন।", + "sessionFull": "বিলম্ব! এই কার্যক্রম পূর্ণ।", + "returnToCourse": "কোর্সে ফিরে যান", + "returnHome": "বাড়িতে ফিরে যান", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_bo.arb b/lib/l10n/intl_bo.arb index 80deb9d0b..ac400b181 100644 --- a/lib/l10n/intl_bo.arb +++ b/lib/l10n/intl_bo.arb @@ -3781,7 +3781,7 @@ "joinPublicTrip": "མི་ཚེས་ལ་ལོག་འབད།", "startOwnTrip": "ངེད་རང་གི་ལོག་ལ་སྦྱོར་བཅོས།", "@@locale": "bo", - "@@last_modified": "2026-02-24 14:59:46.294169", + "@@last_modified": "2026-02-27 12:23:16.997026", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -10701,5 +10701,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Tua richiesta di ingresso è stata accettata! Ora puoi entrare nel corso.", + "sessionFull": "Troppo tardi! Questa attività è piena.", + "returnToCourse": "Torna al corso", + "returnHome": "Torna a casa", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ca.arb b/lib/l10n/intl_ca.arb index 5fd4c9fd5..371dfc566 100644 --- a/lib/l10n/intl_ca.arb +++ b/lib/l10n/intl_ca.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-02-24 14:59:34.223579", + "@@last_modified": "2026-02-27 12:22:46.690959", "about": "Quant a", "@about": { "type": "String", @@ -11460,5 +11460,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "La teva sol·licitud d'unió ha estat acceptada! Ara pots entrar al curs.", + "sessionFull": " massa tard! Aquesta activitat està completa.", + "returnToCourse": "Torna al curs", + "returnHome": "Torna a casa", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_cs.arb b/lib/l10n/intl_cs.arb index a89204b06..2001dc3b0 100644 --- a/lib/l10n/intl_cs.arb +++ b/lib/l10n/intl_cs.arb @@ -1,6 +1,6 @@ { "@@locale": "cs", - "@@last_modified": "2026-02-24 14:59:31.949116", + "@@last_modified": "2026-02-27 12:22:40.747640", "about": "O aplikaci", "@about": { "type": "String", @@ -11872,5 +11872,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Vaše žádost o připojení byla přijata! Nyní můžete vstoupit do kurzu.", + "sessionFull": "Příliš pozdě! Tato aktivita je plná.", + "returnToCourse": "Vrátit se do kurzu", + "returnHome": "Vrátit se domů", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_da.arb b/lib/l10n/intl_da.arb index a2427ee30..617aff378 100644 --- a/lib/l10n/intl_da.arb +++ b/lib/l10n/intl_da.arb @@ -1926,7 +1926,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-02-24 14:59:17.793161", + "@@last_modified": "2026-02-27 12:21:56.303075", "@aboutHomeserver": { "type": "String", "placeholders": { @@ -12500,5 +12500,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Din anmodning om at deltage blev accepteret! Du kan nu deltage i kurset.", + "sessionFull": "For sent! Denne aktivitet er fuld.", + "returnToCourse": "Returner til kurset", + "returnHome": "Returner hjem", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_de.arb b/lib/l10n/intl_de.arb index fd0a054c0..c7df4f493 100644 --- a/lib/l10n/intl_de.arb +++ b/lib/l10n/intl_de.arb @@ -1,6 +1,6 @@ { "@@locale": "de", - "@@last_modified": "2026-02-24 14:59:28.164745", + "@@last_modified": "2026-02-27 12:22:30.095800", "alwaysUse24HourFormat": "true", "@alwaysUse24HourFormat": { "description": "Set to true to always display time of day in 24 hour format." @@ -11289,5 +11289,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Ihre Beitrittsanfrage wurde akzeptiert! Sie können jetzt am Kurs teilnehmen.", + "sessionFull": "Zu spät! Diese Aktivität ist voll.", + "returnToCourse": "Zum Kurs zurückkehren", + "returnHome": "Nach Hause zurückkehren", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_el.arb b/lib/l10n/intl_el.arb index b0989c013..d1376212a 100644 --- a/lib/l10n/intl_el.arb +++ b/lib/l10n/intl_el.arb @@ -4476,7 +4476,7 @@ "playWithAI": "Παίξτε με την Τεχνητή Νοημοσύνη προς το παρόν", "courseStartDesc": "Ο Pangea Bot είναι έτοιμος να ξεκινήσει οποιαδήποτε στιγμή!\n\n...αλλά η μάθηση είναι καλύτερη με φίλους!", "@@locale": "el", - "@@last_modified": "2026-02-24 14:59:51.305832", + "@@last_modified": "2026-02-27 12:23:28.161217", "@checkList": { "type": "String", "placeholders": {} @@ -12459,5 +12459,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Η αίτησή σας για συμμετοχή έγινε αποδεκτή! Μπορείτε τώρα να εισέλθετε στο μάθημα.", + "sessionFull": "Πολύ αργά! Αυτή η δραστηριότητα είναι γεμάτη.", + "returnToCourse": "Επιστροφή στο μάθημα", + "returnHome": "Επιστροφή στην αρχική σελίδα", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index b9b58ede7..47eb03a32 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -5298,6 +5298,8 @@ "languageUpdated": "Target language updated!", "voiceDropdownTitle": "Pangea Bot voice", "knockDesc": "Your request has been sent to course admin! You'll be let in if they approve.", + "knockAccepted": "Your join request was accepted! You can now enter the course.", + "@knockAccepted": {}, "joinSpaceOnboardingDesc": "Do you have an invite code or link to a public course?", "welcomeUser": "Welcome {user}", "@welcomeUser": { diff --git a/lib/l10n/intl_eo.arb b/lib/l10n/intl_eo.arb index 45190ef1b..a801df0fc 100644 --- a/lib/l10n/intl_eo.arb +++ b/lib/l10n/intl_eo.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-02-24 14:59:53.981596", + "@@last_modified": "2026-02-27 12:23:34.605003", "about": "Prio", "@about": { "type": "String", @@ -12523,5 +12523,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Via aliĝpeticio estis akceptita! Vi nun povas eniri la kurson.", + "sessionFull": "Tro malfrue! Ĉi tiu aktiveco estas plena.", + "returnToCourse": "Reiri al la kurso", + "returnHome": "Reiri hejmen", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_es.arb b/lib/l10n/intl_es.arb index 864175f58..bd4d24a86 100644 --- a/lib/l10n/intl_es.arb +++ b/lib/l10n/intl_es.arb @@ -1,6 +1,6 @@ { "@@locale": "es", - "@@last_modified": "2026-02-24 14:59:14.844768", + "@@last_modified": "2026-02-27 12:21:50.256312", "about": "Acerca de", "@about": { "type": "String", @@ -8610,5 +8610,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "¡Tu solicitud de unión fue aceptada! Ahora puedes entrar al curso.", + "sessionFull": "¡Demasiado tarde! Esta actividad está llena.", + "returnToCourse": "Regresar al curso", + "returnHome": "Regresar a casa", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_et.arb b/lib/l10n/intl_et.arb index f4574a416..d325b8b0a 100644 --- a/lib/l10n/intl_et.arb +++ b/lib/l10n/intl_et.arb @@ -1,6 +1,6 @@ { "@@locale": "et", - "@@last_modified": "2026-02-24 14:59:27.455277", + "@@last_modified": "2026-02-27 12:22:27.939566", "about": "Rakenduse teave", "@about": { "type": "String", @@ -11574,5 +11574,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Teie liitumissoovitus on vastu võetud! Nüüd saate kursusele siseneda.", + "sessionFull": "Liiga hilja! See tegevus on täis.", + "returnToCourse": "Tagasi kursusele", + "returnHome": "Tagasi koju", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_eu.arb b/lib/l10n/intl_eu.arb index e41fdbe92..a5bf92698 100644 --- a/lib/l10n/intl_eu.arb +++ b/lib/l10n/intl_eu.arb @@ -1,6 +1,6 @@ { "@@locale": "eu", - "@@last_modified": "2026-02-24 14:59:25.857325", + "@@last_modified": "2026-02-27 12:22:24.260020", "about": "Honi buruz", "@about": { "type": "String", @@ -11300,5 +11300,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Zure parte-hartze eskaera onartu da! Orain ikastaroan sartu zaitezke.", + "sessionFull": "Berandu! Jarduera hau beteta dago.", + "returnToCourse": "Itzuli ikastaroara", + "returnHome": "Itzuli etxera", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_fa.arb b/lib/l10n/intl_fa.arb index fd62091c7..802ca832d 100644 --- a/lib/l10n/intl_fa.arb +++ b/lib/l10n/intl_fa.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-02-24 14:59:48.860067", + "@@last_modified": "2026-02-27 12:23:22.331158", "repeatPassword": "تکرار گذرواژه", "@repeatPassword": {}, "about": "درباره", @@ -11421,5 +11421,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "درخواست پیوستن شما پذیرفته شد! اکنون می‌توانید وارد دوره شوید.", + "sessionFull": "خیلی دیر! این فعالیت پر است.", + "returnToCourse": "بازگشت به دوره", + "returnHome": "بازگشت به خانه", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_fi.arb b/lib/l10n/intl_fi.arb index 5d0cacf63..0a44f0a01 100644 --- a/lib/l10n/intl_fi.arb +++ b/lib/l10n/intl_fi.arb @@ -4604,7 +4604,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-02-24 14:59:16.651035", + "@@last_modified": "2026-02-27 12:21:54.872687", "@notificationRuleJitsi": { "type": "String", "placeholders": {} @@ -11359,5 +11359,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Liittymispyyntösi hyväksyttiin! Voit nyt liittyä kurssille.", + "sessionFull": "Liian myöhäistä! Tämä aktiviteetti on täynnä.", + "returnToCourse": "Palaa kurssille", + "returnHome": "Palaa kotiin", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_fil.arb b/lib/l10n/intl_fil.arb index 486800307..5455e99b2 100644 --- a/lib/l10n/intl_fil.arb +++ b/lib/l10n/intl_fil.arb @@ -2783,7 +2783,7 @@ "selectAll": "Piliin lahat", "deselectAll": "Huwag piliin lahat", "@@locale": "fil", - "@@last_modified": "2026-02-24 14:59:38.624762", + "@@last_modified": "2026-02-27 12:22:55.704285", "@setCustomPermissionLevel": { "type": "String", "placeholders": {} @@ -12415,5 +12415,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Tinanggap ang iyong kahilingan na sumali! Maaari ka nang pumasok sa kurso.", + "sessionFull": "Sobrang huli! Puno na ang aktibidad na ito.", + "returnToCourse": "Bumalik sa kurso", + "returnHome": "Bumalik sa bahay", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index 8bf268516..ec02ac6e1 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -1,6 +1,6 @@ { "@@locale": "fr", - "@@last_modified": "2026-02-24 14:59:58.898976", + "@@last_modified": "2026-02-27 12:23:45.139248", "about": "À propos", "@about": { "type": "String", @@ -11706,5 +11706,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Votre demande de participation a été acceptée ! Vous pouvez maintenant entrer dans le cours.", + "sessionFull": "Trop tard ! Cette activité est complète.", + "returnToCourse": "Retour au cours", + "returnHome": "Retour à l'accueil", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ga.arb b/lib/l10n/intl_ga.arb index edc7373af..53392c87a 100644 --- a/lib/l10n/intl_ga.arb +++ b/lib/l10n/intl_ga.arb @@ -4639,7 +4639,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-02-24 14:59:58.265720", + "@@last_modified": "2026-02-27 12:23:42.906285", "@writeAMessageLangCodes": { "type": "String", "placeholders": { @@ -11311,5 +11311,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Glacadh le do hiarratas chun dul isteach! Is féidir leat anois dul isteach sa chúrsa.", + "sessionFull": "Ró-dhéanach! Tá an gníomhaíocht seo lán.", + "returnToCourse": "Téigh ar ais chuig an gcúrsa", + "returnHome": "Téigh ar ais abhaile", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_gl.arb b/lib/l10n/intl_gl.arb index a64a9e92b..423ef816f 100644 --- a/lib/l10n/intl_gl.arb +++ b/lib/l10n/intl_gl.arb @@ -1,6 +1,6 @@ { "@@locale": "gl", - "@@last_modified": "2026-02-24 14:59:15.793089", + "@@last_modified": "2026-02-27 12:21:52.398653", "about": "Acerca de", "@about": { "type": "String", @@ -11300,5 +11300,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "A túa solicitude de unión foi aceptada! Agora podes entrar no curso.", + "sessionFull": "Demasiado tarde! Esta actividade está completa.", + "returnToCourse": "Volver ao curso", + "returnHome": "Volver a casa", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_he.arb b/lib/l10n/intl_he.arb index 5bafbb3c9..5dd91840d 100644 --- a/lib/l10n/intl_he.arb +++ b/lib/l10n/intl_he.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-02-24 14:59:23.521541", + "@@last_modified": "2026-02-27 12:22:17.196188", "about": "אודות", "@about": { "type": "String", @@ -12483,5 +12483,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "בקשת ההצטרפות שלך התקבלה! אתה יכול עכשיו להיכנס לקורס.", + "sessionFull": "מאוחר מדי! הפעילות הזו מלאה.", + "returnToCourse": "חזור לקורס", + "returnHome": "חזור הביתה", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_hi.arb b/lib/l10n/intl_hi.arb index 58b5be83e..865f0745a 100644 --- a/lib/l10n/intl_hi.arb +++ b/lib/l10n/intl_hi.arb @@ -3999,7 +3999,7 @@ "playWithAI": "अभी के लिए एआई के साथ खेलें", "courseStartDesc": "पैंजिया बॉट कभी भी जाने के लिए तैयार है!\n\n...लेकिन दोस्तों के साथ सीखना बेहतर है!", "@@locale": "hi", - "@@last_modified": "2026-02-24 14:59:52.988187", + "@@last_modified": "2026-02-27 12:23:32.751116", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -12047,5 +12047,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "आपका शामिल होने का अनुरोध स्वीकार कर लिया गया है! आप अब पाठ्यक्रम में प्रवेश कर सकते हैं।", + "sessionFull": "बहुत देर हो गई! यह गतिविधि पूरी हो चुकी है।", + "returnToCourse": "पाठ्यक्रम पर लौटें", + "returnHome": "घर पर लौटें", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_hr.arb b/lib/l10n/intl_hr.arb index 263351bfb..11a6c7fbe 100644 --- a/lib/l10n/intl_hr.arb +++ b/lib/l10n/intl_hr.arb @@ -1,6 +1,6 @@ { "@@locale": "hr", - "@@last_modified": "2026-02-24 14:59:22.923310", + "@@last_modified": "2026-02-27 12:22:10.734115", "about": "Informacije", "@about": { "type": "String", @@ -11793,5 +11793,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Vaš zahtjev za pridruživanje je prihvaćen! Sada možete ući u tečaj.", + "sessionFull": "Prekasno! Ova aktivnost je puna.", + "returnToCourse": "Vrati se na tečaj", + "returnHome": "Vrati se kući", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_hu.arb b/lib/l10n/intl_hu.arb index 04956dbd2..a4b00cc15 100644 --- a/lib/l10n/intl_hu.arb +++ b/lib/l10n/intl_hu.arb @@ -1,6 +1,6 @@ { "@@locale": "hu", - "@@last_modified": "2026-02-24 14:59:18.503383", + "@@last_modified": "2026-02-27 12:21:58.762966", "about": "Névjegy", "@about": { "type": "String", @@ -11437,5 +11437,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "A csatlakozási kérelmedet elfogadták! Most már beléphetsz a kurzusra.", + "sessionFull": "Túl késő! Ez a tevékenység megtelt.", + "returnToCourse": "Vissza a kurzushoz", + "returnHome": "Vissza a főoldalra", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ia.arb b/lib/l10n/intl_ia.arb index b601f37fb..2b709fe2c 100644 --- a/lib/l10n/intl_ia.arb +++ b/lib/l10n/intl_ia.arb @@ -1954,7 +1954,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-02-24 14:59:24.082713", + "@@last_modified": "2026-02-27 12:22:19.124682", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -12512,5 +12512,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Tua peticio de unione est acceptata! Tu nunc potes intrare in cursu.", + "sessionFull": "Sero! Haec actio plena est.", + "returnToCourse": "Redi ad cursum", + "returnHome": "Redi domum", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_id.arb b/lib/l10n/intl_id.arb index ec41bba75..87557683c 100644 --- a/lib/l10n/intl_id.arb +++ b/lib/l10n/intl_id.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-02-24 14:59:19.093750", + "@@last_modified": "2026-02-27 12:22:00.506173", "setAsCanonicalAlias": "Atur sebagai alias utama", "@setAsCanonicalAlias": { "type": "String", @@ -11406,5 +11406,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Permintaan bergabung Anda diterima! Anda sekarang dapat memasuki kursus.", + "sessionFull": "Terlambat! Aktivitas ini sudah penuh.", + "returnToCourse": "Kembali ke kursus", + "returnHome": "Kembali ke beranda", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ie.arb b/lib/l10n/intl_ie.arb index bb01e1e73..231e5d81e 100644 --- a/lib/l10n/intl_ie.arb +++ b/lib/l10n/intl_ie.arb @@ -4000,7 +4000,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-02-24 14:59:21.774012", + "@@last_modified": "2026-02-27 12:22:08.111962", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -12048,5 +12048,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Tú aontuigh an iarratas comhoibrithe! Is féidir leat anois dul isteach sa chúrsa.", + "sessionFull": "Ró-dhéanach! Tá an gníomhaíocht seo lán.", + "returnToCourse": "Téigh ar ais chuig an gcúrsa", + "returnHome": "Téigh ar ais abhaile", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_it.arb b/lib/l10n/intl_it.arb index b49bd0a16..0eef6de71 100644 --- a/lib/l10n/intl_it.arb +++ b/lib/l10n/intl_it.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-02-24 14:59:30.435614", + "@@last_modified": "2026-02-27 12:22:35.902285", "about": "Informazioni", "@about": { "type": "String", @@ -11394,5 +11394,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "La tua richiesta di partecipazione è stata accettata! Puoi ora entrare nel corso.", + "sessionFull": "Troppo tardi! Questa attività è piena.", + "returnToCourse": "Torna al corso", + "returnHome": "Torna a casa", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ja.arb b/lib/l10n/intl_ja.arb index baf8be226..7ff1d3ff9 100644 --- a/lib/l10n/intl_ja.arb +++ b/lib/l10n/intl_ja.arb @@ -1,6 +1,6 @@ { "@@locale": "ja", - "@@last_modified": "2026-02-24 14:59:52.226358", + "@@last_modified": "2026-02-27 12:23:30.414125", "about": "このアプリについて", "@about": { "type": "String", @@ -12224,5 +12224,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "あなたの参加リクエストが承認されました!これでコースに入ることができます。", + "sessionFull": "遅すぎます!このアクティビティは満員です。", + "returnToCourse": "コースに戻る", + "returnHome": "ホームに戻る", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ka.arb b/lib/l10n/intl_ka.arb index 5164100dd..555151f22 100644 --- a/lib/l10n/intl_ka.arb +++ b/lib/l10n/intl_ka.arb @@ -2590,7 +2590,7 @@ "playWithAI": "ამ დროისთვის ითამაშეთ AI-თან", "courseStartDesc": "Pangea Bot მზადაა ნებისმიერ დროს გასასვლელად!\n\n...მაგრამ სწავლა უკეთესია მეგობრებთან ერთად!", "@@locale": "ka", - "@@last_modified": "2026-02-24 14:59:56.446964", + "@@last_modified": "2026-02-27 12:23:38.157366", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -12464,5 +12464,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "თქვენი გაწვდილი მოთხოვნა მიღებულია! ახლა შეგიძლიათ კურსში გაწვდოთ.", + "sessionFull": "მშვენიერი! ეს აქტივობა სავსეა.", + "returnToCourse": "კურსში დაბრუნება", + "returnHome": "მთავარ გვერდზე დაბრუნება", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ko.arb b/lib/l10n/intl_ko.arb index 8892541ab..14a1204af 100644 --- a/lib/l10n/intl_ko.arb +++ b/lib/l10n/intl_ko.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-02-24 14:59:13.645300", + "@@last_modified": "2026-02-27 12:21:46.139305", "about": "소개", "@about": { "type": "String", @@ -11526,5 +11526,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "귀하의 참여 요청이 수락되었습니다! 이제 강의에 들어갈 수 있습니다.", + "sessionFull": "너무 늦었습니다! 이 활동은 가득 찼습니다.", + "returnToCourse": "강의로 돌아가기", + "returnHome": "홈으로 돌아가기", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_lt.arb b/lib/l10n/intl_lt.arb index 98b908ee2..aa74aee67 100644 --- a/lib/l10n/intl_lt.arb +++ b/lib/l10n/intl_lt.arb @@ -3857,7 +3857,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-02-24 14:59:43.333670", + "@@last_modified": "2026-02-27 12:23:08.992471", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -12239,5 +12239,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Jūsų prisijungimo prašymas buvo priimtas! Dabar galite patekti į kursą.", + "sessionFull": "Per vėlu! Ši veikla yra pilna.", + "returnToCourse": "Grįžti į kursą", + "returnHome": "Grįžti namo", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_lv.arb b/lib/l10n/intl_lv.arb index fb4045933..070e4b34d 100644 --- a/lib/l10n/intl_lv.arb +++ b/lib/l10n/intl_lv.arb @@ -4605,7 +4605,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-02-24 14:59:39.254985", + "@@last_modified": "2026-02-27 12:22:59.485868", "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", @@ -11295,5 +11295,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Jūsu pievienošanās pieprasījums tika apstiprināts! Tagad varat piekļūt kursam.", + "sessionFull": "Pārāk vēlu! Šī aktivitāte ir pilna.", + "returnToCourse": "Atgriezties kursā", + "returnHome": "Atgriezties mājās", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_nb.arb b/lib/l10n/intl_nb.arb index b9933dfb7..ade57a564 100644 --- a/lib/l10n/intl_nb.arb +++ b/lib/l10n/intl_nb.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-02-24 14:59:32.787654", + "@@last_modified": "2026-02-27 12:22:42.590942", "about": "Om", "@about": { "type": "String", @@ -11369,5 +11369,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Din forespørsel om å bli med ble akseptert! Du kan nå gå inn i kurset.", + "sessionFull": "For sent! Denne aktiviteten er full.", + "returnToCourse": "Gå tilbake til kurset", + "returnHome": "Gå tilbake til hjem", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_nl.arb b/lib/l10n/intl_nl.arb index 729c667ac..78f31c12e 100644 --- a/lib/l10n/intl_nl.arb +++ b/lib/l10n/intl_nl.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-02-24 14:59:45.694424", + "@@last_modified": "2026-02-27 12:23:15.467657", "about": "Over ons", "@about": { "type": "String", @@ -11300,5 +11300,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Je aanvraag om deel te nemen is geaccepteerd! Je kunt nu de cursus binnenkomen.", + "sessionFull": "Te laat! Deze activiteit is vol.", + "returnToCourse": "Terug naar cursus", + "returnHome": "Terug naar huis", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_pl.arb b/lib/l10n/intl_pl.arb index 185de58a5..9c37340f2 100644 --- a/lib/l10n/intl_pl.arb +++ b/lib/l10n/intl_pl.arb @@ -1,6 +1,6 @@ { "@@locale": "pl", - "@@last_modified": "2026-02-24 14:59:49.576687", + "@@last_modified": "2026-02-27 12:23:24.344739", "about": "O aplikacji", "@about": { "type": "String", @@ -11422,5 +11422,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Twoja prośba o dołączenie została zaakceptowana! Możesz teraz wejść na kurs.", + "sessionFull": "Za późno! Ta aktywność jest pełna.", + "returnToCourse": "Powrót do kursu", + "returnHome": "Powrót do domu", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_pt.arb b/lib/l10n/intl_pt.arb index 80e0e7bc3..eaaded922 100644 --- a/lib/l10n/intl_pt.arb +++ b/lib/l10n/intl_pt.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-02-24 14:59:26.541602", + "@@last_modified": "2026-02-27 12:22:26.128852", "copiedToClipboard": "Copiada para a área de transferência", "@copiedToClipboard": { "type": "String", @@ -12521,5 +12521,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Seu pedido de entrada foi aceito! Você pode agora entrar no curso.", + "sessionFull": "Muito tarde! Esta atividade está cheia.", + "returnToCourse": "Voltar ao curso", + "returnHome": "Voltar para casa", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "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 717b58987..1bc14de76 100644 --- a/lib/l10n/intl_pt_BR.arb +++ b/lib/l10n/intl_pt_BR.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-02-24 14:59:25.036168", + "@@last_modified": "2026-02-27 12:22:21.502851", "about": "Sobre", "@about": { "type": "String", @@ -11300,5 +11300,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Seu pedido de entrada foi aceito! Você pode agora entrar no curso.", + "sessionFull": "Muito tarde! Esta atividade está cheia.", + "returnToCourse": "Voltar ao curso", + "returnHome": "Voltar para casa", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "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 a1059e86f..93d99e585 100644 --- a/lib/l10n/intl_pt_PT.arb +++ b/lib/l10n/intl_pt_PT.arb @@ -3327,7 +3327,7 @@ "selectAll": "Selecionar tudo", "deselectAll": "Desmarcar tudo", "@@locale": "pt_PT", - "@@last_modified": "2026-02-24 14:59:36.068751", + "@@last_modified": "2026-02-27 12:22:50.283963", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -12468,5 +12468,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Seu pedido de entrada foi aceito! Você pode agora entrar no curso.", + "sessionFull": "Muito tarde! Esta atividade está cheia.", + "returnToCourse": "Voltar ao curso", + "returnHome": "Voltar para casa", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ro.arb b/lib/l10n/intl_ro.arb index b2c1337b5..0ac479a65 100644 --- a/lib/l10n/intl_ro.arb +++ b/lib/l10n/intl_ro.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-02-24 14:59:20.082703", + "@@last_modified": "2026-02-27 12:22:02.332584", "about": "Despre", "@about": { "type": "String", @@ -12169,5 +12169,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Cererea ta de aderare a fost acceptată! Acum poți intra în curs.", + "sessionFull": "Prea târziu! Această activitate este plină.", + "returnToCourse": "Întoarce-te la curs", + "returnHome": "Întoarce-te acasă", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index da60aa9f3..7c475d950 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -1,6 +1,6 @@ { "@@locale": "ru", - "@@last_modified": "2026-02-24 14:59:55.748204", + "@@last_modified": "2026-02-27 12:23:35.945707", "about": "О проекте", "@about": { "type": "String", @@ -11300,5 +11300,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Ваш запрос на присоединение был принят! Теперь вы можете войти в курс.", + "sessionFull": "Слишком поздно! Это занятие заполнено.", + "returnToCourse": "Вернуться к курсу", + "returnHome": "Вернуться на главную", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_sk.arb b/lib/l10n/intl_sk.arb index 326db20d8..1d70a354d 100644 --- a/lib/l10n/intl_sk.arb +++ b/lib/l10n/intl_sk.arb @@ -1,6 +1,6 @@ { "@@locale": "sk", - "@@last_modified": "2026-02-24 14:59:21.085850", + "@@last_modified": "2026-02-27 12:22:05.630664", "about": "O aplikácii", "@about": { "type": "String", @@ -12518,5 +12518,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Vaša žiadosť o pripojenie bola prijatá! Teraz môžete vstúpiť do kurzu.", + "sessionFull": "Príliš neskoro! Táto aktivita je plná.", + "returnToCourse": "Vrátiť sa do kurzu", + "returnHome": "Vrátiť sa domov", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_sl.arb b/lib/l10n/intl_sl.arb index 8b5c5833f..da0ee3638 100644 --- a/lib/l10n/intl_sl.arb +++ b/lib/l10n/intl_sl.arb @@ -2460,7 +2460,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-02-24 14:59:28.892881", + "@@last_modified": "2026-02-27 12:22:32.222833", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -12515,5 +12515,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Vaša prošnja za vstop je bila sprejeta! Zdaj lahko vstopite v tečaj.", + "sessionFull": "Prepozno! Ta aktivnost je polna.", + "returnToCourse": "Vrni se v tečaj", + "returnHome": "Vrni se domov", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_sr.arb b/lib/l10n/intl_sr.arb index d5e69ce61..e70a5795b 100644 --- a/lib/l10n/intl_sr.arb +++ b/lib/l10n/intl_sr.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-02-24 14:59:57.459309", + "@@last_modified": "2026-02-27 12:23:40.141969", "about": "О програму", "@about": { "type": "String", @@ -12530,5 +12530,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Vaš zahtev za pridruživanje je prihvaćen! Sada možete ući u kurs.", + "sessionFull": "Prekasno! Ova aktivnost je puna.", + "returnToCourse": "Vrati se na kurs", + "returnHome": "Vrati se kući", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_sv.arb b/lib/l10n/intl_sv.arb index 833f4653e..ebccd38f3 100644 --- a/lib/l10n/intl_sv.arb +++ b/lib/l10n/intl_sv.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-02-24 14:59:50.399849", + "@@last_modified": "2026-02-27 12:23:26.112167", "about": "Om", "@about": { "type": "String", @@ -11912,5 +11912,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Din ansökan om att gå med har accepterats! Du kan nu gå in i kursen.", + "sessionFull": "För sent! Denna aktivitet är full.", + "returnToCourse": "Återvänd till kursen", + "returnHome": "Återvänd hem", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ta.arb b/lib/l10n/intl_ta.arb index 5fd009000..219bfaf78 100644 --- a/lib/l10n/intl_ta.arb +++ b/lib/l10n/intl_ta.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-02-24 14:59:45.075838", + "@@last_modified": "2026-02-27 12:23:13.510421", "acceptedTheInvitation": "👍 {username} அழைப்பை ஏற்றுக்கொண்டது", "@acceptedTheInvitation": { "type": "String", @@ -11420,5 +11420,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "உங்கள் சேர்க்கை கோரிக்கை ஏற்கப்பட்டது! நீங்கள் இப்போது பாடத்தில் நுழையலாம்.", + "sessionFull": "மிகவும் தாமதமாக! இந்த செயல்பாடு நிரம்பியுள்ளது.", + "returnToCourse": "பாடத்திற்கு திரும்பவும்", + "returnHome": "வீட்டிற்கு திரும்பவும்", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_te.arb b/lib/l10n/intl_te.arb index 4f9f919cd..8f94d7a34 100644 --- a/lib/l10n/intl_te.arb +++ b/lib/l10n/intl_te.arb @@ -1916,7 +1916,7 @@ "playWithAI": "ఇప్పుడే AI తో ఆడండి", "courseStartDesc": "పాంజియా బాట్ ఎప్పుడైనా సిద్ధంగా ఉంటుంది!\n\n...కానీ స్నేహితులతో నేర్చుకోవడం మెరుగైనది!", "@@locale": "te", - "@@last_modified": "2026-02-24 14:59:42.206776", + "@@last_modified": "2026-02-27 12:23:05.628176", "@setCustomPermissionLevel": { "type": "String", "placeholders": {} @@ -12523,5 +12523,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "మీ చేరిక అభ్యర్థన ఆమోదించబడింది! మీరు ఇప్పుడు కోర్సులో ప్రవేశించవచ్చు.", + "sessionFull": "చాలా ఆలస్యమైంది! ఈ కార్యకలాపం నిండిపోయింది.", + "returnToCourse": "కోర్సుకు తిరిగి వెళ్ళండి", + "returnHome": "ఇంటికి తిరిగి వెళ్ళండి", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_th.arb b/lib/l10n/intl_th.arb index 65af315c0..084071ade 100644 --- a/lib/l10n/intl_th.arb +++ b/lib/l10n/intl_th.arb @@ -3999,7 +3999,7 @@ "playWithAI": "เล่นกับ AI ชั่วคราว", "courseStartDesc": "Pangea Bot พร้อมที่จะเริ่มต้นได้ทุกเมื่อ!\n\n...แต่การเรียนรู้ดีกว่ากับเพื่อน!", "@@locale": "th", - "@@last_modified": "2026-02-24 14:59:35.264857", + "@@last_modified": "2026-02-27 12:22:48.494331", "@alwaysUse24HourFormat": { "type": "String", "placeholders": {} @@ -12047,5 +12047,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "คำขอเข้าร่วมของคุณได้รับการอนุมัติแล้ว! ตอนนี้คุณสามารถเข้าร่วมหลักสูตรได้แล้ว", + "sessionFull": "สายเกินไป! กิจกรรมนี้เต็มแล้ว", + "returnToCourse": "กลับไปยังหลักสูตร", + "returnHome": "กลับไปยังหน้าแรก", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_tr.arb b/lib/l10n/intl_tr.arb index c71296e8d..5033f3c2b 100644 --- a/lib/l10n/intl_tr.arb +++ b/lib/l10n/intl_tr.arb @@ -1,6 +1,6 @@ { "@@locale": "tr", - "@@last_modified": "2026-02-24 14:59:41.166599", + "@@last_modified": "2026-02-27 12:23:03.199696", "about": "Hakkında", "@about": { "type": "String", @@ -11640,5 +11640,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Katılma isteğiniz kabul edildi! Artık derse girebilirsiniz.", + "sessionFull": "Geç kaldınız! Bu etkinlik dolu.", + "returnToCourse": "Derse geri dön", + "returnHome": "Ana sayfaya geri dön", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_uk.arb b/lib/l10n/intl_uk.arb index 954d3c208..12f7833a6 100644 --- a/lib/l10n/intl_uk.arb +++ b/lib/l10n/intl_uk.arb @@ -1,6 +1,6 @@ { "@@locale": "uk", - "@@last_modified": "2026-02-24 14:59:31.151553", + "@@last_modified": "2026-02-27 12:22:38.290443", "about": "Про застосунок", "@about": { "type": "String", @@ -11300,5 +11300,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Ваш запит на приєднання був прийнятий! Тепер ви можете увійти до курсу.", + "sessionFull": "Занадто пізно! Ця активність заповнена.", + "returnToCourse": "Повернутися до курсу", + "returnHome": "Повернутися додому", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_uz.arb b/lib/l10n/intl_uz.arb index c68f2ef8f..ad05ecd5c 100644 --- a/lib/l10n/intl_uz.arb +++ b/lib/l10n/intl_uz.arb @@ -3495,7 +3495,7 @@ "setupChatBackup": "Chat zaxirasini sozlash", "@setupChatBackup": {}, "@@locale": "uz", - "@@last_modified": "2026-02-24 14:59:37.791267", + "@@last_modified": "2026-02-27 12:22:53.595075", "noMoreResultsFound": "Boshqa natijalar topilmadi", "chatSearchedUntil": "Chat {time} gacha qidirildi", "federationBaseUrl": "Federatsiya Asos URL", @@ -11194,5 +11194,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Sizning qo'shilish so'rovingiz qabul qilindi! Endi kursga kirishingiz mumkin.", + "sessionFull": "Kecha! Ushbu faoliyat to'la.", + "returnToCourse": "Kursga qaytish", + "returnHome": "Boshqaruvga qaytish", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_vi.arb b/lib/l10n/intl_vi.arb index 7845ffa92..ab672fafe 100644 --- a/lib/l10n/intl_vi.arb +++ b/lib/l10n/intl_vi.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-02-24 14:59:44.047251", + "@@last_modified": "2026-02-27 12:23:11.360379", "about": "Giới thiệu", "@about": { "type": "String", @@ -7017,5 +7017,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "Yêu cầu tham gia của bạn đã được chấp nhận! Bạn có thể vào khóa học ngay bây giờ.", + "sessionFull": "Quá muộn! Hoạt động này đã đầy.", + "returnToCourse": "Quay lại khóa học", + "returnHome": "Quay về trang chủ", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_yue.arb b/lib/l10n/intl_yue.arb index 5e5c655aa..f0bc3cfbb 100644 --- a/lib/l10n/intl_yue.arb +++ b/lib/l10n/intl_yue.arb @@ -1852,7 +1852,7 @@ "selectAll": "全選", "deselectAll": "取消全選", "@@locale": "yue", - "@@last_modified": "2026-02-24 14:59:29.472390", + "@@last_modified": "2026-02-27 12:22:34.197254", "@ignoreUser": { "type": "String", "placeholders": {} @@ -12530,5 +12530,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "你的加入請求已被接受!你現在可以進入課程。", + "sessionFull": "太遲了!這個活動已滿。", + "returnToCourse": "返回課程", + "returnHome": "返回主頁", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb index e4d9b724c..a5c6dec9e 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -1,6 +1,6 @@ { "@@locale": "zh", - "@@last_modified": "2026-02-24 14:59:46.957191", + "@@last_modified": "2026-02-27 12:23:18.517226", "about": "关于", "@about": { "type": "String", @@ -11300,5 +11300,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "您的加入请求已被接受!您现在可以进入课程。", + "sessionFull": "太晚了!此活动已满。", + "returnToCourse": "返回课程", + "returnHome": "返回首页", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "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 25082d697..f43585da1 100644 --- a/lib/l10n/intl_zh_Hant.arb +++ b/lib/l10n/intl_zh_Hant.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2026-02-24 14:59:36.723188", + "@@last_modified": "2026-02-27 12:22:51.603823", "about": "關於", "@about": { "type": "String", @@ -11439,5 +11439,25 @@ "@cannotJoinBannedRoom": { "type": "String", "placeholders": {} + }, + "knockAccepted": "您的加入請求已被接受!您現在可以進入課程。", + "sessionFull": "太遲了!此活動已滿。", + "returnToCourse": "返回課程", + "returnHome": "返回首頁", + "@knockAccepted": { + "type": "String", + "placeholders": {} + }, + "@sessionFull": { + "type": "String", + "placeholders": {} + }, + "@returnToCourse": { + "type": "String", + "placeholders": {} + }, + "@returnHome": { + "type": "String", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 5fe3319fa..e59c19fd9 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -14,12 +14,10 @@ import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pages/chat_list/chat_list_view.dart'; import 'package:fluffychat/pangea/chat_list/utils/app_version_util.dart'; import 'package:fluffychat/pangea/chat_list/utils/chat_list_handle_space_tap.dart'; -import 'package:fluffychat/pangea/chat_settings/constants/pangea_room_types.dart'; import 'package:fluffychat/pangea/chat_settings/widgets/chat_context_menu_action.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; import 'package:fluffychat/pangea/join_codes/knock_room_extension.dart'; -import 'package:fluffychat/pangea/join_codes/knock_tracker.dart'; import 'package:fluffychat/pangea/join_codes/space_code_controller.dart'; import 'package:fluffychat/pangea/join_codes/space_code_repo.dart'; import 'package:fluffychat/pangea/navigation/navigation_util.dart'; @@ -539,46 +537,7 @@ class ChatListController extends State //#Pangea _invitedSpaceSubscription = Matrix.of(context).client.onSync.stream .where((event) => event.rooms?.invite != null) - .listen((event) async { - for (final inviteEntry in event.rooms!.invite!.entries) { - if (inviteEntry.value.inviteState == null) continue; - final isSpace = inviteEntry.value.inviteState!.any( - (event) => - event.type == EventTypes.RoomCreate && - event.content['type'] == 'm.space', - ); - final isAnalytics = inviteEntry.value.inviteState!.any( - (event) => - event.type == EventTypes.RoomCreate && - event.content['type'] == PangeaRoomTypes.analytics, - ); - final hasKnocked = KnockTracker.hasKnocked( - Matrix.of(context).client, - inviteEntry.key, - ); - - final room = Matrix.of(context).client.getRoomById(inviteEntry.key); - if (room == null) continue; - if (isAnalytics || hasKnocked) { - try { - await room.joinKnockedRoom(); - } catch (err, s) { - ErrorHandler.logError( - m: "Failed to join analytics room", - e: err, - s: s, - data: {"roomId": room.id}, - ); - } - } else if (isSpace) { - if (room.classCode?.toLowerCase() == - SpaceCodeRepo.recentCode?.toLowerCase()) { - continue; - } - chatListHandleSpaceTap(context, room); - } - } - }); + .listen(_onInviteSync); MatrixState.pangeaController.subscriptionController.subscriptionNotifier .addListener(_onSubscribe); @@ -627,7 +586,7 @@ class ChatListController extends State }); WidgetsBinding.instance.addPostFrameCallback((_) { - _joinInvitedRooms(); + _joinInvitedSpaces(); }); // Pangea# @@ -639,37 +598,53 @@ class ChatListController extends State if (mounted) showSubscribedSnackbar(context); } - Future _joinInvitedRooms() async { - final invitedRooms = Matrix.of( + Future _joinInvitedSpaces() async { + final invitedSpaces = Matrix.of( context, - ).client.rooms.where((r) => r.membership == Membership.invite); + ).client.rooms.where((r) => r.isSpace && r.membership == Membership.invite); - final spaces = []; - for (final room in invitedRooms) { - final hasKnocked = KnockTracker.hasKnocked( - Matrix.of(context).client, - room.id, - ); + for (final space in invitedSpaces) { + await showInviteDialog(space, context); + } + } - if (hasKnocked || room.isAnalyticsRoom) { + Future _onInviteSync(SyncUpdate update) async { + final roomIds = + update.rooms?.invite?.entries.map((e) => e.key).toSet() ?? {}; + + for (final roomId in roomIds) { + final room = Matrix.of(context).client.getRoomById(roomId); + if (room == null) continue; + final isSpace = room.isSpace; + final isAnalytics = room.isAnalyticsRoom; + if (!isSpace && !isAnalytics) continue; + + final hasKnocked = room.hasKnocked; + + // Auto-join analytics rooms or spaces the user has knocked on + if (isAnalytics || hasKnocked) { try { await room.joinKnockedRoom(); } catch (err, s) { ErrorHandler.logError( - m: "Failed to join knocked room", + m: "Failed to join analytics room", e: err, s: s, data: {"roomId": room.id}, ); } - return; + continue; } - if (room.isSpace) spaces.add(room); - } + if (isSpace) { + // If user joined via code, don't show invite popup + final roomCode = room.classCode?.toLowerCase(); + final cachedCode = SpaceCodeRepo.recentCode?.toLowerCase(); + if (cachedCode == roomCode) continue; - for (final space in spaces) { - await showInviteDialog(space, context); + // Show invite popup + chatListHandleSpaceTap(context, room); + } } } // Pangea# diff --git a/lib/pangea/events/constants/pangea_event_types.dart b/lib/pangea/events/constants/pangea_event_types.dart index 580a3a291..c381b64ff 100644 --- a/lib/pangea/events/constants/pangea_event_types.dart +++ b/lib/pangea/events/constants/pangea_event_types.dart @@ -36,4 +36,6 @@ class PangeaEventTypes { static const analyticsSettings = "pangea.analytics_settings"; static const regenerationRequest = "pangea.regeneration_request"; + + static const knockedRooms = 'org.pangea.knocked_rooms'; } diff --git a/lib/pangea/join_codes/knock_notification_utils.dart b/lib/pangea/join_codes/knock_notification_utils.dart new file mode 100644 index 000000000..d6122b5aa --- /dev/null +++ b/lib/pangea/join_codes/knock_notification_utils.dart @@ -0,0 +1,36 @@ +import 'package:matrix/matrix.dart'; + +import 'package:fluffychat/pangea/join_codes/knock_room_extension.dart'; + +/// Returns true when a push notification event is an accepted-knock invite — +/// i.e. an m.room.member invite targeting the current user in a room the user +/// previously knocked on. +/// +/// Extracted as a pure function so it can be unit-tested without a Flutter +/// environment or a live Matrix client. +bool isKnockAcceptedInvite({ + required String eventType, + required String? newMembership, + required String? stateKey, + required String? currentUserId, + required bool hasKnocked, +}) { + return eventType == EventTypes.RoomMember && + newMembership == 'invite' && + stateKey == currentUserId && + hasKnocked; +} + +/// Convenience wrapper that reads [KnockTracker] state from a live [Client]. +bool isKnockAcceptedInviteForClient({ + required Event event, + required Client client, +}) { + return isKnockAcceptedInvite( + eventType: event.type, + newMembership: event.content.tryGet('membership'), + stateKey: event.stateKey, + currentUserId: client.userID, + hasKnocked: client.hasEverKnockedRoom(event.room.id), + ); +} diff --git a/lib/pangea/join_codes/knock_room_extension.dart b/lib/pangea/join_codes/knock_room_extension.dart index 6193bdd99..1b3bb356a 100644 --- a/lib/pangea/join_codes/knock_room_extension.dart +++ b/lib/pangea/join_codes/knock_room_extension.dart @@ -1,24 +1,92 @@ -import 'package:matrix/matrix.dart'; +import 'dart:async'; -import 'package:fluffychat/pangea/join_codes/knock_tracker.dart'; +import 'package:matrix/matrix.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; + +import 'package:fluffychat/pangea/common/utils/error_handler.dart'; +import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; +import 'package:fluffychat/pangea/join_codes/knocked_rooms_model.dart'; extension KnockRoomExtension on Room { - bool get hasKnocked => KnockTracker.hasKnocked(client, id); + bool get hasKnocked => client.hasKnockedRoom(id); Future joinKnockedRoom() async { await join(); - await KnockTracker.clearKnock(client, id); + await client.onJoinKnockedRoom(id); } } extension KnockClientExtension on Client { + KnockedRoomsModel get _knockedRooms { + final data = accountData[PangeaEventTypes.knockedRooms]; + if (data != null) { + return KnockedRoomsModel.fromJson(data.content); + } + return const KnockedRoomsModel(); + } + + Future _setKnockedRooms(KnockedRoomsModel model) async { + final prevModel = _knockedRooms; + if (model == prevModel) { + Logs().w('Knocked rooms model is the same as previous, skipping write.'); + Logs().w('Model: ${model.toJson()}'); + Logs().w('Previous Model: ${prevModel.toJson()}'); + return; + } + + await setAccountData( + userID!, + PangeaEventTypes.knockedRooms, + model.toJson(), + ); + + final updatedModel = _knockedRooms; + if (model == updatedModel) { + try { + await onSync.stream + .firstWhere((sync) => sync.accountData != null) + .timeout(Duration(seconds: 10)); + } catch (e, s) { + ErrorHandler.logError( + e: e, + s: s, + data: { + 'client_user_id': userID, + 'expected_ids': model.toJson(), + 'updated_ids': updatedModel.toJson(), + }, + level: e is TimeoutException + ? SentryLevel.warning + : SentryLevel.error, + ); + } + } + } + Future knockAndRecordRoom( String roomIdOrAlias, { List? via, String? reason, }) async { final resp = await knockRoom(roomIdOrAlias, via: via, reason: reason); - await KnockTracker.recordKnock(this, roomIdOrAlias); + final updatedModel = _knockedRooms.copyWithKnockedRoom(roomIdOrAlias); + await _setKnockedRooms(updatedModel); return resp; } + + Future onJoinKnockedRoom(String roomId) async { + final updatedModel = _knockedRooms.copyWithAcceptedInviteRoom(roomId); + await _setKnockedRooms(updatedModel); + } + + bool hasKnockedRoom(String roomId) { + return _knockedRooms.knockedRoomIds.contains(roomId); + } + + bool hasEverKnockedRoom(String roomId) { + return hasKnockedRoom(roomId) || + _knockedRooms.acceptedInviteRoomIds.contains(roomId); + } + + List get knockedRoomIds => _knockedRooms.knockedRoomIds; } diff --git a/lib/pangea/join_codes/knock_tracker.dart b/lib/pangea/join_codes/knock_tracker.dart deleted file mode 100644 index ed89cc681..000000000 --- a/lib/pangea/join_codes/knock_tracker.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:matrix/matrix.dart'; - -/// Tracks room IDs the user has knocked on so the client can auto-accept -/// invites for previously knocked rooms. -/// -/// Stored in Matrix account data under [_accountDataKey] so the state -/// survives reinstall, logout, and syncs across devices. -class KnockTracker { - static const String _accountDataKey = 'org.pangea.knocked_rooms'; - static const String _roomIdsField = 'room_ids'; - - static Future recordKnock(Client client, String roomId) async { - final ids = _getKnockedRoomIds(client); - if (!ids.contains(roomId)) { - ids.add(roomId); - await _writeIds(client, ids); - } - } - - static bool hasKnocked(Client client, String roomId) { - return _getKnockedRoomIds(client).contains(roomId); - } - - static Future clearKnock(Client client, String roomId) async { - final ids = _getKnockedRoomIds(client); - if (ids.remove(roomId)) { - await _writeIds(client, ids); - } - } - - static List _getKnockedRoomIds(Client client) { - final data = client.accountData[_accountDataKey]; - final list = data?.content[_roomIdsField]; - if (list is List) { - return list.cast().toList(); - } - return []; - } - - static Future _writeIds(Client client, List ids) async { - await client.setAccountData(client.userID!, _accountDataKey, { - _roomIdsField: ids, - }); - } -} diff --git a/lib/pangea/join_codes/knocked_rooms_model.dart b/lib/pangea/join_codes/knocked_rooms_model.dart new file mode 100644 index 000000000..a61a4edfb --- /dev/null +++ b/lib/pangea/join_codes/knocked_rooms_model.dart @@ -0,0 +1,84 @@ +import 'package:collection/collection.dart'; + +class KnockedRoomsModel { + final List knockedRoomIds; + final List acceptedInviteRoomIds; + + const KnockedRoomsModel({ + this.knockedRoomIds = const [], + this.acceptedInviteRoomIds = const [], + }); + + static const String _roomIdsField = 'room_ids'; + static const String _acceptedInviteRoomIdsField = 'accepted_invite_room_ids'; + + bool hasEverKnocked(String roomId) { + return knockedRoomIds.contains(roomId) || + acceptedInviteRoomIds.contains(roomId); + } + + Map toJson() { + return { + _roomIdsField: knockedRoomIds, + _acceptedInviteRoomIdsField: acceptedInviteRoomIds, + }; + } + + factory KnockedRoomsModel.fromJson(Map json) { + final knockedIds = json[_roomIdsField]; + final acceptedInviteIds = json[_acceptedInviteRoomIdsField]; + return KnockedRoomsModel( + knockedRoomIds: knockedIds is List + ? List.from(knockedIds) + : [], + acceptedInviteRoomIds: acceptedInviteIds is List + ? List.from(acceptedInviteIds) + : [], + ); + } + + KnockedRoomsModel copyWithKnockedRoom(String roomId) { + final newKnockedRoomIds = List.from(knockedRoomIds); + if (!newKnockedRoomIds.contains(roomId)) { + newKnockedRoomIds.add(roomId); + } + + final newAcceptedInviteRoomIds = List.from(acceptedInviteRoomIds) + ..remove(roomId); + return KnockedRoomsModel( + knockedRoomIds: newKnockedRoomIds, + acceptedInviteRoomIds: newAcceptedInviteRoomIds, + ); + } + + KnockedRoomsModel copyWithAcceptedInviteRoom(String roomId) { + final newAcceptedInviteRoomIds = List.from(acceptedInviteRoomIds); + if (!newAcceptedInviteRoomIds.contains(roomId)) { + newAcceptedInviteRoomIds.add(roomId); + } + + final newKnockedRoomIds = List.from(knockedRoomIds)..remove(roomId); + return KnockedRoomsModel( + knockedRoomIds: newKnockedRoomIds, + acceptedInviteRoomIds: newAcceptedInviteRoomIds, + ); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is KnockedRoomsModel && + ListEquality().equals(other.knockedRoomIds, knockedRoomIds) && + ListEquality().equals( + other.acceptedInviteRoomIds, + acceptedInviteRoomIds, + ); + } + + @override + int get hashCode => Object.hash( + ListEquality().hash(knockedRoomIds), + ListEquality().hash(acceptedInviteRoomIds), + ); +} diff --git a/lib/utils/notification_background_handler.dart b/lib/utils/notification_background_handler.dart index ddb105c4a..94a148715 100644 --- a/lib/utils/notification_background_handler.dart +++ b/lib/utils/notification_background_handler.dart @@ -163,11 +163,21 @@ Future notificationTap( } // Pangea# - router.go( - client.getRoomById(roomId)?.membership == Membership.invite - ? '/rooms' - : '/rooms/$roomId', - ); + // #Pangea + // router.go( + // client.getRoomById(roomId)?.membership == Membership.invite + // ? '/rooms' + // : '/rooms/$roomId', + // ); + final room = client.getRoomById(roomId); + if (room?.membership == Membership.invite) { + router.go('/rooms'); + } else if (room?.isSpace == true) { + router.go('/rooms/spaces/$roomId'); + } else { + router.go('/rooms/$roomId'); + } + // Pangea# case NotificationResponseType.selectedNotificationAction: final actionType = FluffyChatNotificationActions.values.singleWhereOrNull( (action) => action.name == notificationResponse.actionId, diff --git a/lib/utils/push_helper.dart b/lib/utils/push_helper.dart index 4c347beac..e54830605 100644 --- a/lib/utils/push_helper.dart +++ b/lib/utils/push_helper.dart @@ -13,6 +13,7 @@ import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/setting_keys.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; +import 'package:fluffychat/pangea/join_codes/knock_notification_utils.dart'; import 'package:fluffychat/utils/client_download_content_extension.dart'; import 'package:fluffychat/utils/client_manager.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; @@ -158,11 +159,25 @@ Future _tryPushHelper( final matrixLocals = MatrixLocals(l10n); // Calculate the body - final body = event.type == EventTypes.Encrypted - // #Pangea - // ? l10n.newMessageInFluffyChat + // #Pangea + // final body = event.type == EventTypes.Encrypted + // ? l10n.newMessageInFluffyChat + // : await event.calcLocalizedBody( + // matrixLocals, + // plaintextBody: true, + // withSenderNamePrefix: false, + // hideReply: true, + // hideEdit: true, + // removeMarkdown: true, + // ); + final hasKnocked = isKnockAcceptedInviteForClient( + event: event, + client: client, + ); + final body = hasKnocked + ? l10n.knockAccepted + : event.type == EventTypes.Encrypted ? l10n.newMessageInPangeaChat - // Pangea# : await event.calcLocalizedBody( matrixLocals, plaintextBody: true, @@ -171,6 +186,7 @@ Future _tryPushHelper( hideEdit: true, removeMarkdown: true, ); + // Pangea# // The person object for the android message style notification final avatar = event.room.avatar; diff --git a/lib/widgets/local_notifications_extension.dart b/lib/widgets/local_notifications_extension.dart index 8eb051730..f368b94ff 100644 --- a/lib/widgets/local_notifications_extension.dart +++ b/lib/widgets/local_notifications_extension.dart @@ -13,6 +13,7 @@ import 'package:universal_html/html.dart' as html; import 'package:fluffychat/config/setting_keys.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; +import 'package:fluffychat/pangea/join_codes/knock_notification_utils.dart'; import 'package:fluffychat/utils/client_download_content_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/utils/push_helper.dart'; @@ -35,16 +36,30 @@ extension LocalNotificationsExtension on MatrixState { final title = event.room.getLocalizedDisplayname( MatrixLocals(L10n.of(context)), ); - final body = await event.calcLocalizedBody( - MatrixLocals(L10n.of(context)), - withSenderNamePrefix: - !event.room.isDirectChat || - event.room.lastEvent?.senderId == client.userID, - plaintextBody: true, - hideReply: true, - hideEdit: true, - removeMarkdown: true, - ); + // #Pangea + // final body = await event.calcLocalizedBody( + // MatrixLocals(L10n.of(context)), + // withSenderNamePrefix: + // !event.room.isDirectChat || + // event.room.lastEvent?.senderId == client.userID, + // plaintextBody: true, + // hideReply: true, + // hideEdit: true, + // removeMarkdown: true, + // ); + final body = isKnockAcceptedInviteForClient(event: event, client: client) + ? L10n.of(context).knockAccepted + : await event.calcLocalizedBody( + MatrixLocals(L10n.of(context)), + withSenderNamePrefix: + !event.room.isDirectChat || + event.room.lastEvent?.senderId == client.userID, + plaintextBody: true, + hideReply: true, + hideEdit: true, + removeMarkdown: true, + ); + // Pangea# if (kIsWeb) { // #Pangea diff --git a/test/pangea/knock_notification_utils_test.dart b/test/pangea/knock_notification_utils_test.dart new file mode 100644 index 000000000..2d20ef1df --- /dev/null +++ b/test/pangea/knock_notification_utils_test.dart @@ -0,0 +1,155 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:matrix/matrix.dart'; + +import 'package:fluffychat/pangea/join_codes/knock_notification_utils.dart'; +import 'package:fluffychat/pangea/join_codes/knocked_rooms_model.dart'; + +void main() { + const roomId = '!course:staging.pangea.chat'; + const userId = '@learner:staging.pangea.chat'; + const adminId = '@teacher:staging.pangea.chat'; + + group('isKnockAcceptedInvite', () { + test('returns true when all conditions match a knock-accepted invite', () { + final model = KnockedRoomsModel( + knockedRoomIds: [roomId], + acceptedInviteRoomIds: [], + ); + final result = isKnockAcceptedInvite( + eventType: EventTypes.RoomMember, + newMembership: 'invite', + stateKey: userId, + currentUserId: userId, + hasKnocked: model.hasEverKnocked(roomId), + ); + expect(result, isTrue); + }); + + test('returns false when event is not m.room.member', () { + final model = KnockedRoomsModel( + knockedRoomIds: [roomId], + acceptedInviteRoomIds: [], + ); + final result = isKnockAcceptedInvite( + eventType: 'm.room.message', + newMembership: 'invite', + stateKey: userId, + currentUserId: userId, + hasKnocked: model.hasEverKnocked(roomId), + ); + expect(result, isFalse); + }); + + test('returns false when membership is not invite (e.g. join)', () { + final model = KnockedRoomsModel( + knockedRoomIds: [roomId], + acceptedInviteRoomIds: [], + ); + final result = isKnockAcceptedInvite( + eventType: EventTypes.RoomMember, + newMembership: 'join', + stateKey: userId, + currentUserId: userId, + hasKnocked: model.hasEverKnocked(roomId), + ); + expect(result, isFalse); + }); + + test('returns false when membership is null', () { + final model = KnockedRoomsModel( + knockedRoomIds: [roomId], + acceptedInviteRoomIds: [], + ); + final result = isKnockAcceptedInvite( + eventType: EventTypes.RoomMember, + newMembership: null, + stateKey: userId, + currentUserId: userId, + hasKnocked: model.hasEverKnocked(roomId), + ); + expect(result, isFalse); + }); + + test( + 'returns false when invite targets a different user (not current user)', + () { + final model = KnockedRoomsModel( + knockedRoomIds: [roomId], + acceptedInviteRoomIds: [], + ); + final result = isKnockAcceptedInvite( + eventType: EventTypes.RoomMember, + newMembership: 'invite', + stateKey: adminId, // <-- someone else was invited + currentUserId: userId, + hasKnocked: model.hasEverKnocked(roomId), + ); + expect(result, isFalse); + }, + ); + + test('returns false when stateKey is null', () { + final model = KnockedRoomsModel( + knockedRoomIds: [roomId], + acceptedInviteRoomIds: [], + ); + final result = isKnockAcceptedInvite( + eventType: EventTypes.RoomMember, + newMembership: 'invite', + stateKey: null, + currentUserId: userId, + hasKnocked: model.hasEverKnocked(roomId), + ); + expect(result, isFalse); + }); + + test('returns false when the room was not previously knocked on', () { + final model = KnockedRoomsModel( + knockedRoomIds: [], // <-- no prior knock recorded + acceptedInviteRoomIds: [], + ); + final result = isKnockAcceptedInvite( + eventType: EventTypes.RoomMember, + newMembership: 'invite', + stateKey: userId, + currentUserId: userId, + hasKnocked: model.hasEverKnocked(roomId), + ); + expect(result, isFalse); + }); + + test('returns false when a different room was knocked', () { + final model = KnockedRoomsModel( + knockedRoomIds: ['!other:staging.pangea.chat'], + acceptedInviteRoomIds: [], + ); + final result = isKnockAcceptedInvite( + eventType: EventTypes.RoomMember, + newMembership: 'invite', + stateKey: userId, + currentUserId: userId, + hasKnocked: model.hasEverKnocked(roomId), + ); + expect(result, isFalse); + }); + + test('handles multiple knocked rooms and matches the correct one', () { + final model = KnockedRoomsModel( + knockedRoomIds: [ + '!other1:staging.pangea.chat', + roomId, + '!other2:staging.pangea.chat', + ], + acceptedInviteRoomIds: [], + ); + final result = isKnockAcceptedInvite( + eventType: EventTypes.RoomMember, + newMembership: 'invite', + stateKey: userId, + currentUserId: userId, + hasKnocked: model.hasEverKnocked(roomId), + ); + expect(result, isTrue); + }); + }); +}