From 7ebc05daed7cd80d5ae4e10ced003247a54aa8f7 Mon Sep 17 00:00:00 2001 From: Nik Shevchenko Date: Wed, 14 Oct 2026 19:36:07 -0400 Subject: [PATCH 1/2] feat(app): Yinka's first-conversation onboarding as the yinka arm of a three-way onboarding experiment (control / reid / yinka) --- .../20261014-onboarding-yinka-arm.json | 1 + app/lib/l10n/app_ar.arb | 34 +- app/lib/l10n/app_be.arb | 34 +- app/lib/l10n/app_bg.arb | 34 +- app/lib/l10n/app_bn.arb | 34 +- app/lib/l10n/app_bs.arb | 34 +- app/lib/l10n/app_ca.arb | 34 +- app/lib/l10n/app_cs.arb | 34 +- app/lib/l10n/app_da.arb | 34 +- app/lib/l10n/app_de.arb | 34 +- app/lib/l10n/app_el.arb | 34 +- app/lib/l10n/app_en.arb | 134 ++ app/lib/l10n/app_es.arb | 34 +- app/lib/l10n/app_et.arb | 34 +- app/lib/l10n/app_fa.arb | 34 +- app/lib/l10n/app_fi.arb | 34 +- app/lib/l10n/app_fr.arb | 34 +- app/lib/l10n/app_he.arb | 34 +- app/lib/l10n/app_hi.arb | 34 +- app/lib/l10n/app_hr.arb | 34 +- app/lib/l10n/app_hu.arb | 34 +- app/lib/l10n/app_id.arb | 34 +- app/lib/l10n/app_it.arb | 34 +- app/lib/l10n/app_ja.arb | 34 +- app/lib/l10n/app_kn.arb | 34 +- app/lib/l10n/app_ko.arb | 34 +- app/lib/l10n/app_localizations.dart | 192 +++ app/lib/l10n/app_localizations_ar.dart | 100 ++ app/lib/l10n/app_localizations_be.dart | 100 ++ app/lib/l10n/app_localizations_bg.dart | 100 ++ app/lib/l10n/app_localizations_bn.dart | 100 ++ app/lib/l10n/app_localizations_bs.dart | 100 ++ app/lib/l10n/app_localizations_ca.dart | 100 ++ app/lib/l10n/app_localizations_cs.dart | 100 ++ app/lib/l10n/app_localizations_da.dart | 100 ++ app/lib/l10n/app_localizations_de.dart | 100 ++ app/lib/l10n/app_localizations_el.dart | 101 ++ app/lib/l10n/app_localizations_en.dart | 100 ++ app/lib/l10n/app_localizations_es.dart | 100 ++ app/lib/l10n/app_localizations_et.dart | 100 ++ app/lib/l10n/app_localizations_fa.dart | 100 ++ app/lib/l10n/app_localizations_fi.dart | 100 ++ app/lib/l10n/app_localizations_fr.dart | 101 ++ app/lib/l10n/app_localizations_he.dart | 100 ++ app/lib/l10n/app_localizations_hi.dart | 100 ++ app/lib/l10n/app_localizations_hr.dart | 100 ++ app/lib/l10n/app_localizations_hu.dart | 100 ++ app/lib/l10n/app_localizations_id.dart | 100 ++ app/lib/l10n/app_localizations_it.dart | 100 ++ app/lib/l10n/app_localizations_ja.dart | 98 ++ app/lib/l10n/app_localizations_kn.dart | 100 ++ app/lib/l10n/app_localizations_ko.dart | 99 ++ app/lib/l10n/app_localizations_lt.dart | 100 ++ app/lib/l10n/app_localizations_lv.dart | 100 ++ app/lib/l10n/app_localizations_mk.dart | 100 ++ app/lib/l10n/app_localizations_mr.dart | 100 ++ app/lib/l10n/app_localizations_ms.dart | 100 ++ app/lib/l10n/app_localizations_nl.dart | 100 ++ app/lib/l10n/app_localizations_no.dart | 100 ++ app/lib/l10n/app_localizations_pl.dart | 100 ++ app/lib/l10n/app_localizations_pt.dart | 100 ++ app/lib/l10n/app_localizations_ro.dart | 100 ++ app/lib/l10n/app_localizations_ru.dart | 100 ++ app/lib/l10n/app_localizations_sk.dart | 100 ++ app/lib/l10n/app_localizations_sl.dart | 100 ++ app/lib/l10n/app_localizations_sr.dart | 100 ++ app/lib/l10n/app_localizations_sv.dart | 100 ++ app/lib/l10n/app_localizations_ta.dart | 102 ++ app/lib/l10n/app_localizations_te.dart | 100 ++ app/lib/l10n/app_localizations_th.dart | 100 ++ app/lib/l10n/app_localizations_tl.dart | 100 ++ app/lib/l10n/app_localizations_tr.dart | 100 ++ app/lib/l10n/app_localizations_uk.dart | 100 ++ app/lib/l10n/app_localizations_ur.dart | 100 ++ app/lib/l10n/app_localizations_vi.dart | 100 ++ app/lib/l10n/app_localizations_zh.dart | 98 ++ app/lib/l10n/app_lt.arb | 34 +- app/lib/l10n/app_lv.arb | 34 +- app/lib/l10n/app_mk.arb | 34 +- app/lib/l10n/app_mr.arb | 34 +- app/lib/l10n/app_ms.arb | 34 +- app/lib/l10n/app_nl.arb | 34 +- app/lib/l10n/app_no.arb | 34 +- app/lib/l10n/app_pl.arb | 34 +- app/lib/l10n/app_pt.arb | 34 +- app/lib/l10n/app_ro.arb | 34 +- app/lib/l10n/app_ru.arb | 34 +- app/lib/l10n/app_sk.arb | 34 +- app/lib/l10n/app_sl.arb | 34 +- app/lib/l10n/app_sr.arb | 34 +- app/lib/l10n/app_sv.arb | 34 +- app/lib/l10n/app_ta.arb | 34 +- app/lib/l10n/app_te.arb | 34 +- app/lib/l10n/app_th.arb | 34 +- app/lib/l10n/app_tl.arb | 34 +- app/lib/l10n/app_tr.arb | 34 +- app/lib/l10n/app_uk.arb | 34 +- app/lib/l10n/app_ur.arb | 34 +- app/lib/l10n/app_vi.arb | 34 +- app/lib/l10n/app_zh.arb | 34 +- app/lib/pages/apps/widgets/omi_web_page.dart | 18 +- .../onboarding_permissions_panel.dart | 58 +- .../yinka/activation/activation_capture.dart | 408 ++++++ .../yinka/activation/activation_flow.dart | 1293 +++++++++++++++++ .../yinka/activation/activation_motion.dart | 715 +++++++++ .../yinka/activation/activation_visuals.dart | 206 +++ .../activation/onboarding_plans_sheet.dart | 39 + .../variants/yinka/connect_omi_step.dart | 500 +++++++ .../variants/yinka/name_widget.dart | 90 ++ .../variants/yinka/permissions_widget.dart | 273 ++++ .../yinka/primary_language_widget.dart | 278 ++++ .../yinka/widgets/onboarding_card.dart | 191 +++ .../yinka/widgets/onboarding_plain_step.dart | 151 ++ .../yinka/widgets/onboarding_step_layout.dart | 67 + .../variants/yinka/yinka_onboarding_flow.dart | 318 ++++ app/lib/pages/onboarding/wrapper.dart | 62 +- app/lib/pages/shop/omi_shop.dart | 25 + app/lib/providers/onboarding_provider.dart | 34 + .../services/capture/capture_controller.dart | 16 +- .../experiments/onboarding_variant_gate.dart | 207 +++ .../experiments/onboarding_webview_gate.dart | 139 -- app/lib/widgets/scanning_ripple.dart | 7 +- .../unit/onboarding_variant_gate_test.dart | 212 +++ .../unit/onboarding_webview_gate_test.dart | 190 --- ...yinka_onboarding_activation_flow_test.dart | 328 +++++ .../yinka_onboarding_connect_omi_test.dart | 187 +++ .../yinka_onboarding_summary_text_test.dart | 49 + config/feature-flags.yaml | 2 +- 128 files changed, 12497 insertions(+), 424 deletions(-) create mode 100644 app/changelog/unreleased/20261014-onboarding-yinka-arm.json create mode 100644 app/lib/pages/onboarding/variants/yinka/activation/activation_capture.dart create mode 100644 app/lib/pages/onboarding/variants/yinka/activation/activation_flow.dart create mode 100644 app/lib/pages/onboarding/variants/yinka/activation/activation_motion.dart create mode 100644 app/lib/pages/onboarding/variants/yinka/activation/activation_visuals.dart create mode 100644 app/lib/pages/onboarding/variants/yinka/activation/onboarding_plans_sheet.dart create mode 100644 app/lib/pages/onboarding/variants/yinka/connect_omi_step.dart create mode 100644 app/lib/pages/onboarding/variants/yinka/name_widget.dart create mode 100644 app/lib/pages/onboarding/variants/yinka/permissions_widget.dart create mode 100644 app/lib/pages/onboarding/variants/yinka/primary_language_widget.dart create mode 100644 app/lib/pages/onboarding/variants/yinka/widgets/onboarding_card.dart create mode 100644 app/lib/pages/onboarding/variants/yinka/widgets/onboarding_plain_step.dart create mode 100644 app/lib/pages/onboarding/variants/yinka/widgets/onboarding_step_layout.dart create mode 100644 app/lib/pages/onboarding/variants/yinka/yinka_onboarding_flow.dart create mode 100644 app/lib/pages/shop/omi_shop.dart create mode 100644 app/lib/services/experiments/onboarding_variant_gate.dart delete mode 100644 app/lib/services/experiments/onboarding_webview_gate.dart create mode 100644 app/test/unit/onboarding_variant_gate_test.dart delete mode 100644 app/test/unit/onboarding_webview_gate_test.dart create mode 100644 app/test/widgets/variants/yinka_onboarding_activation_flow_test.dart create mode 100644 app/test/widgets/variants/yinka_onboarding_connect_omi_test.dart create mode 100644 app/test/widgets/variants/yinka_onboarding_summary_text_test.dart diff --git a/app/changelog/unreleased/20261014-onboarding-yinka-arm.json b/app/changelog/unreleased/20261014-onboarding-yinka-arm.json new file mode 100644 index 00000000000..005f4da6232 --- /dev/null +++ b/app/changelog/unreleased/20261014-onboarding-yinka-arm.json @@ -0,0 +1 @@ +{"kind":"none"} diff --git a/app/lib/l10n/app_ar.arb b/app/lib/l10n/app_ar.arb index 4edf9af696f..dd92e92dadc 100644 --- a/app/lib/l10n/app_ar.arb +++ b/app/lib/l10n/app_ar.arb @@ -4080,5 +4080,37 @@ "chatAppsAtCapacity": "{app} وصل إلى الحد الأقصى", "chatAppsJoinWaitlist": "انضم إلى قائمة الانتظار", "chatAppsOnWaitlist": "أنت في قائمة الانتظار", - "chatAppsWaitlistJoined": "أنت في قائمة الانتظار. سنُعلمك عندما يتوفر مكان." + "chatAppsWaitlistJoined": "أنت في قائمة الانتظار. سنُعلمك عندما يتوفر مكان.", + "onboardingActCongratsTitle": "كل شيء جاهز يا {name}", + "onboardingActDemoSummary": "غداء مع سام التي ستنتقل إلى لشبونة في يونيو. تريد إرسال العرض التقديمي إلى فريقك بحلول الجمعة وشراء الحليب في طريق العودة.", + "onboardingActDemoTask1": "إرسال العرض إلى الفريق قبل الجمعة", + "onboardingActDemoTask2": "شراء الحليب في طريق العودة", + "onboardingActDemoTitle": "غداء مع سام", + "onboardingActDictateBody": "قلها بطريقتك، وOmi يتولى الباقي.", + "onboardingActDictateBodyOmi": "تحدّث فقط. Omi يستمع.", + "onboardingActDictateExample": "تغدّيت للتو مع سام، ستنتقل إلى لشبونة في يونيو! ذكّرني بإرسال العرض إلى فريقي قبل الجمعة، وبشراء الحليب في طريق العودة.", + "onboardingActDictateHint": "اضغط على علامة الصح عند الانتهاء", + "onboardingActDictateTitle": "أخبر Omi عن يومك", + "onboardingActIntro": "لنجرّب Omi.", + "onboardingActJustNow": "الآن", + "onboardingActMicAllow": "السماح بالميكروفون", + "onboardingActMicBody": "ليسمعك Omi عندما تتحدث إليه.", + "onboardingActMicFallbackBody": "لا يرسل Omi الصوت بعد. استخدم هاتفك الآن.", + "onboardingActMicTitle": "يحتاج Omi إلى الميكروفون", + "onboardingActNoAudioOmi": "لا أسمع Omi. تأكد من أنه يعمل وقريب منك.", + "onboardingActNoAudioPhone": "لا أسمعك بعد. جرّب التحدث بصوت أعلى قليلاً.", + "onboardingActNotesLabel": "يومك، باختصار", + "onboardingActPhoneMic": "ميكروفون الهاتف", + "onboardingActRateTitle": "هل يعجبك Omi حتى الآن؟", + "onboardingActRemembers": "يتذكّر Omi عنك حتى لا تضطر أنت", + "onboardingActSavedToTasks": "تم الحفظ في مهامك", + "onboardingActTasksLabel": "التقط Omi مهامك", + "onboardingActWriting": "جارٍ التلخيص…", + "onboardingActYourWords": "هذا ما قلته", + "onboardingConnectOmi": "اتصل بـ Omi", + "onboardingConnectTapToConnect": "اضغط على Omi للاتصال.", + "onboardingNoOmiYet": "ليس لديك Omi بعد؟", + "onboardingOmiNotOrdered": "لم أطلبه بعد", + "onboardingOmiOrdered": "لقد طلبته", + "onboardingPermissionTapToFix": "اضغط للإصلاح" } diff --git a/app/lib/l10n/app_be.arb b/app/lib/l10n/app_be.arb index 6ac7ec51440..9e6e6a5ca76 100644 --- a/app/lib/l10n/app_be.arb +++ b/app/lib/l10n/app_be.arb @@ -11506,5 +11506,37 @@ "chatAppsAtCapacity": "{app} цалкам запоўнены", "chatAppsJoinWaitlist": "Далучыцца да спісу чакання", "chatAppsOnWaitlist": "Вы ў спісе чакання", - "chatAppsWaitlistJoined": "Вы ў спісе чакання. Мы паведамім, калі з'явіцца месца." + "chatAppsWaitlistJoined": "Вы ў спісе чакання. Мы паведамім, калі з'явіцца месца.", + "onboardingActCongratsTitle": "Усё гатова, {name}", + "onboardingActDemoSummary": "Абед з Сэм, якая ў чэрвені пераязджае ў Лісабон. Вы хочаце адправіць прэзентацыю камандзе да пятніцы і купіць малако па дарозе дадому.", + "onboardingActDemoTask1": "Адправіць прэзентацыю камандзе да пятніцы", + "onboardingActDemoTask2": "Купіць малако па дарозе дадому", + "onboardingActDemoTitle": "Абед з Сэм", + "onboardingActDictateBody": "Кажыце па-свойму. Астатняе зробіць Omi.", + "onboardingActDictateBodyOmi": "Проста гаварыце. Omi слухае.", + "onboardingActDictateExample": "Толькі што паабедаў з Сэм, яна пераязджае ў Лісабон у чэрвені! Нагадай мне адправіць прэзентацыю камандзе да пятніцы і купіць малако па дарозе дадому.", + "onboardingActDictateHint": "Націсніце галачку, калі скончыце", + "onboardingActDictateTitle": "Раскажыце Omi пра свой дзень", + "onboardingActIntro": "Паспрабуем Omi.", + "onboardingActJustNow": "Толькі што", + "onboardingActMicAllow": "Дазволіць мікрафон", + "onboardingActMicBody": "Каб Omi чуў вас, калі вы з ім размаўляеце.", + "onboardingActMicFallbackBody": "Omi яшчэ не перадае гук. Пакуль скарыстайцеся тэлефонам.", + "onboardingActMicTitle": "Omi патрэбны мікрафон", + "onboardingActNoAudioOmi": "Не чуваць Omi. Пераканайцеся, што ён уключаны і побач.", + "onboardingActNoAudioPhone": "Пакуль вас не чуваць. Паспрабуйце гаварыць крыху гучней.", + "onboardingActNotesLabel": "Ваш дзень коратка", + "onboardingActPhoneMic": "Мікрафон тэлефона", + "onboardingActRateTitle": "Як вам Omi?", + "onboardingActRemembers": "Omi памятае, каб вам не трэба было", + "onboardingActSavedToTasks": "Захавана ў вашыя справы", + "onboardingActTasksLabel": "Omi заўважыў вашы справы", + "onboardingActWriting": "Падсумоўваем…", + "onboardingActYourWords": "Вось што вы сказалі", + "onboardingConnectOmi": "Падключыце Omi", + "onboardingConnectTapToConnect": "Націсніце на Omi, каб падключыць.", + "onboardingNoOmiYet": "Яшчэ няма Omi?", + "onboardingOmiNotOrdered": "Яшчэ не замовіў", + "onboardingOmiOrdered": "Я ўжо замовіў", + "onboardingPermissionTapToFix": "Націсніце, каб выправіць" } diff --git a/app/lib/l10n/app_bg.arb b/app/lib/l10n/app_bg.arb index f64e56c6d4d..3e1d08fd4bf 100644 --- a/app/lib/l10n/app_bg.arb +++ b/app/lib/l10n/app_bg.arb @@ -4080,5 +4080,37 @@ "chatAppsAtCapacity": "{app} е запълнен", "chatAppsJoinWaitlist": "Запиши се в списъка", "chatAppsOnWaitlist": "Вие сте в списъка с чакащи", - "chatAppsWaitlistJoined": "Вие сте в списъка с чакащи. Ще ви уведомим, когато има място." + "chatAppsWaitlistJoined": "Вие сте в списъка с чакащи. Ще ви уведомим, когато има място.", + "onboardingActCongratsTitle": "Всичко е готово, {name}", + "onboardingActDemoSummary": "Обяд със Сам, която се мести в Лисабон през юни. Искате да изпратите презентацията на екипа до петък и да вземете мляко на път за вкъщи.", + "onboardingActDemoTask1": "Да пратя презентацията на екипа до петък", + "onboardingActDemoTask2": "Да купя мляко на връщане", + "onboardingActDemoTitle": "Обяд със Сам", + "onboardingActDictateBody": "Кажете го по своя начин. Omi ще се погрижи за останалото.", + "onboardingActDictateBodyOmi": "Просто говорете. Omi слуша.", + "onboardingActDictateExample": "Току-що обядвах със Сам, мести се в Лисабон през юни! Напомни ми да пратя презентацията на екипа до петък и да купя мляко на връщане.", + "onboardingActDictateHint": "Докоснете отметката, когато сте готови", + "onboardingActDictateTitle": "Разкажете на Omi за деня си", + "onboardingActIntro": "Да пробваме Omi.", + "onboardingActJustNow": "Току-що", + "onboardingActMicAllow": "Разреши микрофона", + "onboardingActMicBody": "За да ви чува Omi, когато говорите с него.", + "onboardingActMicFallbackBody": "Omi все още не изпраща звук. Засега използвайте телефона си.", + "onboardingActMicTitle": "Omi се нуждае от микрофона ви", + "onboardingActNoAudioOmi": "Не чуваме Omi. Уверете се, че е включен и наблизо.", + "onboardingActNoAudioPhone": "Още не ви чуваме. Опитайте да говорите малко по-силно.", + "onboardingActNotesLabel": "Денят ви накратко", + "onboardingActPhoneMic": "Микрофон на телефона", + "onboardingActRateTitle": "Харесва ли ви Omi досега?", + "onboardingActRemembers": "Omi помни вместо вас", + "onboardingActSavedToTasks": "Запазено в задачите ви", + "onboardingActTasksLabel": "Omi улови задачите ви", + "onboardingActWriting": "Обобщаваме…", + "onboardingActYourWords": "Ето какво казахте", + "onboardingConnectOmi": "Свържете Omi", + "onboardingConnectTapToConnect": "Докоснете Omi, за да се свържете.", + "onboardingNoOmiYet": "Все още нямате Omi?", + "onboardingOmiNotOrdered": "Още не съм поръчал", + "onboardingOmiOrdered": "Поръчал съм го", + "onboardingPermissionTapToFix": "Докоснете, за да поправите" } diff --git a/app/lib/l10n/app_bn.arb b/app/lib/l10n/app_bn.arb index 471a9397b4e..0d4e9b3f0cc 100644 --- a/app/lib/l10n/app_bn.arb +++ b/app/lib/l10n/app_bn.arb @@ -11506,5 +11506,37 @@ "chatAppsAtCapacity": "{app} পূর্ণ হয়ে গেছে", "chatAppsJoinWaitlist": "অপেক্ষা তালিকায় যোগ দিন", "chatAppsOnWaitlist": "আপনি অপেক্ষা তালিকায় আছেন", - "chatAppsWaitlistJoined": "আপনি অপেক্ষা তালিকায় আছেন। জায়গা খালি হলে আমরা জানাব।" + "chatAppsWaitlistJoined": "আপনি অপেক্ষা তালিকায় আছেন। জায়গা খালি হলে আমরা জানাব।", + "onboardingActCongratsTitle": "সব তৈরি, {name}", + "onboardingActDemoSummary": "স্যামের সঙ্গে দুপুরের খাবার, যিনি জুনে লিসবনে চলে যাচ্ছেন। শুক্রবারের মধ্যে টিমকে ডেক পাঠাতে আর বাড়ি ফেরার পথে দুধ কিনতে হবে।", + "onboardingActDemoTask1": "শুক্রবারের মধ্যে টিমকে ডেক পাঠানো", + "onboardingActDemoTask2": "ফেরার পথে দুধ কেনা", + "onboardingActDemoTitle": "স্যামের সাথে লাঞ্চ", + "onboardingActDictateBody": "নিজের মতো করে বলুন। বাকিটা Omi সামলাবে।", + "onboardingActDictateBodyOmi": "শুধু কথা বলুন। আপনার Omi শুনছে।", + "onboardingActDictateExample": "এইমাত্র স্যামের সাথে লাঞ্চ করলাম, ও জুনে লিসবনে চলে যাচ্ছে! মনে করিয়ে দিও শুক্রবারের মধ্যে টিমকে ডেকটা পাঠাতে, আর ফেরার পথে দুধ কিনতে।", + "onboardingActDictateHint": "শেষ হলে টিক চিহ্নে ট্যাপ করুন", + "onboardingActDictateTitle": "Omi-কে আপনার দিনের কথা বলুন", + "onboardingActIntro": "চলুন Omi চেষ্টা করি।", + "onboardingActJustNow": "এইমাত্র", + "onboardingActMicAllow": "মাইক্রোফোন অনুমতি দিন", + "onboardingActMicBody": "যাতে আপনি কথা বললে Omi শুনতে পায়।", + "onboardingActMicFallbackBody": "Omi এখনও অডিও পাঠাচ্ছে না। আপাতত আপনার ফোন ব্যবহার করুন।", + "onboardingActMicTitle": "Omi-এর আপনার মাইক্রোফোন দরকার", + "onboardingActNoAudioOmi": "Omi শোনা যাচ্ছে না। এটি চালু আছে ও কাছে আছে কিনা দেখুন।", + "onboardingActNoAudioPhone": "এখনও আপনাকে শোনা যাচ্ছে না। একটু জোরে বলে দেখুন।", + "onboardingActNotesLabel": "আপনার দিন, সংক্ষেপে", + "onboardingActPhoneMic": "ফোনের মাইক্রোফোন", + "onboardingActRateTitle": "এখন পর্যন্ত Omi কেমন লাগছে?", + "onboardingActRemembers": "Omi মনে রাখে, যাতে আপনাকে রাখতে না হয়", + "onboardingActSavedToTasks": "আপনার কাজের তালিকায় সংরক্ষিত", + "onboardingActTasksLabel": "Omi আপনার কাজগুলো ধরে ফেলেছে", + "onboardingActWriting": "সারাংশ তৈরি হচ্ছে…", + "onboardingActYourWords": "আপনি যা বললেন", + "onboardingConnectOmi": "Omi সংযুক্ত করুন", + "onboardingConnectTapToConnect": "সংযোগ করতে আপনার Omi-তে ট্যাপ করুন।", + "onboardingNoOmiYet": "এখনও Omi নেই?", + "onboardingOmiNotOrdered": "এখনও অর্ডার করিনি", + "onboardingOmiOrdered": "আমি অর্ডার করেছি", + "onboardingPermissionTapToFix": "ঠিক করতে ট্যাপ করুন" } diff --git a/app/lib/l10n/app_bs.arb b/app/lib/l10n/app_bs.arb index a7c31cc332a..8c1a2dae5e4 100644 --- a/app/lib/l10n/app_bs.arb +++ b/app/lib/l10n/app_bs.arb @@ -11506,5 +11506,37 @@ "chatAppsAtCapacity": "{app} je popunjen", "chatAppsJoinWaitlist": "Pridruži se listi čekanja", "chatAppsOnWaitlist": "Na listi čekanja ste", - "chatAppsWaitlistJoined": "Na listi čekanja ste. Javit ćemo vam kad bude mjesta." + "chatAppsWaitlistJoined": "Na listi čekanja ste. Javit ćemo vam kad bude mjesta.", + "onboardingActCongratsTitle": "Sve je spremno, {name}", + "onboardingActDemoSummary": "Ručak sa Sam, koja se u junu seli u Lisabon. Želite poslati prezentaciju timu do petka i kupiti mlijeko na putu kući.", + "onboardingActDemoTask1": "Poslati prezentaciju timu do petka", + "onboardingActDemoTask2": "Kupiti mlijeko na putu kući", + "onboardingActDemoTitle": "Ručak sa Sam", + "onboardingActDictateBody": "Recite to na svoj način. Omi će se pobrinuti za ostalo.", + "onboardingActDictateBodyOmi": "Samo pričajte. Vaš Omi sluša.", + "onboardingActDictateExample": "Upravo sam ručao sa Sam, seli se u Lisabon u junu! Podsjeti me da pošaljem prezentaciju timu do petka i da kupim mlijeko na putu kući.", + "onboardingActDictateHint": "Dodirnite kvačicu kad završite", + "onboardingActDictateTitle": "Ispričajte Omiju svoj dan", + "onboardingActIntro": "Isprobajmo Omi.", + "onboardingActJustNow": "Upravo sada", + "onboardingActMicAllow": "Dozvoli mikrofon", + "onboardingActMicBody": "Da vas Omi čuje kad mu se obratite.", + "onboardingActMicFallbackBody": "Omi još ne šalje zvuk. Za sada koristite telefon.", + "onboardingActMicTitle": "Omi treba vaš mikrofon", + "onboardingActNoAudioOmi": "Ne čujemo Omi. Provjerite je li uključen i blizu.", + "onboardingActNoAudioPhone": "Još vas ne čujemo. Pokušajte govoriti malo glasnije.", + "onboardingActNotesLabel": "Vaš dan, ukratko", + "onboardingActPhoneMic": "Mikrofon telefona", + "onboardingActRateTitle": "Sviđa li vam se Omi zasad?", + "onboardingActRemembers": "Omi pamti da vi ne morate", + "onboardingActSavedToTasks": "Sačuvano u vaše zadatke", + "onboardingActTasksLabel": "Omi je uhvatio vaše zadatke", + "onboardingActWriting": "Sažimamo…", + "onboardingActYourWords": "Evo šta ste rekli", + "onboardingConnectOmi": "Povežite Omi", + "onboardingConnectTapToConnect": "Dodirnite svoj Omi da se povežete.", + "onboardingNoOmiYet": "Još nemate Omi?", + "onboardingOmiNotOrdered": "Još nisam naručio/la", + "onboardingOmiOrdered": "Naručio/la sam ga", + "onboardingPermissionTapToFix": "Dodirnite da popravite" } diff --git a/app/lib/l10n/app_ca.arb b/app/lib/l10n/app_ca.arb index 05792bee9ee..0aa44dd6381 100644 --- a/app/lib/l10n/app_ca.arb +++ b/app/lib/l10n/app_ca.arb @@ -4080,5 +4080,37 @@ "chatAppsAtCapacity": "{app} és ple", "chatAppsJoinWaitlist": "Uneix-te a la llista d'espera", "chatAppsOnWaitlist": "Ets a la llista d'espera", - "chatAppsWaitlistJoined": "Ets a la llista d'espera. T'avisarem quan hi hagi lloc." + "chatAppsWaitlistJoined": "Ets a la llista d'espera. T'avisarem quan hi hagi lloc.", + "onboardingActCongratsTitle": "Tot a punt, {name}", + "onboardingActDemoSummary": "Dinar amb en Sam, que es trasllada a Lisboa al juny. Vols enviar la presentació a l'equip abans de divendres i comprar llet de camí a casa.", + "onboardingActDemoTask1": "Enviar la presentació a l'equip abans de divendres", + "onboardingActDemoTask2": "Comprar llet de tornada a casa", + "onboardingActDemoTitle": "Dinar amb la Sam", + "onboardingActDictateBody": "Digues-ho a la teva manera. Omi s'encarrega de la resta.", + "onboardingActDictateBodyOmi": "Només parla. El teu Omi t'escolta.", + "onboardingActDictateExample": "Acabo de dinar amb la Sam, se'n va a viure a Lisboa al juny! Recorda'm d'enviar la presentació a l'equip abans de divendres i de comprar llet de tornada a casa.", + "onboardingActDictateHint": "Toca la marca quan acabis", + "onboardingActDictateTitle": "Explica-li a Omi el teu dia", + "onboardingActIntro": "Provem Omi.", + "onboardingActJustNow": "Ara mateix", + "onboardingActMicAllow": "Permet el micròfon", + "onboardingActMicBody": "Perquè Omi et senti quan li parlis.", + "onboardingActMicFallbackBody": "L'Omi encara no envia àudio. De moment, fes servir el telèfon.", + "onboardingActMicTitle": "Omi necessita el micròfon", + "onboardingActNoAudioOmi": "No sentim l'Omi. Comprova que estigui encès i a prop.", + "onboardingActNoAudioPhone": "Encara no et sentim. Prova de parlar una mica més fort.", + "onboardingActNotesLabel": "El teu dia, resumit", + "onboardingActPhoneMic": "Micròfon del telèfon", + "onboardingActRateTitle": "T'agrada Omi de moment?", + "onboardingActRemembers": "L'Omi ho recorda perquè tu no ho hagis de fer", + "onboardingActSavedToTasks": "Desat a les teves tasques", + "onboardingActTasksLabel": "Omi ha trobat les teves tasques", + "onboardingActWriting": "Resumint…", + "onboardingActYourWords": "Això és el que has dit", + "onboardingConnectOmi": "Connecta l'Omi", + "onboardingConnectTapToConnect": "Toca el teu Omi per connectar-lo.", + "onboardingNoOmiYet": "Encara no tens l'Omi?", + "onboardingOmiNotOrdered": "Encara no l'he demanat", + "onboardingOmiOrdered": "Ja l'he demanat", + "onboardingPermissionTapToFix": "Toca per solucionar-ho" } diff --git a/app/lib/l10n/app_cs.arb b/app/lib/l10n/app_cs.arb index dea8c41a58f..d97f2f49dae 100644 --- a/app/lib/l10n/app_cs.arb +++ b/app/lib/l10n/app_cs.arb @@ -4080,5 +4080,37 @@ "chatAppsAtCapacity": "{app} je plně obsazen", "chatAppsJoinWaitlist": "Připojit se k čekací listině", "chatAppsOnWaitlist": "Jste na čekací listině", - "chatAppsWaitlistJoined": "Jste na čekací listině. Dáme vám vědět, až bude místo." + "chatAppsWaitlistJoined": "Jste na čekací listině. Dáme vám vědět, až bude místo.", + "onboardingActCongratsTitle": "Vše je připraveno, {name}", + "onboardingActDemoSummary": "Oběd se Sam, která se v červnu stěhuje do Lisabonu. Chcete do pátku poslat týmu prezentaci a cestou domů koupit mléko.", + "onboardingActDemoTask1": "Poslat prezentaci týmu do pátku", + "onboardingActDemoTask2": "Koupit cestou domů mléko", + "onboardingActDemoTitle": "Oběd se Sam", + "onboardingActDictateBody": "Řekněte to po svém. Zbytek zařídí Omi.", + "onboardingActDictateBodyOmi": "Prostě mluvte. Vaše Omi poslouchá.", + "onboardingActDictateExample": "Právě jsem obědval se Sam, v červnu se stěhuje do Lisabonu! Připomeň mi poslat prezentaci týmu do pátku a koupit cestou domů mléko.", + "onboardingActDictateHint": "Až budete hotovi, klepněte na fajfku", + "onboardingActDictateTitle": "Řekněte Omi o svém dni", + "onboardingActIntro": "Vyzkoušejme Omi.", + "onboardingActJustNow": "Právě teď", + "onboardingActMicAllow": "Povolit mikrofon", + "onboardingActMicBody": "Aby vás Omi slyšel, když na něj mluvíte.", + "onboardingActMicFallbackBody": "Omi zatím neposílá zvuk. Zatím použijte telefon.", + "onboardingActMicTitle": "Omi potřebuje mikrofon", + "onboardingActNoAudioOmi": "Omi neslyšíme. Zkontrolujte, že je zapnuté a blízko.", + "onboardingActNoAudioPhone": "Zatím vás neslyšíme. Zkuste mluvit trochu hlasitěji.", + "onboardingActNotesLabel": "Váš den ve zkratce", + "onboardingActPhoneMic": "Mikrofon telefonu", + "onboardingActRateTitle": "Líbí se vám zatím Omi?", + "onboardingActRemembers": "Omi si to pamatuje, abyste nemuseli vy", + "onboardingActSavedToTasks": "Uloženo do vašich úkolů", + "onboardingActTasksLabel": "Omi zachytil vaše úkoly", + "onboardingActWriting": "Shrnujeme…", + "onboardingActYourWords": "Tohle jste řekli", + "onboardingConnectOmi": "Připojte Omi", + "onboardingConnectTapToConnect": "Klepnutím na Omi se připojíte.", + "onboardingNoOmiYet": "Ještě nemáte Omi?", + "onboardingOmiNotOrdered": "Ještě neobjednáno", + "onboardingOmiOrdered": "Už jsem ho objednal/a", + "onboardingPermissionTapToFix": "Klepnutím opravíte" } diff --git a/app/lib/l10n/app_da.arb b/app/lib/l10n/app_da.arb index 729cbe54bf4..ff94ae19048 100644 --- a/app/lib/l10n/app_da.arb +++ b/app/lib/l10n/app_da.arb @@ -4122,5 +4122,37 @@ "chatAppsAtCapacity": "{app} er fuldt", "chatAppsJoinWaitlist": "Skriv dig på ventelisten", "chatAppsOnWaitlist": "Du er på ventelisten", - "chatAppsWaitlistJoined": "Du er på ventelisten. Vi giver besked, når der er plads." + "chatAppsWaitlistJoined": "Du er på ventelisten. Vi giver besked, når der er plads.", + "onboardingActCongratsTitle": "Du er klar, {name}", + "onboardingActDemoSummary": "Frokost med Sam, der flytter til Lissabon i juni. Du vil sende præsentationen til teamet inden fredag og købe mælk på vejen hjem.", + "onboardingActDemoTask1": "Send præsentationen til holdet inden fredag", + "onboardingActDemoTask2": "Køb mælk på vej hjem", + "onboardingActDemoTitle": "Frokost med Sam", + "onboardingActDictateBody": "Sig det på din måde. Omi klarer resten.", + "onboardingActDictateBodyOmi": "Bare tal. Din Omi lytter.", + "onboardingActDictateExample": "Har lige spist frokost med Sam, hun flytter til Lissabon i juni! Mind mig om at sende præsentationen til holdet inden fredag og købe mælk på vej hjem.", + "onboardingActDictateHint": "Tryk på fluebenet, når du er færdig", + "onboardingActDictateTitle": "Fortæl Omi om din dag", + "onboardingActIntro": "Lad os prøve Omi.", + "onboardingActJustNow": "Lige nu", + "onboardingActMicAllow": "Tillad mikrofon", + "onboardingActMicBody": "Så Omi kan høre dig, når du taler til den.", + "onboardingActMicFallbackBody": "Din Omi sender ikke lyd endnu. Brug din telefon indtil videre.", + "onboardingActMicTitle": "Omi skal bruge din mikrofon", + "onboardingActNoAudioOmi": "Kan ikke høre din Omi. Tjek, at den er tændt og tæt på.", + "onboardingActNoAudioPhone": "Kan ikke høre dig endnu. Prøv at tale lidt højere.", + "onboardingActNotesLabel": "Din dag, opsummeret", + "onboardingActPhoneMic": "Telefonens mikrofon", + "onboardingActRateTitle": "Kan du lide Omi indtil videre?", + "onboardingActRemembers": "Omi husker det, så du ikke behøver", + "onboardingActSavedToTasks": "Gemt i dine opgaver", + "onboardingActTasksLabel": "Omi fangede dine to-dos", + "onboardingActWriting": "Opsummerer…", + "onboardingActYourWords": "Det her sagde du", + "onboardingConnectOmi": "Forbind Omi", + "onboardingConnectTapToConnect": "Tryk på din Omi for at forbinde.", + "onboardingNoOmiYet": "Har du ikke din Omi endnu?", + "onboardingOmiNotOrdered": "Ikke bestilt endnu", + "onboardingOmiOrdered": "Jeg har bestilt den", + "onboardingPermissionTapToFix": "Tryk for at rette" } diff --git a/app/lib/l10n/app_de.arb b/app/lib/l10n/app_de.arb index 3745ebdab20..2a05847b7d5 100644 --- a/app/lib/l10n/app_de.arb +++ b/app/lib/l10n/app_de.arb @@ -4080,5 +4080,37 @@ "chatAppsAtCapacity": "{app} ist ausgelastet", "chatAppsJoinWaitlist": "Auf die Warteliste", "chatAppsOnWaitlist": "Du stehst auf der Warteliste", - "chatAppsWaitlistJoined": "Du stehst auf der Warteliste. Wir melden uns, sobald ein Platz frei ist." + "chatAppsWaitlistJoined": "Du stehst auf der Warteliste. Wir melden uns, sobald ein Platz frei ist.", + "onboardingActCongratsTitle": "Alles bereit, {name}", + "onboardingActDemoSummary": "Mittagessen mit Sam, der im Juni nach Lissabon zieht. Du willst die Präsentation bis Freitag ans Team schicken und auf dem Heimweg Milch holen.", + "onboardingActDemoTask1": "Präsentation bis Freitag ans Team schicken", + "onboardingActDemoTask2": "Auf dem Heimweg Milch holen", + "onboardingActDemoTitle": "Mittagessen mit Sam", + "onboardingActDictateBody": "Sag es auf deine Art. Omi erledigt den Rest.", + "onboardingActDictateBodyOmi": "Sprich einfach. Dein Omi hört zu.", + "onboardingActDictateExample": "Hatte gerade Mittagessen mit Sam, sie zieht im Juni nach Lissabon! Erinnere mich, die Präsentation bis Freitag ans Team zu schicken und auf dem Heimweg Milch zu holen.", + "onboardingActDictateHint": "Tippe auf den Haken, wenn du fertig bist", + "onboardingActDictateTitle": "Erzähl Omi von deinem Tag", + "onboardingActIntro": "Probier Omi aus.", + "onboardingActJustNow": "Gerade eben", + "onboardingActMicAllow": "Mikrofon erlauben", + "onboardingActMicBody": "Damit Omi dich hört, wenn du mit ihm sprichst.", + "onboardingActMicFallbackBody": "Dein Omi sendet noch keinen Ton. Nutze vorerst dein Handy.", + "onboardingActMicTitle": "Omi braucht dein Mikrofon", + "onboardingActNoAudioOmi": "Dein Omi ist nicht zu hören. Ist es an und in der Nähe?", + "onboardingActNoAudioPhone": "Wir hören dich noch nicht. Sprich etwas lauter.", + "onboardingActNotesLabel": "Dein Tag, zusammengefasst", + "onboardingActPhoneMic": "Handymikrofon", + "onboardingActRateTitle": "Gefällt dir Omi bisher?", + "onboardingActRemembers": "Omi merkt es sich, damit du es nicht musst", + "onboardingActSavedToTasks": "In deinen Aufgaben gespeichert", + "onboardingActTasksLabel": "Omi hat deine To-dos erkannt", + "onboardingActWriting": "Wird zusammengefasst…", + "onboardingActYourWords": "Das hast du gesagt", + "onboardingConnectOmi": "Omi verbinden", + "onboardingConnectTapToConnect": "Tippe auf dein Omi, um es zu verbinden.", + "onboardingNoOmiYet": "Hast du dein Omi noch nicht?", + "onboardingOmiNotOrdered": "Noch nicht bestellt", + "onboardingOmiOrdered": "Ich habe es bestellt", + "onboardingPermissionTapToFix": "Zum Beheben tippen" } diff --git a/app/lib/l10n/app_el.arb b/app/lib/l10n/app_el.arb index c59ff2b9927..4981b5dbe41 100644 --- a/app/lib/l10n/app_el.arb +++ b/app/lib/l10n/app_el.arb @@ -4080,5 +4080,37 @@ "chatAppsAtCapacity": "Το {app} είναι πλήρες", "chatAppsJoinWaitlist": "Εγγραφή στη λίστα αναμονής", "chatAppsOnWaitlist": "Είστε στη λίστα αναμονής", - "chatAppsWaitlistJoined": "Είστε στη λίστα αναμονής. Θα σας ενημερώσουμε όταν υπάρξει θέση." + "chatAppsWaitlistJoined": "Είστε στη λίστα αναμονής. Θα σας ενημερώσουμε όταν υπάρξει θέση.", + "onboardingActCongratsTitle": "Όλα έτοιμα, {name}", + "onboardingActDemoSummary": "Μεσημεριανό με τον Σαμ, που μετακομίζει στη Λισαβόνα τον Ιούνιο. Θέλεις να στείλεις την παρουσίαση στην ομάδα ως την Παρασκευή και να πάρεις γάλα στον δρόμο για το σπίτι.", + "onboardingActDemoTask1": "Αποστολή της παρουσίασης στην ομάδα ως την Παρασκευή", + "onboardingActDemoTask2": "Γάλα στον δρόμο για το σπίτι", + "onboardingActDemoTitle": "Γεύμα με τη Σαμ", + "onboardingActDictateBody": "Πείτε το με τον τρόπο σας. Το Omi φροντίζει τα υπόλοιπα.", + "onboardingActDictateBodyOmi": "Απλώς μίλα. Το Omi σου ακούει.", + "onboardingActDictateExample": "Μόλις έφαγα με τη Σαμ, μετακομίζει στη Λισαβόνα τον Ιούνιο! Θύμισέ μου να στείλω την παρουσίαση στην ομάδα ως την Παρασκευή και να πάρω γάλα στον δρόμο.", + "onboardingActDictateHint": "Πατήστε το τικ όταν τελειώσετε", + "onboardingActDictateTitle": "Πείτε στο Omi για τη μέρα σας", + "onboardingActIntro": "Ας δοκιμάσουμε το Omi.", + "onboardingActJustNow": "Μόλις τώρα", + "onboardingActMicAllow": "Να επιτρέπεται το μικρόφωνο", + "onboardingActMicBody": "Για να σας ακούει το Omi όταν του μιλάτε.", + "onboardingActMicFallbackBody": "Το Omi δεν στέλνει ακόμα ήχο. Χρησιμοποίησε προς το παρόν το τηλέφωνό σου.", + "onboardingActMicTitle": "Το Omi χρειάζεται το μικρόφωνό σας", + "onboardingActNoAudioOmi": "Δεν ακούμε το Omi. Βεβαιώσου ότι είναι ανοιχτό και κοντά σου.", + "onboardingActNoAudioPhone": "Δεν σε ακούμε ακόμα. Δοκίμασε να μιλήσεις λίγο πιο δυνατά.", + "onboardingActNotesLabel": "Η μέρα σου, σε σύνοψη", + "onboardingActPhoneMic": "Μικρόφωνο τηλεφώνου", + "onboardingActRateTitle": "Σας αρέσει το Omi μέχρι τώρα;", + "onboardingActRemembers": "Το Omi θυμάται, για να μη χρειάζεται εσύ", + "onboardingActSavedToTasks": "Αποθηκεύτηκε στις εργασίες σου", + "onboardingActTasksLabel": "Το Omi εντόπισε τις εκκρεμότητές σου", + "onboardingActWriting": "Γίνεται σύνοψη…", + "onboardingActYourWords": "Αυτά είπατε", + "onboardingConnectOmi": "Συνδέστε το Omi", + "onboardingConnectTapToConnect": "Πάτησε το Omi σου για σύνδεση.", + "onboardingNoOmiYet": "Δεν έχετε ακόμα το Omi σας;", + "onboardingOmiNotOrdered": "Δεν το έχω παραγγείλει ακόμα", + "onboardingOmiOrdered": "Το έχω παραγγείλει", + "onboardingPermissionTapToFix": "Πατήστε για διόρθωση" } diff --git a/app/lib/l10n/app_en.arb b/app/lib/l10n/app_en.arb index dd3f2a9cf15..84458832809 100644 --- a/app/lib/l10n/app_en.arb +++ b/app/lib/l10n/app_en.arb @@ -15986,5 +15986,139 @@ "chatAppsWaitlistJoined": "You're on the waitlist. We'll let you know when there's room.", "@chatAppsWaitlistJoined": { "description": "Confirmation after joining a chat app channel waitlist" + }, + "onboardingActCongratsTitle": "You're all set, {name}", + "@onboardingActCongratsTitle": { + "description": "Activation: congratulations title with the user's first name", + "placeholders": { + "name": { + "type": "String", + "example": "Yinka" + } + } + }, + "onboardingActDemoSummary": "Lunch with Sam, who's moving to Lisbon in June. You want to send the deck to your team by Friday and grab milk on the way home.", + "@onboardingActDemoSummary": { + "description": "Activation slide illustration: example summary line" + }, + "onboardingActDemoTask1": "Send the deck to the team by Friday", + "@onboardingActDemoTask1": { + "description": "Activation slide illustration: example task" + }, + "onboardingActDemoTask2": "Grab milk on the way home", + "@onboardingActDemoTask2": { + "description": "Activation slide illustration: example task" + }, + "onboardingActDemoTitle": "Lunch with Sam", + "@onboardingActDemoTitle": { + "description": "Activation slide illustration: example note title" + }, + "onboardingActDictateBody": "Say it your way. Omi handles the rest.", + "@onboardingActDictateBody": { + "description": "Activation: line under the dictation title" + }, + "onboardingActDictateBodyOmi": "Just talk. Your Omi is listening.", + "@onboardingActDictateBodyOmi": { + "description": "Onboarding dictation subtitle when the words come from the connected Omi" + }, + "onboardingActDictateExample": "Just had lunch with Sam, she's moving to Lisbon in June! Remind me to send the deck to my team by Friday, and to grab milk on the way home.", + "@onboardingActDictateExample": { + "description": "Activation: faded example in the dictation card (casual tone; keep 'Remind me' so Omi keeps it; Sam and Lisbon are example names)" + }, + "onboardingActDictateHint": "Tap the check when you're done", + "@onboardingActDictateHint": { + "description": "Activation: hint in the dictation bar next to the check button" + }, + "onboardingActDictateTitle": "Tell Omi about your day", + "@onboardingActDictateTitle": { + "description": "Activation: dictation step title" + }, + "onboardingActIntro": "Let's try Omi.", + "@onboardingActIntro": { + "description": "Activation: animated line before the demo slides" + }, + "onboardingActJustNow": "Just now", + "@onboardingActJustNow": { + "description": "Time label on the first note Omi wrote in onboarding" + }, + "onboardingActMicAllow": "Allow Microphone", + "@onboardingActMicAllow": { + "description": "Activation: button that shows the microphone system prompt" + }, + "onboardingActMicBody": "So Omi can hear you when you talk to it.", + "@onboardingActMicBody": { + "description": "Activation: why the microphone is needed" + }, + "onboardingActMicFallbackBody": "Your Omi isn't sending audio yet. Use your phone for now.", + "@onboardingActMicFallbackBody": { + "description": "Onboarding microphone ask when the connected Omi is not streaming" + }, + "onboardingActMicTitle": "Omi needs your microphone", + "@onboardingActMicTitle": { + "description": "Activation: microphone pre-permission title (phone users)" + }, + "onboardingActNoAudioOmi": "Can't hear your Omi. Make sure it's on and close by.", + "@onboardingActNoAudioOmi": { + "description": "Onboarding dictation hint after a while with no words from the Omi" + }, + "onboardingActNoAudioPhone": "Can't hear you yet. Try speaking a little louder.", + "@onboardingActNoAudioPhone": { + "description": "Onboarding dictation hint after a while with no words from the phone microphone" + }, + "onboardingActNotesLabel": "Your day, summarized", + "@onboardingActNotesLabel": { + "description": "Activation: small label above the generated summary" + }, + "onboardingActPhoneMic": "Phone microphone", + "@onboardingActPhoneMic": { + "description": "Activation: which microphone is listening (phone)" + }, + "onboardingActRateTitle": "Enjoying Omi so far?", + "@onboardingActRateTitle": { + "description": "Activation: rating ask on the congratulations screen" + }, + "onboardingActRemembers": "Omi remembers so you don't have to", + "@onboardingActRemembers": { + "description": "Line above the summary title at the end of the onboarding dictation" + }, + "onboardingActSavedToTasks": "Saved to your tasks", + "@onboardingActSavedToTasks": { + "description": "Confirmation under the to-dos Omi found in onboarding" + }, + "onboardingActTasksLabel": "Omi caught your to-dos", + "@onboardingActTasksLabel": { + "description": "Activation: small label above the generated tasks" + }, + "onboardingActWriting": "Summarizing…", + "@onboardingActWriting": { + "description": "Activation: shown while Omi processes the dictation" + }, + "onboardingActYourWords": "Here's what you said", + "@onboardingActYourWords": { + "description": "Activation: fallback label when no summary could be made" + }, + "onboardingConnectOmi": "Connect Omi", + "@onboardingConnectOmi": { + "description": "Onboarding connect step title; Omi is the brand name of the wearable" + }, + "onboardingConnectTapToConnect": "Tap your Omi to connect.", + "@onboardingConnectTapToConnect": { + "description": "Connect Omi onboarding step, under the title once an Omi is found nearby" + }, + "onboardingNoOmiYet": "Don't have your Omi yet?", + "@onboardingNoOmiYet": { + "description": "Onboarding connect step: small prompt for people without their Omi device" + }, + "onboardingOmiNotOrdered": "Not ordered yet", + "@onboardingOmiNotOrdered": { + "description": "Onboarding connect step: choice for people who have not ordered an Omi device" + }, + "onboardingOmiOrdered": "I've ordered mine", + "@onboardingOmiOrdered": { + "description": "Onboarding connect step: choice for people whose Omi device is on its way" + }, + "onboardingPermissionTapToFix": "Tap to fix", + "@onboardingPermissionTapToFix": { + "description": "Onboarding permissions: shown on a denied permission card; tapping opens Settings" } } diff --git a/app/lib/l10n/app_es.arb b/app/lib/l10n/app_es.arb index 90d36234039..87b8e5d4d5c 100644 --- a/app/lib/l10n/app_es.arb +++ b/app/lib/l10n/app_es.arb @@ -4080,5 +4080,37 @@ "chatAppsAtCapacity": "{app} está completo", "chatAppsJoinWaitlist": "Unirme a la lista de espera", "chatAppsOnWaitlist": "Estás en la lista de espera", - "chatAppsWaitlistJoined": "Estás en la lista de espera. Te avisaremos cuando haya sitio." + "chatAppsWaitlistJoined": "Estás en la lista de espera. Te avisaremos cuando haya sitio.", + "onboardingActCongratsTitle": "Todo listo, {name}", + "onboardingActDemoSummary": "Almuerzo con Sam, que se muda a Lisboa en junio. Quieres enviar la presentación a tu equipo antes del viernes y comprar leche de camino a casa.", + "onboardingActDemoTask1": "Enviar la presentación al equipo antes del viernes", + "onboardingActDemoTask2": "Comprar leche de camino a casa", + "onboardingActDemoTitle": "Comida con Sam", + "onboardingActDictateBody": "Dilo a tu manera. Omi se encarga del resto.", + "onboardingActDictateBodyOmi": "Solo habla. Tu Omi te escucha.", + "onboardingActDictateExample": "¡Acabo de comer con Sam, se muda a Lisboa en junio! Recuérdame enviar la presentación al equipo antes del viernes y comprar leche de camino a casa.", + "onboardingActDictateHint": "Toca el check cuando termines", + "onboardingActDictateTitle": "Cuéntale a Omi tu día", + "onboardingActIntro": "Probemos Omi.", + "onboardingActJustNow": "Ahora mismo", + "onboardingActMicAllow": "Permitir micrófono", + "onboardingActMicBody": "Para que Omi te escuche cuando le hables.", + "onboardingActMicFallbackBody": "Tu Omi aún no envía audio. Usa tu teléfono por ahora.", + "onboardingActMicTitle": "Omi necesita tu micrófono", + "onboardingActNoAudioOmi": "No se oye tu Omi. Comprueba que esté encendido y cerca.", + "onboardingActNoAudioPhone": "Aún no te oímos. Prueba a hablar un poco más alto.", + "onboardingActNotesLabel": "Tu día, resumido", + "onboardingActPhoneMic": "Micrófono del teléfono", + "onboardingActRateTitle": "¿Te gusta Omi hasta ahora?", + "onboardingActRemembers": "Omi lo recuerda para que tú no tengas que hacerlo", + "onboardingActSavedToTasks": "Guardado en tus tareas", + "onboardingActTasksLabel": "Omi detectó tus pendientes", + "onboardingActWriting": "Resumiendo…", + "onboardingActYourWords": "Esto es lo que dijiste", + "onboardingConnectOmi": "Conecta tu Omi", + "onboardingConnectTapToConnect": "Toca tu Omi para conectarlo.", + "onboardingNoOmiYet": "¿Aún no tienes tu Omi?", + "onboardingOmiNotOrdered": "Aún no lo he pedido", + "onboardingOmiOrdered": "Ya lo pedí", + "onboardingPermissionTapToFix": "Toca para solucionarlo" } diff --git a/app/lib/l10n/app_et.arb b/app/lib/l10n/app_et.arb index 09ac94645e5..3fea137c7b6 100644 --- a/app/lib/l10n/app_et.arb +++ b/app/lib/l10n/app_et.arb @@ -4080,5 +4080,37 @@ "chatAppsAtCapacity": "{app} on täis", "chatAppsJoinWaitlist": "Liitu ootenimekirjaga", "chatAppsOnWaitlist": "Oled ootenimekirjas", - "chatAppsWaitlistJoined": "Oled ootenimekirjas. Anname teada, kui koht vabaneb." + "chatAppsWaitlistJoined": "Oled ootenimekirjas. Anname teada, kui koht vabaneb.", + "onboardingActCongratsTitle": "Kõik on valmis, {name}", + "onboardingActDemoSummary": "Lõuna Samiga, kes kolib juunis Lissaboni. Tahad reedeks esitluse meeskonnale saata ja koduteel piima osta.", + "onboardingActDemoTask1": "Saata esitlus reedeks tiimile", + "onboardingActDemoTask2": "Osta koduteel piima", + "onboardingActDemoTitle": "Lõuna Samiga", + "onboardingActDictateBody": "Ütle omal moel. Ülejäänu teeb Omi.", + "onboardingActDictateBodyOmi": "Lihtsalt räägi. Sinu Omi kuulab.", + "onboardingActDictateExample": "Sõin just Samiga lõunat, ta kolib juunis Lissaboni! Tuleta mulle meelde saata esitlus reedeks tiimile ja osta koduteel piima.", + "onboardingActDictateHint": "Kui oled valmis, puuduta linnukest", + "onboardingActDictateTitle": "Räägi Omile oma päevast", + "onboardingActIntro": "Proovime Omit.", + "onboardingActJustNow": "Just praegu", + "onboardingActMicAllow": "Luba mikrofon", + "onboardingActMicBody": "Et Omi kuuleks sind, kui sa temaga räägid.", + "onboardingActMicFallbackBody": "Omi ei saada veel heli. Kasuta praegu oma telefoni.", + "onboardingActMicTitle": "Omi vajab su mikrofoni", + "onboardingActNoAudioOmi": "Omit pole kuulda. Veendu, et see on sees ja lähedal.", + "onboardingActNoAudioPhone": "Me ei kuule sind veel. Proovi natuke valjemini rääkida.", + "onboardingActNotesLabel": "Sinu päev lühidalt", + "onboardingActPhoneMic": "Telefoni mikrofon", + "onboardingActRateTitle": "Kas Omi meeldib sulle seni?", + "onboardingActRemembers": "Omi mäletab, et sina ei peaks", + "onboardingActSavedToTasks": "Salvestatud sinu ülesannetesse", + "onboardingActTasksLabel": "Omi märkas sinu ülesandeid", + "onboardingActWriting": "Teeme kokkuvõtet…", + "onboardingActYourWords": "Seda sa ütlesid", + "onboardingConnectOmi": "Ühenda Omi", + "onboardingConnectTapToConnect": "Ühendamiseks puuduta oma Omit.", + "onboardingNoOmiYet": "Sul pole veel Omit?", + "onboardingOmiNotOrdered": "Pole veel tellinud", + "onboardingOmiOrdered": "Olen selle tellinud", + "onboardingPermissionTapToFix": "Puuduta parandamiseks" } diff --git a/app/lib/l10n/app_fa.arb b/app/lib/l10n/app_fa.arb index 30128a33e8e..94e0615cd21 100644 --- a/app/lib/l10n/app_fa.arb +++ b/app/lib/l10n/app_fa.arb @@ -11506,5 +11506,37 @@ "chatAppsAtCapacity": "ظرفیت {app} تکمیل است", "chatAppsJoinWaitlist": "پیوستن به فهرست انتظار", "chatAppsOnWaitlist": "شما در فهرست انتظار هستید", - "chatAppsWaitlistJoined": "شما در فهرست انتظار هستید. وقتی جا باز شود خبرتان می‌کنیم." + "chatAppsWaitlistJoined": "شما در فهرست انتظار هستید. وقتی جا باز شود خبرتان می‌کنیم.", + "onboardingActCongratsTitle": "همه چیز آماده است، {name}", + "onboardingActDemoSummary": "ناهار با سم که خرداد به لیسبون می‌رود. می‌خواهید تا جمعه ارائه را برای تیم بفرستید و سر راه خانه شیر بخرید.", + "onboardingActDemoTask1": "فرستادن ارائه برای تیم تا جمعه", + "onboardingActDemoTask2": "خریدن شیر سر راه خانه", + "onboardingActDemoTitle": "ناهار با سم", + "onboardingActDictateBody": "به روش خودتان بگویید. بقیه با Omi.", + "onboardingActDictateBodyOmi": "فقط صحبت کنید. Omi گوش می‌دهد.", + "onboardingActDictateExample": "همین الان با سم ناهار خوردم، خرداد به لیسبون می‌رود! یادم بینداز تا جمعه ارائه را برای تیم بفرستم و سر راه خانه شیر بخرم.", + "onboardingActDictateHint": "وقتی تمام شد، روی تیک بزنید", + "onboardingActDictateTitle": "از روزتان به Omi بگویید", + "onboardingActIntro": "بیایید Omi را امتحان کنیم.", + "onboardingActJustNow": "همین الان", + "onboardingActMicAllow": "اجازهٔ میکروفون", + "onboardingActMicBody": "تا Omi وقتی با او حرف می‌زنید صدایتان را بشنود.", + "onboardingActMicFallbackBody": "Omi هنوز صدا نمی‌فرستد. فعلاً از گوشی خود استفاده کنید.", + "onboardingActMicTitle": "Omi به میکروفون شما نیاز دارد", + "onboardingActNoAudioOmi": "صدای Omi نمی‌آید. مطمئن شوید روشن و نزدیک است.", + "onboardingActNoAudioPhone": "هنوز صدایتان نمی‌آید. کمی بلندتر صحبت کنید.", + "onboardingActNotesLabel": "روز شما، خلاصه‌شده", + "onboardingActPhoneMic": "میکروفون گوشی", + "onboardingActRateTitle": "تا اینجا از Omi راضی هستید؟", + "onboardingActRemembers": "Omi به خاطر می‌سپارد تا شما مجبور نباشید", + "onboardingActSavedToTasks": "در کارهای شما ذخیره شد", + "onboardingActTasksLabel": "Omi کارهای شما را پیدا کرد", + "onboardingActWriting": "در حال خلاصه‌سازی…", + "onboardingActYourWords": "این چیزی است که گفتید", + "onboardingConnectOmi": "اتصال Omi", + "onboardingConnectTapToConnect": "برای اتصال روی Omi خود ضربه بزنید.", + "onboardingNoOmiYet": "هنوز Omi ندارید؟", + "onboardingOmiNotOrdered": "هنوز سفارش نداده‌ام", + "onboardingOmiOrdered": "سفارش داده‌ام", + "onboardingPermissionTapToFix": "برای رفع ضربه بزنید" } diff --git a/app/lib/l10n/app_fi.arb b/app/lib/l10n/app_fi.arb index 4f72fa54b2f..2c20dd08297 100644 --- a/app/lib/l10n/app_fi.arb +++ b/app/lib/l10n/app_fi.arb @@ -4080,5 +4080,37 @@ "chatAppsAtCapacity": "{app} on täynnä", "chatAppsJoinWaitlist": "Liity jonotuslistalle", "chatAppsOnWaitlist": "Olet jonotuslistalla", - "chatAppsWaitlistJoined": "Olet jonotuslistalla. Kerromme, kun tilaa vapautuu." + "chatAppsWaitlistJoined": "Olet jonotuslistalla. Kerromme, kun tilaa vapautuu.", + "onboardingActCongratsTitle": "Kaikki valmista, {name}", + "onboardingActDemoSummary": "Lounas Samin kanssa, joka muuttaa kesäkuussa Lissaboniin. Haluat lähettää esityksen tiimille perjantaihin mennessä ja ostaa maitoa kotimatkalla.", + "onboardingActDemoTask1": "Lähetä esitys tiimille perjantaihin mennessä", + "onboardingActDemoTask2": "Hae maitoa kotimatkalla", + "onboardingActDemoTitle": "Lounas Samin kanssa", + "onboardingActDictateBody": "Sano omin sanoin. Omi hoitaa loput.", + "onboardingActDictateBodyOmi": "Puhu vain. Omisi kuuntelee.", + "onboardingActDictateExample": "Söin juuri lounasta Samin kanssa, hän muuttaa kesäkuussa Lissaboniin! Muistuta minua lähettämään esitys tiimille perjantaihin mennessä ja hakemaan maitoa kotimatkalla.", + "onboardingActDictateHint": "Napauta valintamerkkiä, kun olet valmis", + "onboardingActDictateTitle": "Kerro Omille päivästäsi", + "onboardingActIntro": "Kokeillaan Omia.", + "onboardingActJustNow": "Juuri nyt", + "onboardingActMicAllow": "Salli mikrofoni", + "onboardingActMicBody": "Jotta Omi kuulee sinut, kun puhut sille.", + "onboardingActMicFallbackBody": "Omi ei vielä lähetä ääntä. Käytä toistaiseksi puhelinta.", + "onboardingActMicTitle": "Omi tarvitsee mikrofonisi", + "onboardingActNoAudioOmi": "Omia ei kuulu. Varmista, että se on päällä ja lähellä.", + "onboardingActNoAudioPhone": "Emme kuule sinua vielä. Kokeile puhua vähän kovempaa.", + "onboardingActNotesLabel": "Päiväsi tiivistettynä", + "onboardingActPhoneMic": "Puhelimen mikrofoni", + "onboardingActRateTitle": "Pidätkö Omista tähän mennessä?", + "onboardingActRemembers": "Omi muistaa, ettei sinun tarvitse", + "onboardingActSavedToTasks": "Tallennettu tehtäviisi", + "onboardingActTasksLabel": "Omi poimi tehtäväsi", + "onboardingActWriting": "Tiivistetään…", + "onboardingActYourWords": "Tämän sanoit", + "onboardingConnectOmi": "Yhdistä Omi", + "onboardingConnectTapToConnect": "Yhdistä napauttamalla Omiasi.", + "onboardingNoOmiYet": "Eikö Omi ole vielä saapunut?", + "onboardingOmiNotOrdered": "En ole vielä tilannut", + "onboardingOmiOrdered": "Olen tilannut sen", + "onboardingPermissionTapToFix": "Korjaa napauttamalla" } diff --git a/app/lib/l10n/app_fr.arb b/app/lib/l10n/app_fr.arb index 0b409639237..6007ba99dbd 100644 --- a/app/lib/l10n/app_fr.arb +++ b/app/lib/l10n/app_fr.arb @@ -4115,5 +4115,37 @@ "chatAppsAtCapacity": "{app} est complet", "chatAppsJoinWaitlist": "Rejoindre la liste d'attente", "chatAppsOnWaitlist": "Vous êtes sur la liste d'attente", - "chatAppsWaitlistJoined": "Vous êtes sur la liste d'attente. Nous vous préviendrons dès qu'une place se libère." + "chatAppsWaitlistJoined": "Vous êtes sur la liste d'attente. Nous vous préviendrons dès qu'une place se libère.", + "onboardingActCongratsTitle": "Tout est prêt, {name}", + "onboardingActDemoSummary": "Déjeuner avec Sam, qui part vivre à Lisbonne en juin. Vous voulez envoyer la présentation à l'équipe d'ici vendredi et prendre du lait en rentrant.", + "onboardingActDemoTask1": "Envoyer la présentation à l'équipe d'ici vendredi", + "onboardingActDemoTask2": "Prendre du lait en rentrant", + "onboardingActDemoTitle": "Déjeuner avec Sam", + "onboardingActDictateBody": "Dites-le à votre façon. Omi s'occupe du reste.", + "onboardingActDictateBodyOmi": "Parlez simplement. Votre Omi vous écoute.", + "onboardingActDictateExample": "Je viens de déjeuner avec Sam, elle déménage à Lisbonne en juin ! Rappelle-moi d'envoyer la présentation à l'équipe d'ici vendredi et de prendre du lait en rentrant.", + "onboardingActDictateHint": "Touchez la coche quand vous avez fini", + "onboardingActDictateTitle": "Racontez votre journée à Omi", + "onboardingActIntro": "Essayons Omi.", + "onboardingActJustNow": "À l'instant", + "onboardingActMicAllow": "Autoriser le micro", + "onboardingActMicBody": "Pour qu'Omi vous entende quand vous lui parlez.", + "onboardingActMicFallbackBody": "Votre Omi n'envoie pas encore de son. Utilisez votre téléphone pour l'instant.", + "onboardingActMicTitle": "Omi a besoin de votre micro", + "onboardingActNoAudioOmi": "On n'entend pas votre Omi. Vérifiez qu'il est allumé et proche.", + "onboardingActNoAudioPhone": "On ne vous entend pas encore. Parlez un peu plus fort.", + "onboardingActNotesLabel": "Votre journée, résumée", + "onboardingActPhoneMic": "Micro du téléphone", + "onboardingActRateTitle": "Omi vous plaît pour l'instant ?", + "onboardingActRemembers": "Omi s'en souvient pour vous", + "onboardingActSavedToTasks": "Enregistré dans vos tâches", + "onboardingActTasksLabel": "Omi a repéré vos tâches", + "onboardingActWriting": "Résumé en cours…", + "onboardingActYourWords": "Voici ce que vous avez dit", + "onboardingConnectOmi": "Connecter Omi", + "onboardingConnectTapToConnect": "Touchez votre Omi pour le connecter.", + "onboardingNoOmiYet": "Vous n'avez pas encore votre Omi ?", + "onboardingOmiNotOrdered": "Pas encore commandé", + "onboardingOmiOrdered": "Je l'ai commandé", + "onboardingPermissionTapToFix": "Touchez pour corriger" } diff --git a/app/lib/l10n/app_he.arb b/app/lib/l10n/app_he.arb index 395d964f2d6..1fa6201db9e 100644 --- a/app/lib/l10n/app_he.arb +++ b/app/lib/l10n/app_he.arb @@ -11506,5 +11506,37 @@ "chatAppsAtCapacity": "{app} מלא כרגע", "chatAppsJoinWaitlist": "הצטרפות לרשימת ההמתנה", "chatAppsOnWaitlist": "את/ה ברשימת ההמתנה", - "chatAppsWaitlistJoined": "את/ה ברשימת ההמתנה. נעדכן כשיתפנה מקום." + "chatAppsWaitlistJoined": "את/ה ברשימת ההמתנה. נעדכן כשיתפנה מקום.", + "onboardingActCongratsTitle": "הכול מוכן, {name}", + "onboardingActDemoSummary": "ארוחת צהריים עם סם, שעוברת ללישבון ביוני. אתה רוצה לשלוח את המצגת לצוות עד יום שישי ולקנות חלב בדרך הביתה.", + "onboardingActDemoTask1": "לשלוח את המצגת לצוות עד שישי", + "onboardingActDemoTask2": "לקנות חלב בדרך הביתה", + "onboardingActDemoTitle": "צהריים עם סאם", + "onboardingActDictateBody": "תגידו את זה בדרך שלכם. Omi ידאג לשאר.", + "onboardingActDictateBodyOmi": "פשוט דבר. ה-Omi שלך מקשיב.", + "onboardingActDictateExample": "הרגע אכלתי צהריים עם סאם, היא עוברת לליסבון ביוני! תזכיר לי לשלוח את המצגת לצוות עד שישי, ולקנות חלב בדרך הביתה.", + "onboardingActDictateHint": "הקישו על ה-V כשתסיימו", + "onboardingActDictateTitle": "ספרו ל-Omi על היום שלכם", + "onboardingActIntro": "בואו ננסה את Omi.", + "onboardingActJustNow": "עכשיו", + "onboardingActMicAllow": "אפשר מיקרופון", + "onboardingActMicBody": "כדי ש-Omi ישמע אתכם כשאתם מדברים אליו.", + "onboardingActMicFallbackBody": "ה-Omi עדיין לא שולח שמע. בינתיים השתמש בטלפון.", + "onboardingActMicTitle": "Omi צריך את המיקרופון", + "onboardingActNoAudioOmi": "לא שומעים את ה-Omi. ודא שהוא דולק וקרוב.", + "onboardingActNoAudioPhone": "עדיין לא שומעים אותך. נסה לדבר קצת יותר חזק.", + "onboardingActNotesLabel": "היום שלך, בתמצית", + "onboardingActPhoneMic": "מיקרופון הטלפון", + "onboardingActRateTitle": "נהנים מ-Omi עד עכשיו?", + "onboardingActRemembers": "Omi זוכר, כדי שאתם לא תצטרכו", + "onboardingActSavedToTasks": "נשמר במשימות שלך", + "onboardingActTasksLabel": "Omi תפס את המשימות שלך", + "onboardingActWriting": "מסכם…", + "onboardingActYourWords": "זה מה שאמרת", + "onboardingConnectOmi": "חיבור Omi", + "onboardingConnectTapToConnect": "הקש על ה-Omi שלך כדי להתחבר.", + "onboardingNoOmiYet": "עדיין אין לכם Omi?", + "onboardingOmiNotOrdered": "עוד לא הזמנתי", + "onboardingOmiOrdered": "הזמנתי", + "onboardingPermissionTapToFix": "הקישו לתיקון" } diff --git a/app/lib/l10n/app_hi.arb b/app/lib/l10n/app_hi.arb index 63974d428c5..b0355618f19 100644 --- a/app/lib/l10n/app_hi.arb +++ b/app/lib/l10n/app_hi.arb @@ -4080,5 +4080,37 @@ "chatAppsAtCapacity": "{app} की क्षमता पूरी हो गई है", "chatAppsJoinWaitlist": "प्रतीक्षा सूची में शामिल हों", "chatAppsOnWaitlist": "आप प्रतीक्षा सूची में हैं", - "chatAppsWaitlistJoined": "आप प्रतीक्षा सूची में हैं। जगह होने पर हम आपको बताएंगे।" + "chatAppsWaitlistJoined": "आप प्रतीक्षा सूची में हैं। जगह होने पर हम आपको बताएंगे।", + "onboardingActCongratsTitle": "सब तैयार है, {name}", + "onboardingActDemoSummary": "सैम के साथ लंच, जो जून में लिस्बन जा रही है। आपको शुक्रवार तक टीम को डेक भेजना है और घर लौटते हुए दूध लेना है।", + "onboardingActDemoTask1": "शुक्रवार तक टीम को डेक भेजना", + "onboardingActDemoTask2": "घर लौटते हुए दूध लेना", + "onboardingActDemoTitle": "सैम के साथ लंच", + "onboardingActDictateBody": "अपने अंदाज़ में कहिए। बाकी Omi संभाल लेगा।", + "onboardingActDictateBodyOmi": "बस बोलिए। आपका Omi सुन रहा है।", + "onboardingActDictateExample": "अभी सैम के साथ लंच किया, वो जून में लिस्बन जा रही है! याद दिलाना कि शुक्रवार तक टीम को डेक भेजना है, और घर लौटते हुए दूध लेना है।", + "onboardingActDictateHint": "हो जाने पर टिक पर टैप करें", + "onboardingActDictateTitle": "Omi को अपने दिन के बारे में बताएँ", + "onboardingActIntro": "चलिए Omi आज़माएं।", + "onboardingActJustNow": "अभी-अभी", + "onboardingActMicAllow": "माइक्रोफ़ोन की अनुमति दें", + "onboardingActMicBody": "ताकि आप बात करें तो Omi सुन सके।", + "onboardingActMicFallbackBody": "आपका Omi अभी ऑडियो नहीं भेज रहा। फ़िलहाल अपना फ़ोन इस्तेमाल करें।", + "onboardingActMicTitle": "Omi को आपका माइक्रोफ़ोन चाहिए", + "onboardingActNoAudioOmi": "आपका Omi सुनाई नहीं दे रहा। देखें कि वह चालू है और पास है।", + "onboardingActNoAudioPhone": "अभी आपकी आवाज़ नहीं आ रही। थोड़ा ज़ोर से बोलकर देखें।", + "onboardingActNotesLabel": "आपका दिन, संक्षेप में", + "onboardingActPhoneMic": "फ़ोन माइक्रोफ़ोन", + "onboardingActRateTitle": "अब तक Omi कैसा लगा?", + "onboardingActRemembers": "Omi याद रखता है, ताकि आपको न रखना पड़े", + "onboardingActSavedToTasks": "आपके कामों में सहेजा गया", + "onboardingActTasksLabel": "Omi ने आपके काम पकड़ लिए", + "onboardingActWriting": "सारांश बन रहा है…", + "onboardingActYourWords": "आपने यह कहा", + "onboardingConnectOmi": "Omi कनेक्ट करें", + "onboardingConnectTapToConnect": "कनेक्ट करने के लिए अपने Omi पर टैप करें।", + "onboardingNoOmiYet": "अभी तक आपका Omi नहीं आया?", + "onboardingOmiNotOrdered": "अभी ऑर्डर नहीं किया", + "onboardingOmiOrdered": "मैंने ऑर्डर कर दिया है", + "onboardingPermissionTapToFix": "ठीक करने के लिए टैप करें" } diff --git a/app/lib/l10n/app_hr.arb b/app/lib/l10n/app_hr.arb index a9e81ad038a..de549fe611f 100644 --- a/app/lib/l10n/app_hr.arb +++ b/app/lib/l10n/app_hr.arb @@ -11506,5 +11506,37 @@ "chatAppsAtCapacity": "{app} je popunjen", "chatAppsJoinWaitlist": "Pridruži se listi čekanja", "chatAppsOnWaitlist": "Na listi čekanja ste", - "chatAppsWaitlistJoined": "Na listi čekanja ste. Javit ćemo vam kad bude mjesta." + "chatAppsWaitlistJoined": "Na listi čekanja ste. Javit ćemo vam kad bude mjesta.", + "onboardingActCongratsTitle": "Sve je spremno, {name}", + "onboardingActDemoSummary": "Ručak sa Sam, koja se u lipnju seli u Lisabon. Želite poslati prezentaciju timu do petka i kupiti mlijeko na putu kući.", + "onboardingActDemoTask1": "Poslati prezentaciju timu do petka", + "onboardingActDemoTask2": "Kupiti mlijeko na putu kući", + "onboardingActDemoTitle": "Ručak sa Sam", + "onboardingActDictateBody": "Recite to na svoj način. Omi se brine za ostalo.", + "onboardingActDictateBodyOmi": "Samo pričajte. Vaš Omi sluša.", + "onboardingActDictateExample": "Upravo sam ručao sa Sam, u lipnju se seli u Lisabon! Podsjeti me da pošaljem prezentaciju timu do petka i da kupim mlijeko na putu kući.", + "onboardingActDictateHint": "Dodirnite kvačicu kad završite", + "onboardingActDictateTitle": "Ispričajte Omiju svoj dan", + "onboardingActIntro": "Isprobajmo Omi.", + "onboardingActJustNow": "Upravo sada", + "onboardingActMicAllow": "Dopusti mikrofon", + "onboardingActMicBody": "Da vas Omi čuje kad mu se obratite.", + "onboardingActMicFallbackBody": "Omi još ne šalje zvuk. Za sada koristite telefon.", + "onboardingActMicTitle": "Omi treba vaš mikrofon", + "onboardingActNoAudioOmi": "Ne čujemo Omi. Provjerite je li uključen i blizu.", + "onboardingActNoAudioPhone": "Još vas ne čujemo. Pokušajte govoriti malo glasnije.", + "onboardingActNotesLabel": "Vaš dan, ukratko", + "onboardingActPhoneMic": "Mikrofon telefona", + "onboardingActRateTitle": "Sviđa li vam se Omi zasad?", + "onboardingActRemembers": "Omi pamti da vi ne morate", + "onboardingActSavedToTasks": "Spremljeno u vaše zadatke", + "onboardingActTasksLabel": "Omi je uhvatio vaše zadatke", + "onboardingActWriting": "Sažimamo…", + "onboardingActYourWords": "Evo što ste rekli", + "onboardingConnectOmi": "Povežite Omi", + "onboardingConnectTapToConnect": "Dodirnite svoj Omi da biste se povezali.", + "onboardingNoOmiYet": "Još nemate Omi?", + "onboardingOmiNotOrdered": "Još nisam naručio/la", + "onboardingOmiOrdered": "Naručio/la sam ga", + "onboardingPermissionTapToFix": "Dodirnite za popravak" } diff --git a/app/lib/l10n/app_hu.arb b/app/lib/l10n/app_hu.arb index 2a24d8607a2..bbe2aecc5fb 100644 --- a/app/lib/l10n/app_hu.arb +++ b/app/lib/l10n/app_hu.arb @@ -4176,5 +4176,37 @@ "chatAppsAtCapacity": "A(z) {app} megtelt", "chatAppsJoinWaitlist": "Feliratkozás a várólistára", "chatAppsOnWaitlist": "Fent vagy a várólistán", - "chatAppsWaitlistJoined": "Fent vagy a várólistán. Szólunk, ha lesz hely." + "chatAppsWaitlistJoined": "Fent vagy a várólistán. Szólunk, ha lesz hely.", + "onboardingActCongratsTitle": "Minden kész, {name}", + "onboardingActDemoSummary": "Ebéd Sammel, aki júniusban Lisszabonba költözik. Péntekig el akarod küldeni a prezentációt a csapatnak, és hazafelé tejet venni.", + "onboardingActDemoTask1": "Péntekig elküldeni a prezentációt a csapatnak", + "onboardingActDemoTask2": "Hazafelé tejet venni", + "onboardingActDemoTitle": "Ebéd Sammel", + "onboardingActDictateBody": "Mondd a magad módján. A többit az Omi intézi.", + "onboardingActDictateBodyOmi": "Csak beszélj. Az Omid figyel.", + "onboardingActDictateExample": "Most ebédeltem Sammel, júniusban Lisszabonba költözik! Emlékeztess, hogy péntekig küldjem el a prezentációt a csapatnak, és hazafelé vegyek tejet.", + "onboardingActDictateHint": "Ha végeztél, koppints a pipára", + "onboardingActDictateTitle": "Meséld el az Ominak a napod", + "onboardingActIntro": "Próbáljuk ki az Omit.", + "onboardingActJustNow": "Épp most", + "onboardingActMicAllow": "Mikrofon engedélyezése", + "onboardingActMicBody": "Hogy az Omi halljon, amikor beszélsz hozzá.", + "onboardingActMicFallbackBody": "Az Omi még nem küld hangot. Egyelőre használd a telefonodat.", + "onboardingActMicTitle": "Az Ominak kell a mikrofonod", + "onboardingActNoAudioOmi": "Nem halljuk az Omit. Ellenőrizd, hogy be van-e kapcsolva és közel van-e.", + "onboardingActNoAudioPhone": "Még nem hallunk. Próbálj kicsit hangosabban beszélni.", + "onboardingActNotesLabel": "A napod röviden", + "onboardingActPhoneMic": "Telefon mikrofonja", + "onboardingActRateTitle": "Tetszik eddig az Omi?", + "onboardingActRemembers": "Az Omi emlékszik helyetted", + "onboardingActSavedToTasks": "Mentve a teendőid közé", + "onboardingActTasksLabel": "Az Omi kiszúrta a teendőidet", + "onboardingActWriting": "Összefoglalás…", + "onboardingActYourWords": "Ezt mondtad", + "onboardingConnectOmi": "Omi csatlakoztatása", + "onboardingConnectTapToConnect": "Koppints az Omidra a csatlakozáshoz.", + "onboardingNoOmiYet": "Még nincs meg az Omid?", + "onboardingOmiNotOrdered": "Még nem rendeltem", + "onboardingOmiOrdered": "Már megrendeltem", + "onboardingPermissionTapToFix": "Koppints a javításhoz" } diff --git a/app/lib/l10n/app_id.arb b/app/lib/l10n/app_id.arb index 3c3932886a8..a04a763518c 100644 --- a/app/lib/l10n/app_id.arb +++ b/app/lib/l10n/app_id.arb @@ -4115,5 +4115,37 @@ "chatAppsAtCapacity": "{app} sudah penuh", "chatAppsJoinWaitlist": "Gabung daftar tunggu", "chatAppsOnWaitlist": "Anda ada di daftar tunggu", - "chatAppsWaitlistJoined": "Anda ada di daftar tunggu. Kami akan memberi tahu saat ada tempat." + "chatAppsWaitlistJoined": "Anda ada di daftar tunggu. Kami akan memberi tahu saat ada tempat.", + "onboardingActCongratsTitle": "Semua siap, {name}", + "onboardingActDemoSummary": "Makan siang dengan Sam, yang pindah ke Lisbon bulan Juni. Kamu mau kirim deck ke tim sebelum Jumat dan beli susu di jalan pulang.", + "onboardingActDemoTask1": "Kirim deck ke tim sebelum Jumat", + "onboardingActDemoTask2": "Beli susu di jalan pulang", + "onboardingActDemoTitle": "Makan siang dengan Sam", + "onboardingActDictateBody": "Katakan dengan gaya Anda. Omi urus sisanya.", + "onboardingActDictateBodyOmi": "Bicara saja. Omi kamu sedang mendengarkan.", + "onboardingActDictateExample": "Baru makan siang sama Sam, dia pindah ke Lisbon bulan Juni! Ingatkan aku kirim deck ke tim sebelum Jumat, dan beli susu di jalan pulang.", + "onboardingActDictateHint": "Ketuk centang jika sudah selesai", + "onboardingActDictateTitle": "Ceritakan hari Anda ke Omi", + "onboardingActIntro": "Ayo coba Omi.", + "onboardingActJustNow": "Baru saja", + "onboardingActMicAllow": "Izinkan Mikrofon", + "onboardingActMicBody": "Agar Omi bisa mendengar saat Anda bicara.", + "onboardingActMicFallbackBody": "Omi kamu belum mengirim audio. Pakai ponselmu dulu.", + "onboardingActMicTitle": "Omi butuh mikrofon Anda", + "onboardingActNoAudioOmi": "Omi kamu tidak terdengar. Pastikan menyala dan dekat.", + "onboardingActNoAudioPhone": "Suaramu belum terdengar. Coba bicara sedikit lebih keras.", + "onboardingActNotesLabel": "Harimu, dirangkum", + "onboardingActPhoneMic": "Mikrofon ponsel", + "onboardingActRateTitle": "Suka Omi sejauh ini?", + "onboardingActRemembers": "Omi mengingat, jadi kamu tidak perlu", + "onboardingActSavedToTasks": "Disimpan ke tugasmu", + "onboardingActTasksLabel": "Omi menangkap tugasmu", + "onboardingActWriting": "Merangkum…", + "onboardingActYourWords": "Ini yang Anda katakan", + "onboardingConnectOmi": "Hubungkan Omi", + "onboardingConnectTapToConnect": "Ketuk Omi kamu untuk menghubungkan.", + "onboardingNoOmiYet": "Belum punya Omi?", + "onboardingOmiNotOrdered": "Belum memesan", + "onboardingOmiOrdered": "Saya sudah memesannya", + "onboardingPermissionTapToFix": "Ketuk untuk memperbaiki" } diff --git a/app/lib/l10n/app_it.arb b/app/lib/l10n/app_it.arb index 1c7f290cd32..cffa76417f4 100644 --- a/app/lib/l10n/app_it.arb +++ b/app/lib/l10n/app_it.arb @@ -4080,5 +4080,37 @@ "chatAppsAtCapacity": "{app} è al completo", "chatAppsJoinWaitlist": "Unisciti alla lista d'attesa", "chatAppsOnWaitlist": "Sei in lista d'attesa", - "chatAppsWaitlistJoined": "Sei in lista d'attesa. Ti avviseremo quando si libera un posto." + "chatAppsWaitlistJoined": "Sei in lista d'attesa. Ti avviseremo quando si libera un posto.", + "onboardingActCongratsTitle": "Tutto pronto, {name}", + "onboardingActDemoSummary": "Pranzo con Sam, che a giugno si trasferisce a Lisbona. Vuoi mandare la presentazione al team entro venerdì e prendere il latte tornando a casa.", + "onboardingActDemoTask1": "Mandare la presentazione al team entro venerdì", + "onboardingActDemoTask2": "Prendere il latte tornando a casa", + "onboardingActDemoTitle": "Pranzo con Sam", + "onboardingActDictateBody": "Dillo a modo tuo. Al resto pensa Omi.", + "onboardingActDictateBodyOmi": "Parla e basta. Il tuo Omi ti ascolta.", + "onboardingActDictateExample": "Ho appena pranzato con Sam, a giugno si trasferisce a Lisbona! Ricordami di mandare la presentazione al team entro venerdì e di prendere il latte tornando a casa.", + "onboardingActDictateHint": "Tocca la spunta quando hai finito", + "onboardingActDictateTitle": "Racconta a Omi la tua giornata", + "onboardingActIntro": "Proviamo Omi.", + "onboardingActJustNow": "Proprio ora", + "onboardingActMicAllow": "Consenti microfono", + "onboardingActMicBody": "Così Omi ti sente quando gli parli.", + "onboardingActMicFallbackBody": "Il tuo Omi non invia ancora audio. Per ora usa il telefono.", + "onboardingActMicTitle": "Omi ha bisogno del microfono", + "onboardingActNoAudioOmi": "Non sentiamo il tuo Omi. Controlla che sia acceso e vicino.", + "onboardingActNoAudioPhone": "Non ti sentiamo ancora. Prova a parlare un po' più forte.", + "onboardingActNotesLabel": "La tua giornata, in sintesi", + "onboardingActPhoneMic": "Microfono del telefono", + "onboardingActRateTitle": "Ti piace Omi finora?", + "onboardingActRemembers": "Omi ricorda al posto tuo", + "onboardingActSavedToTasks": "Salvato nelle tue attività", + "onboardingActTasksLabel": "Omi ha colto le tue cose da fare", + "onboardingActWriting": "Riassumendo…", + "onboardingActYourWords": "Ecco cosa hai detto", + "onboardingConnectOmi": "Connetti Omi", + "onboardingConnectTapToConnect": "Tocca il tuo Omi per collegarlo.", + "onboardingNoOmiYet": "Non hai ancora il tuo Omi?", + "onboardingOmiNotOrdered": "Non ancora ordinato", + "onboardingOmiOrdered": "L'ho ordinato", + "onboardingPermissionTapToFix": "Tocca per risolvere" } diff --git a/app/lib/l10n/app_ja.arb b/app/lib/l10n/app_ja.arb index 65b3f5b6671..edd6402907b 100644 --- a/app/lib/l10n/app_ja.arb +++ b/app/lib/l10n/app_ja.arb @@ -4080,5 +4080,37 @@ "chatAppsAtCapacity": "{app}は定員に達しています", "chatAppsJoinWaitlist": "ウェイトリストに登録", "chatAppsOnWaitlist": "ウェイトリストに登録済みです", - "chatAppsWaitlistJoined": "ウェイトリストに登録しました。空きが出たらお知らせします。" + "chatAppsWaitlistJoined": "ウェイトリストに登録しました。空きが出たらお知らせします。", + "onboardingActCongratsTitle": "準備完了です、{name}さん", + "onboardingActDemoSummary": "6月にリスボンへ引っ越すサムとランチ。金曜までにチームへ資料を送り、帰りに牛乳を買う予定。", + "onboardingActDemoTask1": "金曜までにチームへ資料を送る", + "onboardingActDemoTask2": "帰りに牛乳を買う", + "onboardingActDemoTitle": "サムとランチ", + "onboardingActDictateBody": "思うままに話してください。あとはOmiにお任せ。", + "onboardingActDictateBodyOmi": "話すだけでOK。Omiが聞いています。", + "onboardingActDictateExample": "さっきサムとランチしたよ。6月にリスボンへ引っ越すんだって!金曜までにチームへ資料を送るのと、帰りに牛乳を買うのをリマインドして。", + "onboardingActDictateHint": "話し終えたらチェックをタップ", + "onboardingActDictateTitle": "今日のことをOmiに話そう", + "onboardingActIntro": "Omiを試してみましょう。", + "onboardingActJustNow": "たった今", + "onboardingActMicAllow": "マイクを許可", + "onboardingActMicBody": "話しかけたときにOmiが聞き取れるようにします。", + "onboardingActMicFallbackBody": "Omiからまだ音声が届いていません。今はスマートフォンを使いましょう。", + "onboardingActMicTitle": "Omiにマイクの許可が必要です", + "onboardingActNoAudioOmi": "Omiの音声が聞こえません。電源が入っていて近くにあるか確認してください。", + "onboardingActNoAudioPhone": "まだ声が聞こえません。少し大きな声で話してみてください。", + "onboardingActNotesLabel": "今日のまとめ", + "onboardingActPhoneMic": "スマホのマイク", + "onboardingActRateTitle": "Omiはいかがですか?", + "onboardingActRemembers": "覚えておくのは Omi にお任せ", + "onboardingActSavedToTasks": "タスクに保存しました", + "onboardingActTasksLabel": "Omiがやることを見つけました", + "onboardingActWriting": "要約しています…", + "onboardingActYourWords": "あなたが話した内容", + "onboardingConnectOmi": "Omiを接続", + "onboardingConnectTapToConnect": "Omiをタップして接続します。", + "onboardingNoOmiYet": "まだOmiをお持ちでないですか?", + "onboardingOmiNotOrdered": "まだ注文していません", + "onboardingOmiOrdered": "注文済みです", + "onboardingPermissionTapToFix": "タップして修正" } diff --git a/app/lib/l10n/app_kn.arb b/app/lib/l10n/app_kn.arb index b4d0388a737..1fae85ef7e1 100644 --- a/app/lib/l10n/app_kn.arb +++ b/app/lib/l10n/app_kn.arb @@ -11506,5 +11506,37 @@ "chatAppsAtCapacity": "{app} ಸಾಮರ್ಥ್ಯ ತುಂಬಿದೆ", "chatAppsJoinWaitlist": "ಕಾಯುವ ಪಟ್ಟಿಗೆ ಸೇರಿ", "chatAppsOnWaitlist": "ನೀವು ಕಾಯುವ ಪಟ್ಟಿಯಲ್ಲಿದ್ದೀರಿ", - "chatAppsWaitlistJoined": "ನೀವು ಕಾಯುವ ಪಟ್ಟಿಯಲ್ಲಿದ್ದೀರಿ. ಸ್ಥಳ ಲಭ್ಯವಾದಾಗ ತಿಳಿಸುತ್ತೇವೆ." + "chatAppsWaitlistJoined": "ನೀವು ಕಾಯುವ ಪಟ್ಟಿಯಲ್ಲಿದ್ದೀರಿ. ಸ್ಥಳ ಲಭ್ಯವಾದಾಗ ತಿಳಿಸುತ್ತೇವೆ.", + "onboardingActCongratsTitle": "ಎಲ್ಲವೂ ಸಿದ್ಧ, {name}", + "onboardingActDemoSummary": "ಜೂನ್‌ನಲ್ಲಿ ಲಿಸ್ಬನ್‌ಗೆ ಹೋಗುತ್ತಿರುವ ಸ್ಯಾಮ್ ಜೊತೆ ಊಟ. ಶುಕ್ರವಾರದೊಳಗೆ ತಂಡಕ್ಕೆ ಡೆಕ್ ಕಳುಹಿಸಬೇಕು, ಮನೆಗೆ ಹೋಗುವಾಗ ಹಾಲು ತರಬೇಕು.", + "onboardingActDemoTask1": "ಶುಕ್ರವಾರದೊಳಗೆ ತಂಡಕ್ಕೆ ಡೆಕ್ ಕಳುಹಿಸುವುದು", + "onboardingActDemoTask2": "ಮನೆಗೆ ಹೋಗುವಾಗ ಹಾಲು ತರುವುದು", + "onboardingActDemoTitle": "ಸ್ಯಾಮ್ ಜೊತೆ ಊಟ", + "onboardingActDictateBody": "ನಿಮ್ಮದೇ ರೀತಿಯಲ್ಲಿ ಹೇಳಿ. ಉಳಿದದ್ದು Omi ನೋಡಿಕೊಳ್ಳುತ್ತದೆ.", + "onboardingActDictateBodyOmi": "ಸುಮ್ಮನೆ ಮಾತಾಡಿ. ನಿಮ್ಮ Omi ಕೇಳುತ್ತಿದೆ.", + "onboardingActDictateExample": "ಈಗಷ್ಟೇ ಸ್ಯಾಮ್ ಜೊತೆ ಊಟ ಮಾಡಿದೆ, ಅವಳು ಜೂನ್‌ನಲ್ಲಿ ಲಿಸ್ಬನ್‌ಗೆ ಹೋಗುತ್ತಿದ್ದಾಳೆ! ಶುಕ್ರವಾರದೊಳಗೆ ತಂಡಕ್ಕೆ ಡೆಕ್ ಕಳುಹಿಸಲು ಮತ್ತು ಮನೆಗೆ ಹೋಗುವಾಗ ಹಾಲು ತರಲು ನೆನಪಿಸು.", + "onboardingActDictateHint": "ಮುಗಿದ ನಂತರ ಟಿಕ್ ಟ್ಯಾಪ್ ಮಾಡಿ", + "onboardingActDictateTitle": "ನಿಮ್ಮ ದಿನದ ಬಗ್ಗೆ Omi ಗೆ ಹೇಳಿ", + "onboardingActIntro": "Omi ಪ್ರಯತ್ನಿಸೋಣ.", + "onboardingActJustNow": "ಈಗಷ್ಟೇ", + "onboardingActMicAllow": "ಮೈಕ್ರೊಫೋನ್ ಅನುಮತಿಸಿ", + "onboardingActMicBody": "ನೀವು ಮಾತನಾಡಿದಾಗ Omi ಕೇಳಲು.", + "onboardingActMicFallbackBody": "ನಿಮ್ಮ Omi ಇನ್ನೂ ಆಡಿಯೋ ಕಳುಹಿಸುತ್ತಿಲ್ಲ. ಸದ್ಯಕ್ಕೆ ನಿಮ್ಮ ಫೋನ್ ಬಳಸಿ.", + "onboardingActMicTitle": "Omi ಗೆ ನಿಮ್ಮ ಮೈಕ್ರೊಫೋನ್ ಬೇಕು", + "onboardingActNoAudioOmi": "ನಿಮ್ಮ Omi ಕೇಳಿಸುತ್ತಿಲ್ಲ. ಅದು ಆನ್ ಆಗಿದೆ ಮತ್ತು ಹತ್ತಿರವಿದೆಯೇ ನೋಡಿ.", + "onboardingActNoAudioPhone": "ಇನ್ನೂ ನಿಮ್ಮ ಧ್ವನಿ ಕೇಳಿಸುತ್ತಿಲ್ಲ. ಸ್ವಲ್ಪ ಜೋರಾಗಿ ಮಾತಾಡಿ ನೋಡಿ.", + "onboardingActNotesLabel": "ನಿಮ್ಮ ದಿನದ ಸಾರಾಂಶ", + "onboardingActPhoneMic": "ಫೋನ್ ಮೈಕ್ರೊಫೋನ್", + "onboardingActRateTitle": "ಇಲ್ಲಿಯವರೆಗೆ Omi ಇಷ್ಟವಾಯಿತೇ?", + "onboardingActRemembers": "ನೀವು ನೆನಪಿಡಬೇಕಿಲ್ಲ, Omi ನೆನಪಿಡುತ್ತದೆ", + "onboardingActSavedToTasks": "ನಿಮ್ಮ ಕೆಲಸಗಳಿಗೆ ಉಳಿಸಲಾಗಿದೆ", + "onboardingActTasksLabel": "Omi ನಿಮ್ಮ ಕೆಲಸಗಳನ್ನು ಗುರುತಿಸಿದೆ", + "onboardingActWriting": "ಸಾರಾಂಶ ಮಾಡುತ್ತಿದೆ…", + "onboardingActYourWords": "ನೀವು ಹೇಳಿದ್ದು ಇದು", + "onboardingConnectOmi": "Omi ಸಂಪರ್ಕಿಸಿ", + "onboardingConnectTapToConnect": "ಸಂಪರ್ಕಿಸಲು ನಿಮ್ಮ Omi ಟ್ಯಾಪ್ ಮಾಡಿ.", + "onboardingNoOmiYet": "ಇನ್ನೂ ನಿಮ್ಮ Omi ಇಲ್ಲವೇ?", + "onboardingOmiNotOrdered": "ಇನ್ನೂ ಆರ್ಡರ್ ಮಾಡಿಲ್ಲ", + "onboardingOmiOrdered": "ನಾನು ಆರ್ಡರ್ ಮಾಡಿದ್ದೇನೆ", + "onboardingPermissionTapToFix": "ಸರಿಪಡಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ" } diff --git a/app/lib/l10n/app_ko.arb b/app/lib/l10n/app_ko.arb index 6d4324ec83c..139279aa792 100644 --- a/app/lib/l10n/app_ko.arb +++ b/app/lib/l10n/app_ko.arb @@ -4080,5 +4080,37 @@ "chatAppsAtCapacity": "{app} 정원이 찼습니다", "chatAppsJoinWaitlist": "대기자 명단 등록", "chatAppsOnWaitlist": "대기자 명단에 등록되었습니다", - "chatAppsWaitlistJoined": "대기자 명단에 등록되었습니다. 자리가 나면 알려 드릴게요." + "chatAppsWaitlistJoined": "대기자 명단에 등록되었습니다. 자리가 나면 알려 드릴게요.", + "onboardingActCongratsTitle": "{name}님, 준비가 끝났어요", + "onboardingActDemoSummary": "6월에 리스본으로 이사하는 샘과 점심. 금요일까지 팀에 자료를 보내고, 집에 가는 길에 우유를 사야 해요.", + "onboardingActDemoTask1": "금요일까지 팀에 발표 자료 보내기", + "onboardingActDemoTask2": "집에 가는 길에 우유 사기", + "onboardingActDemoTitle": "샘과 점심", + "onboardingActDictateBody": "편하게 말하세요. 나머지는 Omi가 할게요.", + "onboardingActDictateBodyOmi": "그냥 말하세요. Omi가 듣고 있어요.", + "onboardingActDictateExample": "방금 샘이랑 점심 먹었어. 6월에 리스본으로 이사 간대! 금요일까지 팀에 발표 자료 보내는 거랑, 집에 가는 길에 우유 사는 거 알려 줘.", + "onboardingActDictateHint": "다 말했으면 체크를 누르세요", + "onboardingActDictateTitle": "Omi에게 오늘 하루를 말해 보세요", + "onboardingActIntro": "Omi를 써 볼까요?", + "onboardingActJustNow": "방금", + "onboardingActMicAllow": "마이크 허용", + "onboardingActMicBody": "말할 때 Omi가 들을 수 있도록요.", + "onboardingActMicFallbackBody": "Omi가 아직 오디오를 보내지 않아요. 지금은 휴대폰을 사용하세요.", + "onboardingActMicTitle": "Omi에 마이크 권한이 필요해요", + "onboardingActNoAudioOmi": "Omi 소리가 들리지 않아요. 켜져 있고 가까이 있는지 확인하세요.", + "onboardingActNoAudioPhone": "아직 목소리가 들리지 않아요. 조금 더 크게 말해 보세요.", + "onboardingActNotesLabel": "오늘 하루 요약", + "onboardingActPhoneMic": "휴대폰 마이크", + "onboardingActRateTitle": "Omi 마음에 드시나요?", + "onboardingActRemembers": "기억은 Omi가 대신해요", + "onboardingActSavedToTasks": "할 일에 저장됨", + "onboardingActTasksLabel": "Omi가 할 일을 찾았어요", + "onboardingActWriting": "요약하는 중…", + "onboardingActYourWords": "이렇게 말했어요", + "onboardingConnectOmi": "Omi 연결", + "onboardingConnectTapToConnect": "Omi를 탭해서 연결하세요.", + "onboardingNoOmiYet": "아직 Omi가 없으신가요?", + "onboardingOmiNotOrdered": "아직 주문하지 않았어요", + "onboardingOmiOrdered": "주문했어요", + "onboardingPermissionTapToFix": "탭하여 해결" } diff --git a/app/lib/l10n/app_localizations.dart b/app/lib/l10n/app_localizations.dart index e6afee137ae..f8028d18f4d 100644 --- a/app/lib/l10n/app_localizations.dart +++ b/app/lib/l10n/app_localizations.dart @@ -23672,6 +23672,198 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'You\'re on the waitlist. We\'ll let you know when there\'s room.'** String get chatAppsWaitlistJoined; + + /// Activation: congratulations title with the user's first name + /// + /// In en, this message translates to: + /// **'You\'re all set, {name}'** + String onboardingActCongratsTitle(String name); + + /// Activation slide illustration: example summary line + /// + /// In en, this message translates to: + /// **'Lunch with Sam, who\'s moving to Lisbon in June. You want to send the deck to your team by Friday and grab milk on the way home.'** + String get onboardingActDemoSummary; + + /// Activation slide illustration: example task + /// + /// In en, this message translates to: + /// **'Send the deck to the team by Friday'** + String get onboardingActDemoTask1; + + /// Activation slide illustration: example task + /// + /// In en, this message translates to: + /// **'Grab milk on the way home'** + String get onboardingActDemoTask2; + + /// Activation slide illustration: example note title + /// + /// In en, this message translates to: + /// **'Lunch with Sam'** + String get onboardingActDemoTitle; + + /// Activation: line under the dictation title + /// + /// In en, this message translates to: + /// **'Say it your way. Omi handles the rest.'** + String get onboardingActDictateBody; + + /// Onboarding dictation subtitle when the words come from the connected Omi + /// + /// In en, this message translates to: + /// **'Just talk. Your Omi is listening.'** + String get onboardingActDictateBodyOmi; + + /// Activation: faded example in the dictation card (casual tone; keep 'Remind me' so Omi keeps it; Sam and Lisbon are example names) + /// + /// In en, this message translates to: + /// **'Just had lunch with Sam, she\'s moving to Lisbon in June! Remind me to send the deck to my team by Friday, and to grab milk on the way home.'** + String get onboardingActDictateExample; + + /// Activation: hint in the dictation bar next to the check button + /// + /// In en, this message translates to: + /// **'Tap the check when you\'re done'** + String get onboardingActDictateHint; + + /// Activation: dictation step title + /// + /// In en, this message translates to: + /// **'Tell Omi about your day'** + String get onboardingActDictateTitle; + + /// Activation: animated line before the demo slides + /// + /// In en, this message translates to: + /// **'Let\'s try Omi.'** + String get onboardingActIntro; + + /// Time label on the first note Omi wrote in onboarding + /// + /// In en, this message translates to: + /// **'Just now'** + String get onboardingActJustNow; + + /// Activation: button that shows the microphone system prompt + /// + /// In en, this message translates to: + /// **'Allow Microphone'** + String get onboardingActMicAllow; + + /// Activation: why the microphone is needed + /// + /// In en, this message translates to: + /// **'So Omi can hear you when you talk to it.'** + String get onboardingActMicBody; + + /// Onboarding microphone ask when the connected Omi is not streaming + /// + /// In en, this message translates to: + /// **'Your Omi isn\'t sending audio yet. Use your phone for now.'** + String get onboardingActMicFallbackBody; + + /// Activation: microphone pre-permission title (phone users) + /// + /// In en, this message translates to: + /// **'Omi needs your microphone'** + String get onboardingActMicTitle; + + /// Onboarding dictation hint after a while with no words from the Omi + /// + /// In en, this message translates to: + /// **'Can\'t hear your Omi. Make sure it\'s on and close by.'** + String get onboardingActNoAudioOmi; + + /// Onboarding dictation hint after a while with no words from the phone microphone + /// + /// In en, this message translates to: + /// **'Can\'t hear you yet. Try speaking a little louder.'** + String get onboardingActNoAudioPhone; + + /// Activation: small label above the generated summary + /// + /// In en, this message translates to: + /// **'Your day, summarized'** + String get onboardingActNotesLabel; + + /// Activation: which microphone is listening (phone) + /// + /// In en, this message translates to: + /// **'Phone microphone'** + String get onboardingActPhoneMic; + + /// Activation: rating ask on the congratulations screen + /// + /// In en, this message translates to: + /// **'Enjoying Omi so far?'** + String get onboardingActRateTitle; + + /// Line above the summary title at the end of the onboarding dictation + /// + /// In en, this message translates to: + /// **'Omi remembers so you don\'t have to'** + String get onboardingActRemembers; + + /// Confirmation under the to-dos Omi found in onboarding + /// + /// In en, this message translates to: + /// **'Saved to your tasks'** + String get onboardingActSavedToTasks; + + /// Activation: small label above the generated tasks + /// + /// In en, this message translates to: + /// **'Omi caught your to-dos'** + String get onboardingActTasksLabel; + + /// Activation: shown while Omi processes the dictation + /// + /// In en, this message translates to: + /// **'Summarizing…'** + String get onboardingActWriting; + + /// Activation: fallback label when no summary could be made + /// + /// In en, this message translates to: + /// **'Here\'s what you said'** + String get onboardingActYourWords; + + /// Onboarding connect step title; Omi is the brand name of the wearable + /// + /// In en, this message translates to: + /// **'Connect Omi'** + String get onboardingConnectOmi; + + /// Connect Omi onboarding step, under the title once an Omi is found nearby + /// + /// In en, this message translates to: + /// **'Tap your Omi to connect.'** + String get onboardingConnectTapToConnect; + + /// Onboarding connect step: small prompt for people without their Omi device + /// + /// In en, this message translates to: + /// **'Don\'t have your Omi yet?'** + String get onboardingNoOmiYet; + + /// Onboarding connect step: choice for people who have not ordered an Omi device + /// + /// In en, this message translates to: + /// **'Not ordered yet'** + String get onboardingOmiNotOrdered; + + /// Onboarding connect step: choice for people whose Omi device is on its way + /// + /// In en, this message translates to: + /// **'I\'ve ordered mine'** + String get onboardingOmiOrdered; + + /// Onboarding permissions: shown on a denied permission card; tapping opens Settings + /// + /// In en, this message translates to: + /// **'Tap to fix'** + String get onboardingPermissionTapToFix; } class _AppLocalizationsDelegate extends LocalizationsDelegate { diff --git a/app/lib/l10n/app_localizations_ar.dart b/app/lib/l10n/app_localizations_ar.dart index 6b8b93181f1..7dbe1c0088d 100644 --- a/app/lib/l10n/app_localizations_ar.dart +++ b/app/lib/l10n/app_localizations_ar.dart @@ -13219,4 +13219,104 @@ class AppLocalizationsAr extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'أنت في قائمة الانتظار. سنُعلمك عندما يتوفر مكان.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'كل شيء جاهز يا $name'; + } + + @override + String get onboardingActDemoSummary => + 'غداء مع سام التي ستنتقل إلى لشبونة في يونيو. تريد إرسال العرض التقديمي إلى فريقك بحلول الجمعة وشراء الحليب في طريق العودة.'; + + @override + String get onboardingActDemoTask1 => 'إرسال العرض إلى الفريق قبل الجمعة'; + + @override + String get onboardingActDemoTask2 => 'شراء الحليب في طريق العودة'; + + @override + String get onboardingActDemoTitle => 'غداء مع سام'; + + @override + String get onboardingActDictateBody => 'قلها بطريقتك، وOmi يتولى الباقي.'; + + @override + String get onboardingActDictateBodyOmi => 'تحدّث فقط. Omi يستمع.'; + + @override + String get onboardingActDictateExample => + 'تغدّيت للتو مع سام، ستنتقل إلى لشبونة في يونيو! ذكّرني بإرسال العرض إلى فريقي قبل الجمعة، وبشراء الحليب في طريق العودة.'; + + @override + String get onboardingActDictateHint => 'اضغط على علامة الصح عند الانتهاء'; + + @override + String get onboardingActDictateTitle => 'أخبر Omi عن يومك'; + + @override + String get onboardingActIntro => 'لنجرّب Omi.'; + + @override + String get onboardingActJustNow => 'الآن'; + + @override + String get onboardingActMicAllow => 'السماح بالميكروفون'; + + @override + String get onboardingActMicBody => 'ليسمعك Omi عندما تتحدث إليه.'; + + @override + String get onboardingActMicFallbackBody => 'لا يرسل Omi الصوت بعد. استخدم هاتفك الآن.'; + + @override + String get onboardingActMicTitle => 'يحتاج Omi إلى الميكروفون'; + + @override + String get onboardingActNoAudioOmi => 'لا أسمع Omi. تأكد من أنه يعمل وقريب منك.'; + + @override + String get onboardingActNoAudioPhone => 'لا أسمعك بعد. جرّب التحدث بصوت أعلى قليلاً.'; + + @override + String get onboardingActNotesLabel => 'يومك، باختصار'; + + @override + String get onboardingActPhoneMic => 'ميكروفون الهاتف'; + + @override + String get onboardingActRateTitle => 'هل يعجبك Omi حتى الآن؟'; + + @override + String get onboardingActRemembers => 'يتذكّر Omi عنك حتى لا تضطر أنت'; + + @override + String get onboardingActSavedToTasks => 'تم الحفظ في مهامك'; + + @override + String get onboardingActTasksLabel => 'التقط Omi مهامك'; + + @override + String get onboardingActWriting => 'جارٍ التلخيص…'; + + @override + String get onboardingActYourWords => 'هذا ما قلته'; + + @override + String get onboardingConnectOmi => 'اتصل بـ Omi'; + + @override + String get onboardingConnectTapToConnect => 'اضغط على Omi للاتصال.'; + + @override + String get onboardingNoOmiYet => 'ليس لديك Omi بعد؟'; + + @override + String get onboardingOmiNotOrdered => 'لم أطلبه بعد'; + + @override + String get onboardingOmiOrdered => 'لقد طلبته'; + + @override + String get onboardingPermissionTapToFix => 'اضغط للإصلاح'; } diff --git a/app/lib/l10n/app_localizations_be.dart b/app/lib/l10n/app_localizations_be.dart index b14ccd5b499..cf297a5234b 100644 --- a/app/lib/l10n/app_localizations_be.dart +++ b/app/lib/l10n/app_localizations_be.dart @@ -13327,4 +13327,104 @@ class AppLocalizationsBe extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Вы ў спісе чакання. Мы паведамім, калі з\'явіцца месца.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Усё гатова, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Абед з Сэм, якая ў чэрвені пераязджае ў Лісабон. Вы хочаце адправіць прэзентацыю камандзе да пятніцы і купіць малако па дарозе дадому.'; + + @override + String get onboardingActDemoTask1 => 'Адправіць прэзентацыю камандзе да пятніцы'; + + @override + String get onboardingActDemoTask2 => 'Купіць малако па дарозе дадому'; + + @override + String get onboardingActDemoTitle => 'Абед з Сэм'; + + @override + String get onboardingActDictateBody => 'Кажыце па-свойму. Астатняе зробіць Omi.'; + + @override + String get onboardingActDictateBodyOmi => 'Проста гаварыце. Omi слухае.'; + + @override + String get onboardingActDictateExample => + 'Толькі што паабедаў з Сэм, яна пераязджае ў Лісабон у чэрвені! Нагадай мне адправіць прэзентацыю камандзе да пятніцы і купіць малако па дарозе дадому.'; + + @override + String get onboardingActDictateHint => 'Націсніце галачку, калі скончыце'; + + @override + String get onboardingActDictateTitle => 'Раскажыце Omi пра свой дзень'; + + @override + String get onboardingActIntro => 'Паспрабуем Omi.'; + + @override + String get onboardingActJustNow => 'Толькі што'; + + @override + String get onboardingActMicAllow => 'Дазволіць мікрафон'; + + @override + String get onboardingActMicBody => 'Каб Omi чуў вас, калі вы з ім размаўляеце.'; + + @override + String get onboardingActMicFallbackBody => 'Omi яшчэ не перадае гук. Пакуль скарыстайцеся тэлефонам.'; + + @override + String get onboardingActMicTitle => 'Omi патрэбны мікрафон'; + + @override + String get onboardingActNoAudioOmi => 'Не чуваць Omi. Пераканайцеся, што ён уключаны і побач.'; + + @override + String get onboardingActNoAudioPhone => 'Пакуль вас не чуваць. Паспрабуйце гаварыць крыху гучней.'; + + @override + String get onboardingActNotesLabel => 'Ваш дзень коратка'; + + @override + String get onboardingActPhoneMic => 'Мікрафон тэлефона'; + + @override + String get onboardingActRateTitle => 'Як вам Omi?'; + + @override + String get onboardingActRemembers => 'Omi памятае, каб вам не трэба было'; + + @override + String get onboardingActSavedToTasks => 'Захавана ў вашыя справы'; + + @override + String get onboardingActTasksLabel => 'Omi заўважыў вашы справы'; + + @override + String get onboardingActWriting => 'Падсумоўваем…'; + + @override + String get onboardingActYourWords => 'Вось што вы сказалі'; + + @override + String get onboardingConnectOmi => 'Падключыце Omi'; + + @override + String get onboardingConnectTapToConnect => 'Націсніце на Omi, каб падключыць.'; + + @override + String get onboardingNoOmiYet => 'Яшчэ няма Omi?'; + + @override + String get onboardingOmiNotOrdered => 'Яшчэ не замовіў'; + + @override + String get onboardingOmiOrdered => 'Я ўжо замовіў'; + + @override + String get onboardingPermissionTapToFix => 'Націсніце, каб выправіць'; } diff --git a/app/lib/l10n/app_localizations_bg.dart b/app/lib/l10n/app_localizations_bg.dart index 076b6f986ad..88016506674 100644 --- a/app/lib/l10n/app_localizations_bg.dart +++ b/app/lib/l10n/app_localizations_bg.dart @@ -13340,4 +13340,104 @@ class AppLocalizationsBg extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Вие сте в списъка с чакащи. Ще ви уведомим, когато има място.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Всичко е готово, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Обяд със Сам, която се мести в Лисабон през юни. Искате да изпратите презентацията на екипа до петък и да вземете мляко на път за вкъщи.'; + + @override + String get onboardingActDemoTask1 => 'Да пратя презентацията на екипа до петък'; + + @override + String get onboardingActDemoTask2 => 'Да купя мляко на връщане'; + + @override + String get onboardingActDemoTitle => 'Обяд със Сам'; + + @override + String get onboardingActDictateBody => 'Кажете го по своя начин. Omi ще се погрижи за останалото.'; + + @override + String get onboardingActDictateBodyOmi => 'Просто говорете. Omi слуша.'; + + @override + String get onboardingActDictateExample => + 'Току-що обядвах със Сам, мести се в Лисабон през юни! Напомни ми да пратя презентацията на екипа до петък и да купя мляко на връщане.'; + + @override + String get onboardingActDictateHint => 'Докоснете отметката, когато сте готови'; + + @override + String get onboardingActDictateTitle => 'Разкажете на Omi за деня си'; + + @override + String get onboardingActIntro => 'Да пробваме Omi.'; + + @override + String get onboardingActJustNow => 'Току-що'; + + @override + String get onboardingActMicAllow => 'Разреши микрофона'; + + @override + String get onboardingActMicBody => 'За да ви чува Omi, когато говорите с него.'; + + @override + String get onboardingActMicFallbackBody => 'Omi все още не изпраща звук. Засега използвайте телефона си.'; + + @override + String get onboardingActMicTitle => 'Omi се нуждае от микрофона ви'; + + @override + String get onboardingActNoAudioOmi => 'Не чуваме Omi. Уверете се, че е включен и наблизо.'; + + @override + String get onboardingActNoAudioPhone => 'Още не ви чуваме. Опитайте да говорите малко по-силно.'; + + @override + String get onboardingActNotesLabel => 'Денят ви накратко'; + + @override + String get onboardingActPhoneMic => 'Микрофон на телефона'; + + @override + String get onboardingActRateTitle => 'Харесва ли ви Omi досега?'; + + @override + String get onboardingActRemembers => 'Omi помни вместо вас'; + + @override + String get onboardingActSavedToTasks => 'Запазено в задачите ви'; + + @override + String get onboardingActTasksLabel => 'Omi улови задачите ви'; + + @override + String get onboardingActWriting => 'Обобщаваме…'; + + @override + String get onboardingActYourWords => 'Ето какво казахте'; + + @override + String get onboardingConnectOmi => 'Свържете Omi'; + + @override + String get onboardingConnectTapToConnect => 'Докоснете Omi, за да се свържете.'; + + @override + String get onboardingNoOmiYet => 'Все още нямате Omi?'; + + @override + String get onboardingOmiNotOrdered => 'Още не съм поръчал'; + + @override + String get onboardingOmiOrdered => 'Поръчал съм го'; + + @override + String get onboardingPermissionTapToFix => 'Докоснете, за да поправите'; } diff --git a/app/lib/l10n/app_localizations_bn.dart b/app/lib/l10n/app_localizations_bn.dart index 3de101b9c0a..e399c6463e2 100644 --- a/app/lib/l10n/app_localizations_bn.dart +++ b/app/lib/l10n/app_localizations_bn.dart @@ -13289,4 +13289,104 @@ class AppLocalizationsBn extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'আপনি অপেক্ষা তালিকায় আছেন। জায়গা খালি হলে আমরা জানাব।'; + + @override + String onboardingActCongratsTitle(String name) { + return 'সব তৈরি, $name'; + } + + @override + String get onboardingActDemoSummary => + 'স্যামের সঙ্গে দুপুরের খাবার, যিনি জুনে লিসবনে চলে যাচ্ছেন। শুক্রবারের মধ্যে টিমকে ডেক পাঠাতে আর বাড়ি ফেরার পথে দুধ কিনতে হবে।'; + + @override + String get onboardingActDemoTask1 => 'শুক্রবারের মধ্যে টিমকে ডেক পাঠানো'; + + @override + String get onboardingActDemoTask2 => 'ফেরার পথে দুধ কেনা'; + + @override + String get onboardingActDemoTitle => 'স্যামের সাথে লাঞ্চ'; + + @override + String get onboardingActDictateBody => 'নিজের মতো করে বলুন। বাকিটা Omi সামলাবে।'; + + @override + String get onboardingActDictateBodyOmi => 'শুধু কথা বলুন। আপনার Omi শুনছে।'; + + @override + String get onboardingActDictateExample => + 'এইমাত্র স্যামের সাথে লাঞ্চ করলাম, ও জুনে লিসবনে চলে যাচ্ছে! মনে করিয়ে দিও শুক্রবারের মধ্যে টিমকে ডেকটা পাঠাতে, আর ফেরার পথে দুধ কিনতে।'; + + @override + String get onboardingActDictateHint => 'শেষ হলে টিক চিহ্নে ট্যাপ করুন'; + + @override + String get onboardingActDictateTitle => 'Omi-কে আপনার দিনের কথা বলুন'; + + @override + String get onboardingActIntro => 'চলুন Omi চেষ্টা করি।'; + + @override + String get onboardingActJustNow => 'এইমাত্র'; + + @override + String get onboardingActMicAllow => 'মাইক্রোফোন অনুমতি দিন'; + + @override + String get onboardingActMicBody => 'যাতে আপনি কথা বললে Omi শুনতে পায়।'; + + @override + String get onboardingActMicFallbackBody => 'Omi এখনও অডিও পাঠাচ্ছে না। আপাতত আপনার ফোন ব্যবহার করুন।'; + + @override + String get onboardingActMicTitle => 'Omi-এর আপনার মাইক্রোফোন দরকার'; + + @override + String get onboardingActNoAudioOmi => 'Omi শোনা যাচ্ছে না। এটি চালু আছে ও কাছে আছে কিনা দেখুন।'; + + @override + String get onboardingActNoAudioPhone => 'এখনও আপনাকে শোনা যাচ্ছে না। একটু জোরে বলে দেখুন।'; + + @override + String get onboardingActNotesLabel => 'আপনার দিন, সংক্ষেপে'; + + @override + String get onboardingActPhoneMic => 'ফোনের মাইক্রোফোন'; + + @override + String get onboardingActRateTitle => 'এখন পর্যন্ত Omi কেমন লাগছে?'; + + @override + String get onboardingActRemembers => 'Omi মনে রাখে, যাতে আপনাকে রাখতে না হয়'; + + @override + String get onboardingActSavedToTasks => 'আপনার কাজের তালিকায় সংরক্ষিত'; + + @override + String get onboardingActTasksLabel => 'Omi আপনার কাজগুলো ধরে ফেলেছে'; + + @override + String get onboardingActWriting => 'সারাংশ তৈরি হচ্ছে…'; + + @override + String get onboardingActYourWords => 'আপনি যা বললেন'; + + @override + String get onboardingConnectOmi => 'Omi সংযুক্ত করুন'; + + @override + String get onboardingConnectTapToConnect => 'সংযোগ করতে আপনার Omi-তে ট্যাপ করুন।'; + + @override + String get onboardingNoOmiYet => 'এখনও Omi নেই?'; + + @override + String get onboardingOmiNotOrdered => 'এখনও অর্ডার করিনি'; + + @override + String get onboardingOmiOrdered => 'আমি অর্ডার করেছি'; + + @override + String get onboardingPermissionTapToFix => 'ঠিক করতে ট্যাপ করুন'; } diff --git a/app/lib/l10n/app_localizations_bs.dart b/app/lib/l10n/app_localizations_bs.dart index d80a65939c2..44da4d776c7 100644 --- a/app/lib/l10n/app_localizations_bs.dart +++ b/app/lib/l10n/app_localizations_bs.dart @@ -13322,4 +13322,104 @@ class AppLocalizationsBs extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Na listi čekanja ste. Javit ćemo vam kad bude mjesta.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Sve je spremno, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Ručak sa Sam, koja se u junu seli u Lisabon. Želite poslati prezentaciju timu do petka i kupiti mlijeko na putu kući.'; + + @override + String get onboardingActDemoTask1 => 'Poslati prezentaciju timu do petka'; + + @override + String get onboardingActDemoTask2 => 'Kupiti mlijeko na putu kući'; + + @override + String get onboardingActDemoTitle => 'Ručak sa Sam'; + + @override + String get onboardingActDictateBody => 'Recite to na svoj način. Omi će se pobrinuti za ostalo.'; + + @override + String get onboardingActDictateBodyOmi => 'Samo pričajte. Vaš Omi sluša.'; + + @override + String get onboardingActDictateExample => + 'Upravo sam ručao sa Sam, seli se u Lisabon u junu! Podsjeti me da pošaljem prezentaciju timu do petka i da kupim mlijeko na putu kući.'; + + @override + String get onboardingActDictateHint => 'Dodirnite kvačicu kad završite'; + + @override + String get onboardingActDictateTitle => 'Ispričajte Omiju svoj dan'; + + @override + String get onboardingActIntro => 'Isprobajmo Omi.'; + + @override + String get onboardingActJustNow => 'Upravo sada'; + + @override + String get onboardingActMicAllow => 'Dozvoli mikrofon'; + + @override + String get onboardingActMicBody => 'Da vas Omi čuje kad mu se obratite.'; + + @override + String get onboardingActMicFallbackBody => 'Omi još ne šalje zvuk. Za sada koristite telefon.'; + + @override + String get onboardingActMicTitle => 'Omi treba vaš mikrofon'; + + @override + String get onboardingActNoAudioOmi => 'Ne čujemo Omi. Provjerite je li uključen i blizu.'; + + @override + String get onboardingActNoAudioPhone => 'Još vas ne čujemo. Pokušajte govoriti malo glasnije.'; + + @override + String get onboardingActNotesLabel => 'Vaš dan, ukratko'; + + @override + String get onboardingActPhoneMic => 'Mikrofon telefona'; + + @override + String get onboardingActRateTitle => 'Sviđa li vam se Omi zasad?'; + + @override + String get onboardingActRemembers => 'Omi pamti da vi ne morate'; + + @override + String get onboardingActSavedToTasks => 'Sačuvano u vaše zadatke'; + + @override + String get onboardingActTasksLabel => 'Omi je uhvatio vaše zadatke'; + + @override + String get onboardingActWriting => 'Sažimamo…'; + + @override + String get onboardingActYourWords => 'Evo šta ste rekli'; + + @override + String get onboardingConnectOmi => 'Povežite Omi'; + + @override + String get onboardingConnectTapToConnect => 'Dodirnite svoj Omi da se povežete.'; + + @override + String get onboardingNoOmiYet => 'Još nemate Omi?'; + + @override + String get onboardingOmiNotOrdered => 'Još nisam naručio/la'; + + @override + String get onboardingOmiOrdered => 'Naručio/la sam ga'; + + @override + String get onboardingPermissionTapToFix => 'Dodirnite da popravite'; } diff --git a/app/lib/l10n/app_localizations_ca.dart b/app/lib/l10n/app_localizations_ca.dart index 8c7cd827ea7..2aa26b923d0 100644 --- a/app/lib/l10n/app_localizations_ca.dart +++ b/app/lib/l10n/app_localizations_ca.dart @@ -13361,4 +13361,104 @@ class AppLocalizationsCa extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Ets a la llista d\'espera. T\'avisarem quan hi hagi lloc.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Tot a punt, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Dinar amb en Sam, que es trasllada a Lisboa al juny. Vols enviar la presentació a l\'equip abans de divendres i comprar llet de camí a casa.'; + + @override + String get onboardingActDemoTask1 => 'Enviar la presentació a l\'equip abans de divendres'; + + @override + String get onboardingActDemoTask2 => 'Comprar llet de tornada a casa'; + + @override + String get onboardingActDemoTitle => 'Dinar amb la Sam'; + + @override + String get onboardingActDictateBody => 'Digues-ho a la teva manera. Omi s\'encarrega de la resta.'; + + @override + String get onboardingActDictateBodyOmi => 'Només parla. El teu Omi t\'escolta.'; + + @override + String get onboardingActDictateExample => + 'Acabo de dinar amb la Sam, se\'n va a viure a Lisboa al juny! Recorda\'m d\'enviar la presentació a l\'equip abans de divendres i de comprar llet de tornada a casa.'; + + @override + String get onboardingActDictateHint => 'Toca la marca quan acabis'; + + @override + String get onboardingActDictateTitle => 'Explica-li a Omi el teu dia'; + + @override + String get onboardingActIntro => 'Provem Omi.'; + + @override + String get onboardingActJustNow => 'Ara mateix'; + + @override + String get onboardingActMicAllow => 'Permet el micròfon'; + + @override + String get onboardingActMicBody => 'Perquè Omi et senti quan li parlis.'; + + @override + String get onboardingActMicFallbackBody => 'L\'Omi encara no envia àudio. De moment, fes servir el telèfon.'; + + @override + String get onboardingActMicTitle => 'Omi necessita el micròfon'; + + @override + String get onboardingActNoAudioOmi => 'No sentim l\'Omi. Comprova que estigui encès i a prop.'; + + @override + String get onboardingActNoAudioPhone => 'Encara no et sentim. Prova de parlar una mica més fort.'; + + @override + String get onboardingActNotesLabel => 'El teu dia, resumit'; + + @override + String get onboardingActPhoneMic => 'Micròfon del telèfon'; + + @override + String get onboardingActRateTitle => 'T\'agrada Omi de moment?'; + + @override + String get onboardingActRemembers => 'L\'Omi ho recorda perquè tu no ho hagis de fer'; + + @override + String get onboardingActSavedToTasks => 'Desat a les teves tasques'; + + @override + String get onboardingActTasksLabel => 'Omi ha trobat les teves tasques'; + + @override + String get onboardingActWriting => 'Resumint…'; + + @override + String get onboardingActYourWords => 'Això és el que has dit'; + + @override + String get onboardingConnectOmi => 'Connecta l\'Omi'; + + @override + String get onboardingConnectTapToConnect => 'Toca el teu Omi per connectar-lo.'; + + @override + String get onboardingNoOmiYet => 'Encara no tens l\'Omi?'; + + @override + String get onboardingOmiNotOrdered => 'Encara no l\'he demanat'; + + @override + String get onboardingOmiOrdered => 'Ja l\'he demanat'; + + @override + String get onboardingPermissionTapToFix => 'Toca per solucionar-ho'; } diff --git a/app/lib/l10n/app_localizations_cs.dart b/app/lib/l10n/app_localizations_cs.dart index 28502e90698..c94bbbd2f44 100644 --- a/app/lib/l10n/app_localizations_cs.dart +++ b/app/lib/l10n/app_localizations_cs.dart @@ -13294,4 +13294,104 @@ class AppLocalizationsCs extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Jste na čekací listině. Dáme vám vědět, až bude místo.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Vše je připraveno, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Oběd se Sam, která se v červnu stěhuje do Lisabonu. Chcete do pátku poslat týmu prezentaci a cestou domů koupit mléko.'; + + @override + String get onboardingActDemoTask1 => 'Poslat prezentaci týmu do pátku'; + + @override + String get onboardingActDemoTask2 => 'Koupit cestou domů mléko'; + + @override + String get onboardingActDemoTitle => 'Oběd se Sam'; + + @override + String get onboardingActDictateBody => 'Řekněte to po svém. Zbytek zařídí Omi.'; + + @override + String get onboardingActDictateBodyOmi => 'Prostě mluvte. Vaše Omi poslouchá.'; + + @override + String get onboardingActDictateExample => + 'Právě jsem obědval se Sam, v červnu se stěhuje do Lisabonu! Připomeň mi poslat prezentaci týmu do pátku a koupit cestou domů mléko.'; + + @override + String get onboardingActDictateHint => 'Až budete hotovi, klepněte na fajfku'; + + @override + String get onboardingActDictateTitle => 'Řekněte Omi o svém dni'; + + @override + String get onboardingActIntro => 'Vyzkoušejme Omi.'; + + @override + String get onboardingActJustNow => 'Právě teď'; + + @override + String get onboardingActMicAllow => 'Povolit mikrofon'; + + @override + String get onboardingActMicBody => 'Aby vás Omi slyšel, když na něj mluvíte.'; + + @override + String get onboardingActMicFallbackBody => 'Omi zatím neposílá zvuk. Zatím použijte telefon.'; + + @override + String get onboardingActMicTitle => 'Omi potřebuje mikrofon'; + + @override + String get onboardingActNoAudioOmi => 'Omi neslyšíme. Zkontrolujte, že je zapnuté a blízko.'; + + @override + String get onboardingActNoAudioPhone => 'Zatím vás neslyšíme. Zkuste mluvit trochu hlasitěji.'; + + @override + String get onboardingActNotesLabel => 'Váš den ve zkratce'; + + @override + String get onboardingActPhoneMic => 'Mikrofon telefonu'; + + @override + String get onboardingActRateTitle => 'Líbí se vám zatím Omi?'; + + @override + String get onboardingActRemembers => 'Omi si to pamatuje, abyste nemuseli vy'; + + @override + String get onboardingActSavedToTasks => 'Uloženo do vašich úkolů'; + + @override + String get onboardingActTasksLabel => 'Omi zachytil vaše úkoly'; + + @override + String get onboardingActWriting => 'Shrnujeme…'; + + @override + String get onboardingActYourWords => 'Tohle jste řekli'; + + @override + String get onboardingConnectOmi => 'Připojte Omi'; + + @override + String get onboardingConnectTapToConnect => 'Klepnutím na Omi se připojíte.'; + + @override + String get onboardingNoOmiYet => 'Ještě nemáte Omi?'; + + @override + String get onboardingOmiNotOrdered => 'Ještě neobjednáno'; + + @override + String get onboardingOmiOrdered => 'Už jsem ho objednal/a'; + + @override + String get onboardingPermissionTapToFix => 'Klepnutím opravíte'; } diff --git a/app/lib/l10n/app_localizations_da.dart b/app/lib/l10n/app_localizations_da.dart index 26605090c65..a0c553071ea 100644 --- a/app/lib/l10n/app_localizations_da.dart +++ b/app/lib/l10n/app_localizations_da.dart @@ -13279,4 +13279,104 @@ class AppLocalizationsDa extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Du er på ventelisten. Vi giver besked, når der er plads.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Du er klar, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Frokost med Sam, der flytter til Lissabon i juni. Du vil sende præsentationen til teamet inden fredag og købe mælk på vejen hjem.'; + + @override + String get onboardingActDemoTask1 => 'Send præsentationen til holdet inden fredag'; + + @override + String get onboardingActDemoTask2 => 'Køb mælk på vej hjem'; + + @override + String get onboardingActDemoTitle => 'Frokost med Sam'; + + @override + String get onboardingActDictateBody => 'Sig det på din måde. Omi klarer resten.'; + + @override + String get onboardingActDictateBodyOmi => 'Bare tal. Din Omi lytter.'; + + @override + String get onboardingActDictateExample => + 'Har lige spist frokost med Sam, hun flytter til Lissabon i juni! Mind mig om at sende præsentationen til holdet inden fredag og købe mælk på vej hjem.'; + + @override + String get onboardingActDictateHint => 'Tryk på fluebenet, når du er færdig'; + + @override + String get onboardingActDictateTitle => 'Fortæl Omi om din dag'; + + @override + String get onboardingActIntro => 'Lad os prøve Omi.'; + + @override + String get onboardingActJustNow => 'Lige nu'; + + @override + String get onboardingActMicAllow => 'Tillad mikrofon'; + + @override + String get onboardingActMicBody => 'Så Omi kan høre dig, når du taler til den.'; + + @override + String get onboardingActMicFallbackBody => 'Din Omi sender ikke lyd endnu. Brug din telefon indtil videre.'; + + @override + String get onboardingActMicTitle => 'Omi skal bruge din mikrofon'; + + @override + String get onboardingActNoAudioOmi => 'Kan ikke høre din Omi. Tjek, at den er tændt og tæt på.'; + + @override + String get onboardingActNoAudioPhone => 'Kan ikke høre dig endnu. Prøv at tale lidt højere.'; + + @override + String get onboardingActNotesLabel => 'Din dag, opsummeret'; + + @override + String get onboardingActPhoneMic => 'Telefonens mikrofon'; + + @override + String get onboardingActRateTitle => 'Kan du lide Omi indtil videre?'; + + @override + String get onboardingActRemembers => 'Omi husker det, så du ikke behøver'; + + @override + String get onboardingActSavedToTasks => 'Gemt i dine opgaver'; + + @override + String get onboardingActTasksLabel => 'Omi fangede dine to-dos'; + + @override + String get onboardingActWriting => 'Opsummerer…'; + + @override + String get onboardingActYourWords => 'Det her sagde du'; + + @override + String get onboardingConnectOmi => 'Forbind Omi'; + + @override + String get onboardingConnectTapToConnect => 'Tryk på din Omi for at forbinde.'; + + @override + String get onboardingNoOmiYet => 'Har du ikke din Omi endnu?'; + + @override + String get onboardingOmiNotOrdered => 'Ikke bestilt endnu'; + + @override + String get onboardingOmiOrdered => 'Jeg har bestilt den'; + + @override + String get onboardingPermissionTapToFix => 'Tryk for at rette'; } diff --git a/app/lib/l10n/app_localizations_de.dart b/app/lib/l10n/app_localizations_de.dart index 1e768a9cf3e..15f3d543ccf 100644 --- a/app/lib/l10n/app_localizations_de.dart +++ b/app/lib/l10n/app_localizations_de.dart @@ -13391,4 +13391,104 @@ class AppLocalizationsDe extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Du stehst auf der Warteliste. Wir melden uns, sobald ein Platz frei ist.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Alles bereit, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Mittagessen mit Sam, der im Juni nach Lissabon zieht. Du willst die Präsentation bis Freitag ans Team schicken und auf dem Heimweg Milch holen.'; + + @override + String get onboardingActDemoTask1 => 'Präsentation bis Freitag ans Team schicken'; + + @override + String get onboardingActDemoTask2 => 'Auf dem Heimweg Milch holen'; + + @override + String get onboardingActDemoTitle => 'Mittagessen mit Sam'; + + @override + String get onboardingActDictateBody => 'Sag es auf deine Art. Omi erledigt den Rest.'; + + @override + String get onboardingActDictateBodyOmi => 'Sprich einfach. Dein Omi hört zu.'; + + @override + String get onboardingActDictateExample => + 'Hatte gerade Mittagessen mit Sam, sie zieht im Juni nach Lissabon! Erinnere mich, die Präsentation bis Freitag ans Team zu schicken und auf dem Heimweg Milch zu holen.'; + + @override + String get onboardingActDictateHint => 'Tippe auf den Haken, wenn du fertig bist'; + + @override + String get onboardingActDictateTitle => 'Erzähl Omi von deinem Tag'; + + @override + String get onboardingActIntro => 'Probier Omi aus.'; + + @override + String get onboardingActJustNow => 'Gerade eben'; + + @override + String get onboardingActMicAllow => 'Mikrofon erlauben'; + + @override + String get onboardingActMicBody => 'Damit Omi dich hört, wenn du mit ihm sprichst.'; + + @override + String get onboardingActMicFallbackBody => 'Dein Omi sendet noch keinen Ton. Nutze vorerst dein Handy.'; + + @override + String get onboardingActMicTitle => 'Omi braucht dein Mikrofon'; + + @override + String get onboardingActNoAudioOmi => 'Dein Omi ist nicht zu hören. Ist es an und in der Nähe?'; + + @override + String get onboardingActNoAudioPhone => 'Wir hören dich noch nicht. Sprich etwas lauter.'; + + @override + String get onboardingActNotesLabel => 'Dein Tag, zusammengefasst'; + + @override + String get onboardingActPhoneMic => 'Handymikrofon'; + + @override + String get onboardingActRateTitle => 'Gefällt dir Omi bisher?'; + + @override + String get onboardingActRemembers => 'Omi merkt es sich, damit du es nicht musst'; + + @override + String get onboardingActSavedToTasks => 'In deinen Aufgaben gespeichert'; + + @override + String get onboardingActTasksLabel => 'Omi hat deine To-dos erkannt'; + + @override + String get onboardingActWriting => 'Wird zusammengefasst…'; + + @override + String get onboardingActYourWords => 'Das hast du gesagt'; + + @override + String get onboardingConnectOmi => 'Omi verbinden'; + + @override + String get onboardingConnectTapToConnect => 'Tippe auf dein Omi, um es zu verbinden.'; + + @override + String get onboardingNoOmiYet => 'Hast du dein Omi noch nicht?'; + + @override + String get onboardingOmiNotOrdered => 'Noch nicht bestellt'; + + @override + String get onboardingOmiOrdered => 'Ich habe es bestellt'; + + @override + String get onboardingPermissionTapToFix => 'Zum Beheben tippen'; } diff --git a/app/lib/l10n/app_localizations_el.dart b/app/lib/l10n/app_localizations_el.dart index cf8bb6086c4..fb5a889c225 100644 --- a/app/lib/l10n/app_localizations_el.dart +++ b/app/lib/l10n/app_localizations_el.dart @@ -13375,4 +13375,105 @@ class AppLocalizationsEl extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Είστε στη λίστα αναμονής. Θα σας ενημερώσουμε όταν υπάρξει θέση.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Όλα έτοιμα, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Μεσημεριανό με τον Σαμ, που μετακομίζει στη Λισαβόνα τον Ιούνιο. Θέλεις να στείλεις την παρουσίαση στην ομάδα ως την Παρασκευή και να πάρεις γάλα στον δρόμο για το σπίτι.'; + + @override + String get onboardingActDemoTask1 => 'Αποστολή της παρουσίασης στην ομάδα ως την Παρασκευή'; + + @override + String get onboardingActDemoTask2 => 'Γάλα στον δρόμο για το σπίτι'; + + @override + String get onboardingActDemoTitle => 'Γεύμα με τη Σαμ'; + + @override + String get onboardingActDictateBody => 'Πείτε το με τον τρόπο σας. Το Omi φροντίζει τα υπόλοιπα.'; + + @override + String get onboardingActDictateBodyOmi => 'Απλώς μίλα. Το Omi σου ακούει.'; + + @override + String get onboardingActDictateExample => + 'Μόλις έφαγα με τη Σαμ, μετακομίζει στη Λισαβόνα τον Ιούνιο! Θύμισέ μου να στείλω την παρουσίαση στην ομάδα ως την Παρασκευή και να πάρω γάλα στον δρόμο.'; + + @override + String get onboardingActDictateHint => 'Πατήστε το τικ όταν τελειώσετε'; + + @override + String get onboardingActDictateTitle => 'Πείτε στο Omi για τη μέρα σας'; + + @override + String get onboardingActIntro => 'Ας δοκιμάσουμε το Omi.'; + + @override + String get onboardingActJustNow => 'Μόλις τώρα'; + + @override + String get onboardingActMicAllow => 'Να επιτρέπεται το μικρόφωνο'; + + @override + String get onboardingActMicBody => 'Για να σας ακούει το Omi όταν του μιλάτε.'; + + @override + String get onboardingActMicFallbackBody => + 'Το Omi δεν στέλνει ακόμα ήχο. Χρησιμοποίησε προς το παρόν το τηλέφωνό σου.'; + + @override + String get onboardingActMicTitle => 'Το Omi χρειάζεται το μικρόφωνό σας'; + + @override + String get onboardingActNoAudioOmi => 'Δεν ακούμε το Omi. Βεβαιώσου ότι είναι ανοιχτό και κοντά σου.'; + + @override + String get onboardingActNoAudioPhone => 'Δεν σε ακούμε ακόμα. Δοκίμασε να μιλήσεις λίγο πιο δυνατά.'; + + @override + String get onboardingActNotesLabel => 'Η μέρα σου, σε σύνοψη'; + + @override + String get onboardingActPhoneMic => 'Μικρόφωνο τηλεφώνου'; + + @override + String get onboardingActRateTitle => 'Σας αρέσει το Omi μέχρι τώρα;'; + + @override + String get onboardingActRemembers => 'Το Omi θυμάται, για να μη χρειάζεται εσύ'; + + @override + String get onboardingActSavedToTasks => 'Αποθηκεύτηκε στις εργασίες σου'; + + @override + String get onboardingActTasksLabel => 'Το Omi εντόπισε τις εκκρεμότητές σου'; + + @override + String get onboardingActWriting => 'Γίνεται σύνοψη…'; + + @override + String get onboardingActYourWords => 'Αυτά είπατε'; + + @override + String get onboardingConnectOmi => 'Συνδέστε το Omi'; + + @override + String get onboardingConnectTapToConnect => 'Πάτησε το Omi σου για σύνδεση.'; + + @override + String get onboardingNoOmiYet => 'Δεν έχετε ακόμα το Omi σας;'; + + @override + String get onboardingOmiNotOrdered => 'Δεν το έχω παραγγείλει ακόμα'; + + @override + String get onboardingOmiOrdered => 'Το έχω παραγγείλει'; + + @override + String get onboardingPermissionTapToFix => 'Πατήστε για διόρθωση'; } diff --git a/app/lib/l10n/app_localizations_en.dart b/app/lib/l10n/app_localizations_en.dart index d6aa51c8cad..df49dc064b3 100644 --- a/app/lib/l10n/app_localizations_en.dart +++ b/app/lib/l10n/app_localizations_en.dart @@ -13277,4 +13277,104 @@ class AppLocalizationsEn extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'You\'re on the waitlist. We\'ll let you know when there\'s room.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'You\'re all set, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Lunch with Sam, who\'s moving to Lisbon in June. You want to send the deck to your team by Friday and grab milk on the way home.'; + + @override + String get onboardingActDemoTask1 => 'Send the deck to the team by Friday'; + + @override + String get onboardingActDemoTask2 => 'Grab milk on the way home'; + + @override + String get onboardingActDemoTitle => 'Lunch with Sam'; + + @override + String get onboardingActDictateBody => 'Say it your way. Omi handles the rest.'; + + @override + String get onboardingActDictateBodyOmi => 'Just talk. Your Omi is listening.'; + + @override + String get onboardingActDictateExample => + 'Just had lunch with Sam, she\'s moving to Lisbon in June! Remind me to send the deck to my team by Friday, and to grab milk on the way home.'; + + @override + String get onboardingActDictateHint => 'Tap the check when you\'re done'; + + @override + String get onboardingActDictateTitle => 'Tell Omi about your day'; + + @override + String get onboardingActIntro => 'Let\'s try Omi.'; + + @override + String get onboardingActJustNow => 'Just now'; + + @override + String get onboardingActMicAllow => 'Allow Microphone'; + + @override + String get onboardingActMicBody => 'So Omi can hear you when you talk to it.'; + + @override + String get onboardingActMicFallbackBody => 'Your Omi isn\'t sending audio yet. Use your phone for now.'; + + @override + String get onboardingActMicTitle => 'Omi needs your microphone'; + + @override + String get onboardingActNoAudioOmi => 'Can\'t hear your Omi. Make sure it\'s on and close by.'; + + @override + String get onboardingActNoAudioPhone => 'Can\'t hear you yet. Try speaking a little louder.'; + + @override + String get onboardingActNotesLabel => 'Your day, summarized'; + + @override + String get onboardingActPhoneMic => 'Phone microphone'; + + @override + String get onboardingActRateTitle => 'Enjoying Omi so far?'; + + @override + String get onboardingActRemembers => 'Omi remembers so you don\'t have to'; + + @override + String get onboardingActSavedToTasks => 'Saved to your tasks'; + + @override + String get onboardingActTasksLabel => 'Omi caught your to-dos'; + + @override + String get onboardingActWriting => 'Summarizing…'; + + @override + String get onboardingActYourWords => 'Here\'s what you said'; + + @override + String get onboardingConnectOmi => 'Connect Omi'; + + @override + String get onboardingConnectTapToConnect => 'Tap your Omi to connect.'; + + @override + String get onboardingNoOmiYet => 'Don\'t have your Omi yet?'; + + @override + String get onboardingOmiNotOrdered => 'Not ordered yet'; + + @override + String get onboardingOmiOrdered => 'I\'ve ordered mine'; + + @override + String get onboardingPermissionTapToFix => 'Tap to fix'; } diff --git a/app/lib/l10n/app_localizations_es.dart b/app/lib/l10n/app_localizations_es.dart index 54aad949eb9..531939f9d97 100644 --- a/app/lib/l10n/app_localizations_es.dart +++ b/app/lib/l10n/app_localizations_es.dart @@ -13332,4 +13332,104 @@ class AppLocalizationsEs extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Estás en la lista de espera. Te avisaremos cuando haya sitio.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Todo listo, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Almuerzo con Sam, que se muda a Lisboa en junio. Quieres enviar la presentación a tu equipo antes del viernes y comprar leche de camino a casa.'; + + @override + String get onboardingActDemoTask1 => 'Enviar la presentación al equipo antes del viernes'; + + @override + String get onboardingActDemoTask2 => 'Comprar leche de camino a casa'; + + @override + String get onboardingActDemoTitle => 'Comida con Sam'; + + @override + String get onboardingActDictateBody => 'Dilo a tu manera. Omi se encarga del resto.'; + + @override + String get onboardingActDictateBodyOmi => 'Solo habla. Tu Omi te escucha.'; + + @override + String get onboardingActDictateExample => + '¡Acabo de comer con Sam, se muda a Lisboa en junio! Recuérdame enviar la presentación al equipo antes del viernes y comprar leche de camino a casa.'; + + @override + String get onboardingActDictateHint => 'Toca el check cuando termines'; + + @override + String get onboardingActDictateTitle => 'Cuéntale a Omi tu día'; + + @override + String get onboardingActIntro => 'Probemos Omi.'; + + @override + String get onboardingActJustNow => 'Ahora mismo'; + + @override + String get onboardingActMicAllow => 'Permitir micrófono'; + + @override + String get onboardingActMicBody => 'Para que Omi te escuche cuando le hables.'; + + @override + String get onboardingActMicFallbackBody => 'Tu Omi aún no envía audio. Usa tu teléfono por ahora.'; + + @override + String get onboardingActMicTitle => 'Omi necesita tu micrófono'; + + @override + String get onboardingActNoAudioOmi => 'No se oye tu Omi. Comprueba que esté encendido y cerca.'; + + @override + String get onboardingActNoAudioPhone => 'Aún no te oímos. Prueba a hablar un poco más alto.'; + + @override + String get onboardingActNotesLabel => 'Tu día, resumido'; + + @override + String get onboardingActPhoneMic => 'Micrófono del teléfono'; + + @override + String get onboardingActRateTitle => '¿Te gusta Omi hasta ahora?'; + + @override + String get onboardingActRemembers => 'Omi lo recuerda para que tú no tengas que hacerlo'; + + @override + String get onboardingActSavedToTasks => 'Guardado en tus tareas'; + + @override + String get onboardingActTasksLabel => 'Omi detectó tus pendientes'; + + @override + String get onboardingActWriting => 'Resumiendo…'; + + @override + String get onboardingActYourWords => 'Esto es lo que dijiste'; + + @override + String get onboardingConnectOmi => 'Conecta tu Omi'; + + @override + String get onboardingConnectTapToConnect => 'Toca tu Omi para conectarlo.'; + + @override + String get onboardingNoOmiYet => '¿Aún no tienes tu Omi?'; + + @override + String get onboardingOmiNotOrdered => 'Aún no lo he pedido'; + + @override + String get onboardingOmiOrdered => 'Ya lo pedí'; + + @override + String get onboardingPermissionTapToFix => 'Toca para solucionarlo'; } diff --git a/app/lib/l10n/app_localizations_et.dart b/app/lib/l10n/app_localizations_et.dart index 1ec0ef1818f..393d8508a08 100644 --- a/app/lib/l10n/app_localizations_et.dart +++ b/app/lib/l10n/app_localizations_et.dart @@ -13286,4 +13286,104 @@ class AppLocalizationsEt extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Oled ootenimekirjas. Anname teada, kui koht vabaneb.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Kõik on valmis, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Lõuna Samiga, kes kolib juunis Lissaboni. Tahad reedeks esitluse meeskonnale saata ja koduteel piima osta.'; + + @override + String get onboardingActDemoTask1 => 'Saata esitlus reedeks tiimile'; + + @override + String get onboardingActDemoTask2 => 'Osta koduteel piima'; + + @override + String get onboardingActDemoTitle => 'Lõuna Samiga'; + + @override + String get onboardingActDictateBody => 'Ütle omal moel. Ülejäänu teeb Omi.'; + + @override + String get onboardingActDictateBodyOmi => 'Lihtsalt räägi. Sinu Omi kuulab.'; + + @override + String get onboardingActDictateExample => + 'Sõin just Samiga lõunat, ta kolib juunis Lissaboni! Tuleta mulle meelde saata esitlus reedeks tiimile ja osta koduteel piima.'; + + @override + String get onboardingActDictateHint => 'Kui oled valmis, puuduta linnukest'; + + @override + String get onboardingActDictateTitle => 'Räägi Omile oma päevast'; + + @override + String get onboardingActIntro => 'Proovime Omit.'; + + @override + String get onboardingActJustNow => 'Just praegu'; + + @override + String get onboardingActMicAllow => 'Luba mikrofon'; + + @override + String get onboardingActMicBody => 'Et Omi kuuleks sind, kui sa temaga räägid.'; + + @override + String get onboardingActMicFallbackBody => 'Omi ei saada veel heli. Kasuta praegu oma telefoni.'; + + @override + String get onboardingActMicTitle => 'Omi vajab su mikrofoni'; + + @override + String get onboardingActNoAudioOmi => 'Omit pole kuulda. Veendu, et see on sees ja lähedal.'; + + @override + String get onboardingActNoAudioPhone => 'Me ei kuule sind veel. Proovi natuke valjemini rääkida.'; + + @override + String get onboardingActNotesLabel => 'Sinu päev lühidalt'; + + @override + String get onboardingActPhoneMic => 'Telefoni mikrofon'; + + @override + String get onboardingActRateTitle => 'Kas Omi meeldib sulle seni?'; + + @override + String get onboardingActRemembers => 'Omi mäletab, et sina ei peaks'; + + @override + String get onboardingActSavedToTasks => 'Salvestatud sinu ülesannetesse'; + + @override + String get onboardingActTasksLabel => 'Omi märkas sinu ülesandeid'; + + @override + String get onboardingActWriting => 'Teeme kokkuvõtet…'; + + @override + String get onboardingActYourWords => 'Seda sa ütlesid'; + + @override + String get onboardingConnectOmi => 'Ühenda Omi'; + + @override + String get onboardingConnectTapToConnect => 'Ühendamiseks puuduta oma Omit.'; + + @override + String get onboardingNoOmiYet => 'Sul pole veel Omit?'; + + @override + String get onboardingOmiNotOrdered => 'Pole veel tellinud'; + + @override + String get onboardingOmiOrdered => 'Olen selle tellinud'; + + @override + String get onboardingPermissionTapToFix => 'Puuduta parandamiseks'; } diff --git a/app/lib/l10n/app_localizations_fa.dart b/app/lib/l10n/app_localizations_fa.dart index b48e0c1e288..d8819a08845 100644 --- a/app/lib/l10n/app_localizations_fa.dart +++ b/app/lib/l10n/app_localizations_fa.dart @@ -13287,4 +13287,104 @@ class AppLocalizationsFa extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'شما در فهرست انتظار هستید. وقتی جا باز شود خبرتان می‌کنیم.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'همه چیز آماده است، $name'; + } + + @override + String get onboardingActDemoSummary => + 'ناهار با سم که خرداد به لیسبون می‌رود. می‌خواهید تا جمعه ارائه را برای تیم بفرستید و سر راه خانه شیر بخرید.'; + + @override + String get onboardingActDemoTask1 => 'فرستادن ارائه برای تیم تا جمعه'; + + @override + String get onboardingActDemoTask2 => 'خریدن شیر سر راه خانه'; + + @override + String get onboardingActDemoTitle => 'ناهار با سم'; + + @override + String get onboardingActDictateBody => 'به روش خودتان بگویید. بقیه با Omi.'; + + @override + String get onboardingActDictateBodyOmi => 'فقط صحبت کنید. Omi گوش می‌دهد.'; + + @override + String get onboardingActDictateExample => + 'همین الان با سم ناهار خوردم، خرداد به لیسبون می‌رود! یادم بینداز تا جمعه ارائه را برای تیم بفرستم و سر راه خانه شیر بخرم.'; + + @override + String get onboardingActDictateHint => 'وقتی تمام شد، روی تیک بزنید'; + + @override + String get onboardingActDictateTitle => 'از روزتان به Omi بگویید'; + + @override + String get onboardingActIntro => 'بیایید Omi را امتحان کنیم.'; + + @override + String get onboardingActJustNow => 'همین الان'; + + @override + String get onboardingActMicAllow => 'اجازهٔ میکروفون'; + + @override + String get onboardingActMicBody => 'تا Omi وقتی با او حرف می‌زنید صدایتان را بشنود.'; + + @override + String get onboardingActMicFallbackBody => 'Omi هنوز صدا نمی‌فرستد. فعلاً از گوشی خود استفاده کنید.'; + + @override + String get onboardingActMicTitle => 'Omi به میکروفون شما نیاز دارد'; + + @override + String get onboardingActNoAudioOmi => 'صدای Omi نمی‌آید. مطمئن شوید روشن و نزدیک است.'; + + @override + String get onboardingActNoAudioPhone => 'هنوز صدایتان نمی‌آید. کمی بلندتر صحبت کنید.'; + + @override + String get onboardingActNotesLabel => 'روز شما، خلاصه‌شده'; + + @override + String get onboardingActPhoneMic => 'میکروفون گوشی'; + + @override + String get onboardingActRateTitle => 'تا اینجا از Omi راضی هستید؟'; + + @override + String get onboardingActRemembers => 'Omi به خاطر می‌سپارد تا شما مجبور نباشید'; + + @override + String get onboardingActSavedToTasks => 'در کارهای شما ذخیره شد'; + + @override + String get onboardingActTasksLabel => 'Omi کارهای شما را پیدا کرد'; + + @override + String get onboardingActWriting => 'در حال خلاصه‌سازی…'; + + @override + String get onboardingActYourWords => 'این چیزی است که گفتید'; + + @override + String get onboardingConnectOmi => 'اتصال Omi'; + + @override + String get onboardingConnectTapToConnect => 'برای اتصال روی Omi خود ضربه بزنید.'; + + @override + String get onboardingNoOmiYet => 'هنوز Omi ندارید؟'; + + @override + String get onboardingOmiNotOrdered => 'هنوز سفارش نداده‌ام'; + + @override + String get onboardingOmiOrdered => 'سفارش داده‌ام'; + + @override + String get onboardingPermissionTapToFix => 'برای رفع ضربه بزنید'; } diff --git a/app/lib/l10n/app_localizations_fi.dart b/app/lib/l10n/app_localizations_fi.dart index cb48baae7ed..5933500d530 100644 --- a/app/lib/l10n/app_localizations_fi.dart +++ b/app/lib/l10n/app_localizations_fi.dart @@ -13297,4 +13297,104 @@ class AppLocalizationsFi extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Olet jonotuslistalla. Kerromme, kun tilaa vapautuu.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Kaikki valmista, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Lounas Samin kanssa, joka muuttaa kesäkuussa Lissaboniin. Haluat lähettää esityksen tiimille perjantaihin mennessä ja ostaa maitoa kotimatkalla.'; + + @override + String get onboardingActDemoTask1 => 'Lähetä esitys tiimille perjantaihin mennessä'; + + @override + String get onboardingActDemoTask2 => 'Hae maitoa kotimatkalla'; + + @override + String get onboardingActDemoTitle => 'Lounas Samin kanssa'; + + @override + String get onboardingActDictateBody => 'Sano omin sanoin. Omi hoitaa loput.'; + + @override + String get onboardingActDictateBodyOmi => 'Puhu vain. Omisi kuuntelee.'; + + @override + String get onboardingActDictateExample => + 'Söin juuri lounasta Samin kanssa, hän muuttaa kesäkuussa Lissaboniin! Muistuta minua lähettämään esitys tiimille perjantaihin mennessä ja hakemaan maitoa kotimatkalla.'; + + @override + String get onboardingActDictateHint => 'Napauta valintamerkkiä, kun olet valmis'; + + @override + String get onboardingActDictateTitle => 'Kerro Omille päivästäsi'; + + @override + String get onboardingActIntro => 'Kokeillaan Omia.'; + + @override + String get onboardingActJustNow => 'Juuri nyt'; + + @override + String get onboardingActMicAllow => 'Salli mikrofoni'; + + @override + String get onboardingActMicBody => 'Jotta Omi kuulee sinut, kun puhut sille.'; + + @override + String get onboardingActMicFallbackBody => 'Omi ei vielä lähetä ääntä. Käytä toistaiseksi puhelinta.'; + + @override + String get onboardingActMicTitle => 'Omi tarvitsee mikrofonisi'; + + @override + String get onboardingActNoAudioOmi => 'Omia ei kuulu. Varmista, että se on päällä ja lähellä.'; + + @override + String get onboardingActNoAudioPhone => 'Emme kuule sinua vielä. Kokeile puhua vähän kovempaa.'; + + @override + String get onboardingActNotesLabel => 'Päiväsi tiivistettynä'; + + @override + String get onboardingActPhoneMic => 'Puhelimen mikrofoni'; + + @override + String get onboardingActRateTitle => 'Pidätkö Omista tähän mennessä?'; + + @override + String get onboardingActRemembers => 'Omi muistaa, ettei sinun tarvitse'; + + @override + String get onboardingActSavedToTasks => 'Tallennettu tehtäviisi'; + + @override + String get onboardingActTasksLabel => 'Omi poimi tehtäväsi'; + + @override + String get onboardingActWriting => 'Tiivistetään…'; + + @override + String get onboardingActYourWords => 'Tämän sanoit'; + + @override + String get onboardingConnectOmi => 'Yhdistä Omi'; + + @override + String get onboardingConnectTapToConnect => 'Yhdistä napauttamalla Omiasi.'; + + @override + String get onboardingNoOmiYet => 'Eikö Omi ole vielä saapunut?'; + + @override + String get onboardingOmiNotOrdered => 'En ole vielä tilannut'; + + @override + String get onboardingOmiOrdered => 'Olen tilannut sen'; + + @override + String get onboardingPermissionTapToFix => 'Korjaa napauttamalla'; } diff --git a/app/lib/l10n/app_localizations_fr.dart b/app/lib/l10n/app_localizations_fr.dart index 9045c314cd7..9a0e4628478 100644 --- a/app/lib/l10n/app_localizations_fr.dart +++ b/app/lib/l10n/app_localizations_fr.dart @@ -13397,4 +13397,105 @@ class AppLocalizationsFr extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Vous êtes sur la liste d\'attente. Nous vous préviendrons dès qu\'une place se libère.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Tout est prêt, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Déjeuner avec Sam, qui part vivre à Lisbonne en juin. Vous voulez envoyer la présentation à l\'équipe d\'ici vendredi et prendre du lait en rentrant.'; + + @override + String get onboardingActDemoTask1 => 'Envoyer la présentation à l\'équipe d\'ici vendredi'; + + @override + String get onboardingActDemoTask2 => 'Prendre du lait en rentrant'; + + @override + String get onboardingActDemoTitle => 'Déjeuner avec Sam'; + + @override + String get onboardingActDictateBody => 'Dites-le à votre façon. Omi s\'occupe du reste.'; + + @override + String get onboardingActDictateBodyOmi => 'Parlez simplement. Votre Omi vous écoute.'; + + @override + String get onboardingActDictateExample => + 'Je viens de déjeuner avec Sam, elle déménage à Lisbonne en juin ! Rappelle-moi d\'envoyer la présentation à l\'équipe d\'ici vendredi et de prendre du lait en rentrant.'; + + @override + String get onboardingActDictateHint => 'Touchez la coche quand vous avez fini'; + + @override + String get onboardingActDictateTitle => 'Racontez votre journée à Omi'; + + @override + String get onboardingActIntro => 'Essayons Omi.'; + + @override + String get onboardingActJustNow => 'À l\'instant'; + + @override + String get onboardingActMicAllow => 'Autoriser le micro'; + + @override + String get onboardingActMicBody => 'Pour qu\'Omi vous entende quand vous lui parlez.'; + + @override + String get onboardingActMicFallbackBody => + 'Votre Omi n\'envoie pas encore de son. Utilisez votre téléphone pour l\'instant.'; + + @override + String get onboardingActMicTitle => 'Omi a besoin de votre micro'; + + @override + String get onboardingActNoAudioOmi => 'On n\'entend pas votre Omi. Vérifiez qu\'il est allumé et proche.'; + + @override + String get onboardingActNoAudioPhone => 'On ne vous entend pas encore. Parlez un peu plus fort.'; + + @override + String get onboardingActNotesLabel => 'Votre journée, résumée'; + + @override + String get onboardingActPhoneMic => 'Micro du téléphone'; + + @override + String get onboardingActRateTitle => 'Omi vous plaît pour l\'instant ?'; + + @override + String get onboardingActRemembers => 'Omi s\'en souvient pour vous'; + + @override + String get onboardingActSavedToTasks => 'Enregistré dans vos tâches'; + + @override + String get onboardingActTasksLabel => 'Omi a repéré vos tâches'; + + @override + String get onboardingActWriting => 'Résumé en cours…'; + + @override + String get onboardingActYourWords => 'Voici ce que vous avez dit'; + + @override + String get onboardingConnectOmi => 'Connecter Omi'; + + @override + String get onboardingConnectTapToConnect => 'Touchez votre Omi pour le connecter.'; + + @override + String get onboardingNoOmiYet => 'Vous n\'avez pas encore votre Omi ?'; + + @override + String get onboardingOmiNotOrdered => 'Pas encore commandé'; + + @override + String get onboardingOmiOrdered => 'Je l\'ai commandé'; + + @override + String get onboardingPermissionTapToFix => 'Touchez pour corriger'; } diff --git a/app/lib/l10n/app_localizations_he.dart b/app/lib/l10n/app_localizations_he.dart index ce8b2cb1d19..a052ebee951 100644 --- a/app/lib/l10n/app_localizations_he.dart +++ b/app/lib/l10n/app_localizations_he.dart @@ -13199,4 +13199,104 @@ class AppLocalizationsHe extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'את/ה ברשימת ההמתנה. נעדכן כשיתפנה מקום.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'הכול מוכן, $name'; + } + + @override + String get onboardingActDemoSummary => + 'ארוחת צהריים עם סם, שעוברת ללישבון ביוני. אתה רוצה לשלוח את המצגת לצוות עד יום שישי ולקנות חלב בדרך הביתה.'; + + @override + String get onboardingActDemoTask1 => 'לשלוח את המצגת לצוות עד שישי'; + + @override + String get onboardingActDemoTask2 => 'לקנות חלב בדרך הביתה'; + + @override + String get onboardingActDemoTitle => 'צהריים עם סאם'; + + @override + String get onboardingActDictateBody => 'תגידו את זה בדרך שלכם. Omi ידאג לשאר.'; + + @override + String get onboardingActDictateBodyOmi => 'פשוט דבר. ה-Omi שלך מקשיב.'; + + @override + String get onboardingActDictateExample => + 'הרגע אכלתי צהריים עם סאם, היא עוברת לליסבון ביוני! תזכיר לי לשלוח את המצגת לצוות עד שישי, ולקנות חלב בדרך הביתה.'; + + @override + String get onboardingActDictateHint => 'הקישו על ה-V כשתסיימו'; + + @override + String get onboardingActDictateTitle => 'ספרו ל-Omi על היום שלכם'; + + @override + String get onboardingActIntro => 'בואו ננסה את Omi.'; + + @override + String get onboardingActJustNow => 'עכשיו'; + + @override + String get onboardingActMicAllow => 'אפשר מיקרופון'; + + @override + String get onboardingActMicBody => 'כדי ש-Omi ישמע אתכם כשאתם מדברים אליו.'; + + @override + String get onboardingActMicFallbackBody => 'ה-Omi עדיין לא שולח שמע. בינתיים השתמש בטלפון.'; + + @override + String get onboardingActMicTitle => 'Omi צריך את המיקרופון'; + + @override + String get onboardingActNoAudioOmi => 'לא שומעים את ה-Omi. ודא שהוא דולק וקרוב.'; + + @override + String get onboardingActNoAudioPhone => 'עדיין לא שומעים אותך. נסה לדבר קצת יותר חזק.'; + + @override + String get onboardingActNotesLabel => 'היום שלך, בתמצית'; + + @override + String get onboardingActPhoneMic => 'מיקרופון הטלפון'; + + @override + String get onboardingActRateTitle => 'נהנים מ-Omi עד עכשיו?'; + + @override + String get onboardingActRemembers => 'Omi זוכר, כדי שאתם לא תצטרכו'; + + @override + String get onboardingActSavedToTasks => 'נשמר במשימות שלך'; + + @override + String get onboardingActTasksLabel => 'Omi תפס את המשימות שלך'; + + @override + String get onboardingActWriting => 'מסכם…'; + + @override + String get onboardingActYourWords => 'זה מה שאמרת'; + + @override + String get onboardingConnectOmi => 'חיבור Omi'; + + @override + String get onboardingConnectTapToConnect => 'הקש על ה-Omi שלך כדי להתחבר.'; + + @override + String get onboardingNoOmiYet => 'עדיין אין לכם Omi?'; + + @override + String get onboardingOmiNotOrdered => 'עוד לא הזמנתי'; + + @override + String get onboardingOmiOrdered => 'הזמנתי'; + + @override + String get onboardingPermissionTapToFix => 'הקישו לתיקון'; } diff --git a/app/lib/l10n/app_localizations_hi.dart b/app/lib/l10n/app_localizations_hi.dart index c30259c2a7e..c33031538fe 100644 --- a/app/lib/l10n/app_localizations_hi.dart +++ b/app/lib/l10n/app_localizations_hi.dart @@ -13273,4 +13273,104 @@ class AppLocalizationsHi extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'आप प्रतीक्षा सूची में हैं। जगह होने पर हम आपको बताएंगे।'; + + @override + String onboardingActCongratsTitle(String name) { + return 'सब तैयार है, $name'; + } + + @override + String get onboardingActDemoSummary => + 'सैम के साथ लंच, जो जून में लिस्बन जा रही है। आपको शुक्रवार तक टीम को डेक भेजना है और घर लौटते हुए दूध लेना है।'; + + @override + String get onboardingActDemoTask1 => 'शुक्रवार तक टीम को डेक भेजना'; + + @override + String get onboardingActDemoTask2 => 'घर लौटते हुए दूध लेना'; + + @override + String get onboardingActDemoTitle => 'सैम के साथ लंच'; + + @override + String get onboardingActDictateBody => 'अपने अंदाज़ में कहिए। बाकी Omi संभाल लेगा।'; + + @override + String get onboardingActDictateBodyOmi => 'बस बोलिए। आपका Omi सुन रहा है।'; + + @override + String get onboardingActDictateExample => + 'अभी सैम के साथ लंच किया, वो जून में लिस्बन जा रही है! याद दिलाना कि शुक्रवार तक टीम को डेक भेजना है, और घर लौटते हुए दूध लेना है।'; + + @override + String get onboardingActDictateHint => 'हो जाने पर टिक पर टैप करें'; + + @override + String get onboardingActDictateTitle => 'Omi को अपने दिन के बारे में बताएँ'; + + @override + String get onboardingActIntro => 'चलिए Omi आज़माएं।'; + + @override + String get onboardingActJustNow => 'अभी-अभी'; + + @override + String get onboardingActMicAllow => 'माइक्रोफ़ोन की अनुमति दें'; + + @override + String get onboardingActMicBody => 'ताकि आप बात करें तो Omi सुन सके।'; + + @override + String get onboardingActMicFallbackBody => 'आपका Omi अभी ऑडियो नहीं भेज रहा। फ़िलहाल अपना फ़ोन इस्तेमाल करें।'; + + @override + String get onboardingActMicTitle => 'Omi को आपका माइक्रोफ़ोन चाहिए'; + + @override + String get onboardingActNoAudioOmi => 'आपका Omi सुनाई नहीं दे रहा। देखें कि वह चालू है और पास है।'; + + @override + String get onboardingActNoAudioPhone => 'अभी आपकी आवाज़ नहीं आ रही। थोड़ा ज़ोर से बोलकर देखें।'; + + @override + String get onboardingActNotesLabel => 'आपका दिन, संक्षेप में'; + + @override + String get onboardingActPhoneMic => 'फ़ोन माइक्रोफ़ोन'; + + @override + String get onboardingActRateTitle => 'अब तक Omi कैसा लगा?'; + + @override + String get onboardingActRemembers => 'Omi याद रखता है, ताकि आपको न रखना पड़े'; + + @override + String get onboardingActSavedToTasks => 'आपके कामों में सहेजा गया'; + + @override + String get onboardingActTasksLabel => 'Omi ने आपके काम पकड़ लिए'; + + @override + String get onboardingActWriting => 'सारांश बन रहा है…'; + + @override + String get onboardingActYourWords => 'आपने यह कहा'; + + @override + String get onboardingConnectOmi => 'Omi कनेक्ट करें'; + + @override + String get onboardingConnectTapToConnect => 'कनेक्ट करने के लिए अपने Omi पर टैप करें।'; + + @override + String get onboardingNoOmiYet => 'अभी तक आपका Omi नहीं आया?'; + + @override + String get onboardingOmiNotOrdered => 'अभी ऑर्डर नहीं किया'; + + @override + String get onboardingOmiOrdered => 'मैंने ऑर्डर कर दिया है'; + + @override + String get onboardingPermissionTapToFix => 'ठीक करने के लिए टैप करें'; } diff --git a/app/lib/l10n/app_localizations_hr.dart b/app/lib/l10n/app_localizations_hr.dart index 2bfffeb8d6f..f972f6a13da 100644 --- a/app/lib/l10n/app_localizations_hr.dart +++ b/app/lib/l10n/app_localizations_hr.dart @@ -13326,4 +13326,104 @@ class AppLocalizationsHr extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Na listi čekanja ste. Javit ćemo vam kad bude mjesta.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Sve je spremno, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Ručak sa Sam, koja se u lipnju seli u Lisabon. Želite poslati prezentaciju timu do petka i kupiti mlijeko na putu kući.'; + + @override + String get onboardingActDemoTask1 => 'Poslati prezentaciju timu do petka'; + + @override + String get onboardingActDemoTask2 => 'Kupiti mlijeko na putu kući'; + + @override + String get onboardingActDemoTitle => 'Ručak sa Sam'; + + @override + String get onboardingActDictateBody => 'Recite to na svoj način. Omi se brine za ostalo.'; + + @override + String get onboardingActDictateBodyOmi => 'Samo pričajte. Vaš Omi sluša.'; + + @override + String get onboardingActDictateExample => + 'Upravo sam ručao sa Sam, u lipnju se seli u Lisabon! Podsjeti me da pošaljem prezentaciju timu do petka i da kupim mlijeko na putu kući.'; + + @override + String get onboardingActDictateHint => 'Dodirnite kvačicu kad završite'; + + @override + String get onboardingActDictateTitle => 'Ispričajte Omiju svoj dan'; + + @override + String get onboardingActIntro => 'Isprobajmo Omi.'; + + @override + String get onboardingActJustNow => 'Upravo sada'; + + @override + String get onboardingActMicAllow => 'Dopusti mikrofon'; + + @override + String get onboardingActMicBody => 'Da vas Omi čuje kad mu se obratite.'; + + @override + String get onboardingActMicFallbackBody => 'Omi još ne šalje zvuk. Za sada koristite telefon.'; + + @override + String get onboardingActMicTitle => 'Omi treba vaš mikrofon'; + + @override + String get onboardingActNoAudioOmi => 'Ne čujemo Omi. Provjerite je li uključen i blizu.'; + + @override + String get onboardingActNoAudioPhone => 'Još vas ne čujemo. Pokušajte govoriti malo glasnije.'; + + @override + String get onboardingActNotesLabel => 'Vaš dan, ukratko'; + + @override + String get onboardingActPhoneMic => 'Mikrofon telefona'; + + @override + String get onboardingActRateTitle => 'Sviđa li vam se Omi zasad?'; + + @override + String get onboardingActRemembers => 'Omi pamti da vi ne morate'; + + @override + String get onboardingActSavedToTasks => 'Spremljeno u vaše zadatke'; + + @override + String get onboardingActTasksLabel => 'Omi je uhvatio vaše zadatke'; + + @override + String get onboardingActWriting => 'Sažimamo…'; + + @override + String get onboardingActYourWords => 'Evo što ste rekli'; + + @override + String get onboardingConnectOmi => 'Povežite Omi'; + + @override + String get onboardingConnectTapToConnect => 'Dodirnite svoj Omi da biste se povezali.'; + + @override + String get onboardingNoOmiYet => 'Još nemate Omi?'; + + @override + String get onboardingOmiNotOrdered => 'Još nisam naručio/la'; + + @override + String get onboardingOmiOrdered => 'Naručio/la sam ga'; + + @override + String get onboardingPermissionTapToFix => 'Dodirnite za popravak'; } diff --git a/app/lib/l10n/app_localizations_hu.dart b/app/lib/l10n/app_localizations_hu.dart index fb25c2538c5..45817976b6a 100644 --- a/app/lib/l10n/app_localizations_hu.dart +++ b/app/lib/l10n/app_localizations_hu.dart @@ -13344,4 +13344,104 @@ class AppLocalizationsHu extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Fent vagy a várólistán. Szólunk, ha lesz hely.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Minden kész, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Ebéd Sammel, aki júniusban Lisszabonba költözik. Péntekig el akarod küldeni a prezentációt a csapatnak, és hazafelé tejet venni.'; + + @override + String get onboardingActDemoTask1 => 'Péntekig elküldeni a prezentációt a csapatnak'; + + @override + String get onboardingActDemoTask2 => 'Hazafelé tejet venni'; + + @override + String get onboardingActDemoTitle => 'Ebéd Sammel'; + + @override + String get onboardingActDictateBody => 'Mondd a magad módján. A többit az Omi intézi.'; + + @override + String get onboardingActDictateBodyOmi => 'Csak beszélj. Az Omid figyel.'; + + @override + String get onboardingActDictateExample => + 'Most ebédeltem Sammel, júniusban Lisszabonba költözik! Emlékeztess, hogy péntekig küldjem el a prezentációt a csapatnak, és hazafelé vegyek tejet.'; + + @override + String get onboardingActDictateHint => 'Ha végeztél, koppints a pipára'; + + @override + String get onboardingActDictateTitle => 'Meséld el az Ominak a napod'; + + @override + String get onboardingActIntro => 'Próbáljuk ki az Omit.'; + + @override + String get onboardingActJustNow => 'Épp most'; + + @override + String get onboardingActMicAllow => 'Mikrofon engedélyezése'; + + @override + String get onboardingActMicBody => 'Hogy az Omi halljon, amikor beszélsz hozzá.'; + + @override + String get onboardingActMicFallbackBody => 'Az Omi még nem küld hangot. Egyelőre használd a telefonodat.'; + + @override + String get onboardingActMicTitle => 'Az Ominak kell a mikrofonod'; + + @override + String get onboardingActNoAudioOmi => 'Nem halljuk az Omit. Ellenőrizd, hogy be van-e kapcsolva és közel van-e.'; + + @override + String get onboardingActNoAudioPhone => 'Még nem hallunk. Próbálj kicsit hangosabban beszélni.'; + + @override + String get onboardingActNotesLabel => 'A napod röviden'; + + @override + String get onboardingActPhoneMic => 'Telefon mikrofonja'; + + @override + String get onboardingActRateTitle => 'Tetszik eddig az Omi?'; + + @override + String get onboardingActRemembers => 'Az Omi emlékszik helyetted'; + + @override + String get onboardingActSavedToTasks => 'Mentve a teendőid közé'; + + @override + String get onboardingActTasksLabel => 'Az Omi kiszúrta a teendőidet'; + + @override + String get onboardingActWriting => 'Összefoglalás…'; + + @override + String get onboardingActYourWords => 'Ezt mondtad'; + + @override + String get onboardingConnectOmi => 'Omi csatlakoztatása'; + + @override + String get onboardingConnectTapToConnect => 'Koppints az Omidra a csatlakozáshoz.'; + + @override + String get onboardingNoOmiYet => 'Még nincs meg az Omid?'; + + @override + String get onboardingOmiNotOrdered => 'Még nem rendeltem'; + + @override + String get onboardingOmiOrdered => 'Már megrendeltem'; + + @override + String get onboardingPermissionTapToFix => 'Koppints a javításhoz'; } diff --git a/app/lib/l10n/app_localizations_id.dart b/app/lib/l10n/app_localizations_id.dart index 5c8dd13c72f..2798ba9c2b9 100644 --- a/app/lib/l10n/app_localizations_id.dart +++ b/app/lib/l10n/app_localizations_id.dart @@ -13302,4 +13302,104 @@ class AppLocalizationsId extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Anda ada di daftar tunggu. Kami akan memberi tahu saat ada tempat.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Semua siap, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Makan siang dengan Sam, yang pindah ke Lisbon bulan Juni. Kamu mau kirim deck ke tim sebelum Jumat dan beli susu di jalan pulang.'; + + @override + String get onboardingActDemoTask1 => 'Kirim deck ke tim sebelum Jumat'; + + @override + String get onboardingActDemoTask2 => 'Beli susu di jalan pulang'; + + @override + String get onboardingActDemoTitle => 'Makan siang dengan Sam'; + + @override + String get onboardingActDictateBody => 'Katakan dengan gaya Anda. Omi urus sisanya.'; + + @override + String get onboardingActDictateBodyOmi => 'Bicara saja. Omi kamu sedang mendengarkan.'; + + @override + String get onboardingActDictateExample => + 'Baru makan siang sama Sam, dia pindah ke Lisbon bulan Juni! Ingatkan aku kirim deck ke tim sebelum Jumat, dan beli susu di jalan pulang.'; + + @override + String get onboardingActDictateHint => 'Ketuk centang jika sudah selesai'; + + @override + String get onboardingActDictateTitle => 'Ceritakan hari Anda ke Omi'; + + @override + String get onboardingActIntro => 'Ayo coba Omi.'; + + @override + String get onboardingActJustNow => 'Baru saja'; + + @override + String get onboardingActMicAllow => 'Izinkan Mikrofon'; + + @override + String get onboardingActMicBody => 'Agar Omi bisa mendengar saat Anda bicara.'; + + @override + String get onboardingActMicFallbackBody => 'Omi kamu belum mengirim audio. Pakai ponselmu dulu.'; + + @override + String get onboardingActMicTitle => 'Omi butuh mikrofon Anda'; + + @override + String get onboardingActNoAudioOmi => 'Omi kamu tidak terdengar. Pastikan menyala dan dekat.'; + + @override + String get onboardingActNoAudioPhone => 'Suaramu belum terdengar. Coba bicara sedikit lebih keras.'; + + @override + String get onboardingActNotesLabel => 'Harimu, dirangkum'; + + @override + String get onboardingActPhoneMic => 'Mikrofon ponsel'; + + @override + String get onboardingActRateTitle => 'Suka Omi sejauh ini?'; + + @override + String get onboardingActRemembers => 'Omi mengingat, jadi kamu tidak perlu'; + + @override + String get onboardingActSavedToTasks => 'Disimpan ke tugasmu'; + + @override + String get onboardingActTasksLabel => 'Omi menangkap tugasmu'; + + @override + String get onboardingActWriting => 'Merangkum…'; + + @override + String get onboardingActYourWords => 'Ini yang Anda katakan'; + + @override + String get onboardingConnectOmi => 'Hubungkan Omi'; + + @override + String get onboardingConnectTapToConnect => 'Ketuk Omi kamu untuk menghubungkan.'; + + @override + String get onboardingNoOmiYet => 'Belum punya Omi?'; + + @override + String get onboardingOmiNotOrdered => 'Belum memesan'; + + @override + String get onboardingOmiOrdered => 'Saya sudah memesannya'; + + @override + String get onboardingPermissionTapToFix => 'Ketuk untuk memperbaiki'; } diff --git a/app/lib/l10n/app_localizations_it.dart b/app/lib/l10n/app_localizations_it.dart index 8eab34197ac..9f577fa7253 100644 --- a/app/lib/l10n/app_localizations_it.dart +++ b/app/lib/l10n/app_localizations_it.dart @@ -13363,4 +13363,104 @@ class AppLocalizationsIt extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Sei in lista d\'attesa. Ti avviseremo quando si libera un posto.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Tutto pronto, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Pranzo con Sam, che a giugno si trasferisce a Lisbona. Vuoi mandare la presentazione al team entro venerdì e prendere il latte tornando a casa.'; + + @override + String get onboardingActDemoTask1 => 'Mandare la presentazione al team entro venerdì'; + + @override + String get onboardingActDemoTask2 => 'Prendere il latte tornando a casa'; + + @override + String get onboardingActDemoTitle => 'Pranzo con Sam'; + + @override + String get onboardingActDictateBody => 'Dillo a modo tuo. Al resto pensa Omi.'; + + @override + String get onboardingActDictateBodyOmi => 'Parla e basta. Il tuo Omi ti ascolta.'; + + @override + String get onboardingActDictateExample => + 'Ho appena pranzato con Sam, a giugno si trasferisce a Lisbona! Ricordami di mandare la presentazione al team entro venerdì e di prendere il latte tornando a casa.'; + + @override + String get onboardingActDictateHint => 'Tocca la spunta quando hai finito'; + + @override + String get onboardingActDictateTitle => 'Racconta a Omi la tua giornata'; + + @override + String get onboardingActIntro => 'Proviamo Omi.'; + + @override + String get onboardingActJustNow => 'Proprio ora'; + + @override + String get onboardingActMicAllow => 'Consenti microfono'; + + @override + String get onboardingActMicBody => 'Così Omi ti sente quando gli parli.'; + + @override + String get onboardingActMicFallbackBody => 'Il tuo Omi non invia ancora audio. Per ora usa il telefono.'; + + @override + String get onboardingActMicTitle => 'Omi ha bisogno del microfono'; + + @override + String get onboardingActNoAudioOmi => 'Non sentiamo il tuo Omi. Controlla che sia acceso e vicino.'; + + @override + String get onboardingActNoAudioPhone => 'Non ti sentiamo ancora. Prova a parlare un po\' più forte.'; + + @override + String get onboardingActNotesLabel => 'La tua giornata, in sintesi'; + + @override + String get onboardingActPhoneMic => 'Microfono del telefono'; + + @override + String get onboardingActRateTitle => 'Ti piace Omi finora?'; + + @override + String get onboardingActRemembers => 'Omi ricorda al posto tuo'; + + @override + String get onboardingActSavedToTasks => 'Salvato nelle tue attività'; + + @override + String get onboardingActTasksLabel => 'Omi ha colto le tue cose da fare'; + + @override + String get onboardingActWriting => 'Riassumendo…'; + + @override + String get onboardingActYourWords => 'Ecco cosa hai detto'; + + @override + String get onboardingConnectOmi => 'Connetti Omi'; + + @override + String get onboardingConnectTapToConnect => 'Tocca il tuo Omi per collegarlo.'; + + @override + String get onboardingNoOmiYet => 'Non hai ancora il tuo Omi?'; + + @override + String get onboardingOmiNotOrdered => 'Non ancora ordinato'; + + @override + String get onboardingOmiOrdered => 'L\'ho ordinato'; + + @override + String get onboardingPermissionTapToFix => 'Tocca per risolvere'; } diff --git a/app/lib/l10n/app_localizations_ja.dart b/app/lib/l10n/app_localizations_ja.dart index 59579fc4b63..ef1056bab12 100644 --- a/app/lib/l10n/app_localizations_ja.dart +++ b/app/lib/l10n/app_localizations_ja.dart @@ -13106,4 +13106,102 @@ class AppLocalizationsJa extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'ウェイトリストに登録しました。空きが出たらお知らせします。'; + + @override + String onboardingActCongratsTitle(String name) { + return '準備完了です、$nameさん'; + } + + @override + String get onboardingActDemoSummary => '6月にリスボンへ引っ越すサムとランチ。金曜までにチームへ資料を送り、帰りに牛乳を買う予定。'; + + @override + String get onboardingActDemoTask1 => '金曜までにチームへ資料を送る'; + + @override + String get onboardingActDemoTask2 => '帰りに牛乳を買う'; + + @override + String get onboardingActDemoTitle => 'サムとランチ'; + + @override + String get onboardingActDictateBody => '思うままに話してください。あとはOmiにお任せ。'; + + @override + String get onboardingActDictateBodyOmi => '話すだけでOK。Omiが聞いています。'; + + @override + String get onboardingActDictateExample => 'さっきサムとランチしたよ。6月にリスボンへ引っ越すんだって!金曜までにチームへ資料を送るのと、帰りに牛乳を買うのをリマインドして。'; + + @override + String get onboardingActDictateHint => '話し終えたらチェックをタップ'; + + @override + String get onboardingActDictateTitle => '今日のことをOmiに話そう'; + + @override + String get onboardingActIntro => 'Omiを試してみましょう。'; + + @override + String get onboardingActJustNow => 'たった今'; + + @override + String get onboardingActMicAllow => 'マイクを許可'; + + @override + String get onboardingActMicBody => '話しかけたときにOmiが聞き取れるようにします。'; + + @override + String get onboardingActMicFallbackBody => 'Omiからまだ音声が届いていません。今はスマートフォンを使いましょう。'; + + @override + String get onboardingActMicTitle => 'Omiにマイクの許可が必要です'; + + @override + String get onboardingActNoAudioOmi => 'Omiの音声が聞こえません。電源が入っていて近くにあるか確認してください。'; + + @override + String get onboardingActNoAudioPhone => 'まだ声が聞こえません。少し大きな声で話してみてください。'; + + @override + String get onboardingActNotesLabel => '今日のまとめ'; + + @override + String get onboardingActPhoneMic => 'スマホのマイク'; + + @override + String get onboardingActRateTitle => 'Omiはいかがですか?'; + + @override + String get onboardingActRemembers => '覚えておくのは Omi にお任せ'; + + @override + String get onboardingActSavedToTasks => 'タスクに保存しました'; + + @override + String get onboardingActTasksLabel => 'Omiがやることを見つけました'; + + @override + String get onboardingActWriting => '要約しています…'; + + @override + String get onboardingActYourWords => 'あなたが話した内容'; + + @override + String get onboardingConnectOmi => 'Omiを接続'; + + @override + String get onboardingConnectTapToConnect => 'Omiをタップして接続します。'; + + @override + String get onboardingNoOmiYet => 'まだOmiをお持ちでないですか?'; + + @override + String get onboardingOmiNotOrdered => 'まだ注文していません'; + + @override + String get onboardingOmiOrdered => '注文済みです'; + + @override + String get onboardingPermissionTapToFix => 'タップして修正'; } diff --git a/app/lib/l10n/app_localizations_kn.dart b/app/lib/l10n/app_localizations_kn.dart index a2711f4037c..f6e5f906715 100644 --- a/app/lib/l10n/app_localizations_kn.dart +++ b/app/lib/l10n/app_localizations_kn.dart @@ -13331,4 +13331,104 @@ class AppLocalizationsKn extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'ನೀವು ಕಾಯುವ ಪಟ್ಟಿಯಲ್ಲಿದ್ದೀರಿ. ಸ್ಥಳ ಲಭ್ಯವಾದಾಗ ತಿಳಿಸುತ್ತೇವೆ.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'ಎಲ್ಲವೂ ಸಿದ್ಧ, $name'; + } + + @override + String get onboardingActDemoSummary => + 'ಜೂನ್‌ನಲ್ಲಿ ಲಿಸ್ಬನ್‌ಗೆ ಹೋಗುತ್ತಿರುವ ಸ್ಯಾಮ್ ಜೊತೆ ಊಟ. ಶುಕ್ರವಾರದೊಳಗೆ ತಂಡಕ್ಕೆ ಡೆಕ್ ಕಳುಹಿಸಬೇಕು, ಮನೆಗೆ ಹೋಗುವಾಗ ಹಾಲು ತರಬೇಕು.'; + + @override + String get onboardingActDemoTask1 => 'ಶುಕ್ರವಾರದೊಳಗೆ ತಂಡಕ್ಕೆ ಡೆಕ್ ಕಳುಹಿಸುವುದು'; + + @override + String get onboardingActDemoTask2 => 'ಮನೆಗೆ ಹೋಗುವಾಗ ಹಾಲು ತರುವುದು'; + + @override + String get onboardingActDemoTitle => 'ಸ್ಯಾಮ್ ಜೊತೆ ಊಟ'; + + @override + String get onboardingActDictateBody => 'ನಿಮ್ಮದೇ ರೀತಿಯಲ್ಲಿ ಹೇಳಿ. ಉಳಿದದ್ದು Omi ನೋಡಿಕೊಳ್ಳುತ್ತದೆ.'; + + @override + String get onboardingActDictateBodyOmi => 'ಸುಮ್ಮನೆ ಮಾತಾಡಿ. ನಿಮ್ಮ Omi ಕೇಳುತ್ತಿದೆ.'; + + @override + String get onboardingActDictateExample => + 'ಈಗಷ್ಟೇ ಸ್ಯಾಮ್ ಜೊತೆ ಊಟ ಮಾಡಿದೆ, ಅವಳು ಜೂನ್‌ನಲ್ಲಿ ಲಿಸ್ಬನ್‌ಗೆ ಹೋಗುತ್ತಿದ್ದಾಳೆ! ಶುಕ್ರವಾರದೊಳಗೆ ತಂಡಕ್ಕೆ ಡೆಕ್ ಕಳುಹಿಸಲು ಮತ್ತು ಮನೆಗೆ ಹೋಗುವಾಗ ಹಾಲು ತರಲು ನೆನಪಿಸು.'; + + @override + String get onboardingActDictateHint => 'ಮುಗಿದ ನಂತರ ಟಿಕ್ ಟ್ಯಾಪ್ ಮಾಡಿ'; + + @override + String get onboardingActDictateTitle => 'ನಿಮ್ಮ ದಿನದ ಬಗ್ಗೆ Omi ಗೆ ಹೇಳಿ'; + + @override + String get onboardingActIntro => 'Omi ಪ್ರಯತ್ನಿಸೋಣ.'; + + @override + String get onboardingActJustNow => 'ಈಗಷ್ಟೇ'; + + @override + String get onboardingActMicAllow => 'ಮೈಕ್ರೊಫೋನ್ ಅನುಮತಿಸಿ'; + + @override + String get onboardingActMicBody => 'ನೀವು ಮಾತನಾಡಿದಾಗ Omi ಕೇಳಲು.'; + + @override + String get onboardingActMicFallbackBody => 'ನಿಮ್ಮ Omi ಇನ್ನೂ ಆಡಿಯೋ ಕಳುಹಿಸುತ್ತಿಲ್ಲ. ಸದ್ಯಕ್ಕೆ ನಿಮ್ಮ ಫೋನ್ ಬಳಸಿ.'; + + @override + String get onboardingActMicTitle => 'Omi ಗೆ ನಿಮ್ಮ ಮೈಕ್ರೊಫೋನ್ ಬೇಕು'; + + @override + String get onboardingActNoAudioOmi => 'ನಿಮ್ಮ Omi ಕೇಳಿಸುತ್ತಿಲ್ಲ. ಅದು ಆನ್ ಆಗಿದೆ ಮತ್ತು ಹತ್ತಿರವಿದೆಯೇ ನೋಡಿ.'; + + @override + String get onboardingActNoAudioPhone => 'ಇನ್ನೂ ನಿಮ್ಮ ಧ್ವನಿ ಕೇಳಿಸುತ್ತಿಲ್ಲ. ಸ್ವಲ್ಪ ಜೋರಾಗಿ ಮಾತಾಡಿ ನೋಡಿ.'; + + @override + String get onboardingActNotesLabel => 'ನಿಮ್ಮ ದಿನದ ಸಾರಾಂಶ'; + + @override + String get onboardingActPhoneMic => 'ಫೋನ್ ಮೈಕ್ರೊಫೋನ್'; + + @override + String get onboardingActRateTitle => 'ಇಲ್ಲಿಯವರೆಗೆ Omi ಇಷ್ಟವಾಯಿತೇ?'; + + @override + String get onboardingActRemembers => 'ನೀವು ನೆನಪಿಡಬೇಕಿಲ್ಲ, Omi ನೆನಪಿಡುತ್ತದೆ'; + + @override + String get onboardingActSavedToTasks => 'ನಿಮ್ಮ ಕೆಲಸಗಳಿಗೆ ಉಳಿಸಲಾಗಿದೆ'; + + @override + String get onboardingActTasksLabel => 'Omi ನಿಮ್ಮ ಕೆಲಸಗಳನ್ನು ಗುರುತಿಸಿದೆ'; + + @override + String get onboardingActWriting => 'ಸಾರಾಂಶ ಮಾಡುತ್ತಿದೆ…'; + + @override + String get onboardingActYourWords => 'ನೀವು ಹೇಳಿದ್ದು ಇದು'; + + @override + String get onboardingConnectOmi => 'Omi ಸಂಪರ್ಕಿಸಿ'; + + @override + String get onboardingConnectTapToConnect => 'ಸಂಪರ್ಕಿಸಲು ನಿಮ್ಮ Omi ಟ್ಯಾಪ್ ಮಾಡಿ.'; + + @override + String get onboardingNoOmiYet => 'ಇನ್ನೂ ನಿಮ್ಮ Omi ಇಲ್ಲವೇ?'; + + @override + String get onboardingOmiNotOrdered => 'ಇನ್ನೂ ಆರ್ಡರ್ ಮಾಡಿಲ್ಲ'; + + @override + String get onboardingOmiOrdered => 'ನಾನು ಆರ್ಡರ್ ಮಾಡಿದ್ದೇನೆ'; + + @override + String get onboardingPermissionTapToFix => 'ಸರಿಪಡಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ'; } diff --git a/app/lib/l10n/app_localizations_ko.dart b/app/lib/l10n/app_localizations_ko.dart index 887281fa737..9e762c5d003 100644 --- a/app/lib/l10n/app_localizations_ko.dart +++ b/app/lib/l10n/app_localizations_ko.dart @@ -13111,4 +13111,103 @@ class AppLocalizationsKo extends AppLocalizations { @override String get chatAppsWaitlistJoined => '대기자 명단에 등록되었습니다. 자리가 나면 알려 드릴게요.'; + + @override + String onboardingActCongratsTitle(String name) { + return '$name님, 준비가 끝났어요'; + } + + @override + String get onboardingActDemoSummary => '6월에 리스본으로 이사하는 샘과 점심. 금요일까지 팀에 자료를 보내고, 집에 가는 길에 우유를 사야 해요.'; + + @override + String get onboardingActDemoTask1 => '금요일까지 팀에 발표 자료 보내기'; + + @override + String get onboardingActDemoTask2 => '집에 가는 길에 우유 사기'; + + @override + String get onboardingActDemoTitle => '샘과 점심'; + + @override + String get onboardingActDictateBody => '편하게 말하세요. 나머지는 Omi가 할게요.'; + + @override + String get onboardingActDictateBodyOmi => '그냥 말하세요. Omi가 듣고 있어요.'; + + @override + String get onboardingActDictateExample => + '방금 샘이랑 점심 먹었어. 6월에 리스본으로 이사 간대! 금요일까지 팀에 발표 자료 보내는 거랑, 집에 가는 길에 우유 사는 거 알려 줘.'; + + @override + String get onboardingActDictateHint => '다 말했으면 체크를 누르세요'; + + @override + String get onboardingActDictateTitle => 'Omi에게 오늘 하루를 말해 보세요'; + + @override + String get onboardingActIntro => 'Omi를 써 볼까요?'; + + @override + String get onboardingActJustNow => '방금'; + + @override + String get onboardingActMicAllow => '마이크 허용'; + + @override + String get onboardingActMicBody => '말할 때 Omi가 들을 수 있도록요.'; + + @override + String get onboardingActMicFallbackBody => 'Omi가 아직 오디오를 보내지 않아요. 지금은 휴대폰을 사용하세요.'; + + @override + String get onboardingActMicTitle => 'Omi에 마이크 권한이 필요해요'; + + @override + String get onboardingActNoAudioOmi => 'Omi 소리가 들리지 않아요. 켜져 있고 가까이 있는지 확인하세요.'; + + @override + String get onboardingActNoAudioPhone => '아직 목소리가 들리지 않아요. 조금 더 크게 말해 보세요.'; + + @override + String get onboardingActNotesLabel => '오늘 하루 요약'; + + @override + String get onboardingActPhoneMic => '휴대폰 마이크'; + + @override + String get onboardingActRateTitle => 'Omi 마음에 드시나요?'; + + @override + String get onboardingActRemembers => '기억은 Omi가 대신해요'; + + @override + String get onboardingActSavedToTasks => '할 일에 저장됨'; + + @override + String get onboardingActTasksLabel => 'Omi가 할 일을 찾았어요'; + + @override + String get onboardingActWriting => '요약하는 중…'; + + @override + String get onboardingActYourWords => '이렇게 말했어요'; + + @override + String get onboardingConnectOmi => 'Omi 연결'; + + @override + String get onboardingConnectTapToConnect => 'Omi를 탭해서 연결하세요.'; + + @override + String get onboardingNoOmiYet => '아직 Omi가 없으신가요?'; + + @override + String get onboardingOmiNotOrdered => '아직 주문하지 않았어요'; + + @override + String get onboardingOmiOrdered => '주문했어요'; + + @override + String get onboardingPermissionTapToFix => '탭하여 해결'; } diff --git a/app/lib/l10n/app_localizations_lt.dart b/app/lib/l10n/app_localizations_lt.dart index 68f58dd4c93..2eb788c69f6 100644 --- a/app/lib/l10n/app_localizations_lt.dart +++ b/app/lib/l10n/app_localizations_lt.dart @@ -13316,4 +13316,104 @@ class AppLocalizationsLt extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Esate laukiančiųjų sąraše. Pranešime, kai atsiras vietos.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Viskas paruošta, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Pietūs su Sem, kuri birželį persikelia į Lisaboną. Iki penktadienio norite išsiųsti komandai pristatymą ir grįžtant nupirkti pieno.'; + + @override + String get onboardingActDemoTask1 => 'Iki penktadienio nusiųsti pristatymą komandai'; + + @override + String get onboardingActDemoTask2 => 'Grįžtant namo nupirkti pieno'; + + @override + String get onboardingActDemoTitle => 'Pietūs su Sem'; + + @override + String get onboardingActDictateBody => 'Sakykite savaip. Visa kita padarys Omi.'; + + @override + String get onboardingActDictateBodyOmi => 'Tiesiog kalbėkite. Jūsų Omi klauso.'; + + @override + String get onboardingActDictateExample => + 'Ką tik pietavau su Sem, birželį ji kraustosi į Lisaboną! Primink man iki penktadienio nusiųsti pristatymą komandai ir grįžtant namo nupirkti pieno.'; + + @override + String get onboardingActDictateHint => 'Baigę bakstelėkite varnelę'; + + @override + String get onboardingActDictateTitle => 'Papasakokite Omi apie savo dieną'; + + @override + String get onboardingActIntro => 'Išbandykime Omi.'; + + @override + String get onboardingActJustNow => 'Ką tik'; + + @override + String get onboardingActMicAllow => 'Leisti mikrofoną'; + + @override + String get onboardingActMicBody => 'Kad Omi girdėtų, kai su juo kalbate.'; + + @override + String get onboardingActMicFallbackBody => 'Omi dar nesiunčia garso. Kol kas naudokite telefoną.'; + + @override + String get onboardingActMicTitle => 'Omi reikia mikrofono'; + + @override + String get onboardingActNoAudioOmi => 'Omi negirdėti. Patikrinkite, ar jis įjungtas ir šalia.'; + + @override + String get onboardingActNoAudioPhone => 'Dar jūsų negirdime. Pabandykite kalbėti šiek tiek garsiau.'; + + @override + String get onboardingActNotesLabel => 'Jūsų diena trumpai'; + + @override + String get onboardingActPhoneMic => 'Telefono mikrofonas'; + + @override + String get onboardingActRateTitle => 'Ar Omi jums patinka?'; + + @override + String get onboardingActRemembers => 'Omi prisimena, kad jums nereikėtų'; + + @override + String get onboardingActSavedToTasks => 'Išsaugota jūsų užduotyse'; + + @override + String get onboardingActTasksLabel => 'Omi pastebėjo jūsų darbus'; + + @override + String get onboardingActWriting => 'Apibendriname…'; + + @override + String get onboardingActYourWords => 'Štai ką pasakėte'; + + @override + String get onboardingConnectOmi => 'Prijunkite Omi'; + + @override + String get onboardingConnectTapToConnect => 'Palieskite savo Omi, kad prisijungtumėte.'; + + @override + String get onboardingNoOmiYet => 'Dar neturite Omi?'; + + @override + String get onboardingOmiNotOrdered => 'Dar neužsisakiau'; + + @override + String get onboardingOmiOrdered => 'Jau užsisakiau'; + + @override + String get onboardingPermissionTapToFix => 'Bakstelėkite, kad pataisytumėte'; } diff --git a/app/lib/l10n/app_localizations_lv.dart b/app/lib/l10n/app_localizations_lv.dart index 1bba5d8436a..c5e3b51c232 100644 --- a/app/lib/l10n/app_localizations_lv.dart +++ b/app/lib/l10n/app_localizations_lv.dart @@ -13321,4 +13321,104 @@ class AppLocalizationsLv extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Jūs esat gaidīšanas sarakstā. Mēs paziņosim, kad būs vieta.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Viss gatavs, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Pusdienas ar Semu, kura jūnijā pārceļas uz Lisabonu. Līdz piektdienai jānosūta prezentācija komandai un mājupceļā jānopērk piens.'; + + @override + String get onboardingActDemoTask1 => 'Līdz piektdienai nosūtīt prezentāciju komandai'; + + @override + String get onboardingActDemoTask2 => 'Pa ceļam mājās nopirkt pienu'; + + @override + String get onboardingActDemoTitle => 'Pusdienas ar Semu'; + + @override + String get onboardingActDictateBody => 'Sakiet savā veidā. Pārējo paveiks Omi.'; + + @override + String get onboardingActDictateBodyOmi => 'Vienkārši runājiet. Jūsu Omi klausās.'; + + @override + String get onboardingActDictateExample => + 'Tikko paēdu pusdienas ar Semu, jūnijā viņa pārceļas uz Lisabonu! Atgādini man līdz piektdienai nosūtīt prezentāciju komandai un pa ceļam mājās nopirkt pienu.'; + + @override + String get onboardingActDictateHint => 'Kad esat pabeidzis, pieskarieties ķeksītim'; + + @override + String get onboardingActDictateTitle => 'Pastāstiet Omi par savu dienu'; + + @override + String get onboardingActIntro => 'Izmēģināsim Omi.'; + + @override + String get onboardingActJustNow => 'Tikko'; + + @override + String get onboardingActMicAllow => 'Atļaut mikrofonu'; + + @override + String get onboardingActMicBody => 'Lai Omi jūs dzirdētu, kad ar to runājat.'; + + @override + String get onboardingActMicFallbackBody => 'Omi vēl nesūta skaņu. Pagaidām izmantojiet tālruni.'; + + @override + String get onboardingActMicTitle => 'Omi vajag jūsu mikrofonu'; + + @override + String get onboardingActNoAudioOmi => 'Omi nav dzirdams. Pārliecinieties, ka tas ir ieslēgts un tuvumā.'; + + @override + String get onboardingActNoAudioPhone => 'Vēl jūs nedzirdam. Mēģiniet runāt mazliet skaļāk.'; + + @override + String get onboardingActNotesLabel => 'Jūsu diena īsumā'; + + @override + String get onboardingActPhoneMic => 'Tālruņa mikrofons'; + + @override + String get onboardingActRateTitle => 'Vai Omi jums patīk?'; + + @override + String get onboardingActRemembers => 'Omi atceras, lai jums nebūtu jāatceras'; + + @override + String get onboardingActSavedToTasks => 'Saglabāts jūsu uzdevumos'; + + @override + String get onboardingActTasksLabel => 'Omi pamanīja jūsu darāmo'; + + @override + String get onboardingActWriting => 'Apkopojam…'; + + @override + String get onboardingActYourWords => 'Lūk, ko jūs teicāt'; + + @override + String get onboardingConnectOmi => 'Savienot Omi'; + + @override + String get onboardingConnectTapToConnect => 'Pieskarieties savam Omi, lai savienotu.'; + + @override + String get onboardingNoOmiYet => 'Vēl nav Omi?'; + + @override + String get onboardingOmiNotOrdered => 'Vēl neesmu pasūtījis'; + + @override + String get onboardingOmiOrdered => 'Esmu to pasūtījis'; + + @override + String get onboardingPermissionTapToFix => 'Pieskarieties, lai labotu'; } diff --git a/app/lib/l10n/app_localizations_mk.dart b/app/lib/l10n/app_localizations_mk.dart index cad89fff086..945b5cbc2ce 100644 --- a/app/lib/l10n/app_localizations_mk.dart +++ b/app/lib/l10n/app_localizations_mk.dart @@ -13353,4 +13353,104 @@ class AppLocalizationsMk extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Вие сте на листата на чекање. Ќе ве известиме кога ќе има место.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Сè е подготвено, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Ручек со Сем, која во јуни се сели во Лисабон. Сакате да ја испратите презентацијата до тимот до петок и да купите млеко на пат кон дома.'; + + @override + String get onboardingActDemoTask1 => 'Да ја пратам презентацијата на тимот до петок'; + + @override + String get onboardingActDemoTask2 => 'Да купам млеко на пат кон дома'; + + @override + String get onboardingActDemoTitle => 'Ручек со Сем'; + + @override + String get onboardingActDictateBody => 'Кажете го на свој начин. Omi ќе се погрижи за останатото.'; + + @override + String get onboardingActDictateBodyOmi => 'Само зборувајте. Вашиот Omi слуша.'; + + @override + String get onboardingActDictateExample => + 'Штотуку ручав со Сем, се сели во Лисабон во јуни! Потсети ме да ја пратам презентацијата на тимот до петок и да купам млеко на пат кон дома.'; + + @override + String get onboardingActDictateHint => 'Допрете ја штиклата кога ќе завршите'; + + @override + String get onboardingActDictateTitle => 'Кажете му на Omi за вашиот ден'; + + @override + String get onboardingActIntro => 'Да го пробаме Omi.'; + + @override + String get onboardingActJustNow => 'Токму сега'; + + @override + String get onboardingActMicAllow => 'Дозволи микрофон'; + + @override + String get onboardingActMicBody => 'За Omi да ве слуша кога зборувате со него.'; + + @override + String get onboardingActMicFallbackBody => 'Omi сè уште не испраќа звук. Засега користете го телефонот.'; + + @override + String get onboardingActMicTitle => 'На Omi му треба микрофонот'; + + @override + String get onboardingActNoAudioOmi => 'Не се слуша Omi. Проверете дали е вклучен и блиску.'; + + @override + String get onboardingActNoAudioPhone => 'Сè уште не ве слушаме. Обидете се да зборувате малку погласно.'; + + @override + String get onboardingActNotesLabel => 'Вашиот ден, накратко'; + + @override + String get onboardingActPhoneMic => 'Микрофон на телефонот'; + + @override + String get onboardingActRateTitle => 'Ви се допаѓа Omi досега?'; + + @override + String get onboardingActRemembers => 'Omi памети наместо вас'; + + @override + String get onboardingActSavedToTasks => 'Зачувано во вашите задачи'; + + @override + String get onboardingActTasksLabel => 'Omi ги фати вашите задачи'; + + @override + String get onboardingActWriting => 'Сумираме…'; + + @override + String get onboardingActYourWords => 'Еве што рековте'; + + @override + String get onboardingConnectOmi => 'Поврзете Omi'; + + @override + String get onboardingConnectTapToConnect => 'Допрете го вашиот Omi за да се поврзете.'; + + @override + String get onboardingNoOmiYet => 'Сè уште немате Omi?'; + + @override + String get onboardingOmiNotOrdered => 'Сè уште не сум нарачал'; + + @override + String get onboardingOmiOrdered => 'Го нарачав'; + + @override + String get onboardingPermissionTapToFix => 'Допрете за да поправите'; } diff --git a/app/lib/l10n/app_localizations_mr.dart b/app/lib/l10n/app_localizations_mr.dart index acbea6ca8e8..b4b6c1ec835 100644 --- a/app/lib/l10n/app_localizations_mr.dart +++ b/app/lib/l10n/app_localizations_mr.dart @@ -13298,4 +13298,104 @@ class AppLocalizationsMr extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'तुम्ही प्रतीक्षा यादीत आहात. जागा मिळाल्यावर आम्ही कळवू.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'सगळे तयार आहे, $name'; + } + + @override + String get onboardingActDemoSummary => + 'जूनमध्ये लिस्बनला जाणाऱ्या सॅमसोबत जेवण. शुक्रवारपर्यंत टीमला डेक पाठवायचा आहे आणि घरी जाताना दूध घ्यायचे आहे.'; + + @override + String get onboardingActDemoTask1 => 'शुक्रवारपर्यंत टीमला डेक पाठवणे'; + + @override + String get onboardingActDemoTask2 => 'घरी जाताना दूध घेणे'; + + @override + String get onboardingActDemoTitle => 'सॅमसोबत जेवण'; + + @override + String get onboardingActDictateBody => 'तुमच्या पद्धतीने सांगा. बाकी Omi पाहील.'; + + @override + String get onboardingActDictateBodyOmi => 'फक्त बोला. तुमचा Omi ऐकत आहे.'; + + @override + String get onboardingActDictateExample => + 'आत्ताच सॅमसोबत जेवलो, ती जूनमध्ये लिस्बनला जातेय! शुक्रवारपर्यंत टीमला डेक पाठवायची आणि घरी जाताना दूध घ्यायची आठवण करून दे.'; + + @override + String get onboardingActDictateHint => 'झाले की टिकवर टॅप करा'; + + @override + String get onboardingActDictateTitle => 'Omi ला तुमच्या दिवसाबद्दल सांगा'; + + @override + String get onboardingActIntro => 'चला, Omi वापरून पाहू.'; + + @override + String get onboardingActJustNow => 'आत्ताच'; + + @override + String get onboardingActMicAllow => 'मायक्रोफोनला परवानगी द्या'; + + @override + String get onboardingActMicBody => 'तुम्ही बोलता तेव्हा Omi ला ऐकू यावे म्हणून.'; + + @override + String get onboardingActMicFallbackBody => 'तुमचा Omi अजून ऑडिओ पाठवत नाही. सध्या तुमचा फोन वापरा.'; + + @override + String get onboardingActMicTitle => 'Omi ला तुमचा मायक्रोफोन हवा आहे'; + + @override + String get onboardingActNoAudioOmi => 'तुमचा Omi ऐकू येत नाही. तो सुरू आहे आणि जवळ आहे का ते पाहा.'; + + @override + String get onboardingActNoAudioPhone => 'अजून तुमचा आवाज ऐकू येत नाही. थोडं मोठ्याने बोलून पाहा.'; + + @override + String get onboardingActNotesLabel => 'तुमचा दिवस, थोडक्यात'; + + @override + String get onboardingActPhoneMic => 'फोनचा मायक्रोफोन'; + + @override + String get onboardingActRateTitle => 'आत्तापर्यंत Omi आवडतोय का?'; + + @override + String get onboardingActRemembers => 'तुम्हाला लक्षात ठेवावं लागू नये म्हणून Omi लक्षात ठेवतो'; + + @override + String get onboardingActSavedToTasks => 'तुमच्या कामांमध्ये जतन केले'; + + @override + String get onboardingActTasksLabel => 'Omi ने तुमची कामे टिपली'; + + @override + String get onboardingActWriting => 'सारांश तयार होत आहे…'; + + @override + String get onboardingActYourWords => 'तुम्ही हे सांगितले'; + + @override + String get onboardingConnectOmi => 'Omi कनेक्ट करा'; + + @override + String get onboardingConnectTapToConnect => 'कनेक्ट करण्यासाठी तुमच्या Omi वर टॅप करा.'; + + @override + String get onboardingNoOmiYet => 'अजून तुमचा Omi आला नाही?'; + + @override + String get onboardingOmiNotOrdered => 'अजून ऑर्डर केलेला नाही'; + + @override + String get onboardingOmiOrdered => 'मी ऑर्डर केला आहे'; + + @override + String get onboardingPermissionTapToFix => 'दुरुस्त करण्यासाठी टॅप करा'; } diff --git a/app/lib/l10n/app_localizations_ms.dart b/app/lib/l10n/app_localizations_ms.dart index f2171d38b07..aa223024617 100644 --- a/app/lib/l10n/app_localizations_ms.dart +++ b/app/lib/l10n/app_localizations_ms.dart @@ -13323,4 +13323,104 @@ class AppLocalizationsMs extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Anda dalam senarai menunggu. Kami akan maklumkan apabila ada tempat.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Semua sudah sedia, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Makan tengah hari dengan Sam, yang berpindah ke Lisbon pada Jun. Anda mahu hantar dek kepada pasukan sebelum Jumaat dan beli susu dalam perjalanan pulang.'; + + @override + String get onboardingActDemoTask1 => 'Hantar deck kepada pasukan sebelum Jumaat'; + + @override + String get onboardingActDemoTask2 => 'Beli susu dalam perjalanan pulang'; + + @override + String get onboardingActDemoTitle => 'Makan tengah hari dengan Sam'; + + @override + String get onboardingActDictateBody => 'Cakap dengan cara anda. Omi uruskan selebihnya.'; + + @override + String get onboardingActDictateBodyOmi => 'Cakap sahaja. Omi anda sedang mendengar.'; + + @override + String get onboardingActDictateExample => + 'Baru makan tengah hari dengan Sam, dia akan pindah ke Lisbon bulan Jun! Ingatkan saya hantar deck kepada pasukan sebelum Jumaat, dan beli susu dalam perjalanan pulang.'; + + @override + String get onboardingActDictateHint => 'Ketik tanda semak apabila selesai'; + + @override + String get onboardingActDictateTitle => 'Ceritakan hari anda kepada Omi'; + + @override + String get onboardingActIntro => 'Jom cuba Omi.'; + + @override + String get onboardingActJustNow => 'Baru sahaja'; + + @override + String get onboardingActMicAllow => 'Benarkan Mikrofon'; + + @override + String get onboardingActMicBody => 'Supaya Omi dapat mendengar anda bercakap.'; + + @override + String get onboardingActMicFallbackBody => 'Omi anda belum menghantar audio. Gunakan telefon anda buat masa ini.'; + + @override + String get onboardingActMicTitle => 'Omi perlukan mikrofon anda'; + + @override + String get onboardingActNoAudioOmi => 'Omi anda tidak kedengaran. Pastikan ia dihidupkan dan dekat.'; + + @override + String get onboardingActNoAudioPhone => 'Suara anda belum kedengaran. Cuba bercakap lebih kuat sedikit.'; + + @override + String get onboardingActNotesLabel => 'Hari anda, diringkaskan'; + + @override + String get onboardingActPhoneMic => 'Mikrofon telefon'; + + @override + String get onboardingActRateTitle => 'Suka Omi setakat ini?'; + + @override + String get onboardingActRemembers => 'Omi ingat supaya anda tidak perlu'; + + @override + String get onboardingActSavedToTasks => 'Disimpan ke tugasan anda'; + + @override + String get onboardingActTasksLabel => 'Omi menangkap tugasan anda'; + + @override + String get onboardingActWriting => 'Meringkaskan…'; + + @override + String get onboardingActYourWords => 'Ini yang anda katakan'; + + @override + String get onboardingConnectOmi => 'Sambungkan Omi'; + + @override + String get onboardingConnectTapToConnect => 'Ketik Omi anda untuk menyambung.'; + + @override + String get onboardingNoOmiYet => 'Belum ada Omi lagi?'; + + @override + String get onboardingOmiNotOrdered => 'Belum memesan'; + + @override + String get onboardingOmiOrdered => 'Saya sudah memesannya'; + + @override + String get onboardingPermissionTapToFix => 'Ketik untuk membetulkan'; } diff --git a/app/lib/l10n/app_localizations_nl.dart b/app/lib/l10n/app_localizations_nl.dart index 401a14cd582..5d3a44b6ee5 100644 --- a/app/lib/l10n/app_localizations_nl.dart +++ b/app/lib/l10n/app_localizations_nl.dart @@ -13328,4 +13328,104 @@ class AppLocalizationsNl extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Je staat op de wachtlijst. We laten het je weten zodra er plek is.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Je bent klaar, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Lunch met Sam, die in juni naar Lissabon verhuist. Je wilt de presentatie vóór vrijdag naar je team sturen en op weg naar huis melk halen.'; + + @override + String get onboardingActDemoTask1 => 'Presentatie vóór vrijdag naar het team sturen'; + + @override + String get onboardingActDemoTask2 => 'Op weg naar huis melk halen'; + + @override + String get onboardingActDemoTitle => 'Lunch met Sam'; + + @override + String get onboardingActDictateBody => 'Zeg het op jouw manier. Omi doet de rest.'; + + @override + String get onboardingActDictateBodyOmi => 'Praat gewoon. Je Omi luistert.'; + + @override + String get onboardingActDictateExample => + 'Net geluncht met Sam, ze verhuist in juni naar Lissabon! Herinner me eraan de presentatie vóór vrijdag naar het team te sturen en op weg naar huis melk te halen.'; + + @override + String get onboardingActDictateHint => 'Tik op het vinkje als je klaar bent'; + + @override + String get onboardingActDictateTitle => 'Vertel Omi over je dag'; + + @override + String get onboardingActIntro => 'Probeer Omi eens.'; + + @override + String get onboardingActJustNow => 'Zojuist'; + + @override + String get onboardingActMicAllow => 'Microfoon toestaan'; + + @override + String get onboardingActMicBody => 'Zodat Omi je hoort als je tegen hem praat.'; + + @override + String get onboardingActMicFallbackBody => 'Je Omi stuurt nog geen geluid. Gebruik voorlopig je telefoon.'; + + @override + String get onboardingActMicTitle => 'Omi heeft je microfoon nodig'; + + @override + String get onboardingActNoAudioOmi => 'We horen je Omi niet. Controleer of hij aan staat en dichtbij is.'; + + @override + String get onboardingActNoAudioPhone => 'We horen je nog niet. Probeer iets harder te praten.'; + + @override + String get onboardingActNotesLabel => 'Je dag, samengevat'; + + @override + String get onboardingActPhoneMic => 'Telefoonmicrofoon'; + + @override + String get onboardingActRateTitle => 'Bevalt Omi tot nu toe?'; + + @override + String get onboardingActRemembers => 'Omi onthoudt het, zodat jij dat niet hoeft'; + + @override + String get onboardingActSavedToTasks => 'Opgeslagen in je taken'; + + @override + String get onboardingActTasksLabel => 'Omi heeft je to-do\'s opgepikt'; + + @override + String get onboardingActWriting => 'Samenvatten…'; + + @override + String get onboardingActYourWords => 'Dit zei je'; + + @override + String get onboardingConnectOmi => 'Omi verbinden'; + + @override + String get onboardingConnectTapToConnect => 'Tik op je Omi om te verbinden.'; + + @override + String get onboardingNoOmiYet => 'Heb je je Omi nog niet?'; + + @override + String get onboardingOmiNotOrdered => 'Nog niet besteld'; + + @override + String get onboardingOmiOrdered => 'Ik heb hem besteld'; + + @override + String get onboardingPermissionTapToFix => 'Tik om te herstellen'; } diff --git a/app/lib/l10n/app_localizations_no.dart b/app/lib/l10n/app_localizations_no.dart index 8572d68b02f..e762ba96ca2 100644 --- a/app/lib/l10n/app_localizations_no.dart +++ b/app/lib/l10n/app_localizations_no.dart @@ -13294,4 +13294,104 @@ class AppLocalizationsNo extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Du står på ventelisten. Vi gir beskjed når det er plass.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Alt er klart, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Lunsj med Sam, som flytter til Lisboa i juni. Du vil sende presentasjonen til teamet innen fredag og kjøpe melk på vei hjem.'; + + @override + String get onboardingActDemoTask1 => 'Send presentasjonen til teamet innen fredag'; + + @override + String get onboardingActDemoTask2 => 'Kjøp melk på vei hjem'; + + @override + String get onboardingActDemoTitle => 'Lunsj med Sam'; + + @override + String get onboardingActDictateBody => 'Si det på din måte. Omi tar seg av resten.'; + + @override + String get onboardingActDictateBodyOmi => 'Bare snakk. Omi-en din lytter.'; + + @override + String get onboardingActDictateExample => + 'Spiste nettopp lunsj med Sam, hun flytter til Lisboa i juni! Minn meg på å sende presentasjonen til teamet innen fredag og kjøpe melk på vei hjem.'; + + @override + String get onboardingActDictateHint => 'Trykk på haken når du er ferdig'; + + @override + String get onboardingActDictateTitle => 'Fortell Omi om dagen din'; + + @override + String get onboardingActIntro => 'La oss prøve Omi.'; + + @override + String get onboardingActJustNow => 'Akkurat nå'; + + @override + String get onboardingActMicAllow => 'Tillat mikrofon'; + + @override + String get onboardingActMicBody => 'Så Omi hører deg når du snakker til den.'; + + @override + String get onboardingActMicFallbackBody => 'Omi-en din sender ikke lyd ennå. Bruk telefonen inntil videre.'; + + @override + String get onboardingActMicTitle => 'Omi trenger mikrofonen din'; + + @override + String get onboardingActNoAudioOmi => 'Hører ikke Omi-en din. Sjekk at den er på og i nærheten.'; + + @override + String get onboardingActNoAudioPhone => 'Hører deg ikke ennå. Prøv å snakke litt høyere.'; + + @override + String get onboardingActNotesLabel => 'Dagen din, oppsummert'; + + @override + String get onboardingActPhoneMic => 'Telefonmikrofon'; + + @override + String get onboardingActRateTitle => 'Liker du Omi så langt?'; + + @override + String get onboardingActRemembers => 'Omi husker det, så du slipper'; + + @override + String get onboardingActSavedToTasks => 'Lagret i oppgavene dine'; + + @override + String get onboardingActTasksLabel => 'Omi fanget opp gjøremålene dine'; + + @override + String get onboardingActWriting => 'Oppsummerer…'; + + @override + String get onboardingActYourWords => 'Dette sa du'; + + @override + String get onboardingConnectOmi => 'Koble til Omi'; + + @override + String get onboardingConnectTapToConnect => 'Trykk på Omi-en din for å koble til.'; + + @override + String get onboardingNoOmiYet => 'Har du ikke fått Omi ennå?'; + + @override + String get onboardingOmiNotOrdered => 'Ikke bestilt ennå'; + + @override + String get onboardingOmiOrdered => 'Jeg har bestilt den'; + + @override + String get onboardingPermissionTapToFix => 'Trykk for å fikse'; } diff --git a/app/lib/l10n/app_localizations_pl.dart b/app/lib/l10n/app_localizations_pl.dart index ad426f865cc..c335b054c35 100644 --- a/app/lib/l10n/app_localizations_pl.dart +++ b/app/lib/l10n/app_localizations_pl.dart @@ -13331,4 +13331,104 @@ class AppLocalizationsPl extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Jesteś na liście oczekujących. Damy znać, gdy zwolni się miejsce.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Wszystko gotowe, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Lunch z Sam, która w czerwcu przeprowadza się do Lizbony. Chcesz do piątku wysłać zespołowi prezentację i kupić mleko w drodze do domu.'; + + @override + String get onboardingActDemoTask1 => 'Wysłać prezentację zespołowi do piątku'; + + @override + String get onboardingActDemoTask2 => 'Kupić mleko w drodze do domu'; + + @override + String get onboardingActDemoTitle => 'Lunch z Sam'; + + @override + String get onboardingActDictateBody => 'Powiedz to po swojemu. Resztą zajmie się Omi.'; + + @override + String get onboardingActDictateBodyOmi => 'Po prostu mów. Twoje Omi słucha.'; + + @override + String get onboardingActDictateExample => + 'Właśnie zjadłem lunch z Sam, w czerwcu przeprowadza się do Lizbony! Przypomnij mi, żeby wysłać prezentację zespołowi do piątku i kupić mleko w drodze do domu.'; + + @override + String get onboardingActDictateHint => 'Gdy skończysz, dotknij znacznika'; + + @override + String get onboardingActDictateTitle => 'Opowiedz Omi o swoim dniu'; + + @override + String get onboardingActIntro => 'Wypróbujmy Omi.'; + + @override + String get onboardingActJustNow => 'Przed chwilą'; + + @override + String get onboardingActMicAllow => 'Zezwól na mikrofon'; + + @override + String get onboardingActMicBody => 'Aby Omi słyszał Cię, gdy do niego mówisz.'; + + @override + String get onboardingActMicFallbackBody => 'Twoje Omi jeszcze nie przesyła dźwięku. Na razie użyj telefonu.'; + + @override + String get onboardingActMicTitle => 'Omi potrzebuje mikrofonu'; + + @override + String get onboardingActNoAudioOmi => 'Nie słychać Omi. Sprawdź, czy jest włączone i blisko.'; + + @override + String get onboardingActNoAudioPhone => 'Jeszcze Cię nie słychać. Spróbuj mówić trochę głośniej.'; + + @override + String get onboardingActNotesLabel => 'Twój dzień w skrócie'; + + @override + String get onboardingActPhoneMic => 'Mikrofon telefonu'; + + @override + String get onboardingActRateTitle => 'Podoba Ci się Omi?'; + + @override + String get onboardingActRemembers => 'Omi pamięta, żebyś Ty nie musiał'; + + @override + String get onboardingActSavedToTasks => 'Zapisano w Twoich zadaniach'; + + @override + String get onboardingActTasksLabel => 'Omi wyłapał Twoje zadania'; + + @override + String get onboardingActWriting => 'Podsumowujemy…'; + + @override + String get onboardingActYourWords => 'Oto, co powiedziałeś'; + + @override + String get onboardingConnectOmi => 'Połącz Omi'; + + @override + String get onboardingConnectTapToConnect => 'Stuknij swoje Omi, aby połączyć.'; + + @override + String get onboardingNoOmiYet => 'Nie masz jeszcze Omi?'; + + @override + String get onboardingOmiNotOrdered => 'Jeszcze nie zamówiłem/am'; + + @override + String get onboardingOmiOrdered => 'Już zamówiłem/am'; + + @override + String get onboardingPermissionTapToFix => 'Dotknij, aby naprawić'; } diff --git a/app/lib/l10n/app_localizations_pt.dart b/app/lib/l10n/app_localizations_pt.dart index edf7b189ec7..c70637e556c 100644 --- a/app/lib/l10n/app_localizations_pt.dart +++ b/app/lib/l10n/app_localizations_pt.dart @@ -13316,4 +13316,104 @@ class AppLocalizationsPt extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Você está na lista de espera. Avisaremos quando houver vaga.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Tudo pronto, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Almoço com o Sam, que vai se mudar para Lisboa em junho. Você quer enviar a apresentação para a equipe até sexta e comprar leite no caminho de casa.'; + + @override + String get onboardingActDemoTask1 => 'Mandar a apresentação para o time até sexta'; + + @override + String get onboardingActDemoTask2 => 'Comprar leite na volta para casa'; + + @override + String get onboardingActDemoTitle => 'Almoço com a Sam'; + + @override + String get onboardingActDictateBody => 'Fale do seu jeito. O Omi cuida do resto.'; + + @override + String get onboardingActDictateBodyOmi => 'É só falar. Seu Omi está ouvindo.'; + + @override + String get onboardingActDictateExample => + 'Acabei de almoçar com a Sam, ela vai se mudar para Lisboa em junho! Me lembra de mandar a apresentação para o time até sexta e de comprar leite na volta para casa.'; + + @override + String get onboardingActDictateHint => 'Toque no check quando terminar'; + + @override + String get onboardingActDictateTitle => 'Conte ao Omi sobre o seu dia'; + + @override + String get onboardingActIntro => 'Vamos testar o Omi.'; + + @override + String get onboardingActJustNow => 'Agora mesmo'; + + @override + String get onboardingActMicAllow => 'Permitir microfone'; + + @override + String get onboardingActMicBody => 'Para o Omi ouvir quando você falar com ele.'; + + @override + String get onboardingActMicFallbackBody => 'Seu Omi ainda não está enviando áudio. Use o celular por enquanto.'; + + @override + String get onboardingActMicTitle => 'O Omi precisa do seu microfone'; + + @override + String get onboardingActNoAudioOmi => 'Não estamos ouvindo seu Omi. Veja se ele está ligado e por perto.'; + + @override + String get onboardingActNoAudioPhone => 'Ainda não estamos te ouvindo. Tente falar um pouco mais alto.'; + + @override + String get onboardingActNotesLabel => 'Seu dia, resumido'; + + @override + String get onboardingActPhoneMic => 'Microfone do celular'; + + @override + String get onboardingActRateTitle => 'Está gostando do Omi?'; + + @override + String get onboardingActRemembers => 'O Omi lembra por você'; + + @override + String get onboardingActSavedToTasks => 'Salvo nas suas tarefas'; + + @override + String get onboardingActTasksLabel => 'O Omi pegou suas tarefas'; + + @override + String get onboardingActWriting => 'Resumindo…'; + + @override + String get onboardingActYourWords => 'Veja o que você disse'; + + @override + String get onboardingConnectOmi => 'Conectar Omi'; + + @override + String get onboardingConnectTapToConnect => 'Toque no seu Omi para conectar.'; + + @override + String get onboardingNoOmiYet => 'Ainda não tem o seu Omi?'; + + @override + String get onboardingOmiNotOrdered => 'Ainda não encomendei'; + + @override + String get onboardingOmiOrdered => 'Já encomendei'; + + @override + String get onboardingPermissionTapToFix => 'Toque para corrigir'; } diff --git a/app/lib/l10n/app_localizations_ro.dart b/app/lib/l10n/app_localizations_ro.dart index f6917c52df4..135e79da82b 100644 --- a/app/lib/l10n/app_localizations_ro.dart +++ b/app/lib/l10n/app_localizations_ro.dart @@ -13347,4 +13347,104 @@ class AppLocalizationsRo extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Ești pe lista de așteptare. Te anunțăm când se eliberează un loc.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Totul e gata, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Prânz cu Sam, care se mută la Lisabona în iunie. Vrei să trimiți prezentarea echipei până vineri și să iei lapte în drum spre casă.'; + + @override + String get onboardingActDemoTask1 => 'Trimite prezentarea echipei până vineri'; + + @override + String get onboardingActDemoTask2 => 'Ia lapte în drum spre casă'; + + @override + String get onboardingActDemoTitle => 'Prânz cu Sam'; + + @override + String get onboardingActDictateBody => 'Spune-o în felul tău. Omi se ocupă de rest.'; + + @override + String get onboardingActDictateBodyOmi => 'Doar vorbește. Omi te ascultă.'; + + @override + String get onboardingActDictateExample => + 'Tocmai am luat prânzul cu Sam, se mută la Lisabona în iunie! Amintește-mi să trimit prezentarea echipei până vineri și să iau lapte în drum spre casă.'; + + @override + String get onboardingActDictateHint => 'Atinge bifa când ai terminat'; + + @override + String get onboardingActDictateTitle => 'Povestește-i lui Omi despre ziua ta'; + + @override + String get onboardingActIntro => 'Hai să încercăm Omi.'; + + @override + String get onboardingActJustNow => 'Chiar acum'; + + @override + String get onboardingActMicAllow => 'Permite microfonul'; + + @override + String get onboardingActMicBody => 'Ca Omi să te audă când vorbești cu el.'; + + @override + String get onboardingActMicFallbackBody => 'Omi nu trimite încă sunet. Deocamdată folosește telefonul.'; + + @override + String get onboardingActMicTitle => 'Omi are nevoie de microfon'; + + @override + String get onboardingActNoAudioOmi => 'Nu se aude Omi. Verifică dacă e pornit și aproape.'; + + @override + String get onboardingActNoAudioPhone => 'Încă nu te auzim. Încearcă să vorbești puțin mai tare.'; + + @override + String get onboardingActNotesLabel => 'Ziua ta, pe scurt'; + + @override + String get onboardingActPhoneMic => 'Microfonul telefonului'; + + @override + String get onboardingActRateTitle => 'Îți place Omi până acum?'; + + @override + String get onboardingActRemembers => 'Omi își amintește, ca să nu fie nevoie să o faci tu'; + + @override + String get onboardingActSavedToTasks => 'Salvat în sarcinile tale'; + + @override + String get onboardingActTasksLabel => 'Omi ți-a prins sarcinile'; + + @override + String get onboardingActWriting => 'Rezumăm…'; + + @override + String get onboardingActYourWords => 'Iată ce ai spus'; + + @override + String get onboardingConnectOmi => 'Conectează Omi'; + + @override + String get onboardingConnectTapToConnect => 'Atinge Omi-ul tău pentru a te conecta.'; + + @override + String get onboardingNoOmiYet => 'Nu ai încă Omi?'; + + @override + String get onboardingOmiNotOrdered => 'Încă nu am comandat'; + + @override + String get onboardingOmiOrdered => 'L-am comandat'; + + @override + String get onboardingPermissionTapToFix => 'Atinge pentru a remedia'; } diff --git a/app/lib/l10n/app_localizations_ru.dart b/app/lib/l10n/app_localizations_ru.dart index ec976cce4fa..39d5f88767b 100644 --- a/app/lib/l10n/app_localizations_ru.dart +++ b/app/lib/l10n/app_localizations_ru.dart @@ -13338,4 +13338,104 @@ class AppLocalizationsRu extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Вы в листе ожидания. Мы сообщим, когда появится место.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Всё готово, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Обед с Сэм, которая в июне переезжает в Лиссабон. Нужно отправить презентацию команде до пятницы и купить молоко по дороге домой.'; + + @override + String get onboardingActDemoTask1 => 'Отправить презентацию команде до пятницы'; + + @override + String get onboardingActDemoTask2 => 'Купить молоко по дороге домой'; + + @override + String get onboardingActDemoTitle => 'Обед с Сэм'; + + @override + String get onboardingActDictateBody => 'Говорите как вам удобно. Остальное сделает Omi.'; + + @override + String get onboardingActDictateBodyOmi => 'Просто говорите. Omi слушает.'; + + @override + String get onboardingActDictateExample => + 'Только что пообедал с Сэм, в июне она переезжает в Лиссабон! Напомни отправить презентацию команде до пятницы и купить молоко по дороге домой.'; + + @override + String get onboardingActDictateHint => 'Нажмите галочку, когда закончите'; + + @override + String get onboardingActDictateTitle => 'Расскажите Omi о своём дне'; + + @override + String get onboardingActIntro => 'Попробуем Omi.'; + + @override + String get onboardingActJustNow => 'Только что'; + + @override + String get onboardingActMicAllow => 'Разрешить микрофон'; + + @override + String get onboardingActMicBody => 'Чтобы Omi слышал вас, когда вы с ним говорите.'; + + @override + String get onboardingActMicFallbackBody => 'Omi пока не передаёт звук. Пока используйте телефон.'; + + @override + String get onboardingActMicTitle => 'Omi нужен микрофон'; + + @override + String get onboardingActNoAudioOmi => 'Omi не слышно. Проверьте, что он включён и рядом.'; + + @override + String get onboardingActNoAudioPhone => 'Пока вас не слышно. Попробуйте говорить чуть громче.'; + + @override + String get onboardingActNotesLabel => 'Ваш день вкратце'; + + @override + String get onboardingActPhoneMic => 'Микрофон телефона'; + + @override + String get onboardingActRateTitle => 'Нравится Omi?'; + + @override + String get onboardingActRemembers => 'Omi запоминает, чтобы вам не пришлось'; + + @override + String get onboardingActSavedToTasks => 'Сохранено в ваши задачи'; + + @override + String get onboardingActTasksLabel => 'Omi нашёл ваши дела'; + + @override + String get onboardingActWriting => 'Подводим итоги…'; + + @override + String get onboardingActYourWords => 'Вот что вы сказали'; + + @override + String get onboardingConnectOmi => 'Подключите Omi'; + + @override + String get onboardingConnectTapToConnect => 'Нажмите на Omi, чтобы подключить.'; + + @override + String get onboardingNoOmiYet => 'Ещё нет Omi?'; + + @override + String get onboardingOmiNotOrdered => 'Ещё не заказал'; + + @override + String get onboardingOmiOrdered => 'Я уже заказал'; + + @override + String get onboardingPermissionTapToFix => 'Нажмите, чтобы исправить'; } diff --git a/app/lib/l10n/app_localizations_sk.dart b/app/lib/l10n/app_localizations_sk.dart index 52a541bc42b..e90013665c9 100644 --- a/app/lib/l10n/app_localizations_sk.dart +++ b/app/lib/l10n/app_localizations_sk.dart @@ -13288,4 +13288,104 @@ class AppLocalizationsSk extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Ste na čakacej listine. Dáme vám vedieť, keď bude miesto.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Všetko je pripravené, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Obed so Sam, ktorá sa v júni sťahuje do Lisabonu. Do piatku chcete poslať tímu prezentáciu a cestou domov kúpiť mlieko.'; + + @override + String get onboardingActDemoTask1 => 'Poslať prezentáciu tímu do piatka'; + + @override + String get onboardingActDemoTask2 => 'Cestou domov kúpiť mlieko'; + + @override + String get onboardingActDemoTitle => 'Obed so Sam'; + + @override + String get onboardingActDictateBody => 'Povedzte to po svojom. Zvyšok zariadi Omi.'; + + @override + String get onboardingActDictateBodyOmi => 'Jednoducho hovorte. Vaše Omi počúva.'; + + @override + String get onboardingActDictateExample => + 'Práve som obedoval so Sam, v júni sa sťahuje do Lisabonu! Pripomeň mi poslať prezentáciu tímu do piatka a cestou domov kúpiť mlieko.'; + + @override + String get onboardingActDictateHint => 'Keď skončíte, ťuknite na fajku'; + + @override + String get onboardingActDictateTitle => 'Povedzte Omi o svojom dni'; + + @override + String get onboardingActIntro => 'Vyskúšajme Omi.'; + + @override + String get onboardingActJustNow => 'Práve teraz'; + + @override + String get onboardingActMicAllow => 'Povoliť mikrofón'; + + @override + String get onboardingActMicBody => 'Aby vás Omi počul, keď naň hovoríte.'; + + @override + String get onboardingActMicFallbackBody => 'Omi zatiaľ neposiela zvuk. Zatiaľ použite telefón.'; + + @override + String get onboardingActMicTitle => 'Omi potrebuje mikrofón'; + + @override + String get onboardingActNoAudioOmi => 'Omi nepočuť. Skontrolujte, či je zapnuté a blízko.'; + + @override + String get onboardingActNoAudioPhone => 'Zatiaľ vás nepočuť. Skúste hovoriť trochu hlasnejšie.'; + + @override + String get onboardingActNotesLabel => 'Váš deň v skratke'; + + @override + String get onboardingActPhoneMic => 'Mikrofón telefónu'; + + @override + String get onboardingActRateTitle => 'Páči sa vám zatiaľ Omi?'; + + @override + String get onboardingActRemembers => 'Omi si to pamätá, aby ste nemuseli vy'; + + @override + String get onboardingActSavedToTasks => 'Uložené do vašich úloh'; + + @override + String get onboardingActTasksLabel => 'Omi zachytil vaše úlohy'; + + @override + String get onboardingActWriting => 'Zhrnujeme…'; + + @override + String get onboardingActYourWords => 'Toto ste povedali'; + + @override + String get onboardingConnectOmi => 'Pripojte Omi'; + + @override + String get onboardingConnectTapToConnect => 'Ťuknutím na Omi sa pripojíte.'; + + @override + String get onboardingNoOmiYet => 'Ešte nemáte Omi?'; + + @override + String get onboardingOmiNotOrdered => 'Ešte neobjednané'; + + @override + String get onboardingOmiOrdered => 'Už som ho objednal/a'; + + @override + String get onboardingPermissionTapToFix => 'Ťuknutím opravíte'; } diff --git a/app/lib/l10n/app_localizations_sl.dart b/app/lib/l10n/app_localizations_sl.dart index 34b004d0379..87b14e7078e 100644 --- a/app/lib/l10n/app_localizations_sl.dart +++ b/app/lib/l10n/app_localizations_sl.dart @@ -13323,4 +13323,104 @@ class AppLocalizationsSl extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Ste na čakalnem seznamu. Sporočili vam bomo, ko bo prostor.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Vse je pripravljeno, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Kosilo s Samom, ki se junija seli v Lizbono. Do petka želite ekipi poslati predstavitev in na poti domov kupiti mleko.'; + + @override + String get onboardingActDemoTask1 => 'Do petka poslati predstavitev ekipi'; + + @override + String get onboardingActDemoTask2 => 'Na poti domov kupiti mleko'; + + @override + String get onboardingActDemoTitle => 'Kosilo s Sam'; + + @override + String get onboardingActDictateBody => 'Povejte po svoje. Za ostalo poskrbi Omi.'; + + @override + String get onboardingActDictateBodyOmi => 'Samo govorite. Vaš Omi posluša.'; + + @override + String get onboardingActDictateExample => + 'Pravkar sem kosil s Sam, junija se seli v Lizbono! Opomni me, naj do petka pošljem predstavitev ekipi in na poti domov kupim mleko.'; + + @override + String get onboardingActDictateHint => 'Ko končate, tapnite kljukico'; + + @override + String get onboardingActDictateTitle => 'Povejte Omiju o svojem dnevu'; + + @override + String get onboardingActIntro => 'Preizkusimo Omi.'; + + @override + String get onboardingActJustNow => 'Pravkar'; + + @override + String get onboardingActMicAllow => 'Dovoli mikrofon'; + + @override + String get onboardingActMicBody => 'Da vas Omi sliši, ko mu govorite.'; + + @override + String get onboardingActMicFallbackBody => 'Omi še ne pošilja zvoka. Za zdaj uporabite telefon.'; + + @override + String get onboardingActMicTitle => 'Omi potrebuje mikrofon'; + + @override + String get onboardingActNoAudioOmi => 'Omija ne slišimo. Preverite, ali je vklopljen in blizu.'; + + @override + String get onboardingActNoAudioPhone => 'Še vas ne slišimo. Poskusite govoriti malo glasneje.'; + + @override + String get onboardingActNotesLabel => 'Vaš dan na kratko'; + + @override + String get onboardingActPhoneMic => 'Mikrofon telefona'; + + @override + String get onboardingActRateTitle => 'Vam je Omi všeč?'; + + @override + String get onboardingActRemembers => 'Omi si zapomni namesto vas'; + + @override + String get onboardingActSavedToTasks => 'Shranjeno med vaša opravila'; + + @override + String get onboardingActTasksLabel => 'Omi je ujel vaša opravila'; + + @override + String get onboardingActWriting => 'Povzemamo…'; + + @override + String get onboardingActYourWords => 'To ste povedali'; + + @override + String get onboardingConnectOmi => 'Povežite Omi'; + + @override + String get onboardingConnectTapToConnect => 'Tapnite svoj Omi za povezavo.'; + + @override + String get onboardingNoOmiYet => 'Še nimate Omi?'; + + @override + String get onboardingOmiNotOrdered => 'Še nisem naročil/a'; + + @override + String get onboardingOmiOrdered => 'Naročil/a sem ga'; + + @override + String get onboardingPermissionTapToFix => 'Tapnite za popravilo'; } diff --git a/app/lib/l10n/app_localizations_sr.dart b/app/lib/l10n/app_localizations_sr.dart index f420e98e687..3c80542d0b1 100644 --- a/app/lib/l10n/app_localizations_sr.dart +++ b/app/lib/l10n/app_localizations_sr.dart @@ -13301,4 +13301,104 @@ class AppLocalizationsSr extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'На листи чекања сте. Јавићемо вам када буде места.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Све је спремно, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Ручак са Сем, која се у јуну сели у Лисабон. Желите да пошаљете презентацију тиму до петка и купите млеко на путу кући.'; + + @override + String get onboardingActDemoTask1 => 'Послати презентацију тиму до петка'; + + @override + String get onboardingActDemoTask2 => 'Купити млеко на путу кући'; + + @override + String get onboardingActDemoTitle => 'Ручак са Сем'; + + @override + String get onboardingActDictateBody => 'Реците то на свој начин. Omi ће се побринути за остало.'; + + @override + String get onboardingActDictateBodyOmi => 'Само причајте. Ваш Omi слуша.'; + + @override + String get onboardingActDictateExample => + 'Управо сам ручао са Сем, у јуну се сели у Лисабон! Подсети ме да пошаљем презентацију тиму до петка и да купим млеко на путу кући.'; + + @override + String get onboardingActDictateHint => 'Додирните квачицу када завршите'; + + @override + String get onboardingActDictateTitle => 'Испричајте Omi-ју свој дан'; + + @override + String get onboardingActIntro => 'Испробајмо Omi.'; + + @override + String get onboardingActJustNow => 'Управо сада'; + + @override + String get onboardingActMicAllow => 'Дозволи микрофон'; + + @override + String get onboardingActMicBody => 'Да вас Omi чује када му се обратите.'; + + @override + String get onboardingActMicFallbackBody => 'Omi још не шаље звук. За сада користите телефон.'; + + @override + String get onboardingActMicTitle => 'Omi-ју треба микрофон'; + + @override + String get onboardingActNoAudioOmi => 'Не чујемо Omi. Проверите да ли је укључен и близу.'; + + @override + String get onboardingActNoAudioPhone => 'Још вас не чујемо. Покушајте да говорите мало гласније.'; + + @override + String get onboardingActNotesLabel => 'Ваш дан, укратко'; + + @override + String get onboardingActPhoneMic => 'Микрофон телефона'; + + @override + String get onboardingActRateTitle => 'Да ли вам се Omi допада?'; + + @override + String get onboardingActRemembers => 'Omi памти уместо вас'; + + @override + String get onboardingActSavedToTasks => 'Сачувано у ваше задатке'; + + @override + String get onboardingActTasksLabel => 'Omi је ухватио ваше задатке'; + + @override + String get onboardingActWriting => 'Сажимамо…'; + + @override + String get onboardingActYourWords => 'Ево шта сте рекли'; + + @override + String get onboardingConnectOmi => 'Повежите Omi'; + + @override + String get onboardingConnectTapToConnect => 'Додирните свој Omi да бисте се повезали.'; + + @override + String get onboardingNoOmiYet => 'Још немате Omi?'; + + @override + String get onboardingOmiNotOrdered => 'Још нисам наручио/ла'; + + @override + String get onboardingOmiOrdered => 'Наручио/ла сам га'; + + @override + String get onboardingPermissionTapToFix => 'Додирните да поправите'; } diff --git a/app/lib/l10n/app_localizations_sv.dart b/app/lib/l10n/app_localizations_sv.dart index 53f14951b7b..253770d2025 100644 --- a/app/lib/l10n/app_localizations_sv.dart +++ b/app/lib/l10n/app_localizations_sv.dart @@ -13297,4 +13297,104 @@ class AppLocalizationsSv extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Du står på väntelistan. Vi hör av oss när det finns plats.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Allt är klart, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Lunch med Sam, som flyttar till Lissabon i juni. Du vill skicka presentationen till teamet senast fredag och köpa mjölk på vägen hem.'; + + @override + String get onboardingActDemoTask1 => 'Skicka presentationen till teamet senast fredag'; + + @override + String get onboardingActDemoTask2 => 'Köp mjölk på vägen hem'; + + @override + String get onboardingActDemoTitle => 'Lunch med Sam'; + + @override + String get onboardingActDictateBody => 'Säg det på ditt sätt. Omi sköter resten.'; + + @override + String get onboardingActDictateBodyOmi => 'Prata bara. Din Omi lyssnar.'; + + @override + String get onboardingActDictateExample => + 'Åt precis lunch med Sam, hon flyttar till Lissabon i juni! Påminn mig om att skicka presentationen till teamet senast fredag och att köpa mjölk på vägen hem.'; + + @override + String get onboardingActDictateHint => 'Tryck på bocken när du är klar'; + + @override + String get onboardingActDictateTitle => 'Berätta för Omi om din dag'; + + @override + String get onboardingActIntro => 'Vi testar Omi.'; + + @override + String get onboardingActJustNow => 'Precis nu'; + + @override + String get onboardingActMicAllow => 'Tillåt mikrofon'; + + @override + String get onboardingActMicBody => 'Så att Omi hör dig när du pratar med den.'; + + @override + String get onboardingActMicFallbackBody => 'Din Omi skickar inget ljud än. Använd telefonen så länge.'; + + @override + String get onboardingActMicTitle => 'Omi behöver din mikrofon'; + + @override + String get onboardingActNoAudioOmi => 'Hör inte din Omi. Kontrollera att den är på och nära.'; + + @override + String get onboardingActNoAudioPhone => 'Hör dig inte än. Försök prata lite högre.'; + + @override + String get onboardingActNotesLabel => 'Din dag, sammanfattad'; + + @override + String get onboardingActPhoneMic => 'Telefonens mikrofon'; + + @override + String get onboardingActRateTitle => 'Gillar du Omi hittills?'; + + @override + String get onboardingActRemembers => 'Omi minns så att du slipper'; + + @override + String get onboardingActSavedToTasks => 'Sparat i dina uppgifter'; + + @override + String get onboardingActTasksLabel => 'Omi fångade dina att-göra'; + + @override + String get onboardingActWriting => 'Sammanfattar…'; + + @override + String get onboardingActYourWords => 'Det här sa du'; + + @override + String get onboardingConnectOmi => 'Anslut Omi'; + + @override + String get onboardingConnectTapToConnect => 'Tryck på din Omi för att ansluta.'; + + @override + String get onboardingNoOmiYet => 'Har du inte fått din Omi än?'; + + @override + String get onboardingOmiNotOrdered => 'Inte beställt än'; + + @override + String get onboardingOmiOrdered => 'Jag har beställt den'; + + @override + String get onboardingPermissionTapToFix => 'Tryck för att åtgärda'; } diff --git a/app/lib/l10n/app_localizations_ta.dart b/app/lib/l10n/app_localizations_ta.dart index 7a62531eb2f..97467a35cd1 100644 --- a/app/lib/l10n/app_localizations_ta.dart +++ b/app/lib/l10n/app_localizations_ta.dart @@ -13382,4 +13382,106 @@ class AppLocalizationsTa extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'நீங்கள் காத்திருப்புப் பட்டியலில் உள்ளீர்கள். இடம் கிடைத்ததும் தெரிவிப்போம்.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'எல்லாம் தயார், $name'; + } + + @override + String get onboardingActDemoSummary => + 'ஜூனில் லிஸ்பனுக்குச் செல்லும் சாமுடன் மதிய உணவு. வெள்ளிக்குள் குழுவுக்கு டெக் அனுப்பவும், வீட்டுக்குப் போகும் வழியில் பால் வாங்கவும் வேண்டும்.'; + + @override + String get onboardingActDemoTask1 => 'வெள்ளிக்குள் குழுவுக்கு டெக் அனுப்புவது'; + + @override + String get onboardingActDemoTask2 => 'வீட்டுக்குப் போகும் வழியில் பால் வாங்குவது'; + + @override + String get onboardingActDemoTitle => 'சாமுடன் மதிய உணவு'; + + @override + String get onboardingActDictateBody => 'உங்கள் பாணியில் சொல்லுங்கள். மீதியை Omi பார்த்துக்கொள்ளும்.'; + + @override + String get onboardingActDictateBodyOmi => 'பேசுங்கள் போதும். உங்கள் Omi கேட்டுக்கொண்டிருக்கிறது.'; + + @override + String get onboardingActDictateExample => + 'இப்போதுதான் சாமுடன் மதிய உணவு சாப்பிட்டேன், அவள் ஜூனில் லிஸ்பனுக்குப் போகிறாள்! வெள்ளிக்குள் குழுவுக்கு டெக் அனுப்பவும், வீட்டுக்குப் போகும் வழியில் பால் வாங்கவும் நினைவூட்டு.'; + + @override + String get onboardingActDictateHint => 'முடிந்ததும் டிக்கைத் தட்டவும்'; + + @override + String get onboardingActDictateTitle => 'உங்கள் நாளைப் பற்றி Omi-யிடம் சொல்லுங்கள்'; + + @override + String get onboardingActIntro => 'Omi-ஐ முயற்சிப்போம்.'; + + @override + String get onboardingActJustNow => 'இப்போதுதான்'; + + @override + String get onboardingActMicAllow => 'மைக்ரோஃபோனை அனுமதி'; + + @override + String get onboardingActMicBody => 'நீங்கள் பேசும்போது Omi கேட்க.'; + + @override + String get onboardingActMicFallbackBody => + 'உங்கள் Omi இன்னும் ஒலியை அனுப்பவில்லை. இப்போதைக்கு உங்கள் போனைப் பயன்படுத்துங்கள்.'; + + @override + String get onboardingActMicTitle => 'Omi-க்கு உங்கள் மைக்ரோஃபோன் தேவை'; + + @override + String get onboardingActNoAudioOmi => + 'உங்கள் Omi கேட்கவில்லை. அது இயக்கத்தில் உள்ளதா, அருகில் உள்ளதா எனப் பாருங்கள்.'; + + @override + String get onboardingActNoAudioPhone => 'இன்னும் உங்கள் குரல் கேட்கவில்லை. சற்று சத்தமாகப் பேசிப் பாருங்கள்.'; + + @override + String get onboardingActNotesLabel => 'உங்கள் நாள், சுருக்கமாக'; + + @override + String get onboardingActPhoneMic => 'தொலைபேசி மைக்ரோஃபோன்'; + + @override + String get onboardingActRateTitle => 'இதுவரை Omi பிடித்திருக்கிறதா?'; + + @override + String get onboardingActRemembers => 'நீங்கள் நினைவில் வைக்க வேண்டியதில்லை, Omi வைத்துக்கொள்ளும்'; + + @override + String get onboardingActSavedToTasks => 'உங்கள் பணிகளில் சேமிக்கப்பட்டது'; + + @override + String get onboardingActTasksLabel => 'Omi உங்கள் வேலைகளைக் கண்டறிந்தது'; + + @override + String get onboardingActWriting => 'சுருக்கப்படுகிறது…'; + + @override + String get onboardingActYourWords => 'நீங்கள் சொன்னது இது'; + + @override + String get onboardingConnectOmi => 'Omi-ஐ இணைக்கவும்'; + + @override + String get onboardingConnectTapToConnect => 'இணைக்க உங்கள் Omi-ஐத் தட்டவும்.'; + + @override + String get onboardingNoOmiYet => 'இன்னும் உங்கள் Omi இல்லையா?'; + + @override + String get onboardingOmiNotOrdered => 'இன்னும் ஆர்டர் செய்யவில்லை'; + + @override + String get onboardingOmiOrdered => 'நான் ஆர்டர் செய்துவிட்டேன்'; + + @override + String get onboardingPermissionTapToFix => 'சரிசெய்ய தட்டவும்'; } diff --git a/app/lib/l10n/app_localizations_te.dart b/app/lib/l10n/app_localizations_te.dart index 6d711a63637..d91dfd03333 100644 --- a/app/lib/l10n/app_localizations_te.dart +++ b/app/lib/l10n/app_localizations_te.dart @@ -13344,4 +13344,104 @@ class AppLocalizationsTe extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'మీరు వెయిట్‌లిస్ట్‌లో ఉన్నారు. స్థలం దొరికినప్పుడు మీకు తెలియజేస్తాము.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'అంతా సిద్ధం, $name'; + } + + @override + String get onboardingActDemoSummary => + 'జూన్‌లో లిస్బన్‌కు వెళ్తున్న సామ్‌తో లంచ్. శుక్రవారం లోపు టీమ్‌కు డెక్ పంపాలి, ఇంటికి వెళ్తూ పాలు కొనాలి.'; + + @override + String get onboardingActDemoTask1 => 'శుక్రవారంలోగా టీమ్‌కి డెక్ పంపడం'; + + @override + String get onboardingActDemoTask2 => 'ఇంటికి వెళ్లేటప్పుడు పాలు తీసుకోవడం'; + + @override + String get onboardingActDemoTitle => 'సామ్‌తో లంచ్'; + + @override + String get onboardingActDictateBody => 'మీ శైలిలో చెప్పండి. మిగతాది Omi చూసుకుంటుంది.'; + + @override + String get onboardingActDictateBodyOmi => 'మాట్లాడండి చాలు. మీ Omi వింటోంది.'; + + @override + String get onboardingActDictateExample => + 'ఇప్పుడే సామ్‌తో లంచ్ చేశా, తను జూన్‌లో లిస్బన్‌కి వెళ్తోంది! శుక్రవారంలోగా టీమ్‌కి డెక్ పంపాలని, ఇంటికి వెళ్లేటప్పుడు పాలు తీసుకోవాలని గుర్తు చేయి.'; + + @override + String get onboardingActDictateHint => 'అయ్యాక టిక్ నొక్కండి'; + + @override + String get onboardingActDictateTitle => 'మీ రోజు గురించి Omiకి చెప్పండి'; + + @override + String get onboardingActIntro => 'Omi ని ప్రయత్నిద్దాం.'; + + @override + String get onboardingActJustNow => 'ఇప్పుడే'; + + @override + String get onboardingActMicAllow => 'మైక్రోఫోన్‌ను అనుమతించండి'; + + @override + String get onboardingActMicBody => 'మీరు మాట్లాడినప్పుడు Omi వినడానికి.'; + + @override + String get onboardingActMicFallbackBody => 'మీ Omi ఇంకా ఆడియో పంపడం లేదు. ప్రస్తుతానికి మీ ఫోన్ వాడండి.'; + + @override + String get onboardingActMicTitle => 'Omiకి మీ మైక్రోఫోన్ కావాలి'; + + @override + String get onboardingActNoAudioOmi => 'మీ Omi వినిపించడం లేదు. అది ఆన్‌లో ఉందో, దగ్గర్లో ఉందో చూడండి.'; + + @override + String get onboardingActNoAudioPhone => 'ఇంకా మీ గొంతు వినిపించడం లేదు. కొంచెం గట్టిగా మాట్లాడి చూడండి.'; + + @override + String get onboardingActNotesLabel => 'మీ రోజు, సంక్షిప్తంగా'; + + @override + String get onboardingActPhoneMic => 'ఫోన్ మైక్రోఫోన్'; + + @override + String get onboardingActRateTitle => 'ఇప్పటివరకు Omi నచ్చిందా?'; + + @override + String get onboardingActRemembers => 'మీరు గుర్తుంచుకోనవసరం లేదు, Omi గుర్తుంచుకుంటుంది'; + + @override + String get onboardingActSavedToTasks => 'మీ పనుల్లో సేవ్ అయింది'; + + @override + String get onboardingActTasksLabel => 'Omi మీ పనులను గుర్తించింది'; + + @override + String get onboardingActWriting => 'సారాంశం తయారవుతోంది…'; + + @override + String get onboardingActYourWords => 'మీరు చెప్పింది ఇది'; + + @override + String get onboardingConnectOmi => 'Omiని కనెక్ట్ చేయండి'; + + @override + String get onboardingConnectTapToConnect => 'కనెక్ట్ చేయడానికి మీ Omi ని ట్యాప్ చేయండి.'; + + @override + String get onboardingNoOmiYet => 'ఇంకా మీ Omi లేదా?'; + + @override + String get onboardingOmiNotOrdered => 'ఇంకా ఆర్డర్ చేయలేదు'; + + @override + String get onboardingOmiOrdered => 'నేను ఆర్డర్ చేశాను'; + + @override + String get onboardingPermissionTapToFix => 'సరిచేయడానికి నొక్కండి'; } diff --git a/app/lib/l10n/app_localizations_th.dart b/app/lib/l10n/app_localizations_th.dart index 5090c109304..f88de8cbfb0 100644 --- a/app/lib/l10n/app_localizations_th.dart +++ b/app/lib/l10n/app_localizations_th.dart @@ -13226,4 +13226,104 @@ class AppLocalizationsTh extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'คุณอยู่ในรายชื่อรอแล้ว เราจะแจ้งให้ทราบเมื่อมีที่ว่าง'; + + @override + String onboardingActCongratsTitle(String name) { + return 'พร้อมแล้ว $name'; + } + + @override + String get onboardingActDemoSummary => + 'ทานข้าวกลางวันกับแซมที่จะย้ายไปลิสบอนเดือนมิถุนายน คุณต้องส่งสไลด์ให้ทีมภายในวันศุกร์ และซื้อนมระหว่างทางกลับบ้าน'; + + @override + String get onboardingActDemoTask1 => 'ส่งสไลด์ให้ทีมภายในวันศุกร์'; + + @override + String get onboardingActDemoTask2 => 'ซื้อนมตอนกลับบ้าน'; + + @override + String get onboardingActDemoTitle => 'มื้อกลางวันกับแซม'; + + @override + String get onboardingActDictateBody => 'พูดแบบที่คุณถนัด ที่เหลือ Omi จัดการเอง'; + + @override + String get onboardingActDictateBodyOmi => 'พูดได้เลย Omi กำลังฟังอยู่'; + + @override + String get onboardingActDictateExample => + 'เพิ่งกินข้าวกลางวันกับแซม เธอจะย้ายไปลิสบอนเดือนมิถุนายน! เตือนฉันส่งสไลด์ให้ทีมภายในวันศุกร์ แล้วก็ซื้อนมตอนกลับบ้านด้วย'; + + @override + String get onboardingActDictateHint => 'แตะเครื่องหมายถูกเมื่อพูดเสร็จ'; + + @override + String get onboardingActDictateTitle => 'เล่าวันของคุณให้ Omi ฟัง'; + + @override + String get onboardingActIntro => 'มาลองใช้ Omi กัน'; + + @override + String get onboardingActJustNow => 'เมื่อสักครู่'; + + @override + String get onboardingActMicAllow => 'อนุญาตไมโครโฟน'; + + @override + String get onboardingActMicBody => 'เพื่อให้ Omi ได้ยินเมื่อคุณพูดด้วย'; + + @override + String get onboardingActMicFallbackBody => 'Omi ยังไม่ส่งเสียงมา ใช้โทรศัพท์ไปก่อนนะ'; + + @override + String get onboardingActMicTitle => 'Omi ต้องใช้ไมโครโฟน'; + + @override + String get onboardingActNoAudioOmi => 'ไม่ได้ยินเสียงจาก Omi ตรวจดูว่าเปิดอยู่และอยู่ใกล้ๆ'; + + @override + String get onboardingActNoAudioPhone => 'ยังไม่ได้ยินเสียงคุณ ลองพูดดังขึ้นอีกนิด'; + + @override + String get onboardingActNotesLabel => 'สรุปวันของคุณ'; + + @override + String get onboardingActPhoneMic => 'ไมโครโฟนโทรศัพท์'; + + @override + String get onboardingActRateTitle => 'ชอบ Omi ไหม'; + + @override + String get onboardingActRemembers => 'Omi จำให้ คุณไม่ต้องจำเอง'; + + @override + String get onboardingActSavedToTasks => 'บันทึกไว้ในงานของคุณแล้ว'; + + @override + String get onboardingActTasksLabel => 'Omi จับสิ่งที่ต้องทำให้แล้ว'; + + @override + String get onboardingActWriting => 'กำลังสรุป…'; + + @override + String get onboardingActYourWords => 'สิ่งที่คุณพูด'; + + @override + String get onboardingConnectOmi => 'เชื่อมต่อ Omi'; + + @override + String get onboardingConnectTapToConnect => 'แตะ Omi ของคุณเพื่อเชื่อมต่อ'; + + @override + String get onboardingNoOmiYet => 'ยังไม่มี Omi ใช่ไหม'; + + @override + String get onboardingOmiNotOrdered => 'ยังไม่ได้สั่งซื้อ'; + + @override + String get onboardingOmiOrdered => 'สั่งซื้อแล้ว'; + + @override + String get onboardingPermissionTapToFix => 'แตะเพื่อแก้ไข'; } diff --git a/app/lib/l10n/app_localizations_tl.dart b/app/lib/l10n/app_localizations_tl.dart index ac85b7baa04..88cabe22e7f 100644 --- a/app/lib/l10n/app_localizations_tl.dart +++ b/app/lib/l10n/app_localizations_tl.dart @@ -13397,4 +13397,104 @@ class AppLocalizationsTl extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Nasa waitlist ka na. Sasabihan ka namin kapag may puwesto na.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Handa ka na, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Tanghalian kasama si Sam, na lilipat sa Lisbon sa Hunyo. Gusto mong ipadala ang deck sa team bago mag-Biyernes at bumili ng gatas pauwi.'; + + @override + String get onboardingActDemoTask1 => 'Ipadala ang deck sa team bago mag-Biyernes'; + + @override + String get onboardingActDemoTask2 => 'Bumili ng gatas pauwi'; + + @override + String get onboardingActDemoTitle => 'Lunch kasama si Sam'; + + @override + String get onboardingActDictateBody => 'Sabihin mo sa sarili mong paraan. Ang Omi na ang bahala.'; + + @override + String get onboardingActDictateBodyOmi => 'Magsalita ka lang. Nakikinig ang Omi mo.'; + + @override + String get onboardingActDictateExample => + 'Kakatapos ko lang mag-lunch kasama si Sam, lilipat na siya sa Lisbon sa Hunyo! Ipaalala mo sa akin na ipadala ang deck sa team bago mag-Biyernes, at bumili ng gatas pauwi.'; + + @override + String get onboardingActDictateHint => 'I-tap ang check kapag tapos ka na'; + + @override + String get onboardingActDictateTitle => 'Ikuwento sa Omi ang araw mo'; + + @override + String get onboardingActIntro => 'Subukan natin ang Omi.'; + + @override + String get onboardingActJustNow => 'Ngayon lang'; + + @override + String get onboardingActMicAllow => 'Payagan ang Mikropono'; + + @override + String get onboardingActMicBody => 'Para marinig ka ng Omi kapag kinakausap mo ito.'; + + @override + String get onboardingActMicFallbackBody => 'Hindi pa nagpapadala ng audio ang Omi mo. Gamitin muna ang iyong phone.'; + + @override + String get onboardingActMicTitle => 'Kailangan ng Omi ang mikropono mo'; + + @override + String get onboardingActNoAudioOmi => 'Hindi marinig ang Omi mo. Siguraduhing naka-on ito at malapit.'; + + @override + String get onboardingActNoAudioPhone => 'Hindi ka pa marinig. Subukang magsalita nang medyo malakas.'; + + @override + String get onboardingActNotesLabel => 'Ang araw mo, buod'; + + @override + String get onboardingActPhoneMic => 'Mikropono ng phone'; + + @override + String get onboardingActRateTitle => 'Gusto mo ba ang Omi sa ngayon?'; + + @override + String get onboardingActRemembers => 'Tinatandaan ng Omi para hindi mo na kailangan'; + + @override + String get onboardingActSavedToTasks => 'Na-save sa mga gawain mo'; + + @override + String get onboardingActTasksLabel => 'Nahuli ng Omi ang mga gagawin mo'; + + @override + String get onboardingActWriting => 'Binubuod…'; + + @override + String get onboardingActYourWords => 'Ito ang sinabi mo'; + + @override + String get onboardingConnectOmi => 'Ikonekta ang Omi'; + + @override + String get onboardingConnectTapToConnect => 'I-tap ang iyong Omi para kumonekta.'; + + @override + String get onboardingNoOmiYet => 'Wala ka pang Omi?'; + + @override + String get onboardingOmiNotOrdered => 'Hindi pa naka-order'; + + @override + String get onboardingOmiOrdered => 'Na-order ko na'; + + @override + String get onboardingPermissionTapToFix => 'I-tap para ayusin'; } diff --git a/app/lib/l10n/app_localizations_tr.dart b/app/lib/l10n/app_localizations_tr.dart index 4df70e0a572..497a0414e65 100644 --- a/app/lib/l10n/app_localizations_tr.dart +++ b/app/lib/l10n/app_localizations_tr.dart @@ -13305,4 +13305,104 @@ class AppLocalizationsTr extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Bekleme listesindesin. Yer açıldığında haber vereceğiz.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Her şey hazır, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Haziranda Lizbon\'a taşınan Sam\'le öğle yemeği. Cumaya kadar sunumu ekibine göndermek ve eve dönerken süt almak istiyorsun.'; + + @override + String get onboardingActDemoTask1 => 'Cumaya kadar sunumu ekibe gönder'; + + @override + String get onboardingActDemoTask2 => 'Eve dönerken süt al'; + + @override + String get onboardingActDemoTitle => 'Sam\'le öğle yemeği'; + + @override + String get onboardingActDictateBody => 'Kendi tarzınla söyle. Gerisini Omi halleder.'; + + @override + String get onboardingActDictateBodyOmi => 'Sadece konuş. Omi\'n dinliyor.'; + + @override + String get onboardingActDictateExample => + 'Az önce Sam\'le öğle yemeği yedim, haziranda Lizbon\'a taşınıyor! Cumaya kadar sunumu ekibe göndermemi ve eve dönerken süt almamı hatırlat.'; + + @override + String get onboardingActDictateHint => 'Bitince onay işaretine dokun'; + + @override + String get onboardingActDictateTitle => 'Omi\'ye gününü anlat'; + + @override + String get onboardingActIntro => 'Hadi Omi\'yi deneyelim.'; + + @override + String get onboardingActJustNow => 'Az önce'; + + @override + String get onboardingActMicAllow => 'Mikrofona İzin Ver'; + + @override + String get onboardingActMicBody => 'Omi\'yle konuştuğunda seni duyabilmesi için.'; + + @override + String get onboardingActMicFallbackBody => 'Omi\'n henüz ses göndermiyor. Şimdilik telefonunu kullan.'; + + @override + String get onboardingActMicTitle => 'Omi\'nin mikrofonuna ihtiyacı var'; + + @override + String get onboardingActNoAudioOmi => 'Omi\'n duyulmuyor. Açık ve yakında olduğundan emin ol.'; + + @override + String get onboardingActNoAudioPhone => 'Henüz seni duyamıyoruz. Biraz daha yüksek sesle konuşmayı dene.'; + + @override + String get onboardingActNotesLabel => 'Günün, özetle'; + + @override + String get onboardingActPhoneMic => 'Telefon mikrofonu'; + + @override + String get onboardingActRateTitle => 'Omi\'yi beğendin mi?'; + + @override + String get onboardingActRemembers => 'Omi hatırlar, senin hatırlamana gerek kalmaz'; + + @override + String get onboardingActSavedToTasks => 'Görevlerine kaydedildi'; + + @override + String get onboardingActTasksLabel => 'Omi yapılacaklarını yakaladı'; + + @override + String get onboardingActWriting => 'Özetleniyor…'; + + @override + String get onboardingActYourWords => 'Söylediklerin'; + + @override + String get onboardingConnectOmi => 'Omi\'yi bağla'; + + @override + String get onboardingConnectTapToConnect => 'Bağlamak için Omi\'ne dokun.'; + + @override + String get onboardingNoOmiYet => 'Omi\'n henüz yok mu?'; + + @override + String get onboardingOmiNotOrdered => 'Henüz sipariş vermedim'; + + @override + String get onboardingOmiOrdered => 'Sipariş verdim'; + + @override + String get onboardingPermissionTapToFix => 'Düzeltmek için dokunun'; } diff --git a/app/lib/l10n/app_localizations_uk.dart b/app/lib/l10n/app_localizations_uk.dart index 55540bd0e33..e52d7f21916 100644 --- a/app/lib/l10n/app_localizations_uk.dart +++ b/app/lib/l10n/app_localizations_uk.dart @@ -13322,4 +13322,104 @@ class AppLocalizationsUk extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Ви в списку очікування. Ми повідомимо, коли з\'явиться місце.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Усе готово, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Обід із Сем, яка в червні переїжджає до Лісабона. Треба надіслати презентацію команді до пʼятниці й купити молоко дорогою додому.'; + + @override + String get onboardingActDemoTask1 => 'Надіслати презентацію команді до п\'ятниці'; + + @override + String get onboardingActDemoTask2 => 'Купити молоко дорогою додому'; + + @override + String get onboardingActDemoTitle => 'Обід із Сем'; + + @override + String get onboardingActDictateBody => 'Кажіть по-своєму. Решту зробить Omi.'; + + @override + String get onboardingActDictateBodyOmi => 'Просто говоріть. Omi слухає.'; + + @override + String get onboardingActDictateExample => + 'Щойно пообідав із Сем, у червні вона переїжджає до Лісабона! Нагадай надіслати презентацію команді до п\'ятниці й купити молоко дорогою додому.'; + + @override + String get onboardingActDictateHint => 'Торкніться галочки, коли закінчите'; + + @override + String get onboardingActDictateTitle => 'Розкажіть Omi про свій день'; + + @override + String get onboardingActIntro => 'Спробуймо Omi.'; + + @override + String get onboardingActJustNow => 'Щойно'; + + @override + String get onboardingActMicAllow => 'Дозволити мікрофон'; + + @override + String get onboardingActMicBody => 'Щоб Omi чув вас, коли ви з ним говорите.'; + + @override + String get onboardingActMicFallbackBody => 'Omi ще не передає звук. Поки що скористайтеся телефоном.'; + + @override + String get onboardingActMicTitle => 'Omi потрібен мікрофон'; + + @override + String get onboardingActNoAudioOmi => 'Omi не чути. Перевірте, що він увімкнений і поруч.'; + + @override + String get onboardingActNoAudioPhone => 'Поки вас не чути. Спробуйте говорити трохи голосніше.'; + + @override + String get onboardingActNotesLabel => 'Ваш день коротко'; + + @override + String get onboardingActPhoneMic => 'Мікрофон телефона'; + + @override + String get onboardingActRateTitle => 'Подобається Omi?'; + + @override + String get onboardingActRemembers => 'Omi пам\'ятає, щоб вам не доводилося'; + + @override + String get onboardingActSavedToTasks => 'Збережено у ваші завдання'; + + @override + String get onboardingActTasksLabel => 'Omi помітив ваші справи'; + + @override + String get onboardingActWriting => 'Підсумовуємо…'; + + @override + String get onboardingActYourWords => 'Ось що ви сказали'; + + @override + String get onboardingConnectOmi => 'Підключіть Omi'; + + @override + String get onboardingConnectTapToConnect => 'Торкніться Omi, щоб підключити.'; + + @override + String get onboardingNoOmiYet => 'Ще немає Omi?'; + + @override + String get onboardingOmiNotOrdered => 'Ще не замовив'; + + @override + String get onboardingOmiOrdered => 'Я вже замовив'; + + @override + String get onboardingPermissionTapToFix => 'Торкніться, щоб виправити'; } diff --git a/app/lib/l10n/app_localizations_ur.dart b/app/lib/l10n/app_localizations_ur.dart index 4c0a4e2923e..43a41404dae 100644 --- a/app/lib/l10n/app_localizations_ur.dart +++ b/app/lib/l10n/app_localizations_ur.dart @@ -13306,4 +13306,104 @@ class AppLocalizationsUr extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'آپ ویٹ لسٹ میں ہیں۔ جگہ ملنے پر ہم آپ کو بتائیں گے۔'; + + @override + String onboardingActCongratsTitle(String name) { + return 'سب تیار ہے، $name'; + } + + @override + String get onboardingActDemoSummary => + 'سام کے ساتھ لنچ، جو جون میں لزبن جا رہی ہے۔ آپ کو جمعہ تک ٹیم کو ڈیک بھیجنا ہے اور گھر جاتے ہوئے دودھ لینا ہے۔'; + + @override + String get onboardingActDemoTask1 => 'جمعہ تک ٹیم کو ڈیک بھیجنا'; + + @override + String get onboardingActDemoTask2 => 'گھر جاتے ہوئے دودھ لینا'; + + @override + String get onboardingActDemoTitle => 'سیم کے ساتھ لنچ'; + + @override + String get onboardingActDictateBody => 'اپنے انداز میں کہیں۔ باقی Omi سنبھال لے گا۔'; + + @override + String get onboardingActDictateBodyOmi => 'بس بولیں۔ آپ کا Omi سن رہا ہے۔'; + + @override + String get onboardingActDictateExample => + 'ابھی سیم کے ساتھ لنچ کیا، وہ جون میں لزبن جا رہی ہے! مجھے یاد دلانا کہ جمعہ تک ٹیم کو ڈیک بھیجنا ہے، اور گھر جاتے ہوئے دودھ لینا ہے۔'; + + @override + String get onboardingActDictateHint => 'ختم ہونے پر ٹک پر ٹیپ کریں'; + + @override + String get onboardingActDictateTitle => 'Omi کو اپنے دن کے بارے میں بتائیں'; + + @override + String get onboardingActIntro => 'آئیے Omi آزمائیں۔'; + + @override + String get onboardingActJustNow => 'ابھی ابھی'; + + @override + String get onboardingActMicAllow => 'مائیکروفون کی اجازت دیں'; + + @override + String get onboardingActMicBody => 'تاکہ جب آپ بات کریں تو Omi سن سکے۔'; + + @override + String get onboardingActMicFallbackBody => 'آپ کا Omi ابھی آڈیو نہیں بھیج رہا۔ فی الحال اپنا فون استعمال کریں۔'; + + @override + String get onboardingActMicTitle => 'Omi کو آپ کا مائیکروفون چاہیے'; + + @override + String get onboardingActNoAudioOmi => 'آپ کا Omi سنائی نہیں دے رہا۔ دیکھیں کہ وہ آن اور قریب ہو۔'; + + @override + String get onboardingActNoAudioPhone => 'ابھی آپ کی آواز نہیں آ رہی۔ تھوڑا اونچا بول کر دیکھیں۔'; + + @override + String get onboardingActNotesLabel => 'آپ کا دن، مختصراً'; + + @override + String get onboardingActPhoneMic => 'فون کا مائیکروفون'; + + @override + String get onboardingActRateTitle => 'اب تک Omi پسند آیا؟'; + + @override + String get onboardingActRemembers => 'Omi یاد رکھتا ہے تاکہ آپ کو نہ رکھنا پڑے'; + + @override + String get onboardingActSavedToTasks => 'آپ کے کاموں میں محفوظ'; + + @override + String get onboardingActTasksLabel => 'Omi نے آپ کے کام پکڑ لیے'; + + @override + String get onboardingActWriting => 'خلاصہ بن رہا ہے…'; + + @override + String get onboardingActYourWords => 'آپ نے یہ کہا'; + + @override + String get onboardingConnectOmi => 'Omi کو جوڑیں'; + + @override + String get onboardingConnectTapToConnect => 'کنیکٹ کرنے کے لیے اپنے Omi پر ٹیپ کریں۔'; + + @override + String get onboardingNoOmiYet => 'ابھی تک Omi نہیں ہے؟'; + + @override + String get onboardingOmiNotOrdered => 'ابھی آرڈر نہیں کیا'; + + @override + String get onboardingOmiOrdered => 'میں نے آرڈر کر دیا ہے'; + + @override + String get onboardingPermissionTapToFix => 'ٹھیک کرنے کے لیے ٹیپ کریں'; } diff --git a/app/lib/l10n/app_localizations_vi.dart b/app/lib/l10n/app_localizations_vi.dart index d032c51d61d..bcf308bd077 100644 --- a/app/lib/l10n/app_localizations_vi.dart +++ b/app/lib/l10n/app_localizations_vi.dart @@ -13290,4 +13290,104 @@ class AppLocalizationsVi extends AppLocalizations { @override String get chatAppsWaitlistJoined => 'Bạn đã có trong danh sách chờ. Chúng tôi sẽ báo khi có chỗ.'; + + @override + String onboardingActCongratsTitle(String name) { + return 'Mọi thứ đã sẵn sàng, $name'; + } + + @override + String get onboardingActDemoSummary => + 'Ăn trưa với Sam, người sẽ chuyển đến Lisbon vào tháng Sáu. Bạn muốn gửi bản trình bày cho nhóm trước thứ Sáu và mua sữa trên đường về.'; + + @override + String get onboardingActDemoTask1 => 'Gửi slide cho nhóm trước thứ Sáu'; + + @override + String get onboardingActDemoTask2 => 'Mua sữa trên đường về nhà'; + + @override + String get onboardingActDemoTitle => 'Ăn trưa với Sam'; + + @override + String get onboardingActDictateBody => 'Cứ nói theo cách của bạn. Omi lo phần còn lại.'; + + @override + String get onboardingActDictateBodyOmi => 'Cứ nói thôi. Omi đang lắng nghe.'; + + @override + String get onboardingActDictateExample => + 'Vừa ăn trưa với Sam, tháng Sáu cô ấy chuyển đến Lisbon! Nhắc mình gửi slide cho nhóm trước thứ Sáu, và mua sữa trên đường về nhà.'; + + @override + String get onboardingActDictateHint => 'Nhấn dấu tích khi bạn nói xong'; + + @override + String get onboardingActDictateTitle => 'Kể cho Omi nghe về ngày của bạn'; + + @override + String get onboardingActIntro => 'Cùng thử Omi nhé.'; + + @override + String get onboardingActJustNow => 'Vừa xong'; + + @override + String get onboardingActMicAllow => 'Cho phép micrô'; + + @override + String get onboardingActMicBody => 'Để Omi nghe được khi bạn nói.'; + + @override + String get onboardingActMicFallbackBody => 'Omi chưa gửi âm thanh. Tạm thời hãy dùng điện thoại.'; + + @override + String get onboardingActMicTitle => 'Omi cần micrô của bạn'; + + @override + String get onboardingActNoAudioOmi => 'Không nghe thấy Omi. Hãy kiểm tra xem nó đã bật và ở gần chưa.'; + + @override + String get onboardingActNoAudioPhone => 'Chưa nghe thấy bạn. Hãy thử nói to hơn một chút.'; + + @override + String get onboardingActNotesLabel => 'Ngày của bạn, tóm tắt'; + + @override + String get onboardingActPhoneMic => 'Micrô điện thoại'; + + @override + String get onboardingActRateTitle => 'Bạn thích Omi chứ?'; + + @override + String get onboardingActRemembers => 'Omi ghi nhớ để bạn không phải nhớ'; + + @override + String get onboardingActSavedToTasks => 'Đã lưu vào việc cần làm'; + + @override + String get onboardingActTasksLabel => 'Omi đã nắm được việc cần làm'; + + @override + String get onboardingActWriting => 'Đang tóm tắt…'; + + @override + String get onboardingActYourWords => 'Đây là những gì bạn nói'; + + @override + String get onboardingConnectOmi => 'Kết nối Omi'; + + @override + String get onboardingConnectTapToConnect => 'Chạm vào Omi của bạn để kết nối.'; + + @override + String get onboardingNoOmiYet => 'Bạn chưa có Omi?'; + + @override + String get onboardingOmiNotOrdered => 'Chưa đặt hàng'; + + @override + String get onboardingOmiOrdered => 'Tôi đã đặt hàng'; + + @override + String get onboardingPermissionTapToFix => 'Nhấn để khắc phục'; } diff --git a/app/lib/l10n/app_localizations_zh.dart b/app/lib/l10n/app_localizations_zh.dart index bc44d9dd7db..db3d3dd15b7 100644 --- a/app/lib/l10n/app_localizations_zh.dart +++ b/app/lib/l10n/app_localizations_zh.dart @@ -13087,4 +13087,102 @@ class AppLocalizationsZh extends AppLocalizations { @override String get chatAppsWaitlistJoined => '你已在候补名单中。有名额时我们会通知你。'; + + @override + String onboardingActCongratsTitle(String name) { + return '一切就绪,$name'; + } + + @override + String get onboardingActDemoSummary => '和 Sam 吃了午饭,她六月要搬去里斯本。你打算周五前把演示稿发给团队,回家路上买牛奶。'; + + @override + String get onboardingActDemoTask1 => '周五前把演示文稿发给团队'; + + @override + String get onboardingActDemoTask2 => '回家路上买牛奶'; + + @override + String get onboardingActDemoTitle => '和 Sam 吃午饭'; + + @override + String get onboardingActDictateBody => '随便说,剩下的交给 Omi。'; + + @override + String get onboardingActDictateBodyOmi => '直接说就好,你的 Omi 正在听。'; + + @override + String get onboardingActDictateExample => '刚和 Sam 吃完午饭,她六月要搬去里斯本!提醒我周五前把演示文稿发给团队,回家路上顺便买牛奶。'; + + @override + String get onboardingActDictateHint => '说完后点对勾'; + + @override + String get onboardingActDictateTitle => '跟 Omi 说说你的一天'; + + @override + String get onboardingActIntro => '来试试 Omi 吧。'; + + @override + String get onboardingActJustNow => '刚刚'; + + @override + String get onboardingActMicAllow => '允许使用麦克风'; + + @override + String get onboardingActMicBody => '这样你说话时 Omi 才能听到。'; + + @override + String get onboardingActMicFallbackBody => '你的 Omi 还没有传来声音,先用手机吧。'; + + @override + String get onboardingActMicTitle => 'Omi 需要使用麦克风'; + + @override + String get onboardingActNoAudioOmi => '听不到你的 Omi。请确认它已开启并在附近。'; + + @override + String get onboardingActNoAudioPhone => '还听不到你的声音,试着大声一点。'; + + @override + String get onboardingActNotesLabel => '你的一天,总结好了'; + + @override + String get onboardingActPhoneMic => '手机麦克风'; + + @override + String get onboardingActRateTitle => '目前喜欢 Omi 吗?'; + + @override + String get onboardingActRemembers => 'Omi 帮你记住,你无需费心'; + + @override + String get onboardingActSavedToTasks => '已保存到你的任务'; + + @override + String get onboardingActTasksLabel => 'Omi 记下了你的待办'; + + @override + String get onboardingActWriting => '正在总结…'; + + @override + String get onboardingActYourWords => '你说的内容'; + + @override + String get onboardingConnectOmi => '连接 Omi'; + + @override + String get onboardingConnectTapToConnect => '轻点你的 Omi 进行连接。'; + + @override + String get onboardingNoOmiYet => '还没有 Omi?'; + + @override + String get onboardingOmiNotOrdered => '还没下单'; + + @override + String get onboardingOmiOrdered => '我已下单'; + + @override + String get onboardingPermissionTapToFix => '点按以修复'; } diff --git a/app/lib/l10n/app_lt.arb b/app/lib/l10n/app_lt.arb index 0b3596c8dde..e27cfddd4cf 100644 --- a/app/lib/l10n/app_lt.arb +++ b/app/lib/l10n/app_lt.arb @@ -4080,5 +4080,37 @@ "chatAppsAtCapacity": "{app} užpildytas", "chatAppsJoinWaitlist": "Prisijungti prie laukiančiųjų sąrašo", "chatAppsOnWaitlist": "Esate laukiančiųjų sąraše", - "chatAppsWaitlistJoined": "Esate laukiančiųjų sąraše. Pranešime, kai atsiras vietos." + "chatAppsWaitlistJoined": "Esate laukiančiųjų sąraše. Pranešime, kai atsiras vietos.", + "onboardingActCongratsTitle": "Viskas paruošta, {name}", + "onboardingActDemoSummary": "Pietūs su Sem, kuri birželį persikelia į Lisaboną. Iki penktadienio norite išsiųsti komandai pristatymą ir grįžtant nupirkti pieno.", + "onboardingActDemoTask1": "Iki penktadienio nusiųsti pristatymą komandai", + "onboardingActDemoTask2": "Grįžtant namo nupirkti pieno", + "onboardingActDemoTitle": "Pietūs su Sem", + "onboardingActDictateBody": "Sakykite savaip. Visa kita padarys Omi.", + "onboardingActDictateBodyOmi": "Tiesiog kalbėkite. Jūsų Omi klauso.", + "onboardingActDictateExample": "Ką tik pietavau su Sem, birželį ji kraustosi į Lisaboną! Primink man iki penktadienio nusiųsti pristatymą komandai ir grįžtant namo nupirkti pieno.", + "onboardingActDictateHint": "Baigę bakstelėkite varnelę", + "onboardingActDictateTitle": "Papasakokite Omi apie savo dieną", + "onboardingActIntro": "Išbandykime Omi.", + "onboardingActJustNow": "Ką tik", + "onboardingActMicAllow": "Leisti mikrofoną", + "onboardingActMicBody": "Kad Omi girdėtų, kai su juo kalbate.", + "onboardingActMicFallbackBody": "Omi dar nesiunčia garso. Kol kas naudokite telefoną.", + "onboardingActMicTitle": "Omi reikia mikrofono", + "onboardingActNoAudioOmi": "Omi negirdėti. Patikrinkite, ar jis įjungtas ir šalia.", + "onboardingActNoAudioPhone": "Dar jūsų negirdime. Pabandykite kalbėti šiek tiek garsiau.", + "onboardingActNotesLabel": "Jūsų diena trumpai", + "onboardingActPhoneMic": "Telefono mikrofonas", + "onboardingActRateTitle": "Ar Omi jums patinka?", + "onboardingActRemembers": "Omi prisimena, kad jums nereikėtų", + "onboardingActSavedToTasks": "Išsaugota jūsų užduotyse", + "onboardingActTasksLabel": "Omi pastebėjo jūsų darbus", + "onboardingActWriting": "Apibendriname…", + "onboardingActYourWords": "Štai ką pasakėte", + "onboardingConnectOmi": "Prijunkite Omi", + "onboardingConnectTapToConnect": "Palieskite savo Omi, kad prisijungtumėte.", + "onboardingNoOmiYet": "Dar neturite Omi?", + "onboardingOmiNotOrdered": "Dar neužsisakiau", + "onboardingOmiOrdered": "Jau užsisakiau", + "onboardingPermissionTapToFix": "Bakstelėkite, kad pataisytumėte" } diff --git a/app/lib/l10n/app_lv.arb b/app/lib/l10n/app_lv.arb index 1ad5eac616b..381c77cfcc2 100644 --- a/app/lib/l10n/app_lv.arb +++ b/app/lib/l10n/app_lv.arb @@ -4080,5 +4080,37 @@ "chatAppsAtCapacity": "{app} ir pilns", "chatAppsJoinWaitlist": "Pievienoties gaidīšanas sarakstam", "chatAppsOnWaitlist": "Jūs esat gaidīšanas sarakstā", - "chatAppsWaitlistJoined": "Jūs esat gaidīšanas sarakstā. Mēs paziņosim, kad būs vieta." + "chatAppsWaitlistJoined": "Jūs esat gaidīšanas sarakstā. Mēs paziņosim, kad būs vieta.", + "onboardingActCongratsTitle": "Viss gatavs, {name}", + "onboardingActDemoSummary": "Pusdienas ar Semu, kura jūnijā pārceļas uz Lisabonu. Līdz piektdienai jānosūta prezentācija komandai un mājupceļā jānopērk piens.", + "onboardingActDemoTask1": "Līdz piektdienai nosūtīt prezentāciju komandai", + "onboardingActDemoTask2": "Pa ceļam mājās nopirkt pienu", + "onboardingActDemoTitle": "Pusdienas ar Semu", + "onboardingActDictateBody": "Sakiet savā veidā. Pārējo paveiks Omi.", + "onboardingActDictateBodyOmi": "Vienkārši runājiet. Jūsu Omi klausās.", + "onboardingActDictateExample": "Tikko paēdu pusdienas ar Semu, jūnijā viņa pārceļas uz Lisabonu! Atgādini man līdz piektdienai nosūtīt prezentāciju komandai un pa ceļam mājās nopirkt pienu.", + "onboardingActDictateHint": "Kad esat pabeidzis, pieskarieties ķeksītim", + "onboardingActDictateTitle": "Pastāstiet Omi par savu dienu", + "onboardingActIntro": "Izmēģināsim Omi.", + "onboardingActJustNow": "Tikko", + "onboardingActMicAllow": "Atļaut mikrofonu", + "onboardingActMicBody": "Lai Omi jūs dzirdētu, kad ar to runājat.", + "onboardingActMicFallbackBody": "Omi vēl nesūta skaņu. Pagaidām izmantojiet tālruni.", + "onboardingActMicTitle": "Omi vajag jūsu mikrofonu", + "onboardingActNoAudioOmi": "Omi nav dzirdams. Pārliecinieties, ka tas ir ieslēgts un tuvumā.", + "onboardingActNoAudioPhone": "Vēl jūs nedzirdam. Mēģiniet runāt mazliet skaļāk.", + "onboardingActNotesLabel": "Jūsu diena īsumā", + "onboardingActPhoneMic": "Tālruņa mikrofons", + "onboardingActRateTitle": "Vai Omi jums patīk?", + "onboardingActRemembers": "Omi atceras, lai jums nebūtu jāatceras", + "onboardingActSavedToTasks": "Saglabāts jūsu uzdevumos", + "onboardingActTasksLabel": "Omi pamanīja jūsu darāmo", + "onboardingActWriting": "Apkopojam…", + "onboardingActYourWords": "Lūk, ko jūs teicāt", + "onboardingConnectOmi": "Savienot Omi", + "onboardingConnectTapToConnect": "Pieskarieties savam Omi, lai savienotu.", + "onboardingNoOmiYet": "Vēl nav Omi?", + "onboardingOmiNotOrdered": "Vēl neesmu pasūtījis", + "onboardingOmiOrdered": "Esmu to pasūtījis", + "onboardingPermissionTapToFix": "Pieskarieties, lai labotu" } diff --git a/app/lib/l10n/app_mk.arb b/app/lib/l10n/app_mk.arb index 78e08836528..23f80483529 100644 --- a/app/lib/l10n/app_mk.arb +++ b/app/lib/l10n/app_mk.arb @@ -11506,5 +11506,37 @@ "chatAppsAtCapacity": "{app} е пополнет", "chatAppsJoinWaitlist": "Приклучи се на листата на чекање", "chatAppsOnWaitlist": "Вие сте на листата на чекање", - "chatAppsWaitlistJoined": "Вие сте на листата на чекање. Ќе ве известиме кога ќе има место." + "chatAppsWaitlistJoined": "Вие сте на листата на чекање. Ќе ве известиме кога ќе има место.", + "onboardingActCongratsTitle": "Сè е подготвено, {name}", + "onboardingActDemoSummary": "Ручек со Сем, која во јуни се сели во Лисабон. Сакате да ја испратите презентацијата до тимот до петок и да купите млеко на пат кон дома.", + "onboardingActDemoTask1": "Да ја пратам презентацијата на тимот до петок", + "onboardingActDemoTask2": "Да купам млеко на пат кон дома", + "onboardingActDemoTitle": "Ручек со Сем", + "onboardingActDictateBody": "Кажете го на свој начин. Omi ќе се погрижи за останатото.", + "onboardingActDictateBodyOmi": "Само зборувајте. Вашиот Omi слуша.", + "onboardingActDictateExample": "Штотуку ручав со Сем, се сели во Лисабон во јуни! Потсети ме да ја пратам презентацијата на тимот до петок и да купам млеко на пат кон дома.", + "onboardingActDictateHint": "Допрете ја штиклата кога ќе завршите", + "onboardingActDictateTitle": "Кажете му на Omi за вашиот ден", + "onboardingActIntro": "Да го пробаме Omi.", + "onboardingActJustNow": "Токму сега", + "onboardingActMicAllow": "Дозволи микрофон", + "onboardingActMicBody": "За Omi да ве слуша кога зборувате со него.", + "onboardingActMicFallbackBody": "Omi сè уште не испраќа звук. Засега користете го телефонот.", + "onboardingActMicTitle": "На Omi му треба микрофонот", + "onboardingActNoAudioOmi": "Не се слуша Omi. Проверете дали е вклучен и блиску.", + "onboardingActNoAudioPhone": "Сè уште не ве слушаме. Обидете се да зборувате малку погласно.", + "onboardingActNotesLabel": "Вашиот ден, накратко", + "onboardingActPhoneMic": "Микрофон на телефонот", + "onboardingActRateTitle": "Ви се допаѓа Omi досега?", + "onboardingActRemembers": "Omi памети наместо вас", + "onboardingActSavedToTasks": "Зачувано во вашите задачи", + "onboardingActTasksLabel": "Omi ги фати вашите задачи", + "onboardingActWriting": "Сумираме…", + "onboardingActYourWords": "Еве што рековте", + "onboardingConnectOmi": "Поврзете Omi", + "onboardingConnectTapToConnect": "Допрете го вашиот Omi за да се поврзете.", + "onboardingNoOmiYet": "Сè уште немате Omi?", + "onboardingOmiNotOrdered": "Сè уште не сум нарачал", + "onboardingOmiOrdered": "Го нарачав", + "onboardingPermissionTapToFix": "Допрете за да поправите" } diff --git a/app/lib/l10n/app_mr.arb b/app/lib/l10n/app_mr.arb index dc590116989..d58b30daf67 100644 --- a/app/lib/l10n/app_mr.arb +++ b/app/lib/l10n/app_mr.arb @@ -11506,5 +11506,37 @@ "chatAppsAtCapacity": "{app} ची क्षमता पूर्ण झाली आहे", "chatAppsJoinWaitlist": "प्रतीक्षा यादीत सामील व्हा", "chatAppsOnWaitlist": "तुम्ही प्रतीक्षा यादीत आहात", - "chatAppsWaitlistJoined": "तुम्ही प्रतीक्षा यादीत आहात. जागा मिळाल्यावर आम्ही कळवू." + "chatAppsWaitlistJoined": "तुम्ही प्रतीक्षा यादीत आहात. जागा मिळाल्यावर आम्ही कळवू.", + "onboardingActCongratsTitle": "सगळे तयार आहे, {name}", + "onboardingActDemoSummary": "जूनमध्ये लिस्बनला जाणाऱ्या सॅमसोबत जेवण. शुक्रवारपर्यंत टीमला डेक पाठवायचा आहे आणि घरी जाताना दूध घ्यायचे आहे.", + "onboardingActDemoTask1": "शुक्रवारपर्यंत टीमला डेक पाठवणे", + "onboardingActDemoTask2": "घरी जाताना दूध घेणे", + "onboardingActDemoTitle": "सॅमसोबत जेवण", + "onboardingActDictateBody": "तुमच्या पद्धतीने सांगा. बाकी Omi पाहील.", + "onboardingActDictateBodyOmi": "फक्त बोला. तुमचा Omi ऐकत आहे.", + "onboardingActDictateExample": "आत्ताच सॅमसोबत जेवलो, ती जूनमध्ये लिस्बनला जातेय! शुक्रवारपर्यंत टीमला डेक पाठवायची आणि घरी जाताना दूध घ्यायची आठवण करून दे.", + "onboardingActDictateHint": "झाले की टिकवर टॅप करा", + "onboardingActDictateTitle": "Omi ला तुमच्या दिवसाबद्दल सांगा", + "onboardingActIntro": "चला, Omi वापरून पाहू.", + "onboardingActJustNow": "आत्ताच", + "onboardingActMicAllow": "मायक्रोफोनला परवानगी द्या", + "onboardingActMicBody": "तुम्ही बोलता तेव्हा Omi ला ऐकू यावे म्हणून.", + "onboardingActMicFallbackBody": "तुमचा Omi अजून ऑडिओ पाठवत नाही. सध्या तुमचा फोन वापरा.", + "onboardingActMicTitle": "Omi ला तुमचा मायक्रोफोन हवा आहे", + "onboardingActNoAudioOmi": "तुमचा Omi ऐकू येत नाही. तो सुरू आहे आणि जवळ आहे का ते पाहा.", + "onboardingActNoAudioPhone": "अजून तुमचा आवाज ऐकू येत नाही. थोडं मोठ्याने बोलून पाहा.", + "onboardingActNotesLabel": "तुमचा दिवस, थोडक्यात", + "onboardingActPhoneMic": "फोनचा मायक्रोफोन", + "onboardingActRateTitle": "आत्तापर्यंत Omi आवडतोय का?", + "onboardingActRemembers": "तुम्हाला लक्षात ठेवावं लागू नये म्हणून Omi लक्षात ठेवतो", + "onboardingActSavedToTasks": "तुमच्या कामांमध्ये जतन केले", + "onboardingActTasksLabel": "Omi ने तुमची कामे टिपली", + "onboardingActWriting": "सारांश तयार होत आहे…", + "onboardingActYourWords": "तुम्ही हे सांगितले", + "onboardingConnectOmi": "Omi कनेक्ट करा", + "onboardingConnectTapToConnect": "कनेक्ट करण्यासाठी तुमच्या Omi वर टॅप करा.", + "onboardingNoOmiYet": "अजून तुमचा Omi आला नाही?", + "onboardingOmiNotOrdered": "अजून ऑर्डर केलेला नाही", + "onboardingOmiOrdered": "मी ऑर्डर केला आहे", + "onboardingPermissionTapToFix": "दुरुस्त करण्यासाठी टॅप करा" } diff --git a/app/lib/l10n/app_ms.arb b/app/lib/l10n/app_ms.arb index 7a89aa15b6f..2aa9b9231bf 100644 --- a/app/lib/l10n/app_ms.arb +++ b/app/lib/l10n/app_ms.arb @@ -4080,5 +4080,37 @@ "chatAppsAtCapacity": "{app} sudah penuh", "chatAppsJoinWaitlist": "Sertai senarai menunggu", "chatAppsOnWaitlist": "Anda dalam senarai menunggu", - "chatAppsWaitlistJoined": "Anda dalam senarai menunggu. Kami akan maklumkan apabila ada tempat." + "chatAppsWaitlistJoined": "Anda dalam senarai menunggu. Kami akan maklumkan apabila ada tempat.", + "onboardingActCongratsTitle": "Semua sudah sedia, {name}", + "onboardingActDemoSummary": "Makan tengah hari dengan Sam, yang berpindah ke Lisbon pada Jun. Anda mahu hantar dek kepada pasukan sebelum Jumaat dan beli susu dalam perjalanan pulang.", + "onboardingActDemoTask1": "Hantar deck kepada pasukan sebelum Jumaat", + "onboardingActDemoTask2": "Beli susu dalam perjalanan pulang", + "onboardingActDemoTitle": "Makan tengah hari dengan Sam", + "onboardingActDictateBody": "Cakap dengan cara anda. Omi uruskan selebihnya.", + "onboardingActDictateBodyOmi": "Cakap sahaja. Omi anda sedang mendengar.", + "onboardingActDictateExample": "Baru makan tengah hari dengan Sam, dia akan pindah ke Lisbon bulan Jun! Ingatkan saya hantar deck kepada pasukan sebelum Jumaat, dan beli susu dalam perjalanan pulang.", + "onboardingActDictateHint": "Ketik tanda semak apabila selesai", + "onboardingActDictateTitle": "Ceritakan hari anda kepada Omi", + "onboardingActIntro": "Jom cuba Omi.", + "onboardingActJustNow": "Baru sahaja", + "onboardingActMicAllow": "Benarkan Mikrofon", + "onboardingActMicBody": "Supaya Omi dapat mendengar anda bercakap.", + "onboardingActMicFallbackBody": "Omi anda belum menghantar audio. Gunakan telefon anda buat masa ini.", + "onboardingActMicTitle": "Omi perlukan mikrofon anda", + "onboardingActNoAudioOmi": "Omi anda tidak kedengaran. Pastikan ia dihidupkan dan dekat.", + "onboardingActNoAudioPhone": "Suara anda belum kedengaran. Cuba bercakap lebih kuat sedikit.", + "onboardingActNotesLabel": "Hari anda, diringkaskan", + "onboardingActPhoneMic": "Mikrofon telefon", + "onboardingActRateTitle": "Suka Omi setakat ini?", + "onboardingActRemembers": "Omi ingat supaya anda tidak perlu", + "onboardingActSavedToTasks": "Disimpan ke tugasan anda", + "onboardingActTasksLabel": "Omi menangkap tugasan anda", + "onboardingActWriting": "Meringkaskan…", + "onboardingActYourWords": "Ini yang anda katakan", + "onboardingConnectOmi": "Sambungkan Omi", + "onboardingConnectTapToConnect": "Ketik Omi anda untuk menyambung.", + "onboardingNoOmiYet": "Belum ada Omi lagi?", + "onboardingOmiNotOrdered": "Belum memesan", + "onboardingOmiOrdered": "Saya sudah memesannya", + "onboardingPermissionTapToFix": "Ketik untuk membetulkan" } diff --git a/app/lib/l10n/app_nl.arb b/app/lib/l10n/app_nl.arb index f681a3360ef..131183e70a1 100644 --- a/app/lib/l10n/app_nl.arb +++ b/app/lib/l10n/app_nl.arb @@ -4080,5 +4080,37 @@ "chatAppsAtCapacity": "{app} zit vol", "chatAppsJoinWaitlist": "Op de wachtlijst", "chatAppsOnWaitlist": "Je staat op de wachtlijst", - "chatAppsWaitlistJoined": "Je staat op de wachtlijst. We laten het je weten zodra er plek is." + "chatAppsWaitlistJoined": "Je staat op de wachtlijst. We laten het je weten zodra er plek is.", + "onboardingActCongratsTitle": "Je bent klaar, {name}", + "onboardingActDemoSummary": "Lunch met Sam, die in juni naar Lissabon verhuist. Je wilt de presentatie vóór vrijdag naar je team sturen en op weg naar huis melk halen.", + "onboardingActDemoTask1": "Presentatie vóór vrijdag naar het team sturen", + "onboardingActDemoTask2": "Op weg naar huis melk halen", + "onboardingActDemoTitle": "Lunch met Sam", + "onboardingActDictateBody": "Zeg het op jouw manier. Omi doet de rest.", + "onboardingActDictateBodyOmi": "Praat gewoon. Je Omi luistert.", + "onboardingActDictateExample": "Net geluncht met Sam, ze verhuist in juni naar Lissabon! Herinner me eraan de presentatie vóór vrijdag naar het team te sturen en op weg naar huis melk te halen.", + "onboardingActDictateHint": "Tik op het vinkje als je klaar bent", + "onboardingActDictateTitle": "Vertel Omi over je dag", + "onboardingActIntro": "Probeer Omi eens.", + "onboardingActJustNow": "Zojuist", + "onboardingActMicAllow": "Microfoon toestaan", + "onboardingActMicBody": "Zodat Omi je hoort als je tegen hem praat.", + "onboardingActMicFallbackBody": "Je Omi stuurt nog geen geluid. Gebruik voorlopig je telefoon.", + "onboardingActMicTitle": "Omi heeft je microfoon nodig", + "onboardingActNoAudioOmi": "We horen je Omi niet. Controleer of hij aan staat en dichtbij is.", + "onboardingActNoAudioPhone": "We horen je nog niet. Probeer iets harder te praten.", + "onboardingActNotesLabel": "Je dag, samengevat", + "onboardingActPhoneMic": "Telefoonmicrofoon", + "onboardingActRateTitle": "Bevalt Omi tot nu toe?", + "onboardingActRemembers": "Omi onthoudt het, zodat jij dat niet hoeft", + "onboardingActSavedToTasks": "Opgeslagen in je taken", + "onboardingActTasksLabel": "Omi heeft je to-do's opgepikt", + "onboardingActWriting": "Samenvatten…", + "onboardingActYourWords": "Dit zei je", + "onboardingConnectOmi": "Omi verbinden", + "onboardingConnectTapToConnect": "Tik op je Omi om te verbinden.", + "onboardingNoOmiYet": "Heb je je Omi nog niet?", + "onboardingOmiNotOrdered": "Nog niet besteld", + "onboardingOmiOrdered": "Ik heb hem besteld", + "onboardingPermissionTapToFix": "Tik om te herstellen" } diff --git a/app/lib/l10n/app_no.arb b/app/lib/l10n/app_no.arb index 0a7887f345d..87ad86a740f 100644 --- a/app/lib/l10n/app_no.arb +++ b/app/lib/l10n/app_no.arb @@ -4080,5 +4080,37 @@ "chatAppsAtCapacity": "{app} er fullt", "chatAppsJoinWaitlist": "Sett meg på ventelisten", "chatAppsOnWaitlist": "Du står på ventelisten", - "chatAppsWaitlistJoined": "Du står på ventelisten. Vi gir beskjed når det er plass." + "chatAppsWaitlistJoined": "Du står på ventelisten. Vi gir beskjed når det er plass.", + "onboardingActCongratsTitle": "Alt er klart, {name}", + "onboardingActDemoSummary": "Lunsj med Sam, som flytter til Lisboa i juni. Du vil sende presentasjonen til teamet innen fredag og kjøpe melk på vei hjem.", + "onboardingActDemoTask1": "Send presentasjonen til teamet innen fredag", + "onboardingActDemoTask2": "Kjøp melk på vei hjem", + "onboardingActDemoTitle": "Lunsj med Sam", + "onboardingActDictateBody": "Si det på din måte. Omi tar seg av resten.", + "onboardingActDictateBodyOmi": "Bare snakk. Omi-en din lytter.", + "onboardingActDictateExample": "Spiste nettopp lunsj med Sam, hun flytter til Lisboa i juni! Minn meg på å sende presentasjonen til teamet innen fredag og kjøpe melk på vei hjem.", + "onboardingActDictateHint": "Trykk på haken når du er ferdig", + "onboardingActDictateTitle": "Fortell Omi om dagen din", + "onboardingActIntro": "La oss prøve Omi.", + "onboardingActJustNow": "Akkurat nå", + "onboardingActMicAllow": "Tillat mikrofon", + "onboardingActMicBody": "Så Omi hører deg når du snakker til den.", + "onboardingActMicFallbackBody": "Omi-en din sender ikke lyd ennå. Bruk telefonen inntil videre.", + "onboardingActMicTitle": "Omi trenger mikrofonen din", + "onboardingActNoAudioOmi": "Hører ikke Omi-en din. Sjekk at den er på og i nærheten.", + "onboardingActNoAudioPhone": "Hører deg ikke ennå. Prøv å snakke litt høyere.", + "onboardingActNotesLabel": "Dagen din, oppsummert", + "onboardingActPhoneMic": "Telefonmikrofon", + "onboardingActRateTitle": "Liker du Omi så langt?", + "onboardingActRemembers": "Omi husker det, så du slipper", + "onboardingActSavedToTasks": "Lagret i oppgavene dine", + "onboardingActTasksLabel": "Omi fanget opp gjøremålene dine", + "onboardingActWriting": "Oppsummerer…", + "onboardingActYourWords": "Dette sa du", + "onboardingConnectOmi": "Koble til Omi", + "onboardingConnectTapToConnect": "Trykk på Omi-en din for å koble til.", + "onboardingNoOmiYet": "Har du ikke fått Omi ennå?", + "onboardingOmiNotOrdered": "Ikke bestilt ennå", + "onboardingOmiOrdered": "Jeg har bestilt den", + "onboardingPermissionTapToFix": "Trykk for å fikse" } diff --git a/app/lib/l10n/app_pl.arb b/app/lib/l10n/app_pl.arb index 512646aaeae..075dca41361 100644 --- a/app/lib/l10n/app_pl.arb +++ b/app/lib/l10n/app_pl.arb @@ -4115,5 +4115,37 @@ "chatAppsAtCapacity": "{app} jest pełny", "chatAppsJoinWaitlist": "Dołącz do listy oczekujących", "chatAppsOnWaitlist": "Jesteś na liście oczekujących", - "chatAppsWaitlistJoined": "Jesteś na liście oczekujących. Damy znać, gdy zwolni się miejsce." + "chatAppsWaitlistJoined": "Jesteś na liście oczekujących. Damy znać, gdy zwolni się miejsce.", + "onboardingActCongratsTitle": "Wszystko gotowe, {name}", + "onboardingActDemoSummary": "Lunch z Sam, która w czerwcu przeprowadza się do Lizbony. Chcesz do piątku wysłać zespołowi prezentację i kupić mleko w drodze do domu.", + "onboardingActDemoTask1": "Wysłać prezentację zespołowi do piątku", + "onboardingActDemoTask2": "Kupić mleko w drodze do domu", + "onboardingActDemoTitle": "Lunch z Sam", + "onboardingActDictateBody": "Powiedz to po swojemu. Resztą zajmie się Omi.", + "onboardingActDictateBodyOmi": "Po prostu mów. Twoje Omi słucha.", + "onboardingActDictateExample": "Właśnie zjadłem lunch z Sam, w czerwcu przeprowadza się do Lizbony! Przypomnij mi, żeby wysłać prezentację zespołowi do piątku i kupić mleko w drodze do domu.", + "onboardingActDictateHint": "Gdy skończysz, dotknij znacznika", + "onboardingActDictateTitle": "Opowiedz Omi o swoim dniu", + "onboardingActIntro": "Wypróbujmy Omi.", + "onboardingActJustNow": "Przed chwilą", + "onboardingActMicAllow": "Zezwól na mikrofon", + "onboardingActMicBody": "Aby Omi słyszał Cię, gdy do niego mówisz.", + "onboardingActMicFallbackBody": "Twoje Omi jeszcze nie przesyła dźwięku. Na razie użyj telefonu.", + "onboardingActMicTitle": "Omi potrzebuje mikrofonu", + "onboardingActNoAudioOmi": "Nie słychać Omi. Sprawdź, czy jest włączone i blisko.", + "onboardingActNoAudioPhone": "Jeszcze Cię nie słychać. Spróbuj mówić trochę głośniej.", + "onboardingActNotesLabel": "Twój dzień w skrócie", + "onboardingActPhoneMic": "Mikrofon telefonu", + "onboardingActRateTitle": "Podoba Ci się Omi?", + "onboardingActRemembers": "Omi pamięta, żebyś Ty nie musiał", + "onboardingActSavedToTasks": "Zapisano w Twoich zadaniach", + "onboardingActTasksLabel": "Omi wyłapał Twoje zadania", + "onboardingActWriting": "Podsumowujemy…", + "onboardingActYourWords": "Oto, co powiedziałeś", + "onboardingConnectOmi": "Połącz Omi", + "onboardingConnectTapToConnect": "Stuknij swoje Omi, aby połączyć.", + "onboardingNoOmiYet": "Nie masz jeszcze Omi?", + "onboardingOmiNotOrdered": "Jeszcze nie zamówiłem/am", + "onboardingOmiOrdered": "Już zamówiłem/am", + "onboardingPermissionTapToFix": "Dotknij, aby naprawić" } diff --git a/app/lib/l10n/app_pt.arb b/app/lib/l10n/app_pt.arb index 5f563e592ae..0813ee11698 100644 --- a/app/lib/l10n/app_pt.arb +++ b/app/lib/l10n/app_pt.arb @@ -4115,5 +4115,37 @@ "chatAppsAtCapacity": "{app} está lotado", "chatAppsJoinWaitlist": "Entrar na lista de espera", "chatAppsOnWaitlist": "Você está na lista de espera", - "chatAppsWaitlistJoined": "Você está na lista de espera. Avisaremos quando houver vaga." + "chatAppsWaitlistJoined": "Você está na lista de espera. Avisaremos quando houver vaga.", + "onboardingActCongratsTitle": "Tudo pronto, {name}", + "onboardingActDemoSummary": "Almoço com o Sam, que vai se mudar para Lisboa em junho. Você quer enviar a apresentação para a equipe até sexta e comprar leite no caminho de casa.", + "onboardingActDemoTask1": "Mandar a apresentação para o time até sexta", + "onboardingActDemoTask2": "Comprar leite na volta para casa", + "onboardingActDemoTitle": "Almoço com a Sam", + "onboardingActDictateBody": "Fale do seu jeito. O Omi cuida do resto.", + "onboardingActDictateBodyOmi": "É só falar. Seu Omi está ouvindo.", + "onboardingActDictateExample": "Acabei de almoçar com a Sam, ela vai se mudar para Lisboa em junho! Me lembra de mandar a apresentação para o time até sexta e de comprar leite na volta para casa.", + "onboardingActDictateHint": "Toque no check quando terminar", + "onboardingActDictateTitle": "Conte ao Omi sobre o seu dia", + "onboardingActIntro": "Vamos testar o Omi.", + "onboardingActJustNow": "Agora mesmo", + "onboardingActMicAllow": "Permitir microfone", + "onboardingActMicBody": "Para o Omi ouvir quando você falar com ele.", + "onboardingActMicFallbackBody": "Seu Omi ainda não está enviando áudio. Use o celular por enquanto.", + "onboardingActMicTitle": "O Omi precisa do seu microfone", + "onboardingActNoAudioOmi": "Não estamos ouvindo seu Omi. Veja se ele está ligado e por perto.", + "onboardingActNoAudioPhone": "Ainda não estamos te ouvindo. Tente falar um pouco mais alto.", + "onboardingActNotesLabel": "Seu dia, resumido", + "onboardingActPhoneMic": "Microfone do celular", + "onboardingActRateTitle": "Está gostando do Omi?", + "onboardingActRemembers": "O Omi lembra por você", + "onboardingActSavedToTasks": "Salvo nas suas tarefas", + "onboardingActTasksLabel": "O Omi pegou suas tarefas", + "onboardingActWriting": "Resumindo…", + "onboardingActYourWords": "Veja o que você disse", + "onboardingConnectOmi": "Conectar Omi", + "onboardingConnectTapToConnect": "Toque no seu Omi para conectar.", + "onboardingNoOmiYet": "Ainda não tem o seu Omi?", + "onboardingOmiNotOrdered": "Ainda não encomendei", + "onboardingOmiOrdered": "Já encomendei", + "onboardingPermissionTapToFix": "Toque para corrigir" } diff --git a/app/lib/l10n/app_ro.arb b/app/lib/l10n/app_ro.arb index a16a9d6b130..2a31ce6d548 100644 --- a/app/lib/l10n/app_ro.arb +++ b/app/lib/l10n/app_ro.arb @@ -4080,5 +4080,37 @@ "chatAppsAtCapacity": "{app} este plin", "chatAppsJoinWaitlist": "Intră pe lista de așteptare", "chatAppsOnWaitlist": "Ești pe lista de așteptare", - "chatAppsWaitlistJoined": "Ești pe lista de așteptare. Te anunțăm când se eliberează un loc." + "chatAppsWaitlistJoined": "Ești pe lista de așteptare. Te anunțăm când se eliberează un loc.", + "onboardingActCongratsTitle": "Totul e gata, {name}", + "onboardingActDemoSummary": "Prânz cu Sam, care se mută la Lisabona în iunie. Vrei să trimiți prezentarea echipei până vineri și să iei lapte în drum spre casă.", + "onboardingActDemoTask1": "Trimite prezentarea echipei până vineri", + "onboardingActDemoTask2": "Ia lapte în drum spre casă", + "onboardingActDemoTitle": "Prânz cu Sam", + "onboardingActDictateBody": "Spune-o în felul tău. Omi se ocupă de rest.", + "onboardingActDictateBodyOmi": "Doar vorbește. Omi te ascultă.", + "onboardingActDictateExample": "Tocmai am luat prânzul cu Sam, se mută la Lisabona în iunie! Amintește-mi să trimit prezentarea echipei până vineri și să iau lapte în drum spre casă.", + "onboardingActDictateHint": "Atinge bifa când ai terminat", + "onboardingActDictateTitle": "Povestește-i lui Omi despre ziua ta", + "onboardingActIntro": "Hai să încercăm Omi.", + "onboardingActJustNow": "Chiar acum", + "onboardingActMicAllow": "Permite microfonul", + "onboardingActMicBody": "Ca Omi să te audă când vorbești cu el.", + "onboardingActMicFallbackBody": "Omi nu trimite încă sunet. Deocamdată folosește telefonul.", + "onboardingActMicTitle": "Omi are nevoie de microfon", + "onboardingActNoAudioOmi": "Nu se aude Omi. Verifică dacă e pornit și aproape.", + "onboardingActNoAudioPhone": "Încă nu te auzim. Încearcă să vorbești puțin mai tare.", + "onboardingActNotesLabel": "Ziua ta, pe scurt", + "onboardingActPhoneMic": "Microfonul telefonului", + "onboardingActRateTitle": "Îți place Omi până acum?", + "onboardingActRemembers": "Omi își amintește, ca să nu fie nevoie să o faci tu", + "onboardingActSavedToTasks": "Salvat în sarcinile tale", + "onboardingActTasksLabel": "Omi ți-a prins sarcinile", + "onboardingActWriting": "Rezumăm…", + "onboardingActYourWords": "Iată ce ai spus", + "onboardingConnectOmi": "Conectează Omi", + "onboardingConnectTapToConnect": "Atinge Omi-ul tău pentru a te conecta.", + "onboardingNoOmiYet": "Nu ai încă Omi?", + "onboardingOmiNotOrdered": "Încă nu am comandat", + "onboardingOmiOrdered": "L-am comandat", + "onboardingPermissionTapToFix": "Atinge pentru a remedia" } diff --git a/app/lib/l10n/app_ru.arb b/app/lib/l10n/app_ru.arb index 001562ad212..9b1cda1d03a 100644 --- a/app/lib/l10n/app_ru.arb +++ b/app/lib/l10n/app_ru.arb @@ -4115,5 +4115,37 @@ "chatAppsAtCapacity": "{app} заполнен", "chatAppsJoinWaitlist": "Встать в лист ожидания", "chatAppsOnWaitlist": "Вы в листе ожидания", - "chatAppsWaitlistJoined": "Вы в листе ожидания. Мы сообщим, когда появится место." + "chatAppsWaitlistJoined": "Вы в листе ожидания. Мы сообщим, когда появится место.", + "onboardingActCongratsTitle": "Всё готово, {name}", + "onboardingActDemoSummary": "Обед с Сэм, которая в июне переезжает в Лиссабон. Нужно отправить презентацию команде до пятницы и купить молоко по дороге домой.", + "onboardingActDemoTask1": "Отправить презентацию команде до пятницы", + "onboardingActDemoTask2": "Купить молоко по дороге домой", + "onboardingActDemoTitle": "Обед с Сэм", + "onboardingActDictateBody": "Говорите как вам удобно. Остальное сделает Omi.", + "onboardingActDictateBodyOmi": "Просто говорите. Omi слушает.", + "onboardingActDictateExample": "Только что пообедал с Сэм, в июне она переезжает в Лиссабон! Напомни отправить презентацию команде до пятницы и купить молоко по дороге домой.", + "onboardingActDictateHint": "Нажмите галочку, когда закончите", + "onboardingActDictateTitle": "Расскажите Omi о своём дне", + "onboardingActIntro": "Попробуем Omi.", + "onboardingActJustNow": "Только что", + "onboardingActMicAllow": "Разрешить микрофон", + "onboardingActMicBody": "Чтобы Omi слышал вас, когда вы с ним говорите.", + "onboardingActMicFallbackBody": "Omi пока не передаёт звук. Пока используйте телефон.", + "onboardingActMicTitle": "Omi нужен микрофон", + "onboardingActNoAudioOmi": "Omi не слышно. Проверьте, что он включён и рядом.", + "onboardingActNoAudioPhone": "Пока вас не слышно. Попробуйте говорить чуть громче.", + "onboardingActNotesLabel": "Ваш день вкратце", + "onboardingActPhoneMic": "Микрофон телефона", + "onboardingActRateTitle": "Нравится Omi?", + "onboardingActRemembers": "Omi запоминает, чтобы вам не пришлось", + "onboardingActSavedToTasks": "Сохранено в ваши задачи", + "onboardingActTasksLabel": "Omi нашёл ваши дела", + "onboardingActWriting": "Подводим итоги…", + "onboardingActYourWords": "Вот что вы сказали", + "onboardingConnectOmi": "Подключите Omi", + "onboardingConnectTapToConnect": "Нажмите на Omi, чтобы подключить.", + "onboardingNoOmiYet": "Ещё нет Omi?", + "onboardingOmiNotOrdered": "Ещё не заказал", + "onboardingOmiOrdered": "Я уже заказал", + "onboardingPermissionTapToFix": "Нажмите, чтобы исправить" } diff --git a/app/lib/l10n/app_sk.arb b/app/lib/l10n/app_sk.arb index 965a3131c1b..6fca1228da6 100644 --- a/app/lib/l10n/app_sk.arb +++ b/app/lib/l10n/app_sk.arb @@ -4080,5 +4080,37 @@ "chatAppsAtCapacity": "{app} je plne obsadený", "chatAppsJoinWaitlist": "Pridať sa na čakaciu listinu", "chatAppsOnWaitlist": "Ste na čakacej listine", - "chatAppsWaitlistJoined": "Ste na čakacej listine. Dáme vám vedieť, keď bude miesto." + "chatAppsWaitlistJoined": "Ste na čakacej listine. Dáme vám vedieť, keď bude miesto.", + "onboardingActCongratsTitle": "Všetko je pripravené, {name}", + "onboardingActDemoSummary": "Obed so Sam, ktorá sa v júni sťahuje do Lisabonu. Do piatku chcete poslať tímu prezentáciu a cestou domov kúpiť mlieko.", + "onboardingActDemoTask1": "Poslať prezentáciu tímu do piatka", + "onboardingActDemoTask2": "Cestou domov kúpiť mlieko", + "onboardingActDemoTitle": "Obed so Sam", + "onboardingActDictateBody": "Povedzte to po svojom. Zvyšok zariadi Omi.", + "onboardingActDictateBodyOmi": "Jednoducho hovorte. Vaše Omi počúva.", + "onboardingActDictateExample": "Práve som obedoval so Sam, v júni sa sťahuje do Lisabonu! Pripomeň mi poslať prezentáciu tímu do piatka a cestou domov kúpiť mlieko.", + "onboardingActDictateHint": "Keď skončíte, ťuknite na fajku", + "onboardingActDictateTitle": "Povedzte Omi o svojom dni", + "onboardingActIntro": "Vyskúšajme Omi.", + "onboardingActJustNow": "Práve teraz", + "onboardingActMicAllow": "Povoliť mikrofón", + "onboardingActMicBody": "Aby vás Omi počul, keď naň hovoríte.", + "onboardingActMicFallbackBody": "Omi zatiaľ neposiela zvuk. Zatiaľ použite telefón.", + "onboardingActMicTitle": "Omi potrebuje mikrofón", + "onboardingActNoAudioOmi": "Omi nepočuť. Skontrolujte, či je zapnuté a blízko.", + "onboardingActNoAudioPhone": "Zatiaľ vás nepočuť. Skúste hovoriť trochu hlasnejšie.", + "onboardingActNotesLabel": "Váš deň v skratke", + "onboardingActPhoneMic": "Mikrofón telefónu", + "onboardingActRateTitle": "Páči sa vám zatiaľ Omi?", + "onboardingActRemembers": "Omi si to pamätá, aby ste nemuseli vy", + "onboardingActSavedToTasks": "Uložené do vašich úloh", + "onboardingActTasksLabel": "Omi zachytil vaše úlohy", + "onboardingActWriting": "Zhrnujeme…", + "onboardingActYourWords": "Toto ste povedali", + "onboardingConnectOmi": "Pripojte Omi", + "onboardingConnectTapToConnect": "Ťuknutím na Omi sa pripojíte.", + "onboardingNoOmiYet": "Ešte nemáte Omi?", + "onboardingOmiNotOrdered": "Ešte neobjednané", + "onboardingOmiOrdered": "Už som ho objednal/a", + "onboardingPermissionTapToFix": "Ťuknutím opravíte" } diff --git a/app/lib/l10n/app_sl.arb b/app/lib/l10n/app_sl.arb index d84948483e4..057f31e301a 100644 --- a/app/lib/l10n/app_sl.arb +++ b/app/lib/l10n/app_sl.arb @@ -11506,5 +11506,37 @@ "chatAppsAtCapacity": "{app} je zaseden", "chatAppsJoinWaitlist": "Pridruži se čakalnemu seznamu", "chatAppsOnWaitlist": "Ste na čakalnem seznamu", - "chatAppsWaitlistJoined": "Ste na čakalnem seznamu. Sporočili vam bomo, ko bo prostor." + "chatAppsWaitlistJoined": "Ste na čakalnem seznamu. Sporočili vam bomo, ko bo prostor.", + "onboardingActCongratsTitle": "Vse je pripravljeno, {name}", + "onboardingActDemoSummary": "Kosilo s Samom, ki se junija seli v Lizbono. Do petka želite ekipi poslati predstavitev in na poti domov kupiti mleko.", + "onboardingActDemoTask1": "Do petka poslati predstavitev ekipi", + "onboardingActDemoTask2": "Na poti domov kupiti mleko", + "onboardingActDemoTitle": "Kosilo s Sam", + "onboardingActDictateBody": "Povejte po svoje. Za ostalo poskrbi Omi.", + "onboardingActDictateBodyOmi": "Samo govorite. Vaš Omi posluša.", + "onboardingActDictateExample": "Pravkar sem kosil s Sam, junija se seli v Lizbono! Opomni me, naj do petka pošljem predstavitev ekipi in na poti domov kupim mleko.", + "onboardingActDictateHint": "Ko končate, tapnite kljukico", + "onboardingActDictateTitle": "Povejte Omiju o svojem dnevu", + "onboardingActIntro": "Preizkusimo Omi.", + "onboardingActJustNow": "Pravkar", + "onboardingActMicAllow": "Dovoli mikrofon", + "onboardingActMicBody": "Da vas Omi sliši, ko mu govorite.", + "onboardingActMicFallbackBody": "Omi še ne pošilja zvoka. Za zdaj uporabite telefon.", + "onboardingActMicTitle": "Omi potrebuje mikrofon", + "onboardingActNoAudioOmi": "Omija ne slišimo. Preverite, ali je vklopljen in blizu.", + "onboardingActNoAudioPhone": "Še vas ne slišimo. Poskusite govoriti malo glasneje.", + "onboardingActNotesLabel": "Vaš dan na kratko", + "onboardingActPhoneMic": "Mikrofon telefona", + "onboardingActRateTitle": "Vam je Omi všeč?", + "onboardingActRemembers": "Omi si zapomni namesto vas", + "onboardingActSavedToTasks": "Shranjeno med vaša opravila", + "onboardingActTasksLabel": "Omi je ujel vaša opravila", + "onboardingActWriting": "Povzemamo…", + "onboardingActYourWords": "To ste povedali", + "onboardingConnectOmi": "Povežite Omi", + "onboardingConnectTapToConnect": "Tapnite svoj Omi za povezavo.", + "onboardingNoOmiYet": "Še nimate Omi?", + "onboardingOmiNotOrdered": "Še nisem naročil/a", + "onboardingOmiOrdered": "Naročil/a sem ga", + "onboardingPermissionTapToFix": "Tapnite za popravilo" } diff --git a/app/lib/l10n/app_sr.arb b/app/lib/l10n/app_sr.arb index f1c4aa896e2..538edb2cc94 100644 --- a/app/lib/l10n/app_sr.arb +++ b/app/lib/l10n/app_sr.arb @@ -11506,5 +11506,37 @@ "chatAppsAtCapacity": "{app} је попуњен", "chatAppsJoinWaitlist": "Придружи се листи чекања", "chatAppsOnWaitlist": "На листи чекања сте", - "chatAppsWaitlistJoined": "На листи чекања сте. Јавићемо вам када буде места." + "chatAppsWaitlistJoined": "На листи чекања сте. Јавићемо вам када буде места.", + "onboardingActCongratsTitle": "Све је спремно, {name}", + "onboardingActDemoSummary": "Ручак са Сем, која се у јуну сели у Лисабон. Желите да пошаљете презентацију тиму до петка и купите млеко на путу кући.", + "onboardingActDemoTask1": "Послати презентацију тиму до петка", + "onboardingActDemoTask2": "Купити млеко на путу кући", + "onboardingActDemoTitle": "Ручак са Сем", + "onboardingActDictateBody": "Реците то на свој начин. Omi ће се побринути за остало.", + "onboardingActDictateBodyOmi": "Само причајте. Ваш Omi слуша.", + "onboardingActDictateExample": "Управо сам ручао са Сем, у јуну се сели у Лисабон! Подсети ме да пошаљем презентацију тиму до петка и да купим млеко на путу кући.", + "onboardingActDictateHint": "Додирните квачицу када завршите", + "onboardingActDictateTitle": "Испричајте Omi-ју свој дан", + "onboardingActIntro": "Испробајмо Omi.", + "onboardingActJustNow": "Управо сада", + "onboardingActMicAllow": "Дозволи микрофон", + "onboardingActMicBody": "Да вас Omi чује када му се обратите.", + "onboardingActMicFallbackBody": "Omi још не шаље звук. За сада користите телефон.", + "onboardingActMicTitle": "Omi-ју треба микрофон", + "onboardingActNoAudioOmi": "Не чујемо Omi. Проверите да ли је укључен и близу.", + "onboardingActNoAudioPhone": "Још вас не чујемо. Покушајте да говорите мало гласније.", + "onboardingActNotesLabel": "Ваш дан, укратко", + "onboardingActPhoneMic": "Микрофон телефона", + "onboardingActRateTitle": "Да ли вам се Omi допада?", + "onboardingActRemembers": "Omi памти уместо вас", + "onboardingActSavedToTasks": "Сачувано у ваше задатке", + "onboardingActTasksLabel": "Omi је ухватио ваше задатке", + "onboardingActWriting": "Сажимамо…", + "onboardingActYourWords": "Ево шта сте рекли", + "onboardingConnectOmi": "Повежите Omi", + "onboardingConnectTapToConnect": "Додирните свој Omi да бисте се повезали.", + "onboardingNoOmiYet": "Још немате Omi?", + "onboardingOmiNotOrdered": "Још нисам наручио/ла", + "onboardingOmiOrdered": "Наручио/ла сам га", + "onboardingPermissionTapToFix": "Додирните да поправите" } diff --git a/app/lib/l10n/app_sv.arb b/app/lib/l10n/app_sv.arb index becaa5c0e88..f80b167cdeb 100644 --- a/app/lib/l10n/app_sv.arb +++ b/app/lib/l10n/app_sv.arb @@ -4080,5 +4080,37 @@ "chatAppsAtCapacity": "{app} är fullt", "chatAppsJoinWaitlist": "Gå med i väntelistan", "chatAppsOnWaitlist": "Du står på väntelistan", - "chatAppsWaitlistJoined": "Du står på väntelistan. Vi hör av oss när det finns plats." + "chatAppsWaitlistJoined": "Du står på väntelistan. Vi hör av oss när det finns plats.", + "onboardingActCongratsTitle": "Allt är klart, {name}", + "onboardingActDemoSummary": "Lunch med Sam, som flyttar till Lissabon i juni. Du vill skicka presentationen till teamet senast fredag och köpa mjölk på vägen hem.", + "onboardingActDemoTask1": "Skicka presentationen till teamet senast fredag", + "onboardingActDemoTask2": "Köp mjölk på vägen hem", + "onboardingActDemoTitle": "Lunch med Sam", + "onboardingActDictateBody": "Säg det på ditt sätt. Omi sköter resten.", + "onboardingActDictateBodyOmi": "Prata bara. Din Omi lyssnar.", + "onboardingActDictateExample": "Åt precis lunch med Sam, hon flyttar till Lissabon i juni! Påminn mig om att skicka presentationen till teamet senast fredag och att köpa mjölk på vägen hem.", + "onboardingActDictateHint": "Tryck på bocken när du är klar", + "onboardingActDictateTitle": "Berätta för Omi om din dag", + "onboardingActIntro": "Vi testar Omi.", + "onboardingActJustNow": "Precis nu", + "onboardingActMicAllow": "Tillåt mikrofon", + "onboardingActMicBody": "Så att Omi hör dig när du pratar med den.", + "onboardingActMicFallbackBody": "Din Omi skickar inget ljud än. Använd telefonen så länge.", + "onboardingActMicTitle": "Omi behöver din mikrofon", + "onboardingActNoAudioOmi": "Hör inte din Omi. Kontrollera att den är på och nära.", + "onboardingActNoAudioPhone": "Hör dig inte än. Försök prata lite högre.", + "onboardingActNotesLabel": "Din dag, sammanfattad", + "onboardingActPhoneMic": "Telefonens mikrofon", + "onboardingActRateTitle": "Gillar du Omi hittills?", + "onboardingActRemembers": "Omi minns så att du slipper", + "onboardingActSavedToTasks": "Sparat i dina uppgifter", + "onboardingActTasksLabel": "Omi fångade dina att-göra", + "onboardingActWriting": "Sammanfattar…", + "onboardingActYourWords": "Det här sa du", + "onboardingConnectOmi": "Anslut Omi", + "onboardingConnectTapToConnect": "Tryck på din Omi för att ansluta.", + "onboardingNoOmiYet": "Har du inte fått din Omi än?", + "onboardingOmiNotOrdered": "Inte beställt än", + "onboardingOmiOrdered": "Jag har beställt den", + "onboardingPermissionTapToFix": "Tryck för att åtgärda" } diff --git a/app/lib/l10n/app_ta.arb b/app/lib/l10n/app_ta.arb index ca0a459b1dc..2dbe21e5fd4 100644 --- a/app/lib/l10n/app_ta.arb +++ b/app/lib/l10n/app_ta.arb @@ -11506,5 +11506,37 @@ "chatAppsAtCapacity": "{app} நிரம்பியுள்ளது", "chatAppsJoinWaitlist": "காத்திருப்புப் பட்டியலில் சேர்", "chatAppsOnWaitlist": "நீங்கள் காத்திருப்புப் பட்டியலில் உள்ளீர்கள்", - "chatAppsWaitlistJoined": "நீங்கள் காத்திருப்புப் பட்டியலில் உள்ளீர்கள். இடம் கிடைத்ததும் தெரிவிப்போம்." + "chatAppsWaitlistJoined": "நீங்கள் காத்திருப்புப் பட்டியலில் உள்ளீர்கள். இடம் கிடைத்ததும் தெரிவிப்போம்.", + "onboardingActCongratsTitle": "எல்லாம் தயார், {name}", + "onboardingActDemoSummary": "ஜூனில் லிஸ்பனுக்குச் செல்லும் சாமுடன் மதிய உணவு. வெள்ளிக்குள் குழுவுக்கு டெக் அனுப்பவும், வீட்டுக்குப் போகும் வழியில் பால் வாங்கவும் வேண்டும்.", + "onboardingActDemoTask1": "வெள்ளிக்குள் குழுவுக்கு டெக் அனுப்புவது", + "onboardingActDemoTask2": "வீட்டுக்குப் போகும் வழியில் பால் வாங்குவது", + "onboardingActDemoTitle": "சாமுடன் மதிய உணவு", + "onboardingActDictateBody": "உங்கள் பாணியில் சொல்லுங்கள். மீதியை Omi பார்த்துக்கொள்ளும்.", + "onboardingActDictateBodyOmi": "பேசுங்கள் போதும். உங்கள் Omi கேட்டுக்கொண்டிருக்கிறது.", + "onboardingActDictateExample": "இப்போதுதான் சாமுடன் மதிய உணவு சாப்பிட்டேன், அவள் ஜூனில் லிஸ்பனுக்குப் போகிறாள்! வெள்ளிக்குள் குழுவுக்கு டெக் அனுப்பவும், வீட்டுக்குப் போகும் வழியில் பால் வாங்கவும் நினைவூட்டு.", + "onboardingActDictateHint": "முடிந்ததும் டிக்கைத் தட்டவும்", + "onboardingActDictateTitle": "உங்கள் நாளைப் பற்றி Omi-யிடம் சொல்லுங்கள்", + "onboardingActIntro": "Omi-ஐ முயற்சிப்போம்.", + "onboardingActJustNow": "இப்போதுதான்", + "onboardingActMicAllow": "மைக்ரோஃபோனை அனுமதி", + "onboardingActMicBody": "நீங்கள் பேசும்போது Omi கேட்க.", + "onboardingActMicFallbackBody": "உங்கள் Omi இன்னும் ஒலியை அனுப்பவில்லை. இப்போதைக்கு உங்கள் போனைப் பயன்படுத்துங்கள்.", + "onboardingActMicTitle": "Omi-க்கு உங்கள் மைக்ரோஃபோன் தேவை", + "onboardingActNoAudioOmi": "உங்கள் Omi கேட்கவில்லை. அது இயக்கத்தில் உள்ளதா, அருகில் உள்ளதா எனப் பாருங்கள்.", + "onboardingActNoAudioPhone": "இன்னும் உங்கள் குரல் கேட்கவில்லை. சற்று சத்தமாகப் பேசிப் பாருங்கள்.", + "onboardingActNotesLabel": "உங்கள் நாள், சுருக்கமாக", + "onboardingActPhoneMic": "தொலைபேசி மைக்ரோஃபோன்", + "onboardingActRateTitle": "இதுவரை Omi பிடித்திருக்கிறதா?", + "onboardingActRemembers": "நீங்கள் நினைவில் வைக்க வேண்டியதில்லை, Omi வைத்துக்கொள்ளும்", + "onboardingActSavedToTasks": "உங்கள் பணிகளில் சேமிக்கப்பட்டது", + "onboardingActTasksLabel": "Omi உங்கள் வேலைகளைக் கண்டறிந்தது", + "onboardingActWriting": "சுருக்கப்படுகிறது…", + "onboardingActYourWords": "நீங்கள் சொன்னது இது", + "onboardingConnectOmi": "Omi-ஐ இணைக்கவும்", + "onboardingConnectTapToConnect": "இணைக்க உங்கள் Omi-ஐத் தட்டவும்.", + "onboardingNoOmiYet": "இன்னும் உங்கள் Omi இல்லையா?", + "onboardingOmiNotOrdered": "இன்னும் ஆர்டர் செய்யவில்லை", + "onboardingOmiOrdered": "நான் ஆர்டர் செய்துவிட்டேன்", + "onboardingPermissionTapToFix": "சரிசெய்ய தட்டவும்" } diff --git a/app/lib/l10n/app_te.arb b/app/lib/l10n/app_te.arb index 11217b3c463..d6360ff2d05 100644 --- a/app/lib/l10n/app_te.arb +++ b/app/lib/l10n/app_te.arb @@ -11506,5 +11506,37 @@ "chatAppsAtCapacity": "{app} నిండిపోయింది", "chatAppsJoinWaitlist": "వెయిట్‌లిస్ట్‌లో చేరండి", "chatAppsOnWaitlist": "మీరు వెయిట్‌లిస్ట్‌లో ఉన్నారు", - "chatAppsWaitlistJoined": "మీరు వెయిట్‌లిస్ట్‌లో ఉన్నారు. స్థలం దొరికినప్పుడు మీకు తెలియజేస్తాము." + "chatAppsWaitlistJoined": "మీరు వెయిట్‌లిస్ట్‌లో ఉన్నారు. స్థలం దొరికినప్పుడు మీకు తెలియజేస్తాము.", + "onboardingActCongratsTitle": "అంతా సిద్ధం, {name}", + "onboardingActDemoSummary": "జూన్‌లో లిస్బన్‌కు వెళ్తున్న సామ్‌తో లంచ్. శుక్రవారం లోపు టీమ్‌కు డెక్ పంపాలి, ఇంటికి వెళ్తూ పాలు కొనాలి.", + "onboardingActDemoTask1": "శుక్రవారంలోగా టీమ్‌కి డెక్ పంపడం", + "onboardingActDemoTask2": "ఇంటికి వెళ్లేటప్పుడు పాలు తీసుకోవడం", + "onboardingActDemoTitle": "సామ్‌తో లంచ్", + "onboardingActDictateBody": "మీ శైలిలో చెప్పండి. మిగతాది Omi చూసుకుంటుంది.", + "onboardingActDictateBodyOmi": "మాట్లాడండి చాలు. మీ Omi వింటోంది.", + "onboardingActDictateExample": "ఇప్పుడే సామ్‌తో లంచ్ చేశా, తను జూన్‌లో లిస్బన్‌కి వెళ్తోంది! శుక్రవారంలోగా టీమ్‌కి డెక్ పంపాలని, ఇంటికి వెళ్లేటప్పుడు పాలు తీసుకోవాలని గుర్తు చేయి.", + "onboardingActDictateHint": "అయ్యాక టిక్ నొక్కండి", + "onboardingActDictateTitle": "మీ రోజు గురించి Omiకి చెప్పండి", + "onboardingActIntro": "Omi ని ప్రయత్నిద్దాం.", + "onboardingActJustNow": "ఇప్పుడే", + "onboardingActMicAllow": "మైక్రోఫోన్‌ను అనుమతించండి", + "onboardingActMicBody": "మీరు మాట్లాడినప్పుడు Omi వినడానికి.", + "onboardingActMicFallbackBody": "మీ Omi ఇంకా ఆడియో పంపడం లేదు. ప్రస్తుతానికి మీ ఫోన్ వాడండి.", + "onboardingActMicTitle": "Omiకి మీ మైక్రోఫోన్ కావాలి", + "onboardingActNoAudioOmi": "మీ Omi వినిపించడం లేదు. అది ఆన్‌లో ఉందో, దగ్గర్లో ఉందో చూడండి.", + "onboardingActNoAudioPhone": "ఇంకా మీ గొంతు వినిపించడం లేదు. కొంచెం గట్టిగా మాట్లాడి చూడండి.", + "onboardingActNotesLabel": "మీ రోజు, సంక్షిప్తంగా", + "onboardingActPhoneMic": "ఫోన్ మైక్రోఫోన్", + "onboardingActRateTitle": "ఇప్పటివరకు Omi నచ్చిందా?", + "onboardingActRemembers": "మీరు గుర్తుంచుకోనవసరం లేదు, Omi గుర్తుంచుకుంటుంది", + "onboardingActSavedToTasks": "మీ పనుల్లో సేవ్ అయింది", + "onboardingActTasksLabel": "Omi మీ పనులను గుర్తించింది", + "onboardingActWriting": "సారాంశం తయారవుతోంది…", + "onboardingActYourWords": "మీరు చెప్పింది ఇది", + "onboardingConnectOmi": "Omiని కనెక్ట్ చేయండి", + "onboardingConnectTapToConnect": "కనెక్ట్ చేయడానికి మీ Omi ని ట్యాప్ చేయండి.", + "onboardingNoOmiYet": "ఇంకా మీ Omi లేదా?", + "onboardingOmiNotOrdered": "ఇంకా ఆర్డర్ చేయలేదు", + "onboardingOmiOrdered": "నేను ఆర్డర్ చేశాను", + "onboardingPermissionTapToFix": "సరిచేయడానికి నొక్కండి" } diff --git a/app/lib/l10n/app_th.arb b/app/lib/l10n/app_th.arb index 7700cd2e525..5c741a06632 100644 --- a/app/lib/l10n/app_th.arb +++ b/app/lib/l10n/app_th.arb @@ -4080,5 +4080,37 @@ "chatAppsAtCapacity": "{app} เต็มแล้ว", "chatAppsJoinWaitlist": "เข้าร่วมรายชื่อรอ", "chatAppsOnWaitlist": "คุณอยู่ในรายชื่อรอแล้ว", - "chatAppsWaitlistJoined": "คุณอยู่ในรายชื่อรอแล้ว เราจะแจ้งให้ทราบเมื่อมีที่ว่าง" + "chatAppsWaitlistJoined": "คุณอยู่ในรายชื่อรอแล้ว เราจะแจ้งให้ทราบเมื่อมีที่ว่าง", + "onboardingActCongratsTitle": "พร้อมแล้ว {name}", + "onboardingActDemoSummary": "ทานข้าวกลางวันกับแซมที่จะย้ายไปลิสบอนเดือนมิถุนายน คุณต้องส่งสไลด์ให้ทีมภายในวันศุกร์ และซื้อนมระหว่างทางกลับบ้าน", + "onboardingActDemoTask1": "ส่งสไลด์ให้ทีมภายในวันศุกร์", + "onboardingActDemoTask2": "ซื้อนมตอนกลับบ้าน", + "onboardingActDemoTitle": "มื้อกลางวันกับแซม", + "onboardingActDictateBody": "พูดแบบที่คุณถนัด ที่เหลือ Omi จัดการเอง", + "onboardingActDictateBodyOmi": "พูดได้เลย Omi กำลังฟังอยู่", + "onboardingActDictateExample": "เพิ่งกินข้าวกลางวันกับแซม เธอจะย้ายไปลิสบอนเดือนมิถุนายน! เตือนฉันส่งสไลด์ให้ทีมภายในวันศุกร์ แล้วก็ซื้อนมตอนกลับบ้านด้วย", + "onboardingActDictateHint": "แตะเครื่องหมายถูกเมื่อพูดเสร็จ", + "onboardingActDictateTitle": "เล่าวันของคุณให้ Omi ฟัง", + "onboardingActIntro": "มาลองใช้ Omi กัน", + "onboardingActJustNow": "เมื่อสักครู่", + "onboardingActMicAllow": "อนุญาตไมโครโฟน", + "onboardingActMicBody": "เพื่อให้ Omi ได้ยินเมื่อคุณพูดด้วย", + "onboardingActMicFallbackBody": "Omi ยังไม่ส่งเสียงมา ใช้โทรศัพท์ไปก่อนนะ", + "onboardingActMicTitle": "Omi ต้องใช้ไมโครโฟน", + "onboardingActNoAudioOmi": "ไม่ได้ยินเสียงจาก Omi ตรวจดูว่าเปิดอยู่และอยู่ใกล้ๆ", + "onboardingActNoAudioPhone": "ยังไม่ได้ยินเสียงคุณ ลองพูดดังขึ้นอีกนิด", + "onboardingActNotesLabel": "สรุปวันของคุณ", + "onboardingActPhoneMic": "ไมโครโฟนโทรศัพท์", + "onboardingActRateTitle": "ชอบ Omi ไหม", + "onboardingActRemembers": "Omi จำให้ คุณไม่ต้องจำเอง", + "onboardingActSavedToTasks": "บันทึกไว้ในงานของคุณแล้ว", + "onboardingActTasksLabel": "Omi จับสิ่งที่ต้องทำให้แล้ว", + "onboardingActWriting": "กำลังสรุป…", + "onboardingActYourWords": "สิ่งที่คุณพูด", + "onboardingConnectOmi": "เชื่อมต่อ Omi", + "onboardingConnectTapToConnect": "แตะ Omi ของคุณเพื่อเชื่อมต่อ", + "onboardingNoOmiYet": "ยังไม่มี Omi ใช่ไหม", + "onboardingOmiNotOrdered": "ยังไม่ได้สั่งซื้อ", + "onboardingOmiOrdered": "สั่งซื้อแล้ว", + "onboardingPermissionTapToFix": "แตะเพื่อแก้ไข" } diff --git a/app/lib/l10n/app_tl.arb b/app/lib/l10n/app_tl.arb index cc8163b838c..7c1893054e6 100644 --- a/app/lib/l10n/app_tl.arb +++ b/app/lib/l10n/app_tl.arb @@ -11506,5 +11506,37 @@ "chatAppsAtCapacity": "Puno na ang {app}", "chatAppsJoinWaitlist": "Sumali sa waitlist", "chatAppsOnWaitlist": "Nasa waitlist ka na", - "chatAppsWaitlistJoined": "Nasa waitlist ka na. Sasabihan ka namin kapag may puwesto na." + "chatAppsWaitlistJoined": "Nasa waitlist ka na. Sasabihan ka namin kapag may puwesto na.", + "onboardingActCongratsTitle": "Handa ka na, {name}", + "onboardingActDemoSummary": "Tanghalian kasama si Sam, na lilipat sa Lisbon sa Hunyo. Gusto mong ipadala ang deck sa team bago mag-Biyernes at bumili ng gatas pauwi.", + "onboardingActDemoTask1": "Ipadala ang deck sa team bago mag-Biyernes", + "onboardingActDemoTask2": "Bumili ng gatas pauwi", + "onboardingActDemoTitle": "Lunch kasama si Sam", + "onboardingActDictateBody": "Sabihin mo sa sarili mong paraan. Ang Omi na ang bahala.", + "onboardingActDictateBodyOmi": "Magsalita ka lang. Nakikinig ang Omi mo.", + "onboardingActDictateExample": "Kakatapos ko lang mag-lunch kasama si Sam, lilipat na siya sa Lisbon sa Hunyo! Ipaalala mo sa akin na ipadala ang deck sa team bago mag-Biyernes, at bumili ng gatas pauwi.", + "onboardingActDictateHint": "I-tap ang check kapag tapos ka na", + "onboardingActDictateTitle": "Ikuwento sa Omi ang araw mo", + "onboardingActIntro": "Subukan natin ang Omi.", + "onboardingActJustNow": "Ngayon lang", + "onboardingActMicAllow": "Payagan ang Mikropono", + "onboardingActMicBody": "Para marinig ka ng Omi kapag kinakausap mo ito.", + "onboardingActMicFallbackBody": "Hindi pa nagpapadala ng audio ang Omi mo. Gamitin muna ang iyong phone.", + "onboardingActMicTitle": "Kailangan ng Omi ang mikropono mo", + "onboardingActNoAudioOmi": "Hindi marinig ang Omi mo. Siguraduhing naka-on ito at malapit.", + "onboardingActNoAudioPhone": "Hindi ka pa marinig. Subukang magsalita nang medyo malakas.", + "onboardingActNotesLabel": "Ang araw mo, buod", + "onboardingActPhoneMic": "Mikropono ng phone", + "onboardingActRateTitle": "Gusto mo ba ang Omi sa ngayon?", + "onboardingActRemembers": "Tinatandaan ng Omi para hindi mo na kailangan", + "onboardingActSavedToTasks": "Na-save sa mga gawain mo", + "onboardingActTasksLabel": "Nahuli ng Omi ang mga gagawin mo", + "onboardingActWriting": "Binubuod…", + "onboardingActYourWords": "Ito ang sinabi mo", + "onboardingConnectOmi": "Ikonekta ang Omi", + "onboardingConnectTapToConnect": "I-tap ang iyong Omi para kumonekta.", + "onboardingNoOmiYet": "Wala ka pang Omi?", + "onboardingOmiNotOrdered": "Hindi pa naka-order", + "onboardingOmiOrdered": "Na-order ko na", + "onboardingPermissionTapToFix": "I-tap para ayusin" } diff --git a/app/lib/l10n/app_tr.arb b/app/lib/l10n/app_tr.arb index 32322538913..489fc92689c 100644 --- a/app/lib/l10n/app_tr.arb +++ b/app/lib/l10n/app_tr.arb @@ -4115,5 +4115,37 @@ "chatAppsAtCapacity": "{app} kapasitesi dolu", "chatAppsJoinWaitlist": "Bekleme listesine katıl", "chatAppsOnWaitlist": "Bekleme listesindesin", - "chatAppsWaitlistJoined": "Bekleme listesindesin. Yer açıldığında haber vereceğiz." + "chatAppsWaitlistJoined": "Bekleme listesindesin. Yer açıldığında haber vereceğiz.", + "onboardingActCongratsTitle": "Her şey hazır, {name}", + "onboardingActDemoSummary": "Haziranda Lizbon'a taşınan Sam'le öğle yemeği. Cumaya kadar sunumu ekibine göndermek ve eve dönerken süt almak istiyorsun.", + "onboardingActDemoTask1": "Cumaya kadar sunumu ekibe gönder", + "onboardingActDemoTask2": "Eve dönerken süt al", + "onboardingActDemoTitle": "Sam'le öğle yemeği", + "onboardingActDictateBody": "Kendi tarzınla söyle. Gerisini Omi halleder.", + "onboardingActDictateBodyOmi": "Sadece konuş. Omi'n dinliyor.", + "onboardingActDictateExample": "Az önce Sam'le öğle yemeği yedim, haziranda Lizbon'a taşınıyor! Cumaya kadar sunumu ekibe göndermemi ve eve dönerken süt almamı hatırlat.", + "onboardingActDictateHint": "Bitince onay işaretine dokun", + "onboardingActDictateTitle": "Omi'ye gününü anlat", + "onboardingActIntro": "Hadi Omi'yi deneyelim.", + "onboardingActJustNow": "Az önce", + "onboardingActMicAllow": "Mikrofona İzin Ver", + "onboardingActMicBody": "Omi'yle konuştuğunda seni duyabilmesi için.", + "onboardingActMicFallbackBody": "Omi'n henüz ses göndermiyor. Şimdilik telefonunu kullan.", + "onboardingActMicTitle": "Omi'nin mikrofonuna ihtiyacı var", + "onboardingActNoAudioOmi": "Omi'n duyulmuyor. Açık ve yakında olduğundan emin ol.", + "onboardingActNoAudioPhone": "Henüz seni duyamıyoruz. Biraz daha yüksek sesle konuşmayı dene.", + "onboardingActNotesLabel": "Günün, özetle", + "onboardingActPhoneMic": "Telefon mikrofonu", + "onboardingActRateTitle": "Omi'yi beğendin mi?", + "onboardingActRemembers": "Omi hatırlar, senin hatırlamana gerek kalmaz", + "onboardingActSavedToTasks": "Görevlerine kaydedildi", + "onboardingActTasksLabel": "Omi yapılacaklarını yakaladı", + "onboardingActWriting": "Özetleniyor…", + "onboardingActYourWords": "Söylediklerin", + "onboardingConnectOmi": "Omi'yi bağla", + "onboardingConnectTapToConnect": "Bağlamak için Omi'ne dokun.", + "onboardingNoOmiYet": "Omi'n henüz yok mu?", + "onboardingOmiNotOrdered": "Henüz sipariş vermedim", + "onboardingOmiOrdered": "Sipariş verdim", + "onboardingPermissionTapToFix": "Düzeltmek için dokunun" } diff --git a/app/lib/l10n/app_uk.arb b/app/lib/l10n/app_uk.arb index d4d53ee1441..77bce57adc1 100644 --- a/app/lib/l10n/app_uk.arb +++ b/app/lib/l10n/app_uk.arb @@ -4080,5 +4080,37 @@ "chatAppsAtCapacity": "{app} заповнено", "chatAppsJoinWaitlist": "Приєднатися до списку очікування", "chatAppsOnWaitlist": "Ви в списку очікування", - "chatAppsWaitlistJoined": "Ви в списку очікування. Ми повідомимо, коли з'явиться місце." + "chatAppsWaitlistJoined": "Ви в списку очікування. Ми повідомимо, коли з'явиться місце.", + "onboardingActCongratsTitle": "Усе готово, {name}", + "onboardingActDemoSummary": "Обід із Сем, яка в червні переїжджає до Лісабона. Треба надіслати презентацію команді до пʼятниці й купити молоко дорогою додому.", + "onboardingActDemoTask1": "Надіслати презентацію команді до п'ятниці", + "onboardingActDemoTask2": "Купити молоко дорогою додому", + "onboardingActDemoTitle": "Обід із Сем", + "onboardingActDictateBody": "Кажіть по-своєму. Решту зробить Omi.", + "onboardingActDictateBodyOmi": "Просто говоріть. Omi слухає.", + "onboardingActDictateExample": "Щойно пообідав із Сем, у червні вона переїжджає до Лісабона! Нагадай надіслати презентацію команді до п'ятниці й купити молоко дорогою додому.", + "onboardingActDictateHint": "Торкніться галочки, коли закінчите", + "onboardingActDictateTitle": "Розкажіть Omi про свій день", + "onboardingActIntro": "Спробуймо Omi.", + "onboardingActJustNow": "Щойно", + "onboardingActMicAllow": "Дозволити мікрофон", + "onboardingActMicBody": "Щоб Omi чув вас, коли ви з ним говорите.", + "onboardingActMicFallbackBody": "Omi ще не передає звук. Поки що скористайтеся телефоном.", + "onboardingActMicTitle": "Omi потрібен мікрофон", + "onboardingActNoAudioOmi": "Omi не чути. Перевірте, що він увімкнений і поруч.", + "onboardingActNoAudioPhone": "Поки вас не чути. Спробуйте говорити трохи голосніше.", + "onboardingActNotesLabel": "Ваш день коротко", + "onboardingActPhoneMic": "Мікрофон телефона", + "onboardingActRateTitle": "Подобається Omi?", + "onboardingActRemembers": "Omi пам'ятає, щоб вам не доводилося", + "onboardingActSavedToTasks": "Збережено у ваші завдання", + "onboardingActTasksLabel": "Omi помітив ваші справи", + "onboardingActWriting": "Підсумовуємо…", + "onboardingActYourWords": "Ось що ви сказали", + "onboardingConnectOmi": "Підключіть Omi", + "onboardingConnectTapToConnect": "Торкніться Omi, щоб підключити.", + "onboardingNoOmiYet": "Ще немає Omi?", + "onboardingOmiNotOrdered": "Ще не замовив", + "onboardingOmiOrdered": "Я вже замовив", + "onboardingPermissionTapToFix": "Торкніться, щоб виправити" } diff --git a/app/lib/l10n/app_ur.arb b/app/lib/l10n/app_ur.arb index 4addc335a6a..7653f7c1beb 100644 --- a/app/lib/l10n/app_ur.arb +++ b/app/lib/l10n/app_ur.arb @@ -11506,5 +11506,37 @@ "chatAppsAtCapacity": "{app} کی گنجائش پوری ہو گئی ہے", "chatAppsJoinWaitlist": "ویٹ لسٹ میں شامل ہوں", "chatAppsOnWaitlist": "آپ ویٹ لسٹ میں ہیں", - "chatAppsWaitlistJoined": "آپ ویٹ لسٹ میں ہیں۔ جگہ ملنے پر ہم آپ کو بتائیں گے۔" + "chatAppsWaitlistJoined": "آپ ویٹ لسٹ میں ہیں۔ جگہ ملنے پر ہم آپ کو بتائیں گے۔", + "onboardingActCongratsTitle": "سب تیار ہے، {name}", + "onboardingActDemoSummary": "سام کے ساتھ لنچ، جو جون میں لزبن جا رہی ہے۔ آپ کو جمعہ تک ٹیم کو ڈیک بھیجنا ہے اور گھر جاتے ہوئے دودھ لینا ہے۔", + "onboardingActDemoTask1": "جمعہ تک ٹیم کو ڈیک بھیجنا", + "onboardingActDemoTask2": "گھر جاتے ہوئے دودھ لینا", + "onboardingActDemoTitle": "سیم کے ساتھ لنچ", + "onboardingActDictateBody": "اپنے انداز میں کہیں۔ باقی Omi سنبھال لے گا۔", + "onboardingActDictateBodyOmi": "بس بولیں۔ آپ کا Omi سن رہا ہے۔", + "onboardingActDictateExample": "ابھی سیم کے ساتھ لنچ کیا، وہ جون میں لزبن جا رہی ہے! مجھے یاد دلانا کہ جمعہ تک ٹیم کو ڈیک بھیجنا ہے، اور گھر جاتے ہوئے دودھ لینا ہے۔", + "onboardingActDictateHint": "ختم ہونے پر ٹک پر ٹیپ کریں", + "onboardingActDictateTitle": "Omi کو اپنے دن کے بارے میں بتائیں", + "onboardingActIntro": "آئیے Omi آزمائیں۔", + "onboardingActJustNow": "ابھی ابھی", + "onboardingActMicAllow": "مائیکروفون کی اجازت دیں", + "onboardingActMicBody": "تاکہ جب آپ بات کریں تو Omi سن سکے۔", + "onboardingActMicFallbackBody": "آپ کا Omi ابھی آڈیو نہیں بھیج رہا۔ فی الحال اپنا فون استعمال کریں۔", + "onboardingActMicTitle": "Omi کو آپ کا مائیکروفون چاہیے", + "onboardingActNoAudioOmi": "آپ کا Omi سنائی نہیں دے رہا۔ دیکھیں کہ وہ آن اور قریب ہو۔", + "onboardingActNoAudioPhone": "ابھی آپ کی آواز نہیں آ رہی۔ تھوڑا اونچا بول کر دیکھیں۔", + "onboardingActNotesLabel": "آپ کا دن، مختصراً", + "onboardingActPhoneMic": "فون کا مائیکروفون", + "onboardingActRateTitle": "اب تک Omi پسند آیا؟", + "onboardingActRemembers": "Omi یاد رکھتا ہے تاکہ آپ کو نہ رکھنا پڑے", + "onboardingActSavedToTasks": "آپ کے کاموں میں محفوظ", + "onboardingActTasksLabel": "Omi نے آپ کے کام پکڑ لیے", + "onboardingActWriting": "خلاصہ بن رہا ہے…", + "onboardingActYourWords": "آپ نے یہ کہا", + "onboardingConnectOmi": "Omi کو جوڑیں", + "onboardingConnectTapToConnect": "کنیکٹ کرنے کے لیے اپنے Omi پر ٹیپ کریں۔", + "onboardingNoOmiYet": "ابھی تک Omi نہیں ہے؟", + "onboardingOmiNotOrdered": "ابھی آرڈر نہیں کیا", + "onboardingOmiOrdered": "میں نے آرڈر کر دیا ہے", + "onboardingPermissionTapToFix": "ٹھیک کرنے کے لیے ٹیپ کریں" } diff --git a/app/lib/l10n/app_vi.arb b/app/lib/l10n/app_vi.arb index 74759531dec..3aac8aa9e63 100644 --- a/app/lib/l10n/app_vi.arb +++ b/app/lib/l10n/app_vi.arb @@ -4080,5 +4080,37 @@ "chatAppsAtCapacity": "{app} đã hết chỗ", "chatAppsJoinWaitlist": "Tham gia danh sách chờ", "chatAppsOnWaitlist": "Bạn đã có trong danh sách chờ", - "chatAppsWaitlistJoined": "Bạn đã có trong danh sách chờ. Chúng tôi sẽ báo khi có chỗ." + "chatAppsWaitlistJoined": "Bạn đã có trong danh sách chờ. Chúng tôi sẽ báo khi có chỗ.", + "onboardingActCongratsTitle": "Mọi thứ đã sẵn sàng, {name}", + "onboardingActDemoSummary": "Ăn trưa với Sam, người sẽ chuyển đến Lisbon vào tháng Sáu. Bạn muốn gửi bản trình bày cho nhóm trước thứ Sáu và mua sữa trên đường về.", + "onboardingActDemoTask1": "Gửi slide cho nhóm trước thứ Sáu", + "onboardingActDemoTask2": "Mua sữa trên đường về nhà", + "onboardingActDemoTitle": "Ăn trưa với Sam", + "onboardingActDictateBody": "Cứ nói theo cách của bạn. Omi lo phần còn lại.", + "onboardingActDictateBodyOmi": "Cứ nói thôi. Omi đang lắng nghe.", + "onboardingActDictateExample": "Vừa ăn trưa với Sam, tháng Sáu cô ấy chuyển đến Lisbon! Nhắc mình gửi slide cho nhóm trước thứ Sáu, và mua sữa trên đường về nhà.", + "onboardingActDictateHint": "Nhấn dấu tích khi bạn nói xong", + "onboardingActDictateTitle": "Kể cho Omi nghe về ngày của bạn", + "onboardingActIntro": "Cùng thử Omi nhé.", + "onboardingActJustNow": "Vừa xong", + "onboardingActMicAllow": "Cho phép micrô", + "onboardingActMicBody": "Để Omi nghe được khi bạn nói.", + "onboardingActMicFallbackBody": "Omi chưa gửi âm thanh. Tạm thời hãy dùng điện thoại.", + "onboardingActMicTitle": "Omi cần micrô của bạn", + "onboardingActNoAudioOmi": "Không nghe thấy Omi. Hãy kiểm tra xem nó đã bật và ở gần chưa.", + "onboardingActNoAudioPhone": "Chưa nghe thấy bạn. Hãy thử nói to hơn một chút.", + "onboardingActNotesLabel": "Ngày của bạn, tóm tắt", + "onboardingActPhoneMic": "Micrô điện thoại", + "onboardingActRateTitle": "Bạn thích Omi chứ?", + "onboardingActRemembers": "Omi ghi nhớ để bạn không phải nhớ", + "onboardingActSavedToTasks": "Đã lưu vào việc cần làm", + "onboardingActTasksLabel": "Omi đã nắm được việc cần làm", + "onboardingActWriting": "Đang tóm tắt…", + "onboardingActYourWords": "Đây là những gì bạn nói", + "onboardingConnectOmi": "Kết nối Omi", + "onboardingConnectTapToConnect": "Chạm vào Omi của bạn để kết nối.", + "onboardingNoOmiYet": "Bạn chưa có Omi?", + "onboardingOmiNotOrdered": "Chưa đặt hàng", + "onboardingOmiOrdered": "Tôi đã đặt hàng", + "onboardingPermissionTapToFix": "Nhấn để khắc phục" } diff --git a/app/lib/l10n/app_zh.arb b/app/lib/l10n/app_zh.arb index 37949075c22..1fd912423d2 100644 --- a/app/lib/l10n/app_zh.arb +++ b/app/lib/l10n/app_zh.arb @@ -4080,5 +4080,37 @@ "chatAppsAtCapacity": "{app} 名额已满", "chatAppsJoinWaitlist": "加入候补名单", "chatAppsOnWaitlist": "你已在候补名单中", - "chatAppsWaitlistJoined": "你已在候补名单中。有名额时我们会通知你。" + "chatAppsWaitlistJoined": "你已在候补名单中。有名额时我们会通知你。", + "onboardingActCongratsTitle": "一切就绪,{name}", + "onboardingActDemoSummary": "和 Sam 吃了午饭,她六月要搬去里斯本。你打算周五前把演示稿发给团队,回家路上买牛奶。", + "onboardingActDemoTask1": "周五前把演示文稿发给团队", + "onboardingActDemoTask2": "回家路上买牛奶", + "onboardingActDemoTitle": "和 Sam 吃午饭", + "onboardingActDictateBody": "随便说,剩下的交给 Omi。", + "onboardingActDictateBodyOmi": "直接说就好,你的 Omi 正在听。", + "onboardingActDictateExample": "刚和 Sam 吃完午饭,她六月要搬去里斯本!提醒我周五前把演示文稿发给团队,回家路上顺便买牛奶。", + "onboardingActDictateHint": "说完后点对勾", + "onboardingActDictateTitle": "跟 Omi 说说你的一天", + "onboardingActIntro": "来试试 Omi 吧。", + "onboardingActJustNow": "刚刚", + "onboardingActMicAllow": "允许使用麦克风", + "onboardingActMicBody": "这样你说话时 Omi 才能听到。", + "onboardingActMicFallbackBody": "你的 Omi 还没有传来声音,先用手机吧。", + "onboardingActMicTitle": "Omi 需要使用麦克风", + "onboardingActNoAudioOmi": "听不到你的 Omi。请确认它已开启并在附近。", + "onboardingActNoAudioPhone": "还听不到你的声音,试着大声一点。", + "onboardingActNotesLabel": "你的一天,总结好了", + "onboardingActPhoneMic": "手机麦克风", + "onboardingActRateTitle": "目前喜欢 Omi 吗?", + "onboardingActRemembers": "Omi 帮你记住,你无需费心", + "onboardingActSavedToTasks": "已保存到你的任务", + "onboardingActTasksLabel": "Omi 记下了你的待办", + "onboardingActWriting": "正在总结…", + "onboardingActYourWords": "你说的内容", + "onboardingConnectOmi": "连接 Omi", + "onboardingConnectTapToConnect": "轻点你的 Omi 进行连接。", + "onboardingNoOmiYet": "还没有 Omi?", + "onboardingOmiNotOrdered": "还没下单", + "onboardingOmiOrdered": "我已下单", + "onboardingPermissionTapToFix": "点按以修复" } diff --git a/app/lib/pages/apps/widgets/omi_web_page.dart b/app/lib/pages/apps/widgets/omi_web_page.dart index d5d6da38978..d0c1414a5bb 100644 --- a/app/lib/pages/apps/widgets/omi_web_page.dart +++ b/app/lib/pages/apps/widgets/omi_web_page.dart @@ -12,7 +12,7 @@ import 'package:omi/utils/l10n_extensions.dart'; /// skip past it.) /// * First load shows a spinner; a failed main-frame load shows the reason and **Try Again**. class OmiWebPage extends StatefulWidget { - const OmiWebPage({super.key, required this.title, required this.url, this.userAgent}); + const OmiWebPage({super.key, required this.title, required this.url, this.userAgent, this.showClose = false}); final String title; final Uri url; @@ -20,6 +20,10 @@ class OmiWebPage extends StatefulWidget { /// Overrides the web view's user agent (some app home pages sniff it). final String? userAgent; + /// A Close (×) that dismisses the whole page, for a page presented as a sheet (the Omi store). + /// The default back button steps back through the web history first. + final bool showClose; + @override State createState() => _OmiWebPageState(); } @@ -75,7 +79,8 @@ class _OmiWebPageState extends State { @override Widget build(BuildContext context) { return PopScope( - canPop: !_canGoBack || _failed, + // A sheet closes as a whole; a pushed page walks back through its web history first. + canPop: widget.showClose || !_canGoBack || _failed, onPopInvokedWithResult: (didPop, _) async { if (didPop) return; await _controller.goBack(); @@ -84,7 +89,14 @@ class _OmiWebPageState extends State { }, child: Scaffold( appBar: AppBar( - leading: const OmiBackButton(), + leading: widget.showClose + ? OmiIconButton( + key: const Key('omi_web_page_close'), + icon: const Icon(Icons.close), + label: context.l10n.close, + onPressed: () => Navigator.of(context).pop(), + ) + : const OmiBackButton(), title: Text(widget.title, maxLines: 1, overflow: TextOverflow.ellipsis), ), body: Stack( diff --git a/app/lib/pages/onboarding/permissions/onboarding_permissions_panel.dart b/app/lib/pages/onboarding/permissions/onboarding_permissions_panel.dart index 03019b66714..5e8ac2a7524 100644 --- a/app/lib/pages/onboarding/permissions/onboarding_permissions_panel.dart +++ b/app/lib/pages/onboarding/permissions/onboarding_permissions_panel.dart @@ -8,6 +8,7 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; +import 'package:omi/l10n/app_localizations.dart'; import 'package:omi/providers/onboarding_provider.dart'; import 'package:omi/ui/components/omi_permission_row.dart'; import 'package:omi/ui/omi_tokens.dart'; @@ -18,7 +19,10 @@ import 'package:omi/utils/logger.dart'; /// /// "Always" location is deliberately not here: it is asked by the feature that needs it /// (Settings → Permissions), never during first run (docs/ux-contract.md §15). -enum OnboardingPermission { background, location, notifications } +/// +/// [bluetooth] is asked as the app opens; the first-run step shows it again only when it was not +/// allowed. The returning-user interstitial does not list it. +enum OnboardingPermission { bluetooth, background, location, notifications } /// Reads and requests [OnboardingPermission]s. The default reads the platform directly and requests /// through [OnboardingProvider] (which keeps its telemetry and flags); tests pass a fake. @@ -43,6 +47,16 @@ class PlatformOnboardingPermissionsSource implements OnboardingPermissionsSource @override Future status(OnboardingPermission permission) async { switch (permission) { + case OnboardingPermission.bluetooth: + if (Platform.isIOS) return OmiPermissionStatus.fromStatus(await Permission.bluetooth.status); + final scan = OmiPermissionStatus.fromStatus(await Permission.bluetoothScan.status); + final connect = OmiPermissionStatus.fromStatus(await Permission.bluetoothConnect.status); + if (scan == OmiPermissionStatus.granted && connect == OmiPermissionStatus.granted) { + return OmiPermissionStatus.granted; + } + return scan == OmiPermissionStatus.blocked || connect == OmiPermissionStatus.blocked + ? OmiPermissionStatus.blocked + : OmiPermissionStatus.askable; case OnboardingPermission.background: final granted = await FlutterForegroundTask.isIgnoringBatteryOptimizations; return granted ? OmiPermissionStatus.granted : OmiPermissionStatus.askable; @@ -57,6 +71,8 @@ class PlatformOnboardingPermissionsSource implements OnboardingPermissionsSource @override Future request(OnboardingPermission permission) async { switch (permission) { + case OnboardingPermission.bluetooth: + await provider.askForBluetoothPermissions(); case OnboardingPermission.background: await provider.askForBackgroundPermissions(); case OnboardingPermission.location: @@ -67,6 +83,28 @@ class PlatformOnboardingPermissionsSource implements OnboardingPermissionsSource } } +/// The same glyphs as the Settings permission list. +FaIconData onboardingPermissionIcon(OnboardingPermission permission) => switch (permission) { + OnboardingPermission.bluetooth => FontAwesomeIcons.bluetoothB, + OnboardingPermission.background => FontAwesomeIcons.batteryFull, + OnboardingPermission.location => FontAwesomeIcons.locationArrow, + OnboardingPermission.notifications => FontAwesomeIcons.solidBell, + }; + +String onboardingPermissionTitle(AppLocalizations l10n, OnboardingPermission permission) => switch (permission) { + OnboardingPermission.bluetooth => l10n.bluetooth, + OnboardingPermission.background => l10n.backgroundActivity, + OnboardingPermission.location => l10n.locationAccess, + OnboardingPermission.notifications => l10n.notifications, + }; + +String onboardingPermissionReason(AppLocalizations l10n, OnboardingPermission permission) => switch (permission) { + OnboardingPermission.bluetooth => l10n.onboardingBluetoothRequired, + OnboardingPermission.background => l10n.backgroundActivityDesc, + OnboardingPermission.location => l10n.locationAccessDesc, + OnboardingPermission.notifications => l10n.notificationsDesc, + }; + /// [source], or the platform source over the ambient [OnboardingProvider]. OnboardingPermissionsSource resolveOnboardingPermissionsSource( BuildContext context, @@ -164,21 +202,9 @@ class _OnboardingPermissionsPanelState extends State OmiPermissionRow( key: ValueKey('onboarding_permission_${permission.name}'), // The same glyphs as the Settings permission list. - leading: FaIcon(switch (permission) { - OnboardingPermission.background => FontAwesomeIcons.batteryFull, - OnboardingPermission.location => FontAwesomeIcons.locationArrow, - OnboardingPermission.notifications => FontAwesomeIcons.solidBell, - }), - title: switch (permission) { - OnboardingPermission.background => l10n.backgroundActivity, - OnboardingPermission.location => l10n.locationAccess, - OnboardingPermission.notifications => l10n.notifications, - }, - reason: switch (permission) { - OnboardingPermission.background => l10n.backgroundActivityDesc, - OnboardingPermission.location => l10n.locationAccessDesc, - OnboardingPermission.notifications => l10n.notificationsDesc, - }, + leading: FaIcon(onboardingPermissionIcon(permission)), + title: onboardingPermissionTitle(l10n, permission), + reason: onboardingPermissionReason(l10n, permission), status: _statuses[permission] ?? OmiPermissionStatus.askable, onAllow: () => _allow(permission), ), diff --git a/app/lib/pages/onboarding/variants/yinka/activation/activation_capture.dart b/app/lib/pages/onboarding/variants/yinka/activation/activation_capture.dart new file mode 100644 index 00000000000..a3174e921c6 --- /dev/null +++ b/app/lib/pages/onboarding/variants/yinka/activation/activation_capture.dart @@ -0,0 +1,408 @@ +import 'dart:async'; +import 'dart:math' as math; +import 'dart:io' show File; +import 'dart:typed_data'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; + +import 'package:path_provider/path_provider.dart'; +import 'package:permission_handler/permission_handler.dart'; + +import 'package:provider/provider.dart'; + +import 'package:omi/backend/http/api/speech_profile.dart'; +import 'package:omi/backend/preferences.dart'; +import 'package:omi/backend/schema/conversation.dart'; +import 'package:omi/providers/capture_provider.dart'; +import 'package:omi/providers/conversation_provider.dart'; +import 'package:omi/providers/home_provider.dart'; +import 'package:omi/services/capture/optimistic_processing.dart'; +import 'package:omi/utils/enums.dart'; +import 'package:omi/utils/audio/wav_bytes.dart'; +import 'package:omi/utils/logger.dart'; +import 'package:omi/utils/platform/platform_manager.dart'; + +/// What the first-run dictation became: the conversation Omi wrote from it, or (when Omi could not +/// make one in time, or judged it not worth keeping) just the words the reader said. +@immutable +class ActivationResult { + const ActivationResult({ + required this.transcript, + this.emoji = '', + this.title = '', + this.overview = '', + this.tasks = const [], + this.notesPending = false, + }); + + final String transcript; + final String emoji; + final String title; + final String overview; + final List tasks; + + /// Omi had not finished its notes when the reader was moved on; they land on Home later. + final bool notesPending; + + /// Omi wrote notes from it; otherwise only [transcript] is shown. + bool get hasNotes => title.isNotEmpty || overview.isNotEmpty; +} + +/// How starting to listen went. +enum ActivationStart { + listening, + + /// The reader's Omi is not streaming and the phone microphone is not allowed yet: ask for it first. + needsMicrophone, + failed, +} + +/// The recording behind the first-run "Tell Omi about your day" step. +/// +/// Uses the app's own capture pipeline, so the dictation becomes a real conversation that is first +/// on Home: through the reader's Omi when it is already streaming, otherwise through the phone +/// microphone. Replaceable in tests. +abstract class ActivationCapture { + /// Whether the words come from the reader's Omi (true) or the phone microphone. + bool get usesDevice; + + /// What has been heard since [start], updated live. + ValueListenable get liveText; + + /// How loud the audio coming in is, 0–1, updated live: Omi visibly hears the person before any + /// words come back. + ValueListenable get level; + + /// Starts listening. + Future start(); + + /// Stops listening and waits (up to [timeout]) for Omi's notes. + Future finish({Duration timeout}); + + /// Stops listening without making notes. + Future cancel(); +} + +class PipelineActivationCapture implements ActivationCapture { + PipelineActivationCapture(BuildContext context, {this.expectDevice = false}) + : _capture = context.read(), + _conversations = context.read(), + _home = context.read(); + + /// The reader connected an Omi: wait for it to stream rather than taking over with the phone. + final bool expectDevice; + + final CaptureProvider _capture; + final ConversationProvider _conversations; + final HomeProvider _home; + final ValueNotifier _live = ValueNotifier(''); + final ValueNotifier _level = ValueNotifier(0); + int _startIndex = 0; + bool _device = false; + bool _phoneStarted = false; + bool _listening = false; + + /// Set by [cancel]. [start] checks it after each wait, so a Skip while it is still starting does + /// not leave the phone microphone recording behind the next screen. + bool _cancelled = false; + + /// When the dictation began, to tell its conversation from the audio before it. + DateTime? _listeningSince; + + /// The phone dictation's own audio, kept to enroll the reader's voice (the old voice step did this; + /// without it Omi cannot tell the reader apart from the people they talk to). + final BytesBuilder _voice = BytesBuilder(copy: false); + + // 16 kHz mono PCM16. The server takes 5–180 s; a few sentences is plenty. + static const _bytesPerSecond = 16000 * 2; + static const _minVoiceSeconds = 6; + + /// Words the reader must have said for the audio to be worth a voiceprint: silence, a TV or + /// someone else talking makes few or no words of the reader's own. + static const _minVoiceWords = 10; + static const _maxVoiceSeconds = 170; + + @override + bool get usesDevice => _device; + + @override + ValueListenable get liveText => _live; + + @override + ValueListenable get level => _level; + + /// Eases toward [target] so the meter moves with speech without flickering. + void _meter(double target) { + final next = _level.value * 0.6 + target.clamp(0.0, 1.0) * 0.4; + if ((next - _level.value).abs() > 0.01) _level.value = next; + } + + /// Loudness of 16-bit little-endian PCM: speech sits roughly between 500 and 6000 RMS. + static double _pcmLevel(List pcm) { + var sum = 0.0; + var n = 0; + for (var i = 0; i + 1 < pcm.length; i += 2) { + var v = pcm[i] | (pcm[i + 1] << 8); + if (v >= 0x8000) v -= 0x10000; + sum += v * v; + n++; + } + if (n == 0) return 0; + return ((math.sqrt(sum / n) - 300) / 4000).clamp(0.0, 1.0); + } + + /// The pendant's packets are compressed: louder speech makes bigger packets, silence small ones. + static double _packetLevel(List packet) => ((packet.length - 30) / 110).clamp(0.0, 1.0); + + void _onCapture() { + if (!_listening) return; + final segments = _capture.segments; + // Fewer segments than when we started means the conversation was reset: all of them are new. + if (segments.length < _startIndex) _startIndex = 0; + final start = _startIndex; + _live.value = segments.skip(start).map((s) => s.text.trim()).where((t) => t.isNotEmpty).join(' '); + } + + @override + Future start() async { + try { + // Live words need the streaming transport, not "transcribe later". + await _capture.suspendBatchModeForOnboarding(); + // A just-connected Omi can take a moment to start streaming. + for (var waited = 0; expectDevice && !_isDeviceRecording && waited < 5000; waited += 250) { + await Future.delayed(const Duration(milliseconds: 250)); + if (_cancelled) return ActivationStart.failed; + } + if (_cancelled) return ActivationStart.failed; + _device = _isDeviceRecording; + if (_device) { + _capture.deviceAudioTap = (packet) => _meter(_packetLevel(packet)); + // The Omi has been listening since it connected (unboxing, the screens before this one). + // Close that first, so the dictation is a conversation of its own. + if (_capture.segments.isNotEmpty) await _capture.forceProcessingCurrentConversation(); + } else { + if (!await Permission.microphone.isGranted) return ActivationStart.needsMicrophone; + if (_cancelled) return ActivationStart.failed; + _phoneStarted = true; + final keepVoice = !SharedPreferencesUtil().hasSpeakerProfile; + _capture.phoneMicAudioTap = (pcm) { + _meter(_pcmLevel(pcm)); + if (keepVoice && _voice.length < _maxVoiceSeconds * _bytesPerSecond) _voice.add(pcm); + }; + await _capture.streamRecording(); + // Skipped while the microphone was starting: cancel()'s stop may have run before it was on, + // so stop it again now that it is. + if (_cancelled) { + _phoneStarted = true; + await _stopPhone(); + return ActivationStart.failed; + } + } + if (_cancelled) return ActivationStart.failed; + _capture.suppressShortCaptureNotice = true; + _startIndex = _capture.segments.length; + _listeningSince = DateTime.now(); + _listening = true; + _capture.addListener(_onCapture); + return ActivationStart.listening; + } catch (e) { + Logger.debug('Activation capture could not start: $e'); + return ActivationStart.failed; + } + } + + bool get _isDeviceRecording => _capture.recordingState == RecordingState.deviceRecord; + + static final _word = RegExp(r"[\p{L}\p{N}']+", unicode: true); + + static Iterable _wordsOf(String text) => _word.allMatches(text.toLowerCase()).map((m) => m.group(0)!); + + /// Whether [conversation] holds the dictation. The audio from before it (the unboxing, the screens + /// before this one) can become a conversation in the same moment, so: it must not have begun well + /// before the dictation, and its transcript must share most of the reader's first real words + /// (whole words; "a", "to" and the like match anything). + bool _isDictation(ServerConversation conversation, String heard) { + final began = conversation.startedAt; + final since = _listeningSince; + // Server and phone clocks can differ a little; the earlier audio began minutes before. + if (began != null && since != null && began.isBefore(since.subtract(const Duration(seconds: 20)))) { + return false; + } + final words = _wordsOf(heard).where((w) => w.length > 2).take(6).toList(); + if (words.isEmpty) return true; + final said = _wordsOf(conversation.transcriptSegments.map((s) => s.text).join(' ')).toSet(); + // Not loaded with the list: trust it only when its start time says it is the dictation. + if (said.isEmpty) return began != null && since != null; + return words.where(said.contains).length >= (words.length / 2).ceil(); + } + + void _stopListening() { + _listening = false; + _capture.removeListener(_onCapture); + _capture.phoneMicAudioTap = null; + _capture.deviceAudioTap = null; + _capture.suppressShortCaptureNotice = false; + _level.value = 0; + } + + /// Uploads the dictation as the reader's voice profile, in the background: it never holds up the + /// notes, and a failure only means the Home card or Settings asks for it later. + Future _enrollVoice(Uint8List pcm, String heard) async { + if (pcm.length < _minVoiceSeconds * _bytesPerSecond || SharedPreferencesUtil().hasSpeakerProfile) return; + if (_wordsOf(heard).length < _minVoiceWords) return; + File? file; + try { + final directory = await getTemporaryDirectory(); + file = await File('${directory.path}/omi-first-voice-${DateTime.now().millisecondsSinceEpoch}.wav') + .writeAsBytes(WavBytes.fromPcm(pcm, sampleRate: 16000, numChannels: 1).asBytes()); + if (await uploadProfile(file).timeout(const Duration(seconds: 45))) { + SharedPreferencesUtil().hasSpeakerProfile = true; + _home.setSpeakerProfile(true); + PlatformManager.instance.analytics.speechProfileUploadSucceeded(); + PlatformManager.instance.analytics.speechProfileEmbeddingStored(); + } + } on SpeechProfileUploadException catch (e) { + PlatformManager.instance.analytics.speechProfileUploadFailed( + reason: 'http_${e.statusCode}', + statusCode: e.statusCode, + ); + } catch (e) { + Logger.debug('Activation voice enrollment failed: $e'); + PlatformManager.instance.analytics.speechProfileUploadFailed(reason: 'activation_upload_failed'); + } finally { + if (file != null && await file.exists()) await file.delete(); + } + } + + @override + Future finish({Duration timeout = const Duration(seconds: 30)}) async { + final transcript = _live.value; + _stopListening(); + final known = _conversations.conversations.map((c) => c.id).toSet(); + final created = Completer(); + void watch() { + for (final conversation in _conversations.conversations) { + if (conversation.id == OptimisticProcessingPlaceholder.id || known.contains(conversation.id)) continue; + // The audio from before the dictation may land here too; only the dictation's own counts. + if (!_isDictation(conversation, transcript)) { + known.add(conversation.id); + continue; + } + if (!created.isCompleted) created.complete(conversation); + return; + } + } + + _conversations.addListener(watch); + try { + if (_phoneStarted) { + // If an Omi is connected but was not streaming, give it back the recording. + await _capture.stopStreamRecording(resumeHandedOffPendant: expectDevice); + _phoneStarted = false; + } + unawaited(_enrollVoice(_voice.takeBytes(), transcript)); + // Ends the conversation now instead of after the usual silence, and adds it to Home. + await _capture.forceProcessingCurrentConversation(); + final conversation = await created.future.timeout(timeout); + final structured = conversation.structured; + if (conversation.discarded) return ActivationResult(transcript: transcript); + return ActivationResult( + transcript: transcript, + emoji: '${structured.getEmoji()}', + title: structured.title.trim(), + overview: structured.overview.trim(), + tasks: structured.actionItems + .where((a) => !a.deleted) + .map((a) => a.description.trim()) + .where((d) => d.isNotEmpty) + .take(4) + .toList(), + ); + } catch (e) { + Logger.debug('Activation notes not ready: $e'); + return ActivationResult(transcript: transcript); + } finally { + _conversations.removeListener(watch); + } + } + + @override + Future cancel() async { + _cancelled = true; + _stopListening(); + _voice.clear(); + await _stopPhone(); + } + + Future _stopPhone() async { + if (!_phoneStarted) return; + _phoneStarted = false; + try { + await _capture.stopStreamRecording(resumeHandedOffPendant: expectDevice); + } catch (_) {} + } +} + +/// Debug-only stand-in for local servers without speech-to-text: "hears" [script] word by word and +/// returns sample notes, so the activation can be reviewed end to end. Turn on with +/// `--dart-define=OMI_ACTIVATION_DEMO=1`, or `=device` to also act as if an Omi were connected +/// (simulators have no Bluetooth). Never active in release builds. +class DemoActivationCapture implements ActivationCapture { + DemoActivationCapture({required this.script, required this.result}); + + static const _flag = String.fromEnvironment('OMI_ACTIVATION_DEMO'); + static bool get enabled => kDebugMode && (_flag == '1' || _flag == 'device'); + + /// Review the device owner's path without a device. + static bool get pretendsDevice => kDebugMode && _flag == 'device'; + + final String script; + final ActivationResult result; + final ValueNotifier _live = ValueNotifier(''); + final ValueNotifier _level = ValueNotifier(0); + Timer? _timer; + int _beat = 0; + + @override + bool get usesDevice => pretendsDevice; + + @override + ValueListenable get liveText => _live; + + @override + ValueListenable get level => _level; + + @override + Future start() async { + final words = script.split(' '); + var shown = 0; + _timer = Timer.periodic(const Duration(milliseconds: 260), (timer) { + if (shown >= words.length) { + _level.value = 0; + return timer.cancel(); + } + shown++; + _live.value = words.take(shown).join(' '); + // A speaking rhythm for the meter. + _level.value = 0.35 + 0.5 * ((_beat++ * 7) % 10) / 10; + }); + return ActivationStart.listening; + } + + @override + Future finish({Duration timeout = const Duration(seconds: 30)}) async { + _timer?.cancel(); + await Future.delayed(const Duration(seconds: 2)); + return ActivationResult( + transcript: _live.value, + emoji: result.emoji, + title: result.title, + overview: result.overview, + tasks: result.tasks, + ); + } + + @override + Future cancel() async => _timer?.cancel(); +} diff --git a/app/lib/pages/onboarding/variants/yinka/activation/activation_flow.dart b/app/lib/pages/onboarding/variants/yinka/activation/activation_flow.dart new file mode 100644 index 00000000000..80b22aad5a2 --- /dev/null +++ b/app/lib/pages/onboarding/variants/yinka/activation/activation_flow.dart @@ -0,0 +1,1293 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:in_app_review/in_app_review.dart'; +import 'package:permission_handler/permission_handler.dart'; + +import 'package:omi/backend/preferences.dart'; +import 'package:omi/l10n/app_localizations.dart'; +import 'package:omi/pages/onboarding/variants/yinka/activation/activation_capture.dart'; +import 'package:omi/pages/onboarding/variants/yinka/activation/activation_motion.dart'; +import 'package:omi/pages/onboarding/variants/yinka/activation/activation_visuals.dart'; +import 'package:omi/ui/ui.dart'; +import 'package:omi/utils/l10n_extensions.dart'; +import 'package:omi/utils/platform/platform_manager.dart'; +import 'package:omi/widgets/scanning_ripple.dart'; +import 'package:omi/pages/onboarding/variants/yinka/widgets/onboarding_card.dart'; + +/// Height of the row above every title: Skip sits in it on the dictation step. +const double _kTopRowHeight = 44; + +/// Space between that row and every title: low enough to feel centred, high enough that the title +/// stays clear of the system prompts (microphone, App Store rating) that open mid-screen. +const double _kHeadingGap = 76; + +/// The area under the card on dictation, "Summarizing…", the summary and the tasks. One height +/// for all four, so the card stays in the same place while the words turn into notes. +const double _kControlHeight = 172; + +/// The stages of the first-run activation, in order. Some are skipped: [microphone] with an Omi (or +/// when already allowed), [taskList] when Omi found no tasks. How to wear and use an Omi is the +/// device tutorial Home shows after onboarding. +enum ActivationStage { intro, microphone, dictate, writing, summary, taskList, congrats, rating } + +/// Shows people what Omi does with their own words, then hands over to the paywall and Home. +/// +/// Everyone sees "Let's try Omi." and then says a few +/// sentences. Omi turns them into a summary and to-dos (a real conversation, first on Home), then +/// come a congratulations screen, the rating ask and the paywall. Skipping the dictation shows a +/// welcome card on Home instead. +class ActivationFlow extends StatefulWidget { + const ActivationFlow({ + super.key, + required this.hasDevice, + required this.onFinished, + this.showPaywall, + this.capture, + this.microphoneGranted, + this.requestMicrophone, + this.requestReview, + this.notesDeadline = const Duration(seconds: 12), + }); + + /// The reader connected a wearable in onboarding: they dictate through it. + final bool hasDevice; + + /// Called once at the very end; Home follows. + /// Saves onboarding as done and leaves for Home; false when the save failed (the reader can retry). + final Future Function() onFinished; + + /// Shows the paywall after the rating ask, last before Home; completes when it is closed. + final Future Function()? showPaywall; + + /// Defaults to the app's capture pipeline; replaceable in tests. + final ActivationCapture Function(BuildContext context)? capture; + final Future Function()? microphoneGranted; + final Future Function()? requestMicrophone; + final Future Function()? requestReview; + + /// How long "Summarizing…" waits for Omi's notes. A few sentences usually take seconds; past this + /// the reader moves on with their own words, and the notes arrive on Home. + final Duration notesDeadline; + + /// Set when the reader skipped the dictation, so Home shows the welcome card. + static const welcomeCardKey = 'onboarding/showWelcomeCard'; + + @override + State createState() => _ActivationFlowState(); +} + +class _ActivationFlowState extends State { + ActivationStage _stage = ActivationStage.intro; + ActivationCapture? _capture; + + /// The connected wearable was not streaming, so this dictation uses the phone microphone. + bool _phoneInstead = false; + String _heard = ''; + ActivationResult? _result; + bool _finished = false; + bool _paywallShown = false; + + void _track(String step) => + PlatformManager.instance.analytics.track('Onboarding Activation Step', properties: {'step': step}); + + void _go(ActivationStage next) { + if (!mounted) return; + _track(next.name); + // Each visit to the dictation listens afresh. + if (next == ActivationStage.dictate) _capture = null; + setState(() => _stage = next); + } + + /// The wearable was not streaming and the phone microphone is not allowed yet. + void _needsMicrophone() { + _phoneInstead = true; + _track('device_not_streaming'); + _go(ActivationStage.microphone); + } + + Future _afterIntro() async { + if (widget.hasDevice) return _go(ActivationStage.dictate); + final granted = await (widget.microphoneGranted ?? () => Permission.microphone.isGranted)(); + _go(granted ? ActivationStage.dictate : ActivationStage.microphone); + } + + Future _allowMicrophone() async { + final granted = await (widget.requestMicrophone ?? () async => (await Permission.microphone.request()).isGranted)(); + if (granted) { + OmiHaptics.light(); + _go(ActivationStage.dictate); + } else { + _skipped(); + } + } + + void _skipped() { + SharedPreferencesUtil().saveBool(ActivationFlow.welcomeCardKey, true); + _track('skipped'); + _go(ActivationStage.congrats); + } + + void _dictated(String heard, Future result) { + SharedPreferencesUtil().saveBool(ActivationFlow.welcomeCardKey, false); + _heard = heard; + _go(ActivationStage.writing); + // Keep "Summarizing…" up long enough to see the words being read, even when Omi is quick. + final notes = result.timeout( + widget.notesDeadline, + onTimeout: () => ActivationResult(transcript: heard, notesPending: true), + ); + Future.wait([notes, Future.delayed(const Duration(milliseconds: 1800))]).then((values) { + _result = values.first as ActivationResult; + _go(ActivationStage.summary); + }); + } + + ActivationCapture _newCapture(BuildContext context) { + if (widget.capture != null) return widget.capture!(context); + if (DemoActivationCapture.enabled) { + final l10n = context.l10n; + return DemoActivationCapture( + script: l10n.onboardingActDictateExample, + result: ActivationResult( + transcript: '', + emoji: '🥗', + title: l10n.onboardingActDemoTitle, + overview: l10n.onboardingActDemoSummary, + tasks: [l10n.onboardingActDemoTask1, l10n.onboardingActDemoTask2], + ), + ); + } + return PipelineActivationCapture(context, expectDevice: widget.hasDevice && !_phoneInstead); + } + + Future _afterRating() async { + if (_finished) return; + _finished = true; + if (!_paywallShown) { + _paywallShown = true; + await widget.showPaywall?.call(); + } + // A failed save (the wrapper says so) must not strand the reader: Continue tries again. + if (!await widget.onFinished()) _finished = false; + } + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final Widget child = switch (_stage) { + ActivationStage.intro => _IntroLine( + key: const ValueKey('intro'), + text: l10n.onboardingActIntro, + onDone: _afterIntro, + ), + ActivationStage.microphone => _Stage( + key: const ValueKey('microphone'), + title: l10n.onboardingActMicTitle, + subtitle: _phoneInstead ? l10n.onboardingActMicFallbackBody : l10n.onboardingActMicBody, + body: const _MicVisual(), + bottom: _ContinueButton( + stage: 'microphone', + label: l10n.onboardingActMicAllow, + onPressed: _allowMicrophone, + ), + ), + ActivationStage.dictate => _DictationStep( + key: const ValueKey('dictate'), + capture: _capture ??= _newCapture(context), + onDone: _dictated, + onSkip: _skipped, + onNeedsMicrophone: _needsMicrophone, + throughOmi: widget.hasDevice && !_phoneInstead, + ), + ActivationStage.writing => _Stage( + key: const ValueKey('writing'), + title: l10n.onboardingActWriting, + // The dictation's subtitle keeps its space (unseen), so the card doesn't move between the two. + subtitle: + widget.hasDevice && !_phoneInstead ? l10n.onboardingActDictateBodyOmi : l10n.onboardingActDictateBody, + subtitleHidden: true, + body: CompactingCard(text: _heard, style: _wordsStyle), + bottom: const CondensingControl(), + bottomHeight: _kControlHeight, + ), + ActivationStage.summary => _Stage( + key: const ValueKey('summary'), + eyebrow: l10n.onboardingActRemembers, + title: _result!.hasNotes ? l10n.onboardingActNotesLabel : l10n.onboardingActYourWords, + body: SummaryReveal(child: _SummaryCard(result: _result!, heard: _heard)), + bottom: _ContinueButton( + stage: 'summary', + onPressed: () => _go( + _result!.hasNotes && _result!.tasks.isNotEmpty ? ActivationStage.taskList : ActivationStage.congrats, + ), + ), + bottomHeight: _kControlHeight, + ), + ActivationStage.taskList => _Stage( + key: const ValueKey('taskList'), + title: l10n.onboardingActTasksLabel, + body: _TasksCard(tasks: _result!.tasks), + bottom: _ContinueButton(stage: 'tasks', onPressed: () => _go(ActivationStage.congrats)), + bottomHeight: _kControlHeight, + ), + ActivationStage.congrats => + _Hooray(key: const ValueKey('congrats'), onContinue: () => _go(ActivationStage.rating)), + ActivationStage.rating => _RatingAsk( + key: const ValueKey('rating'), + requestReview: widget.requestReview ?? InAppReview.instance.requestReview, + onDone: _afterRating, + ), + }; + final reduce = OmiMotion.of(context).emphasized == Duration.zero; + return AnimatedSwitcher( + duration: reduce ? Duration.zero : const Duration(milliseconds: 450), + switchInCurve: Curves.easeOutCubic, + switchOutCurve: Curves.easeInCubic, + layoutBuilder: (current, previous) => + Stack(fit: StackFit.expand, children: [...previous, if (current != null) current]), + transitionBuilder: (child, animation) => FadeTransition(opacity: animation, child: child), + child: child, + ); + } +} + +/// Every titled activation screen. The title has one size and one height throughout, so moving from +/// step to step only changes what is under it. [bottomHeight] fixes the area under the body (so the +/// body sits in the same place on screens that share it); [bodyAtTop] puts the body right under +/// the title instead of in the middle. +class _Stage extends StatelessWidget { + const _Stage({ + super.key, + required this.title, + this.eyebrow, + this.subtitle, + this.subtitleHidden = false, + required this.body, + this.bottom, + this.topRight, + this.bottomHeight, + this.bodyAtTop = false, + }); + + final String title; + + /// A quiet line above the title. + final String? eyebrow; + final String? subtitle; + + /// Keeps the subtitle's space without showing it, so the body sits where it did on the step before. + final bool subtitleHidden; + final Widget body; + final Widget? bottom; + final Widget? topRight; + final double? bottomHeight; + final bool bodyAtTop; + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Padding( + padding: const EdgeInsets.fromLTRB(OmiSpacing.xl, 0, OmiSpacing.xl, kOnboardingButtonBottomGap), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SizedBox( + height: _kTopRowHeight, + child: Align(alignment: Alignment.centerRight, child: topRight), + ), + // The eyebrow takes its line out of the gap, so the title stays at the same height. + SizedBox(height: eyebrow == null ? _kHeadingGap : _kHeadingGap - 26), + if (eyebrow != null) + SizedBox( + height: 26, + child: Text( + eyebrow!, + textAlign: TextAlign.center, + style: OmiType.subhead.copyWith(color: OmiColors.textSecondary), + ), + ), + Semantics(header: true, child: Text(title, style: OmiType.title1, textAlign: TextAlign.center)), + if (subtitle != null) ...[ + const SizedBox(height: OmiSpacing.xs), + Opacity( + opacity: subtitleHidden ? 0 : 1, + child: Text( + subtitle!, + style: OmiType.subhead.copyWith(color: OmiColors.textSecondary), + textAlign: TextAlign.center, + ), + ), + ], + Expanded( + child: bodyAtTop + ? Padding( + padding: const EdgeInsets.only(top: OmiSpacing.md), + child: Align(alignment: Alignment.topCenter, child: body), + ) + : LayoutBuilder( + builder: (context, constraints) => SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints(minHeight: constraints.maxHeight), + child: Center(child: body), + ), + ), + ), + ), + if (bottom != null) + bottomHeight == null + ? Padding(padding: const EdgeInsets.only(top: OmiSpacing.md), child: bottom) + : SizedBox(height: bottomHeight, child: Align(alignment: Alignment.bottomCenter, child: bottom)), + ], + ), + ), + ); + } +} + +class _ContinueButton extends StatelessWidget { + const _ContinueButton({required this.stage, required this.onPressed, this.label}); + + final String stage; + final String? label; + final VoidCallback onPressed; + + @override + Widget build(BuildContext context) { + return OmiButton( + key: Key('activation_continue_$stage'), + label: label ?? context.l10n.continueButton, + expand: true, + onPressed: () { + OmiHaptics.selection(); + onPressed(); + }, + ); + } +} + +/// "Let's try Omi." Word by word, then moves on by itself. Nothing to tap. +class _IntroLine extends StatefulWidget { + const _IntroLine({super.key, required this.text, required this.onDone}); + + final String text; + final VoidCallback onDone; + + @override + State<_IntroLine> createState() => _IntroLineState(); +} + +class _IntroLineState extends State<_IntroLine> { + Timer? _timer; + + @override + void initState() { + super.initState(); + _timer = Timer(const Duration(milliseconds: 3000), widget.onDone); + } + + @override + void dispose() { + _timer?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final words = widget.text.split(' '); + return Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: OmiSpacing.xl), + child: OneShot( + duration: const Duration(milliseconds: 1400), + builder: (context, t) => Wrap( + alignment: WrapAlignment.center, + children: [ + for (var i = 0; i < words.length; i++) + rise( + segment(t, i / (words.length + 1), (i + 2) / (words.length + 1)), + Text('${words[i]} ', style: OmiType.largeTitle), + distance: 14, + ), + ], + ), + ), + ), + ); + } +} + +/// The microphone ask: the dark round control and soft ripple of the listening screen that follows, +/// so allowing the microphone feels like the first step of talking to Omi. +class _MicVisual extends StatelessWidget { + const _MicVisual(); + + static const double _size = 180; + + @override + Widget build(BuildContext context) { + return OneShot( + duration: const Duration(milliseconds: 900), + builder: (context, t) => rise( + t, + SizedBox( + width: _size, + height: _size, + child: Stack( + alignment: Alignment.center, + children: [ + ScanningRippleWidget(isScanning: true, size: _size, color: OmiColors.textTertiary), + Container( + width: 112, + height: 112, + decoration: BoxDecoration(color: OmiColors.textPrimary.withValues(alpha: 0.06), shape: BoxShape.circle), + ), + Container( + width: 80, + height: 80, + decoration: BoxDecoration(color: OmiColors.accent, shape: BoxShape.circle), + child: Icon(Icons.mic_rounded, size: 36, color: OmiColors.onAccent), + ), + ], + ), + ), + distance: 16, + ), + ); + } +} + +/// The card that holds the reader's words, the same on the dictation and "Summarizing…" screens so +/// it reads as one card. +class _WordsCard extends StatelessWidget { + const _WordsCard({super.key, required this.child}); + + final Widget child; + + @override + Widget build(BuildContext context) => NoteCard(minHeight: 150, child: child); +} + +TextStyle get _wordsStyle => OmiType.body.copyWith(height: 1.45); + +/// "Tell Omi about your day": a card that fills with the reader's words, Skip (grey, top right) and +/// a round ✓ (done, enabled once Omi has heard something). +class _DictationStep extends StatefulWidget { + const _DictationStep({ + super.key, + required this.capture, + required this.onDone, + required this.onSkip, + required this.onNeedsMicrophone, + this.throughOmi = false, + }); + + final ActivationCapture capture; + final void Function(String heard, Future result) onDone; + final VoidCallback onSkip; + final VoidCallback onNeedsMicrophone; + + /// The reader dictates through their Omi, not the phone. + final bool throughOmi; + + @override + State<_DictationStep> createState() => _DictationStepState(); +} + +class _DictationStepState extends State<_DictationStep> { + bool _started = false; + bool _failed = false; + bool _leaving = false; + + /// Nothing heard for a while after listening started: say so, instead of listening in silence. + bool _quiet = false; + Timer? _quietTimer; + + /// Still nothing heard well after the hint: offer a plain way on instead of a grey ✓ and a corner + /// Skip (the microphone, the Omi or transcription may simply not be working right now). + bool _stuck = false; + Timer? _stuckTimer; + + @override + void initState() { + super.initState(); + widget.capture.liveText.addListener(_onWords); + // From the moment the step opens, so a start that hangs (waiting for the Omi, the microphone) + // still ends in Continue. + _stuckTimer = Timer(const Duration(seconds: 25), () { + if (mounted && widget.capture.liveText.value.trim().isEmpty) setState(() => _stuck = true); + }); + WidgetsBinding.instance.addPostFrameCallback((_) async { + final start = await widget.capture.start(); + if (!mounted) return; + if (start == ActivationStart.needsMicrophone) { + _leaving = true; + widget.onNeedsMicrophone(); + return; + } + setState(() { + _started = start == ActivationStart.listening; + _failed = !_started; + }); + if (_started) { + _quietTimer = Timer(const Duration(seconds: 12), () { + if (mounted && widget.capture.liveText.value.trim().isEmpty) setState(() => _quiet = true); + }); + } + }); + } + + bool _heardOnce = false; + + /// A light tap when the first words land: the moment Omi visibly understood. + void _onWords() { + if (_heardOnce || widget.capture.liveText.value.trim().isEmpty) return; + _heardOnce = true; + OmiHaptics.light(); + } + + @override + void dispose() { + widget.capture.liveText.removeListener(_onWords); + _quietTimer?.cancel(); + _stuckTimer?.cancel(); + if (!_leaving) unawaited(widget.capture.cancel()); + super.dispose(); + } + + void _done() { + if (_leaving) return; + _leaving = true; + OmiHaptics.medium(); + widget.onDone(widget.capture.liveText.value, widget.capture.finish()); + } + + Future _skip() async { + if (_leaving) return; + _leaving = true; + OmiHaptics.selection(); + await widget.capture.cancel(); + widget.onSkip(); + } + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final source = widget.capture.usesDevice ? 'Omi' : l10n.onboardingActPhoneMic; + return ValueListenableBuilder( + valueListenable: widget.capture.liveText, + builder: (context, text, _) { + final heard = text.trim().isNotEmpty; + return _Stage( + title: l10n.onboardingActDictateTitle, + subtitle: widget.throughOmi ? l10n.onboardingActDictateBodyOmi : l10n.onboardingActDictateBody, + topRight: _QuietSkip(label: l10n.skip, onTap: _skip), + body: ReadAlongCard( + key: const Key('activation_dictation_card'), + script: l10n.onboardingActDictateExample, + live: widget.capture.liveText, + level: widget.capture.level, + listening: _started && !_failed, + levelDriven: !widget.capture.usesDevice, + style: _wordsStyle, + ), + // Words that arrive late still bring the ✓ back. + bottom: !heard && (_failed || _stuck) + ? _WayOn( + status: _failed + ? l10n.somethingWentWrong + : (widget.capture.usesDevice ? l10n.onboardingActNoAudioOmi : l10n.onboardingActNoAudioPhone), + onContinue: _skip, + ) + : _ListeningControl( + level: widget.capture.level, + heard: heard, + listening: _started, + failed: _failed, + status: _failed + ? l10n.somethingWentWrong + : heard + ? l10n.onboardingActDictateHint + : !_started + ? l10n.listening + : _quiet + ? (widget.capture.usesDevice + ? l10n.onboardingActNoAudioOmi + : l10n.onboardingActNoAudioPhone) + : '${l10n.listening} · $source', + onDone: _done, + ), + bottomHeight: _kControlHeight, + ); + }, + ); + } +} + +/// The dictation's bottom when listening failed or nothing came through: what went wrong, in one +/// quiet line, and Continue (the same as Skip: the hooray, with a welcome card left for Home). +class _WayOn extends StatelessWidget { + const _WayOn({required this.status, required this.onContinue}); + + final String status; + final VoidCallback onContinue; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + status, + key: const Key('activation_dictation_status'), + textAlign: TextAlign.center, + style: OmiType.footnote.copyWith(color: OmiColors.textSecondary), + ), + const SizedBox(height: OmiSpacing.md), + _ContinueButton(stage: 'dictation', onPressed: onContinue), + ], + ); + } +} + +/// The bottom of the dictation step, in the style of Connect Omi: soft rings around one round ✓ +/// while Omi listens, and one quiet line under it. ✓ stays grey until Omi has heard something. +class _ListeningControl extends StatelessWidget { + const _ListeningControl({ + required this.heard, + required this.listening, + required this.failed, + required this.status, + required this.onDone, + required this.level, + }); + + /// How loud the person is right now: the ring around ✓ and the meter follow it, so it is + /// obvious Omi hears them even before words come back. + final ValueListenable level; + final bool heard; + final bool listening; + final bool failed; + final String status; + final VoidCallback onDone; + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: 140, + height: 140, + child: Stack( + alignment: Alignment.center, + children: [ + if (listening && !failed) + ScanningRippleWidget(isScanning: true, size: 140, color: OmiColors.textTertiary), + if (listening && !failed) + ValueListenableBuilder( + valueListenable: level, + builder: (context, v, _) => AnimatedContainer( + key: const Key('activation_voice_ring'), + duration: const Duration(milliseconds: 90), + curve: Curves.easeOut, + width: 76 + 60 * v, + height: 76 + 60 * v, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: OmiColors.textPrimary.withValues(alpha: 0.05 + 0.09 * v), + ), + ), + ), + Semantics( + button: true, + enabled: heard, + label: l10n.done, + excludeSemantics: true, + child: GestureDetector( + key: const Key('activation_dictation_done'), + onTap: heard ? onDone : null, + child: AnimatedContainer( + duration: const Duration(milliseconds: 250), + curve: Curves.easeOut, + width: 68, + height: 68, + decoration: BoxDecoration( + color: heard ? OmiColors.accent : OmiColors.surface3, + shape: BoxShape.circle, + ), + child: Icon( + Icons.check_rounded, + size: 32, + color: heard ? OmiColors.onAccent : OmiColors.textTertiary, + ), + ), + ), + ), + ], + ), + ), + const SizedBox(height: OmiSpacing.xs), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (listening && !failed) ...[ + _LevelMeter(level: level), + const SizedBox(width: OmiSpacing.xs), + ], + Flexible( + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 250), + child: Text( + status, + key: ValueKey(status), + textAlign: TextAlign.center, + style: OmiType.footnote.copyWith(color: OmiColors.textSecondary), + ), + ), + ), + ], + ), + ], + ); + } +} + +/// Four small bars that move with the voice, next to "Listening". +class _LevelMeter extends StatelessWidget { + const _LevelMeter({required this.level}); + + final ValueListenable level; + + static const _shape = [0.55, 1.0, 0.75, 0.4]; + + @override + Widget build(BuildContext context) { + return ExcludeSemantics( + child: ValueListenableBuilder( + valueListenable: level, + builder: (context, v, _) => Row( + key: const Key('activation_level_meter'), + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + for (final s in _shape) + AnimatedContainer( + duration: const Duration(milliseconds: 90), + margin: const EdgeInsets.symmetric(horizontal: 1), + width: 3, + height: 3 + 11 * (v * s).clamp(0.0, 1.0), + decoration: BoxDecoration( + color: v > 0.05 ? OmiColors.textPrimary : OmiColors.textTertiary, + borderRadius: OmiRadius.pillAll, + ), + ), + ], + ), + ), + ); + } +} + +/// What Omi made of the words, built in front of the reader the way it appears on Home: the emoji +/// pops in, the title and "Just now" rise, a line draws across, the summary writes itself, and the +/// reader's own words wait underneath behind "Transcript". +/// +/// When Omi could not make notes, the card holds the reader's words alone. +class _SummaryCard extends StatefulWidget { + const _SummaryCard({required this.result, required this.heard}); + + final ActivationResult result; + final String heard; + + @override + State<_SummaryCard> createState() => _SummaryCardState(); +} + +class _SummaryCardState extends State<_SummaryCard> { + final List _beats = []; + bool _showTranscript = false; + + ActivationResult get _r => widget.result; + String get _words => _r.transcript.isNotEmpty ? _r.transcript : widget.heard; + String get _summary => _r.overview.isNotEmpty ? _r.overview : _words; + + Duration get _duration => Duration(milliseconds: (900 + 55 * _summary.split(' ').length).clamp(1600, 4600)); + + @override + void initState() { + super.initState(); + // A light tap as the emoji lands. + if (_r.hasNotes) { + _beats.add(Timer(Duration(milliseconds: (_duration.inMilliseconds * 0.12).round()), OmiHaptics.light)); + } + } + + @override + void dispose() { + for (final beat in _beats) { + beat.cancel(); + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final bodyStyle = OmiType.body.copyWith(color: OmiColors.textPrimary, height: 1.45); + return OneShot( + duration: _duration, + builder: (context, t) { + if (!_r.hasNotes) { + final card = _WordsCard( + key: const Key('activation_summary_card'), + child: WordReveal(text: _words, t: segment(t, 0, 1, Curves.linear), style: _wordsStyle), + ); + return card; + } + return NoteCard( + key: const Key('activation_summary_card'), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Transform.scale( + scale: Curves.elasticOut.transform(segment(t, 0.02, 0.3, Curves.linear)), + child: Container( + width: 44, + height: 44, + alignment: Alignment.center, + decoration: BoxDecoration(color: OmiColors.surface2, borderRadius: OmiRadius.mdAll), + child: Text(_r.emoji, + style: const TextStyle( + fontSize: 22)), // omi-ux-allow: font-size-literal -- emoji glyph sized to its 44 pt tile + ), + ), + const SizedBox(width: OmiSpacing.sm), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + rise(segment(t, 0.08, 0.26), Text(_r.title, style: OmiType.headline), distance: 8), + const SizedBox(height: 2), + rise( + segment(t, 0.14, 0.32), + Text(l10n.onboardingActJustNow, + style: OmiType.footnote.copyWith(color: OmiColors.textTertiary)), + distance: 6, + ), + ], + ), + ), + ], + ), + const SizedBox(height: OmiSpacing.md), + Align( + alignment: Alignment.centerLeft, + child: FractionallySizedBox( + widthFactor: segment(t, 0.2, 0.4), + child: Container(height: 1, color: OmiColors.border), + ), + ), + const SizedBox(height: OmiSpacing.md), + SummaryText(text: _summary, t: segment(t, 0.3, 0.9, Curves.linear), style: bodyStyle), + if (_words.isNotEmpty) ...[ + const SizedBox(height: OmiSpacing.md), + rise(segment(t, 0.88, 1), _transcriptToggle(l10n), distance: 6), + ], + ], + ), + ); + }, + ); + } + + Widget _transcriptToggle(AppLocalizations l10n) { + final quiet = OmiType.footnote.copyWith(color: OmiColors.textSecondary); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Semantics( + button: true, + expanded: _showTranscript, + child: GestureDetector( + key: const Key('activation_summary_transcript'), + behavior: HitTestBehavior.opaque, + onTap: () { + OmiHaptics.selection(); + setState(() => _showTranscript = !_showTranscript); + }, + child: SizedBox( + height: 32, + child: Row( + children: [ + Icon(Icons.graphic_eq_rounded, size: 16, color: OmiColors.textTertiary), + const SizedBox(width: OmiSpacing.xs), + Expanded(child: Text(l10n.transcript, style: quiet)), + AnimatedRotation( + turns: _showTranscript ? 0.5 : 0, + duration: const Duration(milliseconds: 200), + child: Icon(Icons.expand_more_rounded, size: 18, color: OmiColors.textTertiary), + ), + ], + ), + ), + ), + ), + AnimatedSize( + duration: const Duration(milliseconds: 220), + curve: Curves.easeOutCubic, + alignment: Alignment.topCenter, + child: _showTranscript + ? Padding( + padding: const EdgeInsets.only(top: OmiSpacing.xs), + child: Text(_words, style: quiet.copyWith(color: OmiColors.textTertiary, height: 1.4)), + ) + : const SizedBox(width: double.infinity), + ), + ], + ); + } +} + +/// The to-dos Omi caught, sliding in one by one with a tap each, then "Saved to your tasks". +class _TasksCard extends StatefulWidget { + const _TasksCard({required this.tasks}); + + final List tasks; + + @override + State<_TasksCard> createState() => _TasksCardState(); +} + +class _TasksCardState extends State<_TasksCard> { + final List _beats = []; + static const _first = 300; + static const _gap = 520; + + int get _n => widget.tasks.length; + Duration get _duration => Duration(milliseconds: _first + _gap * _n + 900); + + /// When task [i] arrives, as a share of [_duration]. + double _at(int i) => (_first + _gap * i) / _duration.inMilliseconds; + + @override + void initState() { + super.initState(); + for (var i = 0; i < _n; i++) { + _beats.add(Timer(Duration(milliseconds: _first + _gap * i + 120), OmiHaptics.light)); + } + _beats.add(Timer(Duration(milliseconds: _first + _gap * _n + 200), OmiHaptics.success)); + } + + @override + void dispose() { + for (final beat in _beats) { + beat.cancel(); + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final span = _gap / _duration.inMilliseconds; + return OneShot( + duration: _duration, + builder: (context, t) { + final saved = segment(t, _at(_n), _at(_n) + span); + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + NoteCard( + key: const Key('activation_tasks_card'), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + for (var i = 0; i < _n; i++) ...[ + if (i > 0) Container(height: 1, color: OmiColors.border), + _arriving(segment(t, _at(i), _at(i) + span * 1.4, Curves.linear), widget.tasks[i]), + ], + ], + ), + ), + const SizedBox(height: OmiSpacing.md), + Opacity( + opacity: saved, + child: Transform.scale( + scale: 0.9 + 0.1 * Curves.easeOutBack.transform(saved), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: OmiSpacing.md, vertical: OmiSpacing.xs), + decoration: BoxDecoration(color: OmiColors.surface1, borderRadius: OmiRadius.pillAll), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.check_circle_rounded, size: 16, color: OmiColors.success), + const SizedBox(width: OmiSpacing.xs), + Text(l10n.onboardingActSavedToTasks, + style: OmiType.footnote.copyWith(color: OmiColors.textSecondary)), + ], + ), + ), + ), + ), + ], + ); + }, + ); + } + + /// One task sliding in from the right with a brief highlight behind it as it lands. + Widget _arriving(double p, String text) { + final move = Curves.easeOutBack.transform(p.clamp(0.0, 1.0)); + final glow = p <= 0 ? 0.0 : (1 - p).clamp(0.0, 1.0); + return Opacity( + opacity: segment(p, 0, 0.4), + child: Transform.translate( + offset: Offset((1 - move) * 36, 0), + child: Container( + decoration: BoxDecoration( + color: OmiColors.accent.withValues(alpha: 0.06 * glow), + borderRadius: OmiRadius.smAll, + ), + padding: const EdgeInsets.symmetric(vertical: OmiSpacing.xxs), + child: TaskRow(text: text), + ), + ), + ); + } +} + +/// Skip in the top right, in grey: there if needed, without competing with the dictation. +class _QuietSkip extends StatelessWidget { + const _QuietSkip({required this.label, required this.onTap}); + + final String label; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return Semantics( + button: true, + label: label, + excludeSemantics: true, + child: GestureDetector( + key: const Key('activation_dictation_skip'), + behavior: HitTestBehavior.opaque, + onTap: onTap, + child: ConstrainedBox( + constraints: const BoxConstraints(minWidth: 44, minHeight: 44), + child: Center( + widthFactor: 1, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: OmiSpacing.xs), + child: Text(label, style: OmiType.callout.copyWith(color: OmiColors.textTertiary)), + ), + ), + ), + ), + ); + } +} + +/// "You're all set": the title in its usual place, and close under it a check that pops in with a +/// burst of confetti. One Continue. +class _Hooray extends StatefulWidget { + const _Hooray({super.key, required this.onContinue}); + + final VoidCallback onContinue; + + @override + State<_Hooray> createState() => _HoorayState(); +} + +class _HoorayState extends State<_Hooray> { + Timer? _haptic; + + @override + void initState() { + super.initState(); + // In step with the check popping in. + _haptic = Timer(const Duration(milliseconds: 380), OmiHaptics.success); + } + + @override + void dispose() { + _haptic?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final name = SharedPreferencesUtil().givenName.trim(); + return _Stage( + title: name.isEmpty ? l10n.youreAllSet : l10n.onboardingActCongratsTitle(name), + // Lifted above the middle so the check sits close under the title. + body: Padding( + padding: const EdgeInsets.only(bottom: 120), + child: OneShot( + duration: const Duration(milliseconds: 2000), + builder: (context, t) => SizedBox( + width: 280, + height: 280, + child: Stack( + alignment: Alignment.center, + children: [ + Positioned.fill(child: CustomPaint(painter: ConfettiPainter(t: segment(t, 0.2, 1, Curves.linear)))), + Transform.scale( + scale: Curves.elasticOut.transform(segment(t, 0.12, 0.55, Curves.linear)), + child: Container( + width: 104, + height: 104, + decoration: BoxDecoration(color: OmiColors.accent, shape: BoxShape.circle), + child: Icon(Icons.check_rounded, size: 56, color: OmiColors.onAccent), + ), + ), + ], + ), + ), + ), + ), + bottom: _ContinueButton(stage: 'congrats', onPressed: widget.onContinue), + ); + } +} + +/// After the celebration: "Enjoying Omi so far?" with five gold stars popping in one by one under +/// it, both above where the App Store rating pop-up opens by itself. The paywall follows once the +/// pop-up is closed (or Continue, if it never showed). +class _RatingAsk extends StatefulWidget { + const _RatingAsk({super.key, required this.requestReview, required this.onDone}); + + final Future Function() requestReview; + final VoidCallback onDone; + + @override + State<_RatingAsk> createState() => _RatingAskState(); +} + +class _RatingAskState extends State<_RatingAsk> with WidgetsBindingObserver { + static const _gold = Color(0xFFFFC53D); // omi-ux-allow: color-literal -- rating stars in App Store gold + Timer? _ask; + Timer? _starsLanded; + Timer? _offerContinue; + bool _promptShowing = false; + // Continue stays hidden until the App Store pop-up has had time to appear, so a quick tap can't + // skip past it. + bool _canContinue = false; + bool _asked = false; + bool _done = false; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + // A light tap as the stars land, then the App Store prompt. + _starsLanded = Timer(const Duration(milliseconds: 650), OmiHaptics.light); + _ask = Timer(const Duration(milliseconds: 1500), _requestReview); + } + + @override + void dispose() { + _ask?.cancel(); + _starsLanded?.cancel(); + _offerContinue?.cancel(); + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + Future _requestReview() async { + PlatformManager.instance.analytics.track('Onboarding Rating Asked'); + try { + await widget.requestReview(); + } catch (_) {} + _asked = true; + // iOS gives no callback when its pop-up closes (and on many builds the app never goes + // inactive under it), so don't wait for it: once it is up, move on underneath it. Closing it + // then lands on the paywall instead of back on this screen. + _offerContinue = Timer(const Duration(milliseconds: 900), () { + if (!mounted) return; + // Continue stays as the way on if leaving fails (a failed save is retried from here). + setState(() => _canContinue = true); + _leave(); + }); + } + + /// iOS gives no callback for its rating pop-up, but the app goes inactive while it is up and + /// becomes active again when it closes: that return is the cue to move on. + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.inactive) _promptShowing = true; + if (state == AppLifecycleState.resumed && _promptShowing) _leave(); + } + + void _leave() { + if (_done || !mounted) return; + _done = true; + widget.onDone(); + } + + /// Continue always reaches [widget.onDone], so a failed save can be retried. + void _continue() { + if (!mounted) return; + _done = true; + widget.onDone(); + } + + @override + Widget build(BuildContext context) { + // Not every iOS build makes the app inactive under the pop-up (the simulator never does). The + // pop-up takes every touch while it is up, so the first tap here after asking means it closed. + return Listener( + behavior: HitTestBehavior.translucent, + // After the tap has been handled: a tap on Continue has then already moved on (or retried a + // failed save), and this cue must not count the same tap twice. + onPointerUp: (_) { + if (_asked && _canContinue) Future(_leave); + }, + child: _buildStage(context), + ); + } + + Widget _buildStage(BuildContext context) { + return _Stage( + title: context.l10n.onboardingActRateTitle, + bodyAtTop: true, + body: OneShot( + duration: const Duration(milliseconds: 1200), + builder: (context, t) => Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + for (var i = 0; i < 5; i++) + Transform.scale( + scale: Curves.elasticOut.transform(segment(t, i * 0.1, 0.45 + i * 0.1, Curves.linear)), + child: const Padding( + padding: EdgeInsets.symmetric(horizontal: 3), + child: Icon(Icons.star_rounded, size: 46, color: _gold), + ), + ), + ], + ), + ), + bottom: IgnorePointer( + ignoring: !_canContinue, + child: AnimatedOpacity( + opacity: _canContinue ? 1 : 0, + duration: const Duration(milliseconds: 300), + child: OmiButton( + key: const Key('activation_continue_rating'), + label: context.l10n.continueButton, + expand: true, + onPressed: _continue, + ), + ), + ), + ); + } +} diff --git a/app/lib/pages/onboarding/variants/yinka/activation/activation_motion.dart b/app/lib/pages/onboarding/variants/yinka/activation/activation_motion.dart new file mode 100644 index 00000000000..a843756ae19 --- /dev/null +++ b/app/lib/pages/onboarding/variants/yinka/activation/activation_motion.dart @@ -0,0 +1,715 @@ +import 'dart:async'; +import 'dart:math' as math; +import 'dart:ui' show ImageFilter; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; + +import 'package:omi/pages/onboarding/variants/yinka/activation/activation_visuals.dart'; +import 'package:omi/ui/ui.dart'; + +/// Lower-case words without punctuation, for comparing what was said with the script. +List activationTokens(String text) => text + .toLowerCase() + .split(RegExp(r'\s+')) + .map((w) => w.replaceAll(RegExp(r"[^\p{L}\p{N}']", unicode: true), '')) + .where((w) => w.isNotEmpty) + .toList(); + +/// How much of [script] the reader has said, judging by [heard]: the script index just past the last +/// heard word found in order, and the share of heard words that were found. A low share means the +/// reader is saying something else. +({int reached, double match}) readAlongProgress(List script, List heard) { + if (heard.isEmpty) return (reached: 0, match: 1); + var i = 0; + var found = 0; + var reached = 0; + for (final word in heard) { + // Look a few words ahead, so a skipped or misheard word doesn't lose the place. + for (var j = i; j < script.length && j < i + 4; j++) { + if (script[j] == word) { + found++; + i = j + 1; + reached = i; + break; + } + } + } + return (reached: reached, match: found / heard.length); +} + +/// The dictation card. Before Omi's words come back (they come a sentence at a time), the script +/// lights up word by word while the reader's voice is heard, with a soft tick per word, and waits +/// when they pause. When a sentence arrives it either confirms the script (the lit words turn solid) +/// or, when the reader said something else, their own words ink in instead. A waveform along the +/// bottom moves with the voice from the first moment. +class ReadAlongCard extends StatefulWidget { + const ReadAlongCard({ + super.key, + required this.script, + required this.live, + required this.level, + required this.listening, + required this.style, + this.levelDriven = true, + }); + + final String script; + + /// What Omi heard so far (whole sentences). + final ValueListenable live; + + /// Voice loudness, 0..1. + final ValueListenable level; + final bool listening; + final TextStyle style; + + /// False when the voice level is not available (talking through the Omi): the highlight then + /// moves at speaking pace, a few words ahead of what Omi confirmed at most. + final bool levelDriven; + + @override + State createState() => _ReadAlongCardState(); +} + +class _ReadAlongCardState extends State with SingleTickerProviderStateMixin { + static const _wordsPerSecond = 2.6; + static const _voiceThreshold = 0.1; + static const _bars = 32; + + late final List _scriptWords = widget.script.split(RegExp(r'\s+')); + late final List _scriptTokens = activationTokens(widget.script); + late final Ticker _ticker = createTicker(_tick); + Duration _last = Duration.zero; + + /// Words lit so far (fractional while one is lighting). + double _cursor = 0; + int _confirmed = 0; + bool _ownWords = false; + DateTime _lastTick = DateTime.fromMillisecondsSinceEpoch(0); + final List _history = List.filled(_bars, 0, growable: true); + double _sinceSample = 0; + + /// When each word was heard by Omi (script index, or own-word index): drives its flash. + final Map _heardAt = {}; + int _ownSeen = 0; + static const _flash = Duration(milliseconds: 750); + + @override + void initState() { + super.initState(); + widget.live.addListener(_onLive); + _ticker.start(); + } + + @override + void dispose() { + widget.live.removeListener(_onLive); + _ticker.dispose(); + super.dispose(); + } + + void _onLive() { + final heard = activationTokens(widget.live.value); + final p = readAlongProgress(_scriptTokens, heard); + final now = DateTime.now(); + var landed = false; + setState(() { + if (heard.length >= 3 && p.match < 0.5) { + _ownWords = true; + } + if (_ownWords) { + final count = widget.live.value.trim().split(RegExp(r'\s+')).length; + for (var i = _ownSeen; i < count; i++) { + _heardAt[i] = now.add(Duration(milliseconds: 45 * (i - _ownSeen))); + } + landed = count > _ownSeen; + _ownSeen = math.max(_ownSeen, count); + } else if (p.reached > _confirmed) { + // Omi's words landed: each newly heard word flashes in turn. + for (var i = _confirmed; i < p.reached; i++) { + _heardAt[i] = now.add(Duration(milliseconds: 45 * (i - _confirmed))); + } + _confirmed = p.reached; + _cursor = math.max(_cursor, _confirmed.toDouble()); + landed = true; + } + }); + if (landed) OmiHaptics.light(); + } + + /// 1 the moment a word is heard by Omi, easing to 0 over [_flash]; 0 when not flashing. + double _flashOf(int i) { + final at = _heardAt[i]; + if (at == null) return 0; + final ms = DateTime.now().difference(at).inMilliseconds; + if (ms < 0) return 0; + return 1 - Curves.easeOut.transform((ms / _flash.inMilliseconds).clamp(0.0, 1.0)); + } + + bool get _flashing { + final now = DateTime.now(); + return _heardAt.values.any((at) => now.difference(at) < _flash + const Duration(milliseconds: 600)); + } + + void _tick(Duration now) { + final dt = (now - _last).inMicroseconds / 1e6; + _last = now; + if (dt <= 0 || dt > 0.25) return; + final level = widget.level.value; + _sinceSample += dt; + var changed = false; + if (_sinceSample >= 0.06) { + _sinceSample = 0; + _history.removeAt(0); + _history.add(widget.listening ? level : 0); + changed = true; + } + if (widget.listening && !_ownWords) { + final speaking = widget.levelDriven ? level > _voiceThreshold : true; + final cap = widget.levelDriven + ? _scriptWords.length.toDouble() + : math.min(_scriptWords.length.toDouble(), _confirmed + 6.0); + if (speaking && _cursor < cap) { + final before = _cursor.floor(); + _cursor = math.min(cap, _cursor + dt * _wordsPerSecond); + if (_cursor.floor() > before) _wordTick(); + changed = true; + } + } + if ((changed || _flashing) && mounted) setState(() {}); + } + + void _wordTick() { + final now = DateTime.now(); + if (now.difference(_lastTick).inMilliseconds < 80) return; + _lastTick = now; + OmiHaptics.selection(); + } + + @override + Widget build(BuildContext context) { + final reduce = OmiMotion.of(context).emphasized == Duration.zero; + return NoteCard( + minHeight: 150, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + AnimatedSwitcher( + duration: Duration(milliseconds: reduce ? 0 : 280), + child: _ownWords ? _own(reduce) : _scripted(reduce), + ), + const SizedBox(height: OmiSpacing.md), + SizedBox(height: 22, child: CustomPaint(painter: _Waveform(_history, OmiColors.textPrimary))), + ], + ), + ); + } + + Widget _scripted(bool reduce) { + return Wrap( + key: const ValueKey('script'), + children: [ + for (var i = 0; i < _scriptWords.length; i++) _word(i, reduce), + ], + ); + } + + /// Three clear states: not said yet (faint), heard by the microphone but not yet by Omi (grey), + /// and heard by Omi (full ink, arriving with a flash behind it). + Widget _word(int i, bool reduce) { + final lit = (_cursor - i).clamp(0.0, 1.0); + final confirmed = i < _confirmed; + final color = confirmed + ? OmiColors.textPrimary + : Color.lerp(OmiColors.textTertiary.withValues(alpha: 0.55), OmiColors.textSecondary, lit)!; + // A word lifts a touch as the voice lights it, then settles. + final lift = reduce || confirmed ? 0.0 : -2.0 * math.sin(math.pi * lit); + return _flashWord(Text('${_scriptWords[i]} ', style: widget.style.copyWith(color: color)), reduce ? 0 : _flashOf(i), + lift: lift); + } + + /// The moment Omi hears a word: a soft highlight behind it and a small pop, fading out. + Widget _flashWord(Widget word, double f, {double lift = 0}) { + return Transform.translate( + offset: Offset(0, lift), + child: Transform.scale( + scale: 1 + 0.07 * f, + child: DecoratedBox( + decoration: BoxDecoration( + color: OmiColors.textPrimary.withValues(alpha: 0.14 * f), + borderRadius: OmiRadius.smAll, + ), + child: word, + ), + ), + ); + } + + Widget _own(bool reduce) { + final words = widget.live.value.trim().split(RegExp(r'\s+')); + return Wrap( + key: const ValueKey('own'), + children: [ + for (var i = 0; i < words.length; i++) + TweenAnimationBuilder( + key: ValueKey('own_$i'), + tween: Tween(begin: reduce ? 1 : 0, end: 1), + duration: Duration(milliseconds: 260 + 30 * (i % 6)), + curve: Curves.easeOutCubic, + builder: (context, t, child) => Opacity( + opacity: t, + child: Transform.translate(offset: Offset(0, 6 * (1 - t)), child: child), + ), + child: _flashWord( + Text('${words[i]} ', style: widget.style.copyWith(color: OmiColors.textPrimary)), + reduce ? 0 : _flashOf(i), + ), + ), + ], + ); + } +} + +/// Rounded bars of the last couple of seconds of voice, newest on the right. +class _Waveform extends CustomPainter { + _Waveform(List history, this.color) : history = List.of(history); + + final List history; + final Color color; + + @override + void paint(Canvas canvas, Size size) { + final n = history.length; + final gap = size.width / n; + final w = math.min(3.0, gap * 0.5); + for (var i = 0; i < n; i++) { + final v = history[i].clamp(0.0, 1.0); + final h = 3 + (size.height - 3) * v; + final x = gap * i + gap / 2; + final paint = Paint()..color = color.withValues(alpha: 0.18 + 0.7 * v); + canvas.drawRRect( + RRect.fromRectAndRadius( + Rect.fromCenter(center: Offset(x, size.height / 2), width: w, height: h), Radius.circular(w)), + paint, + ); + } + } + + @override + bool shouldRepaint(covariant _Waveform old) => !listEquals(old.history, history) || old.color != color; +} + +const _fillerWords = { + 'a', + 'an', + 'the', + 'and', + 'or', + 'but', + 'so', + 'to', + 'of', + 'in', + 'on', + 'at', + 'by', + 'for', + 'with', + 'from', + 'just', + 'had', + 'have', + 'has', + 'is', + 'was', + 'are', + 'be', + 'me', + 'my', + 'i', + 'im', + "i'm", + 'she', + 'he', + 'they', + 'we', + 'you', + 'it', + 'its', + "it's", + "she's", + "he's", + 'that', + 'this', + 'then', + 'um', + 'uh', + 'like', + 'really', + 'also', + 'way', + 'up', + 'out', + 'about', + 'remind', + 'grab', + 'get', + 'pick', +}; + +/// "Summarizing…": the reader's words stay in the same card and visibly condense. Filler words fade +/// and fold away, the words that matter draw together and darken, and the card shrinks around them; +/// then a light passes over what is left while Omi finishes. +class CompactingCard extends StatefulWidget { + const CompactingCard({super.key, required this.text, required this.style}); + + final String text; + final TextStyle style; + + @override + State createState() => _CompactingCardState(); +} + +class _CompactingCardState extends State with TickerProviderStateMixin { + late final AnimationController _fold = AnimationController(vsync: this, duration: const Duration(milliseconds: 1500)) + ..forward(); + late final AnimationController _sweep = + AnimationController(vsync: this, duration: const Duration(milliseconds: 1400)); + late final List _words = widget.text.trim().split(RegExp(r'\s+')); + late final List _keep = [ + for (final w in _words) + () { + final t = activationTokens(w); + return t.isNotEmpty && !_fillerWords.contains(t.first) && t.first.length > 2; + }(), + ]; + + @override + void initState() { + super.initState(); + _fold.addStatusListener((s) { + if (s == AnimationStatus.completed && mounted) _sweep.repeat(); + }); + } + + @override + void dispose() { + _fold.dispose(); + _sweep.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final reduce = OmiMotion.of(context).emphasized == Duration.zero; + if (reduce) { + return NoteCard( + minHeight: 150, + child: Text( + [ + for (var i = 0; i < _words.length; i++) + if (_keep[i]) _words[i] + ].join(' '), + style: widget.style.copyWith(color: OmiColors.textPrimary), + ), + ); + } + return AnimatedBuilder( + animation: Listenable.merge([_fold, _sweep]), + builder: (context, _) { + final f = Curves.easeInOutCubic.transform(_fold.value); + final words = Wrap( + children: [ + for (var i = 0; i < _words.length; i++) + if (_keep[i]) _kept(i, f) else _dropped(i, f), + ], + ); + return AnimatedSize( + duration: const Duration(milliseconds: 300), + curve: Curves.easeOutCubic, + alignment: Alignment.topCenter, + child: NoteCard( + // The card lets go of its minimum height as the words fold, so it shrinks around them. + minHeight: 150 * (1 - f) + 60 * f, + child: _fold.isCompleted ? _withSweep(words) : words, + ), + ); + }, + ); + } + + /// Each dropped word starts going a little after the one before, so the folding travels along. + Widget _dropped(int i, double f) { + final start = (i / _words.length) * 0.5; + final t = ((f - start) / 0.5).clamp(0.0, 1.0); + return ClipRect( + child: Align( + alignment: Alignment.centerLeft, + widthFactor: 1 - t, + heightFactor: 1, + child: Opacity( + opacity: (1 - t * 1.6).clamp(0.0, 1.0), + child: Text('${_words[i]} ', style: widget.style.copyWith(color: OmiColors.textTertiary)), + ), + ), + ); + } + + Widget _kept(int i, double f) { + final color = Color.lerp(OmiColors.textSecondary, OmiColors.textPrimary, f)!; + return Text( + '${_words[i]} ', + style: widget.style.copyWith(color: color, fontWeight: FontWeight.lerp(FontWeight.w400, FontWeight.w600, f)), + ); + } + + Widget _withSweep(Widget child) { + final x = -1.0 + 3.0 * _sweep.value; + return ShaderMask( + blendMode: BlendMode.srcATop, + shaderCallback: (bounds) => LinearGradient( + begin: Alignment(x - 1, -0.2), + end: Alignment(x + 1, 0.2), + colors: [ + OmiColors.surface1.withValues(alpha: 0), + OmiColors.surface1.withValues(alpha: 0.75), + OmiColors.surface1.withValues(alpha: 0) + ], + stops: const [0.35, 0.5, 0.65], + ).createShader(bounds), + child: child, + ); + } +} + +/// Where the ✓ was while Omi writes: the same dark circle, holding three lines that shorten into +/// one and grow back, over and over — notes condensing. +class CondensingControl extends StatefulWidget { + const CondensingControl({super.key}); + + @override + State createState() => _CondensingControlState(); +} + +class _CondensingControlState extends State with SingleTickerProviderStateMixin { + late final AnimationController _c = AnimationController(vsync: this, duration: const Duration(milliseconds: 1600)) + ..repeat(); + + @override + void dispose() { + _c.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final reduce = OmiMotion.of(context).emphasized == Duration.zero; + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: 140, + height: 140, + child: Center( + child: Container( + width: 68, + height: 68, + decoration: BoxDecoration(color: OmiColors.accent, shape: BoxShape.circle), + child: AnimatedBuilder( + animation: _c, + builder: (context, _) => CustomPaint( + painter: _CondensingLines(t: reduce ? 0.5 : _c.value, color: OmiColors.onAccent), + ), + ), + ), + ), + ), + // Keeps the circle level with the ✓ it replaces (which has a status line under it). + const SizedBox(height: OmiSpacing.xs + 20), + ], + ); + } +} + +class _CondensingLines extends CustomPainter { + _CondensingLines({required this.t, required this.color}); + + final double t; + final Color color; + + @override + void paint(Canvas canvas, Size size) { + // 0→0.5: three lines draw together into one short line; 0.5→1: they spread back out. + final phase = t < 0.5 ? Curves.easeInOutCubic.transform(t * 2) : 1 - Curves.easeInOutCubic.transform((t - 0.5) * 2); + final c = size.center(Offset.zero); + final paint = Paint() + ..color = color + ..strokeCap = StrokeCap.round + ..strokeWidth = 3; + const widths = [26.0, 20.0, 14.0]; + for (var k = 0; k < 3; k++) { + final y = c.dy + (k - 1) * 8 * (1 - phase); + final w = widths[k] * (1 - phase) + 18 * phase; + canvas.drawLine(Offset(c.dx - w / 2, y), Offset(c.dx + w / 2, y), + paint..color = color.withValues(alpha: k == 1 ? 1 : 1 - 0.6 * phase)); + } + } + + @override + bool shouldRepaint(covariant _CondensingLines old) => old.t != t || old.color != color; +} + +/// The summary's entrance: the card comes up out of a soft blur, then a light passes across it once. +class SummaryReveal extends StatefulWidget { + const SummaryReveal({super.key, required this.child}); + + final Widget child; + + @override + State createState() => _SummaryRevealState(); +} + +class _SummaryRevealState extends State with SingleTickerProviderStateMixin { + late final AnimationController _c = AnimationController(vsync: this, duration: const Duration(milliseconds: 1500)) + ..forward(); + Timer? _done; + + @override + void initState() { + super.initState(); + _done = Timer(const Duration(milliseconds: 1250), OmiHaptics.medium); + } + + @override + void dispose() { + _done?.cancel(); + _c.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (OmiMotion.of(context).emphasized == Duration.zero) return widget.child; + return AnimatedBuilder( + animation: _c, + child: widget.child, + builder: (context, child) { + final v = _c.value; + final appear = Curves.easeOutCubic.transform((v / 0.45).clamp(0.0, 1.0)); + final sweep = ((v - 0.55) / 0.45).clamp(0.0, 1.0); + Widget out = Opacity( + opacity: appear, + child: Transform.scale( + scale: 0.94 + 0.06 * appear, + child: ImageFiltered( + imageFilter: ImageFilter.blur(sigmaX: 12 * (1 - appear), sigmaY: 12 * (1 - appear)), + child: child, + ), + ), + ); + if (sweep > 0 && sweep < 1) { + final x = -1.5 + 3.0 * Curves.easeInOut.transform(sweep); + out = ShaderMask( + blendMode: BlendMode.srcATop, + shaderCallback: (bounds) => LinearGradient( + begin: Alignment(x - 0.6, -1), + end: Alignment(x + 0.6, 1), + colors: [ + OmiColors.surface1.withValues(alpha: 0), + OmiColors.surface1.withValues(alpha: 0.6), + OmiColors.surface1.withValues(alpha: 0) + ], + ).createShader(bounds), + child: out, + ); + } + return out; + }, + ); + } +} + +/// One line of a summary: a heading (`## …`), a bullet (`- …`), or plain text. +@visibleForTesting +typedef SummaryLine = ({String text, bool heading, bool bullet}); + +/// Omi's notes arrive as light markdown; the onboarding card shows them as text: headings become +/// short bold lines, `-`/`*` items become bullets, emphasis markers go, blank lines collapse. +@visibleForTesting +List summaryLines(String markdown) { + final out = []; + for (final raw in markdown.split('\n')) { + var line = raw.trim(); + if (line.isEmpty) continue; + line = line.replaceAll('**', '').replaceAll('__', ''); + final heading = RegExp(r'^#{1,6}\s+').firstMatch(line); + final bullet = RegExp(r'^([-*•]|\d+[.)])\s+').firstMatch(line); + if (heading != null) { + out.add((text: line.substring(heading.end).trim(), heading: true, bullet: false)); + } else if (bullet != null) { + out.add((text: line.substring(bullet.end).trim(), heading: false, bullet: true)); + } else { + out.add((text: line, heading: false, bullet: false)); + } + } + return out; +} + +/// The summary in the onboarding card, written in word by word ([t] 0→1) across its lines, in a +/// box that never grows past [maxHeight]: longer notes scroll inside it, with a fade at the bottom. +class SummaryText extends StatelessWidget { + const SummaryText({super.key, required this.text, required this.t, required this.style, this.maxHeight = 190}); + + final String text; + final double t; + final TextStyle style; + final double maxHeight; + + @override + Widget build(BuildContext context) { + final lines = summaryLines(text); + final total = lines.fold(0, (n, l) => n + l.text.split(' ').length); + final shown = (total * t).ceil(); + var index = 0; + final children = []; + for (var k = 0; k < lines.length; k++) { + final line = lines[k]; + final lineStyle = line.heading ? style.copyWith(fontWeight: FontWeight.w600) : style; + final words = line.text.split(' '); + final spans = [ + if (line.bullet) + TextSpan(text: '• ', style: lineStyle.copyWith(color: index < shown ? null : Colors.transparent)), + for (var i = 0; i < words.length; i++) + TextSpan( + text: i == words.length - 1 ? words[i] : '${words[i]} ', + style: lineStyle.copyWith( + color: index + i < shown ? lineStyle.color : lineStyle.color?.withValues(alpha: 0), + ), + ), + ]; + index += words.length; + children.add(Padding( + padding: EdgeInsets.only(top: k == 0 ? 0 : (line.heading ? OmiSpacing.md : OmiSpacing.xs)), + child: Text.rich(TextSpan(children: spans)), + )); + } + final column = + Column(crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: children); + return ConstrainedBox( + constraints: BoxConstraints(maxHeight: maxHeight), + child: ShaderMask( + blendMode: BlendMode.dstIn, + shaderCallback: (bounds) => LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: const [Colors.black, Colors.black, Colors.transparent], + stops: [0, bounds.height >= maxHeight - 1 ? 0.82 : 1, 1], + ).createShader(bounds), + child: SingleChildScrollView(physics: const ClampingScrollPhysics(), child: column), + ), + ); + } +} diff --git a/app/lib/pages/onboarding/variants/yinka/activation/activation_visuals.dart b/app/lib/pages/onboarding/variants/yinka/activation/activation_visuals.dart new file mode 100644 index 00000000000..c97f56b1d11 --- /dev/null +++ b/app/lib/pages/onboarding/variants/yinka/activation/activation_visuals.dart @@ -0,0 +1,206 @@ +import 'dart:math' as math; + +import 'package:flutter/material.dart'; + +import 'package:omi/ui/ui.dart'; + +const _cardShadow = BoxShadow( + color: Color(0x14000000), blurRadius: 24, offset: Offset(0, 8)); // omi-ux-allow: color-literal -- soft card shadow + +/// Runs [builder] with a 0→1 value once over [duration] (instant under Reduce Motion). +class OneShot extends StatefulWidget { + const OneShot({super.key, required this.duration, required this.builder, this.delay = Duration.zero}); + + final Duration duration; + final Duration delay; + final Widget Function(BuildContext context, double t) builder; + + @override + State createState() => _OneShotState(); +} + +class _OneShotState extends State with SingleTickerProviderStateMixin { + late final AnimationController _c = AnimationController(vsync: this, duration: widget.duration); + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + if (_c.status != AnimationStatus.dismissed) return; + if (OmiMotion.of(context).emphasized == Duration.zero) { + _c.value = 1; + } else { + Future.delayed(widget.delay, () { + if (mounted) _c.forward(); + }); + } + } + + @override + void dispose() { + _c.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) => + AnimatedBuilder(animation: _c, builder: (context, _) => widget.builder(context, _c.value)); +} + +/// The part of [t] that falls inside [start]..[end], eased, as 0→1. +double segment(double t, double start, double end, [Curve curve = Curves.easeOutCubic]) => + curve.transform(((t - start) / (end - start)).clamp(0.0, 1.0)); + +/// Fades and lifts [child] in as [t] goes 0→1. +Widget rise(double t, Widget child, {double distance = 10}) => Opacity( + opacity: t, + child: Transform.translate(offset: Offset(0, (1 - t) * distance), child: child), + ); + +/// A note card in the app's style: optional emoji + title, then body text. +class NoteCard extends StatelessWidget { + const NoteCard({super.key, this.emoji, this.title, this.body, this.child, this.minHeight = 0}); + + final String? emoji; + final String? title; + final String? body; + final Widget? child; + final double minHeight; + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + constraints: BoxConstraints(minHeight: minHeight), + padding: const EdgeInsets.all(OmiSpacing.lg), + decoration: BoxDecoration( + color: OmiColors.surface1, + borderRadius: OmiRadius.lgAll, + boxShadow: const [_cardShadow], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + if (title != null) + Text( + emoji == null || emoji!.isEmpty ? title! : '${emoji!} ${title!}', + style: OmiType.headline, + ), + if (title != null && (body != null || child != null)) const SizedBox(height: OmiSpacing.xs), + if (body != null) Text(body!, style: OmiType.subhead.copyWith(color: OmiColors.textSecondary, height: 1.4)), + if (child != null) child!, + ], + ), + ); + } +} + +/// One task row: a round check that fills at [check] (0→1) and the task text. +class TaskRow extends StatelessWidget { + const TaskRow({super.key, required this.text, this.check = 0}); + + final String text; + final double check; + + @override + Widget build(BuildContext context) { + final fill = Color.lerp(Colors.transparent, OmiColors.accent, check)!; + return Padding( + padding: const EdgeInsets.symmetric(vertical: OmiSpacing.xs), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 22, + height: 22, + margin: const EdgeInsets.only(top: 1), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: fill, + border: Border.all(color: Color.lerp(OmiColors.textTertiary, OmiColors.accent, check)!, width: 1.5), + ), + child: check > 0.5 + ? Icon(Icons.check_rounded, size: 15, color: OmiColors.onAccent.withValues(alpha: (check - 0.5) * 2)) + : null, + ), + const SizedBox(width: OmiSpacing.sm), + Expanded(child: Text(text, style: OmiType.callout.copyWith(height: 1.35))), + ], + ), + ); + } +} + +/// Text that appears word by word as [t] goes 0→1. +class WordReveal extends StatelessWidget { + const WordReveal({super.key, required this.text, required this.t, this.style, this.textAlign}); + + final String text; + final double t; + final TextStyle? style; + final TextAlign? textAlign; + + @override + Widget build(BuildContext context) { + final words = text.split(' '); + final shown = (words.length * t).ceil().clamp(0, words.length); + final base = style ?? DefaultTextStyle.of(context).style; + return Text.rich( + TextSpan( + children: [ + for (var i = 0; i < words.length; i++) + TextSpan( + text: i == words.length - 1 ? words[i] : '${words[i]} ', + style: base.copyWith(color: i < shown ? base.color : base.color?.withValues(alpha: 0)), + ), + ], + ), + textAlign: textAlign, + ); + } +} + +/// A burst of confetti from the centre: pieces fly out, turn and fade as [t] goes 0→1. +class ConfettiPainter extends CustomPainter { + ConfettiPainter({required this.t}); + + final double t; + + static const _colors = [ + Color(0xFFFFC53D), // omi-ux-allow: color-literal -- celebration confetti palette + Color(0xFFFF6B6B), // omi-ux-allow: color-literal -- celebration confetti palette + Color(0xFF4DABF7), // omi-ux-allow: color-literal -- celebration confetti palette + Color(0xFF51CF66), // omi-ux-allow: color-literal -- celebration confetti palette + Color(0xFF38D9A9), // omi-ux-allow: color-literal -- celebration confetti palette + Color(0xFFFF922B), // omi-ux-allow: color-literal -- celebration confetti palette + ]; + + @override + void paint(Canvas canvas, Size size) { + if (t <= 0 || t >= 1) return; + final center = size.center(Offset.zero); + final random = math.Random(7); + final travel = Curves.easeOutCubic.transform(t); + final fade = t < 0.7 ? 1.0 : (1 - (t - 0.7) / 0.3); + for (var i = 0; i < 36; i++) { + final angle = random.nextDouble() * 2 * math.pi; + final reach = size.width * (0.28 + random.nextDouble() * 0.24); + final fall = 40 * t * t; + final position = center + Offset(math.cos(angle) * reach * travel, math.sin(angle) * reach * travel + fall); + final paint = Paint()..color = _colors[i % _colors.length].withValues(alpha: fade.clamp(0.0, 1.0)); + canvas.save(); + canvas.translate(position.dx, position.dy); + canvas.rotate(angle + t * 6 * (random.nextBool() ? 1 : -1)); + final w = 5 + random.nextDouble() * 4; + canvas.drawRRect( + RRect.fromRectAndRadius( + Rect.fromCenter(center: Offset.zero, width: w, height: w * 1.8), const Radius.circular(1.5)), + paint, + ); + canvas.restore(); + } + } + + @override + bool shouldRepaint(ConfettiPainter oldDelegate) => oldDelegate.t != t; +} diff --git a/app/lib/pages/onboarding/variants/yinka/activation/onboarding_plans_sheet.dart b/app/lib/pages/onboarding/variants/yinka/activation/onboarding_plans_sheet.dart new file mode 100644 index 00000000000..b95080243f4 --- /dev/null +++ b/app/lib/pages/onboarding/variants/yinka/activation/onboarding_plans_sheet.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; + +import 'package:omi/pages/settings/widgets/plans_sheet.dart'; + +/// The plans sheet shown at the end of onboarding (until the Superwall paywall replaces it). +class OnboardingPlansSheet extends StatefulWidget { + const OnboardingPlansSheet({super.key}); + + @override + State createState() => _OnboardingPlansSheetState(); +} + +class _OnboardingPlansSheetState extends State with TickerProviderStateMixin { + late final AnimationController _wave = AnimationController(duration: const Duration(milliseconds: 18000), vsync: this) + ..repeat(); + late final AnimationController _arrow = AnimationController(duration: const Duration(milliseconds: 800), vsync: this) + ..repeat(reverse: true); + late final Animation _arrowOffset = + Tween(begin: 0, end: 3).animate(CurvedAnimation(parent: _arrow, curve: Curves.easeInOut)); + + @override + void dispose() { + _wave.dispose(); + _arrow.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return PlansSheet( + waveController: _wave, + notesController: _wave, + arrowController: _arrow, + arrowAnimation: _arrowOffset, + // Not yet a registered paywall source; reported as "unknown" until added to the registry. + analyticsSource: 'onboarding', + ); + } +} diff --git a/app/lib/pages/onboarding/variants/yinka/connect_omi_step.dart b/app/lib/pages/onboarding/variants/yinka/connect_omi_step.dart new file mode 100644 index 00000000000..048ebd6fd31 --- /dev/null +++ b/app/lib/pages/onboarding/variants/yinka/connect_omi_step.dart @@ -0,0 +1,500 @@ +import 'dart:async'; +import 'dart:math' as math; + +import 'package:flutter/material.dart'; + +import 'package:provider/provider.dart'; + +import 'package:omi/gen/assets.gen.dart'; +import 'package:omi/pages/onboarding/find_device/found_devices.dart'; +import 'package:omi/pages/shop/omi_shop.dart'; +import 'package:omi/providers/onboarding_provider.dart'; +import 'package:omi/services/devices/bluetooth_readiness.dart'; +import 'package:omi/ui/ui.dart'; +import 'package:omi/utils/l10n_extensions.dart'; +import 'package:omi/utils/platform/platform_manager.dart'; +import 'package:omi/widgets/connection_guide_sheet.dart'; +import 'package:omi/widgets/device_widget.dart'; +import 'package:omi/widgets/scanning_ripple.dart'; +import 'package:omi/pages/onboarding/variants/yinka/widgets/onboarding_card.dart'; + +/// What the reader told the connect step about their Omi. Sent as `choice` on +/// `Onboarding Device Choice`. +enum OnboardingDeviceChoice { + connected('connected'), + ordered('ordered'), + notOrdered('not_ordered'); + + const OnboardingDeviceChoice(this.wireValue); + final String wireValue; +} + +/// First-run "Connect your Omi" step. +/// +/// The search started in the background right after sign-in, so a reader whose Omi is on and nearby +/// sees it revealed as the step opens. Otherwise the step searches with instructions, and readers +/// without their Omi say whether it is on its way or not ordered yet. +class ConnectOmiStep extends StatefulWidget { + const ConnectOmiStep({super.key, required this.onDone, this.openShop = openOmiShop}); + + /// Called once with how the reader left the step. + final void Function(OnboardingDeviceChoice choice) onDone; + + /// Opens the Omi store for "Not ordered yet"; replaceable in tests (no web view there). + final Future Function(BuildContext context, {required String source}) openShop; + + @override + State createState() => _ConnectOmiStepState(); +} + +class _ConnectOmiStepState extends State with TickerProviderStateMixin { + OnboardingProvider? _provider; + bool _done = false; + + /// The arrival: the device rises and grows in, then the title, the line under it and the links + /// fade up one after another. A light tap marks the moment the device lands. + late final AnimationController _entrance = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 1400), + ); + late final Animation _device = CurvedAnimation( + parent: _entrance, + curve: const Interval(0.0, 0.55, curve: Curves.easeOutBack), + ); + late final Animation _title = CurvedAnimation( + parent: _entrance, + curve: const Interval(0.35, 0.7, curve: Curves.easeOutCubic), + ); + late final Animation _line = CurvedAnimation( + parent: _entrance, + curve: const Interval(0.45, 0.8, curve: Curves.easeOutCubic), + ); + late final Animation _links = CurvedAnimation( + parent: _entrance, + curve: const Interval(0.7, 1.0, curve: Curves.easeOut), + ); + Timer? _landingTap; + + /// The moment the Omi connects: a ring draws around it, a ✓ lands on the ring and one soft pulse + /// goes out, the title says Connected, then the step moves on by itself. + late final AnimationController _celebration = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 1100), + ); + bool _celebrating = false; + + @override + void initState() { + super.initState(); + _provider = context.read(); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; + if (OmiMotion.of(context).emphasized == Duration.zero) { + _entrance.value = 1; + _scan(); + } else { + // Let the arrival play before any Bluetooth guidance can cover it. A search started after + // sign-in keeps finding devices in the meantime. + _entrance.forward().whenComplete(() { + if (mounted) _scan(); + }); + _landingTap = Timer(const Duration(milliseconds: 620), () { + if (mounted) OmiHaptics.selection(); + }); + } + }); + } + + /// Fades [child] up from slightly below as [animation] runs. + Widget _rise(Animation animation, Widget child, {double distance = 12}) { + return AnimatedBuilder( + animation: animation, + child: child, + builder: (context, child) { + final t = animation.value.clamp(0.0, 1.0); + return Opacity( + opacity: t, + child: Transform.translate(offset: Offset(0, (1 - t) * distance), child: child), + ); + }, + ); + } + + @override + void dispose() { + _landingTap?.cancel(); + _entrance.dispose(); + _celebration.dispose(); + _provider?.endSearch(); + _provider = null; + super.dispose(); + } + + /// A missing permission goes through the shared Bluetooth guidance prompt, like the pairing page. + Future _scan() async { + void publishGuidance() { + if (mounted) unawaited(BluetoothReadiness.instance.ensureReady(BluetoothUse.discovery)); + } + + await _provider?.scanDevices(onShowDialog: publishGuidance, onShowLocationDialog: publishGuidance); + } + + Future _scanAgain() async { + OmiHaptics.selection(); + _provider?.cancelActiveScan(); + _provider?.enableInstructions = false; + setState(() {}); + await _scan(); + } + + /// "Not ordered yet": the Omi store opens inside the app; closing it carries on with onboarding. + bool _shopOpen = false; + Future _notOrdered() async { + if (_done || _shopOpen) return; + _shopOpen = true; + OmiHaptics.selection(); + await widget.openShop(context, source: 'onboarding_not_ordered'); + _shopOpen = false; + if (mounted) _finish(OnboardingDeviceChoice.notOrdered); + } + + /// How the reader left the step: `connected`, `ordered` or `not_ordered`. Also kept on the person, + /// so buyers who say they ordered can be matched to Shopify orders later. + static void _trackDeviceChoice(String choice) { + PlatformManager.instance.analytics.track('Onboarding Device Choice', properties: {'choice': choice}); + PlatformManager.instance.analytics.setUserProperty('Onboarding Device Choice', choice); + } + + void _finish(OnboardingDeviceChoice choice) { + if (_done) return; + _done = true; + _trackDeviceChoice(choice.wireValue); + widget.onDone(choice); + } + + /// Connected: hold the step for the celebration (long enough to read "Connected"), then move on. + Future _connected() async { + if (_done) return; + _done = true; + _trackDeviceChoice(OnboardingDeviceChoice.connected.wireValue); + final still = OmiMotion.of(context).emphasized == Duration.zero; + setState(() => _celebrating = true); + if (still) { + _celebration.value = 1; + } else { + await _celebration.forward(); + } + await Future.delayed(Duration(milliseconds: still ? 1200 : 700)); + if (mounted) widget.onDone(OnboardingDeviceChoice.connected); + } + + void _showConnectionGuide() { + PlatformManager.instance.analytics.connectionGuideOpened(); + ConnectionGuideSheet.show(context); + } + + bool _feltFound = false; + bool _feltConnected = false; + + /// A light tap when the Omi appears, a firmer one when it is connected: felt, not only seen. + void _feelProgress({required bool found, required bool connected}) { + if (found && !_feltFound) { + _feltFound = true; + WidgetsBinding.instance.addPostFrameCallback((_) => OmiHaptics.light()); + } + if (connected && !_feltConnected) { + _feltConnected = true; + WidgetsBinding.instance.addPostFrameCallback((_) => OmiHaptics.success()); + } + } + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final compact = MediaQuery.sizeOf(context).height <= 700; + final stage = compact ? 220.0 : 260.0; + return Consumer( + builder: (context, provider, child) { + final devices = provider.visibleDeviceList; + final found = provider.isConnected || devices.isNotEmpty; + final cantFind = !found && provider.enableInstructions; + final shown = devices.isNotEmpty ? devices.first : null; + _feelProgress(found: found, connected: provider.isConnected); + + final Widget visual = found + ? TweenAnimationBuilder( + // The reveal: the found device grows in from the search. + key: ValueKey('connect_omi_reveal_${shown?.id ?? provider.deviceId}'), + tween: Tween(begin: 0, end: 1), + duration: OmiMotion.of(context).emphasized == Duration.zero + ? Duration.zero + : const Duration(milliseconds: 700), + curve: Curves.easeOutBack, + builder: (context, t, child) => Opacity( + opacity: t.clamp(0.0, 1.0), + child: Transform.scale(scale: 0.6 + 0.4 * t, child: child), + ), + child: DeviceAnimationWidget( + sizeMultiplier: compact ? 0.65 : 0.75, + isConnected: provider.isConnected, + deviceName: provider.isConnected ? provider.deviceName : shown?.name, + deviceType: provider.isConnected ? provider.deviceType : shown?.type, + modelNumber: shown?.modelNumber, + ), + ) + : Stack( + alignment: Alignment.center, + children: [ + if (!cantFind) ScanningRippleWidget(isScanning: true, size: stage, color: OmiColors.textTertiary), + Assets.images.omiWithoutRopeTurnedOff.image(width: stage * 0.42, height: stage * 0.42), + ], + ); + + final String subtitle = provider.isConnected + ? (provider.deviceName.isNotEmpty ? provider.deviceName : l10n.connected) + : found + ? l10n.onboardingConnectTapToConnect + : cantFind + ? l10n.cantFindDeviceHint + : l10n.pairingDescOmi; + + return SafeArea( + child: Padding( + padding: const EdgeInsets.fromLTRB(OmiSpacing.xl, 0, OmiSpacing.xl, kOnboardingButtonBottomGap), + child: Column( + children: [ + Expanded( + child: Center( + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + AnimatedBuilder( + animation: _device, + child: SizedBox( + height: stage, + child: _ConnectedHalo( + progress: _celebration, + active: _celebrating, + diameter: stage * 0.86, + child: Center(child: visual), + ), + ), + builder: (context, child) { + final t = _device.value; + return Opacity( + opacity: t.clamp(0.0, 1.0), + child: Transform.translate( + offset: Offset(0, (1 - t) * 24), + child: Transform.scale(scale: 0.7 + 0.3 * t, child: child), + ), + ); + }, + ), + const SizedBox(height: OmiSpacing.xxl), + _rise( + _title, + Semantics( + header: true, + liveRegion: true, + // The title becomes the news once the Omi is connected. + child: AnimatedSwitcher( + duration: OmiMotion.of(context).standard, + transitionBuilder: (child, animation) => FadeTransition( + opacity: animation, + child: SlideTransition( + position: Tween(begin: const Offset(0, 0.25), end: Offset.zero).animate(animation), + child: child, + ), + ), + child: Text( + _celebrating ? l10n.connected : l10n.onboardingConnectOmi, + key: ValueKey(_celebrating), + style: OmiType.title1, + textAlign: TextAlign.center, + ), + ), + ), + ), + const SizedBox(height: OmiSpacing.xs), + // A narrow measure so the instruction wraps evenly instead of leaving one word behind. + _rise( + _line, + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 280), + child: Text( + subtitle, + key: const Key('connect_omi_status'), + style: OmiType.subhead.copyWith(color: OmiColors.textSecondary, height: 1.35), + textAlign: TextAlign.center, + ), + ), + ), + const SizedBox(height: OmiSpacing.lg), + // The tappable device rows; stays mounted because it owns the connection handling. + FoundDevices( + goNext: _connected, + isFromOnboarding: true, + onRescan: _scanAgain, + showStatus: false, + ), + if (cantFind) ...[ + OmiButton( + key: const Key('connect_omi_scan_again'), + label: l10n.scanAgain, + icon: Icons.refresh, + onPressed: _scanAgain, + ), + OmiButton.tertiary( + key: const Key('connect_omi_how_to_pair'), + label: l10n.howToPair, + onPressed: _showConnectionGuide, + ), + ], + ], + ), + ), + ), + ), + // Once the reader is connecting their own Omi, "Don't have your Omi yet?" no longer applies. + // It fades out but keeps its space, so the device and title above don't jump. + AnimatedOpacity( + opacity: provider.isConnected || provider.isClicked ? 0 : 1, + duration: const Duration(milliseconds: 200), + child: IgnorePointer( + ignoring: provider.isConnected || provider.isClicked, + child: _rise( + _links, + Column(mainAxisSize: MainAxisSize.min, children: [ + Text( + l10n.onboardingNoOmiYet, + style: OmiType.footnote.copyWith(color: OmiColors.textTertiary), + textAlign: TextAlign.center, + ), + // Quiet links, not buttons: they must not compete with the device above. + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: OmiButton.tertiary( + key: const Key('connect_omi_ordered'), + label: l10n.onboardingOmiOrdered, + size: OmiButtonSize.compact, + onPressed: () => _finish(OnboardingDeviceChoice.ordered), + ), + ), + Container(width: 1, height: 14, color: OmiColors.border), + Flexible( + child: OmiButton.tertiary( + key: const Key('connect_omi_not_ordered'), + label: l10n.onboardingOmiNotOrdered, + size: OmiButtonSize.compact, + onPressed: _notOrdered, + ), + ), + ], + ), + ]), + distance: 8), + ), + ), + ], + ), + ), + ); + }, + ); + } +} + +/// Around the connected Omi: a ring that draws itself, a ✓ that lands where the ring closes, and +/// one pulse outward. Nothing shows until [active]. +class _ConnectedHalo extends StatelessWidget { + const _ConnectedHalo({required this.progress, required this.active, required this.diameter, required this.child}); + + final Animation progress; + final bool active; + final double diameter; + final Widget child; + + static const _badge = 40.0; + + @override + Widget build(BuildContext context) { + if (!active) return child; + return AnimatedBuilder( + animation: progress, + child: child, + builder: (context, child) { + final t = progress.value; + final ring = Curves.easeInOutCubic.transform((t / 0.55).clamp(0.0, 1.0)); + final badge = Curves.easeOutBack.transform(((t - 0.45) / 0.35).clamp(0.0, 1.0)); + final pulse = Curves.easeOut.transform(((t - 0.5) / 0.5).clamp(0.0, 1.0)); + // The ✓ sits on the ring at its lower right, where the drawing ends. + const angle = math.pi / 4; + final r = diameter / 2; + return Stack( + alignment: Alignment.center, + clipBehavior: Clip.none, + children: [ + // One pulse outward as the ✓ lands. + if (pulse > 0 && pulse < 1) + Container( + width: diameter * (1 + 0.22 * pulse), + height: diameter * (1 + 0.22 * pulse), + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: OmiColors.textPrimary.withValues(alpha: 0.3 * (1 - pulse)), width: 2), + ), + ), + SizedBox.square( + dimension: diameter, + child: CustomPaint(painter: _RingPainter(progress: ring, color: OmiColors.textPrimary)), + ), + child!, + Transform.translate( + offset: Offset(r * math.cos(angle), r * math.sin(angle)), + child: Transform.scale( + scale: badge, + child: Container( + key: const Key('connect_omi_connected_badge'), + width: _badge, + height: _badge, + decoration: BoxDecoration( + color: OmiColors.accent, + shape: BoxShape.circle, + border: Border.all(color: OmiColors.surface0, width: 3), + ), + child: Icon(Icons.check_rounded, size: 22, color: OmiColors.onAccent), + ), + ), + ), + ], + ); + }, + ); + } +} + +class _RingPainter extends CustomPainter { + _RingPainter({required this.progress, required this.color}); + + final double progress; + final Color color; + + @override + void paint(Canvas canvas, Size size) { + if (progress <= 0) return; + final rect = Offset.zero & size; + final paint = Paint() + ..color = color + ..style = PaintingStyle.stroke + ..strokeWidth = 2.5 + ..strokeCap = StrokeCap.round; + // Starts and closes under the ✓ (lower right), clockwise. + canvas.drawArc(rect.deflate(1.25), math.pi / 4, 2 * math.pi * progress, false, paint); + } + + @override + bool shouldRepaint(covariant _RingPainter old) => old.progress != progress || old.color != color; +} diff --git a/app/lib/pages/onboarding/variants/yinka/name_widget.dart b/app/lib/pages/onboarding/variants/yinka/name_widget.dart new file mode 100644 index 00000000000..95e8a9a2e2b --- /dev/null +++ b/app/lib/pages/onboarding/variants/yinka/name_widget.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; + +import 'package:omi/backend/preferences.dart'; +import 'package:omi/services/auth_service.dart'; +import 'package:omi/pages/onboarding/variants/yinka/widgets/onboarding_plain_step.dart'; +import 'package:omi/ui/ui.dart'; +import 'package:omi/utils/l10n_extensions.dart'; + +class NameWidget extends StatefulWidget { + final Function goNext; + + const NameWidget({super.key, required this.goNext}); + + @override + State createState() => _NameWidgetState(); +} + +class _NameWidgetState extends State { + late TextEditingController nameController; + var focusNode = FocusNode(); + + @override + void initState() { + nameController = TextEditingController(text: SharedPreferencesUtil().givenName); + super.initState(); + + // The step opens with the keyboard up, ready to type. + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) focusNode.requestFocus(); + }); + } + + @override + void dispose() { + nameController.dispose(); + focusNode.dispose(); + super.dispose(); + } + + bool get _canContinue => nameController.text.trim().isNotEmpty; + + void _submit() { + if (!_canContinue) return; + FocusManager.instance.primaryFocus?.unfocus(); + AuthService.instance.updateGivenName(nameController.text.trim()); + OmiHaptics.selection(); + widget.goNext(); + } + + @override + Widget build(BuildContext context) { + return OnboardingPlainStep( + title: context.l10n.whatsYourName, + centered: true, + body: DecoratedBox( + decoration: BoxDecoration(color: OmiColors.surface1, borderRadius: OmiRadius.lgAll), + child: TextField( + controller: nameController, + focusNode: focusNode, + style: OmiType.title3, + textAlign: TextAlign.center, + textCapitalization: TextCapitalization.words, + textInputAction: TextInputAction.done, + autocorrect: false, + cursorColor: OmiColors.textPrimary, + onSubmitted: (_) => _submit(), + // Keep the keyboard up when a finger lands outside the field, so a tap on Continue lands on + // Continue instead of only closing the keyboard (and moving the button). Tapping empty + // space still closes it through the onboarding host. + onTapOutside: (_) {}, + decoration: InputDecoration( + hintText: context.l10n.enterYourName, + hintStyle: OmiType.title3.copyWith(color: OmiColors.textTertiary, fontWeight: FontWeight.w400), + border: InputBorder.none, + contentPadding: const EdgeInsets.symmetric(horizontal: OmiSpacing.xl, vertical: OmiSpacing.lg), + ), + onChanged: (value) { + setState(() {}); // Trigger rebuild to update button state + }, + ), + ), + action: OmiButton( + key: const Key('onboarding_name_continue'), + label: context.l10n.continueButton, + expand: true, + onPressed: _canContinue ? _submit : null, + ), + ); + } +} diff --git a/app/lib/pages/onboarding/variants/yinka/permissions_widget.dart b/app/lib/pages/onboarding/variants/yinka/permissions_widget.dart new file mode 100644 index 00000000000..42a8c18d20f --- /dev/null +++ b/app/lib/pages/onboarding/variants/yinka/permissions_widget.dart @@ -0,0 +1,273 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:permission_handler/permission_handler.dart'; + +import 'package:omi/backend/preferences.dart'; +import 'package:omi/pages/onboarding/permissions/onboarding_permissions_panel.dart'; +import 'package:omi/pages/onboarding/variants/yinka/widgets/onboarding_plain_step.dart'; +import 'package:omi/ui/ui.dart'; +import 'package:omi/utils/l10n_extensions.dart'; +import 'package:omi/utils/logger.dart'; + +/// How one card on the first-run permissions step looks. +enum OnboardingPermissionCardState { + /// Not answered yet: grey, switch off. + unanswered, + + /// Allowed: switch on. + allowed, + + /// Answered with no (or switched off in Settings). Tapping opens Settings. + denied, +} + +/// First-run permissions step: one switch card per permission (docs/ux-contract.md §15). +/// +/// Each card is answered by toggling it, which shows that permission's system prompt. A "no" is an +/// answer: the card turns orange (notifications: red with "Tap to fix") and the reader can still +/// continue. Continue unlocks once every card has been answered. Bluetooth is asked as the app +/// opens, so its card only appears when it was not allowed. +class PermissionsWidget extends StatefulWidget { + final VoidCallback goNext; + final OnboardingPermissionsSource? source; + + const PermissionsWidget({super.key, required this.goNext, this.source}); + + @override + State createState() => _PermissionsWidgetState(); +} + +class _PermissionsWidgetState extends State with WidgetsBindingObserver { + late final OnboardingPermissionsSource _source = resolveOnboardingPermissionsSource(context, widget.source); + + /// The cards, fixed when the step opens: Bluetooth stays listed after it is allowed here. + List? _permissions; + final Map _statuses = {}; + + /// Cards whose system prompt the reader has already seen (on this step, or Bluetooth at launch). + final Set _asked = {}; + OnboardingPermission? _busy; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + if (SharedPreferencesUtil().getBool('onboarding/bluetoothAskedAtLaunch')) { + _asked.add(OnboardingPermission.bluetooth); + } + unawaited(_load()); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + // Back from Settings: show what the reader switched on there. + if (state == AppLifecycleState.resumed) unawaited(_refresh()); + } + + Future _read(OnboardingPermission permission) async { + try { + return await _source.status(permission); + } catch (_) { + return OmiPermissionStatus.askable; + } + } + + Future _load() async { + final bluetooth = await _read(OnboardingPermission.bluetooth); + if (!mounted) return; + setState(() { + _permissions = [ + if (bluetooth != OmiPermissionStatus.granted) OnboardingPermission.bluetooth, + ..._source.permissions, + ]; + _statuses[OnboardingPermission.bluetooth] = bluetooth; + }); + await _refresh(); + } + + Future _refresh() async { + final permissions = _permissions; + if (permissions == null) return; + for (final permission in permissions) { + final status = await _read(permission); + if (!mounted) return; + setState(() => _statuses[permission] = status); + } + } + + OnboardingPermissionCardState _stateOf(OnboardingPermission permission) { + final status = _statuses[permission] ?? OmiPermissionStatus.askable; + if (status == OmiPermissionStatus.granted) return OnboardingPermissionCardState.allowed; + if (status == OmiPermissionStatus.blocked || status == OmiPermissionStatus.serviceOff) { + return OnboardingPermissionCardState.denied; + } + return _asked.contains(permission) + ? OnboardingPermissionCardState.denied + : OnboardingPermissionCardState.unanswered; + } + + bool get _allAnswered { + final permissions = _permissions; + if (permissions == null) return false; + return permissions.every((p) => _stateOf(p) != OnboardingPermissionCardState.unanswered); + } + + Future _onTap(OnboardingPermission permission) async { + if (_busy != null) return; + final state = _stateOf(permission); + if (state == OnboardingPermissionCardState.allowed) return; + OmiHaptics.selection(); + if (state == OnboardingPermissionCardState.denied) { + // The system will not ask again (or the reader said no): only Settings can turn it on. + await openAppSettings(); + return; + } + setState(() => _busy = permission); + try { + await _source.request(permission); + } catch (e) { + Logger.debug('Onboarding permission request failed for ${permission.name}: $e'); + } finally { + _asked.add(permission); + final status = await _read(permission); + if (mounted) { + // The toggle landing on: a light confirmation. + if (status == OmiPermissionStatus.granted) OmiHaptics.light(); + setState(() { + _statuses[permission] = status; + _busy = null; + }); + } + } + } + + @override + Widget build(BuildContext context) { + final permissions = _permissions ?? const []; + return OnboardingPlainStep( + title: context.l10n.grantPermissions, + centered: true, + body: Column( + children: [ + for (final permission in permissions) ...[ + _OnboardingPermissionRow( + key: ValueKey('onboarding_permission_${permission.name}'), + permission: permission, + state: _stateOf(permission), + busy: _busy == permission, + onTap: () => _onTap(permission), + ), + const SizedBox(height: OmiSpacing.sm), + ], + ], + ), + action: OmiButton( + key: const Key('onboarding_permissions_continue'), + label: context.l10n.continueButton, + expand: true, + onPressed: _allAnswered + ? () { + OmiHaptics.selection(); + widget.goNext(); + } + : null, + ), + ); + } +} + +/// One permission as its own card: icon tile, title, and the switch. A line under the title +/// appears only when there is something to do (Bluetooth off, notifications to fix). +class _OnboardingPermissionRow extends StatelessWidget { + const _OnboardingPermissionRow({ + super.key, + required this.permission, + required this.state, + required this.busy, + required this.onTap, + }); + + final OnboardingPermission permission; + final OnboardingPermissionCardState state; + final bool busy; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final denied = state == OnboardingPermissionCardState.denied; + final String? note = !denied + ? null + : switch (permission) { + OnboardingPermission.notifications => l10n.onboardingPermissionTapToFix, + OnboardingPermission.bluetooth => l10n.onboardingBluetoothRequired, + _ => null, + }; + final title = onboardingPermissionTitle(l10n, permission); + + return Semantics( + button: true, + toggled: state == OnboardingPermissionCardState.allowed, + label: note == null ? title : '$title, $note', + excludeSemantics: true, + child: Material( + color: OmiColors.surface1, + borderRadius: OmiRadius.lgAll, + clipBehavior: Clip.antiAlias, + child: InkWell( + onTap: busy ? null : onTap, + child: ConstrainedBox( + constraints: const BoxConstraints(minHeight: 64), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: OmiSpacing.md, vertical: OmiSpacing.sm), + child: Row( + children: [ + Container( + width: 32, + height: 32, + decoration: BoxDecoration(color: OmiColors.surface0, borderRadius: OmiRadius.smAll), + child: Center( + child: FaIcon(onboardingPermissionIcon(permission), size: 15, color: OmiColors.textPrimary), + ), + ), + const SizedBox(width: OmiSpacing.sm), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text(title, style: OmiType.callout.copyWith(fontWeight: FontWeight.w600)), + if (note != null) ...[ + const SizedBox(height: 2), + Text(note, style: OmiType.footnote.copyWith(color: OmiColors.textSecondary)), + ], + ], + ), + ), + const SizedBox(width: OmiSpacing.sm), + OnboardingPermissionToggle( + on: state == OnboardingPermissionCardState.allowed, + denied: denied, + busy: busy, + // Notifications that are off are worth fixing (red); the rest are a caution (orange). + deniedColor: + permission == OnboardingPermission.notifications ? OmiColors.danger : OmiColors.warning, + ), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/app/lib/pages/onboarding/variants/yinka/primary_language_widget.dart b/app/lib/pages/onboarding/variants/yinka/primary_language_widget.dart new file mode 100644 index 00000000000..2e0b08b71ef --- /dev/null +++ b/app/lib/pages/onboarding/variants/yinka/primary_language_widget.dart @@ -0,0 +1,278 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/material.dart'; + +import 'package:provider/provider.dart'; + +import 'package:omi/backend/preferences.dart'; +import 'package:omi/providers/home_provider.dart'; +import 'package:omi/providers/user_provider.dart'; +import 'package:omi/pages/onboarding/variants/yinka/widgets/onboarding_plain_step.dart'; +import 'package:omi/ui/ui.dart'; +import 'package:omi/utils/l10n_extensions.dart'; +import 'package:omi/utils/logger.dart'; + +class PrimaryLanguageWidget extends StatefulWidget { + final Function goNext; + + const PrimaryLanguageWidget({super.key, required this.goNext}); + + @override + State createState() => _PrimaryLanguageWidgetState(); +} + +class LanguageSelectorWidget extends StatefulWidget { + final Map availableLanguages; + final String? selectedLanguage; + final String? selectedLanguageName; + final ScrollController languageScrollController; + final Function(String?, String?) onLanguageSelected; + + const LanguageSelectorWidget({ + super.key, + required this.availableLanguages, + this.selectedLanguage, + this.selectedLanguageName, + required this.languageScrollController, + required this.onLanguageSelected, + }); + + @override + State createState() => _LanguageSelectorWidgetState(); +} + +class _LanguageSelectorWidgetState extends State { + late List> languages; + late List> filteredLanguages; + String searchQuery = ''; + String? currentSelectedLanguage; + + @override + void initState() { + super.initState(); + languages = widget.availableLanguages.entries.toList(); + filteredLanguages = List.from(languages); + currentSelectedLanguage = widget.selectedLanguage; + } + + void filterLanguages(String query) { + Logger.debug(query); + setState(() { + searchQuery = query.toLowerCase(); + if (query.isEmpty) { + filteredLanguages = List.from(languages); + } else { + filteredLanguages = languages.where((lang) { + return lang.key.toLowerCase().contains(searchQuery) || lang.value.toLowerCase().contains(searchQuery); + }).toList(); + } + + // Debug print to verify filtering + Logger.debug('Search query: $searchQuery, Found ${filteredLanguages.length} languages'); + for (var lang in filteredLanguages) { + Logger.debug('Filtered language: ${lang.key} (${lang.value})'); + } + }); + } + + @override + Widget build(BuildContext context) { + return SizedBox( + height: MediaQuery.sizeOf(context).height * 0.7, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text(context.l10n.languageBenefits, style: OmiType.footnote.copyWith(color: OmiColors.textSecondary)), + const SizedBox(height: OmiSpacing.md), + OmiSearchField(placeholder: context.l10n.searchLanguageHint, onChanged: filterLanguages), + const SizedBox(height: OmiSpacing.md), + Expanded( + child: filteredLanguages.isEmpty + ? OmiEmptyState(icon: Icons.translate, title: context.l10n.noLanguagesFound) + : ListView.builder( + controller: widget.languageScrollController, + key: ValueKey(searchQuery), // Force rebuild when search changes + itemCount: filteredLanguages.length, + itemBuilder: (context, index) { + final language = filteredLanguages[index]; + final isSelected = currentSelectedLanguage == language.value; + return ListTile( + title: Text(language.key, style: OmiType.body), + trailing: isSelected ? Icon(Icons.check_circle, color: OmiColors.accent) : null, + selected: isSelected, + selectedTileColor: OmiColors.surface2, + shape: const RoundedRectangleBorder(borderRadius: OmiRadius.smAll), + // Picking a language is the answer: it selects and closes the picker. + onTap: () { + OmiHaptics.selection(); + widget.onLanguageSelected(language.value, language.key); + Navigator.of(context).pop(); + }, + ); + }, + ), + ), + ], + ), + ); + } +} + +class _PrimaryLanguageWidgetState extends State { + String? selectedLanguage; + String? selectedLanguageName; + final ScrollController _languageScrollController = ScrollController(); + + @override + void initState() { + super.initState(); + // Initialize with the user's saved primary language if available, or auto-detect from device + WidgetsBinding.instance.addPostFrameCallback((_) { + final savedLanguage = SharedPreferencesUtil().userPrimaryLanguage; + final homeProvider = Provider.of(context, listen: false); + + if (savedLanguage.isNotEmpty) { + setState(() { + selectedLanguage = savedLanguage; + // Find the language name for the saved language code + try { + selectedLanguageName = + homeProvider.availableLanguages.entries.firstWhere((entry) => entry.value == savedLanguage).key; + } catch (e) { + // If language not found in the map, just use the code + selectedLanguageName = savedLanguage; + } + }); + } else { + // Auto-detect from device system language + _autoSelectDeviceLanguage(homeProvider.availableLanguages); + } + }); + } + + void _autoSelectDeviceLanguage(Map availableLanguages) { + try { + // Get device locale (e.g., "en_US", "ja_JP", "zh_CN") + final deviceLocale = Platform.localeName; + final languageCode = deviceLocale.split('_').first.toLowerCase(); + + Logger.debug('Device locale: $deviceLocale, language code: $languageCode'); + + // Try to find a matching language in available languages + for (final entry in availableLanguages.entries) { + final availableCode = entry.value.toLowerCase(); + // Match by language code (e.g., "en" matches "en", "ja" matches "ja") + if (availableCode == languageCode || availableCode.startsWith('$languageCode-')) { + setState(() { + selectedLanguage = entry.value; + selectedLanguageName = entry.key; + }); + Logger.debug('Auto-selected language: ${entry.key} (${entry.value})'); + return; + } + } + Logger.debug('No matching language found for device locale: $deviceLocale'); + } catch (e) { + Logger.debug('Error auto-detecting device language: $e'); + } + } + + void _showLanguageSelector(BuildContext context, Map availableLanguages) { + showOmiSheet( + context: context, + title: context.l10n.selectPrimaryLanguage, + builder: (context) { + return LanguageSelectorWidget( + availableLanguages: availableLanguages, + selectedLanguage: selectedLanguage, + selectedLanguageName: selectedLanguageName, + languageScrollController: _languageScrollController, + onLanguageSelected: (language, name) { + setState(() { + selectedLanguage = language; + selectedLanguageName = name; + }); + }, + ); + }, + ); + } + + @override + void dispose() { + _languageScrollController.dispose(); + super.dispose(); + } + + Future _continue() async { + FocusManager.instance.primaryFocus?.unfocus(); + final homeProvider = Provider.of(context, listen: false); + final userProvider = Provider.of(context, listen: false); + final success = await homeProvider.updateUserPrimaryLanguage(selectedLanguage!, userProvider: userProvider); + if (!mounted) return; + if (!success) { + OmiHaptics.error(); + OmiFeedback.error( + context, + context.l10n.failedToSetLanguage, + actionLabel: context.l10n.tryAgain, + onAction: () => unawaited(_continue()), + ); + return; + } + OmiHaptics.selection(); + widget.goNext(); + } + + @override + Widget build(BuildContext context) { + final hasSelection = selectedLanguageName != null; + return OnboardingPlainStep( + title: context.l10n.chooseYourLanguage, + centered: true, + body: Semantics( + button: true, + label: context.l10n.selectPrimaryLanguage, + value: selectedLanguageName, + excludeSemantics: true, + child: Material( + color: OmiColors.surface1, + shape: const RoundedRectangleBorder(borderRadius: OmiRadius.lgAll), + child: InkWell( + customBorder: const RoundedRectangleBorder(borderRadius: OmiRadius.lgAll), + onTap: () { + final homeProvider = Provider.of(context, listen: false); + _showLanguageSelector(context, homeProvider.availableLanguages); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: OmiSpacing.xl, vertical: OmiSpacing.lg), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: Text( + selectedLanguageName ?? context.l10n.selectYourLanguage, + style: OmiType.title3.copyWith( + color: hasSelection ? OmiColors.textPrimary : OmiColors.textTertiary, + ), + ), + ), + const SizedBox(width: OmiSpacing.sm), + Icon(Icons.keyboard_arrow_down, color: OmiColors.textTertiary, size: 24), + ], + ), + ), + ), + ), + ), + // Async: the button shows a spinner and ignores taps while the language saves. + action: OmiButton( + key: const Key('onboarding_language_continue'), + label: context.l10n.continueButton, + expand: true, + onPressed: selectedLanguage == null ? null : _continue, + ), + ); + } +} diff --git a/app/lib/pages/onboarding/variants/yinka/widgets/onboarding_card.dart b/app/lib/pages/onboarding/variants/yinka/widgets/onboarding_card.dart new file mode 100644 index 00000000000..d3551cd2cc6 --- /dev/null +++ b/app/lib/pages/onboarding/variants/yinka/widgets/onboarding_card.dart @@ -0,0 +1,191 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:omi/env/env.dart'; +import 'package:omi/env/environment_profile.dart'; +import 'package:omi/ui/omi_tokens.dart'; +import 'package:omi/ui/ui.dart'; + +/// Space between every onboarding step's primary button and the bottom safe area: high enough to +/// sit under the thumb, the same on every step so the button never moves between steps. +const double kOnboardingButtonBottomGap = OmiSpacing.xl; + +/// The bottom block of a first-run step (title, text, buttons), pinned to the bottom of the step. +/// No card: it sits straight on the onboarding surface, under the Omi pendant. +/// +/// [content] scrolls when they do not fit (large text, small phones); [footer] (the step's +/// buttons) stays visible under them. The card reserves the bottom system inset once, through its +/// [SafeArea], not twice. +/// +/// ```dart +/// OnboardingStep(card: OnboardingCard(content: [...], footer: [OmiButton(...)])) +/// ``` +class OnboardingCard extends StatelessWidget { + const OnboardingCard({ + super.key, + required this.content, + this.footer = const [], + this.padding = + const EdgeInsets.fromLTRB(OmiSpacing.xxl, OmiSpacing.xxl, OmiSpacing.xxl, kOnboardingButtonBottomGap), + this.crossAxisAlignment = CrossAxisAlignment.center, + this.reserveBottomInset = true, + }); + + final List content; + final List footer; + final EdgeInsets padding; + final CrossAxisAlignment crossAxisAlignment; + + /// False when the footer lays out the home-indicator area itself (e.g. a fine-print line under + /// the button that sits in the space the other steps leave below theirs). + final bool reserveBottomInset; + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + padding: padding, + child: SafeArea( + top: false, + bottom: reserveBottomInset, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Flexible( + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: crossAxisAlignment, + children: content, + ), + ), + ), + ...footer, + ], + ), + ), + ); + } +} + +/// A first-run step: the pendant shows above, the [card] block sits at the bottom and grows +/// up to the full height before its content scrolls. +class OnboardingStep extends StatelessWidget { + const OnboardingStep({super.key, required this.card}); + + final Widget card; + + @override + Widget build(BuildContext context) { + // The top padding already includes the progress dots and back button when the wrapper draws + // them (OnboardingStepLayout), so this only adds a small gap above a full-height card. + return Padding( + padding: EdgeInsets.only(top: MediaQuery.paddingOf(context).top + OmiSpacing.md), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [Flexible(child: card)], + ), + ); + } +} + +/// How sign-in and the data & privacy step look: the Omi pendant on the light onboarding surface +/// (default), or the launch film full-screen under white type. The film is a design comparison for +/// now: `--dart-define=OMI_ONBOARDING_HERO=video`, honoured in debug and local_dev builds only. +abstract final class OnboardingHero { + static const _flag = String.fromEnvironment('OMI_ONBOARDING_HERO'); + + static bool get video => (kDebugMode || Env.profile == AppEnvironmentProfile.localDev) && _flag == 'video'; + + /// Type and buttons: white over the film, the theme's own over the pendant. + static OmiPalette get palette => video ? OmiPalette.dark : OmiColors.active; +} + +/// Sign-in and data & privacy: the hero (pendant or film) above, [headline] centred in the space +/// between it and the buttons, and [buttons] pinned exactly where every other step's Continue sits +/// (24pt in, 48pt tall, ending [kOnboardingButtonBottomGap] above the safe area) with [legal] in the space below. +class OnboardingHeroStep extends StatelessWidget { + const OnboardingHeroStep({super.key, required this.headline, required this.buttons, required this.legal}); + + final Widget headline; + final List buttons; + final Widget legal; + + @override + Widget build(BuildContext context) { + final media = MediaQuery.of(context); + final belowButton = media.padding.bottom + kOnboardingButtonBottomGap; + // The pendant's body ends around 40% down; the film fills the screen, so its type sits lower. + final heroBottom = media.size.height * (OnboardingHero.video ? 0.46 : 0.42); + return Padding( + padding: const EdgeInsets.symmetric(horizontal: OmiSpacing.xl), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SizedBox(height: heroBottom), + // The headline sits low, just above the button(s). + Expanded( + child: Align(alignment: Alignment.bottomCenter, child: SingleChildScrollView(child: headline)), + ), + const SizedBox(height: OmiSpacing.xxl), + ...buttons, + SizedBox( + height: belowButton, + child: Padding(padding: const EdgeInsets.only(top: OmiSpacing.xs), child: legal), + ), + ], + ), + ); + } +} + +/// The onboarding action button in the hero steps: the same 48pt pill as the other steps' Continue, +/// coloured from [OnboardingHero.palette] so it inverts over the film. +class OnboardingHeroButton extends StatelessWidget { + const OnboardingHeroButton( + {super.key, required this.label, required this.onPressed, this.icon, this.outlined = false}); + + final String label; + final VoidCallback onPressed; + final Widget? icon; + final bool outlined; + + @override + Widget build(BuildContext context) { + final p = OnboardingHero.palette; + final style = OmiType.headline.copyWith(color: outlined ? p.textPrimary : p.onAccent); + final child = Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (icon != null) ...[ + IconTheme(data: IconThemeData(color: style.color, size: 20), child: icon!), + const SizedBox(width: OmiSpacing.xs), + ], + Flexible(child: Text(label, style: style, overflow: TextOverflow.ellipsis)), + ], + ); + return SizedBox( + height: 48, + child: outlined + ? OutlinedButton( + onPressed: onPressed, + style: OutlinedButton.styleFrom( + side: BorderSide(color: p.textPrimary.withValues(alpha: 0.4)), + shape: const RoundedRectangleBorder(borderRadius: OmiRadius.mdAll), + ), + child: child, + ) + : ElevatedButton( + onPressed: onPressed, + style: ElevatedButton.styleFrom( + backgroundColor: p.accent, + foregroundColor: p.onAccent, + elevation: 0, + shape: const RoundedRectangleBorder(borderRadius: OmiRadius.mdAll), + ), + child: child, + ), + ); + } +} diff --git a/app/lib/pages/onboarding/variants/yinka/widgets/onboarding_plain_step.dart b/app/lib/pages/onboarding/variants/yinka/widgets/onboarding_plain_step.dart new file mode 100644 index 00000000000..c6c304fcca3 --- /dev/null +++ b/app/lib/pages/onboarding/variants/yinka/widgets/onboarding_plain_step.dart @@ -0,0 +1,151 @@ +import 'package:flutter/material.dart'; + +import 'package:omi/ui/ui.dart'; +import 'package:omi/pages/onboarding/variants/yinka/widgets/onboarding_card.dart'; + +const _thumbShadow = BoxShadow( + color: Color(0x26000000), // omi-ux-allow: color-literal -- switch thumb shadow + blurRadius: 4, + offset: Offset(0, 2)); +const _bangStyle = TextStyle( + fontSize: 15, // omi-ux-allow: font-size-literal -- "!" sized to the switch thumb + fontWeight: FontWeight.w800, + height: 1); + +/// A first-run step without artwork: the title (and an optional line under it) at the top, the +/// step's [body] below, and the step's [action] pinned to the bottom, above the keyboard when it is +/// up. The host's [MediaQuery] top padding already clears the progress line and back button. +/// +/// With [centered], the title stays at the top and the body sits in the middle of the space between +/// the title and the action, staying centred as the keyboard opens and closes. +class OnboardingPlainStep extends StatelessWidget { + const OnboardingPlainStep({ + super.key, + required this.title, + this.subtitle, + required this.body, + this.action, + this.centered = false, + }); + + final String title; + final String? subtitle; + final Widget body; + final Widget? action; + final bool centered; + + @override + Widget build(BuildContext context) { + final heading = [ + Semantics(header: true, child: Text(title, style: OmiType.title1, textAlign: TextAlign.center)), + if (subtitle != null) ...[ + const SizedBox(height: OmiSpacing.xs), + Text(subtitle!, style: OmiType.subhead.copyWith(color: OmiColors.textSecondary), textAlign: TextAlign.center), + ], + ]; + final Widget content = Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ...heading, + const SizedBox(height: OmiSpacing.xxl), + Expanded( + child: centered + // The body alone sits in the middle of the space between the title and the action. + ? LayoutBuilder( + builder: (context, constraints) => SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints(minHeight: constraints.maxHeight), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [body], + ), + ), + ), + ) + : SingleChildScrollView(child: body), + ), + ], + ); + return SafeArea( + child: Padding( + padding: const EdgeInsets.fromLTRB(OmiSpacing.xl, OmiSpacing.lg, OmiSpacing.xl, kOnboardingButtonBottomGap), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded(child: content), + if (action != null) ...[const SizedBox(height: OmiSpacing.md), action!], + ], + ), + ), + ); + } +} + +/// The toggle on a first-run permission row. The state's colour lives only inside the switch: +/// grey track when unanswered, the accent when allowed, orange (or [deniedColor]) when the reader +/// said no, with a "!" on the thumb so the state does not rely on colour alone. +class OnboardingPermissionToggle extends StatelessWidget { + const OnboardingPermissionToggle({ + super.key, + required this.on, + required this.denied, + this.busy = false, + this.deniedColor, + }); + + final bool on; + final bool denied; + final bool busy; + final Color? deniedColor; + + static const double _width = 51; + static const double _height = 31; + static const double _thumb = 27; + + @override + Widget build(BuildContext context) { + final motion = OmiMotion.of(context); + final denyColor = deniedColor ?? OmiColors.warning; + final track = on + ? OmiColors.accent + : denied + ? denyColor + : OmiColors.surface3; + // On the accent track the thumb takes the accent's contrast colour (white on black, black on white). + final thumb = + on ? OmiColors.onAccent : const Color(0xFFFFFFFF); // omi-ux-allow: color-literal -- white thumb, as iOS + return AnimatedContainer( + duration: motion.standard, + curve: Curves.easeOut, + width: _width, + height: _height, + padding: const EdgeInsets.all(2), + decoration: BoxDecoration(color: track, borderRadius: OmiRadius.pillAll), + child: AnimatedAlign( + duration: motion.standard, + curve: Curves.easeOut, + alignment: on ? Alignment.centerRight : Alignment.centerLeft, + child: Container( + width: _thumb, + height: _thumb, + decoration: BoxDecoration( + color: thumb, + shape: BoxShape.circle, + boxShadow: const [_thumbShadow], + ), + child: Center( + child: busy + ? const SizedBox(width: 14, height: 14, child: OmiSpinner(size: OmiSpinnerSize.small)) + : denied && !on + ? Text( + '!', + style: _bangStyle.copyWith(color: denyColor), + ) + : null, + ), + ), + ), + ); + } +} diff --git a/app/lib/pages/onboarding/variants/yinka/widgets/onboarding_step_layout.dart b/app/lib/pages/onboarding/variants/yinka/widgets/onboarding_step_layout.dart new file mode 100644 index 00000000000..c6169324d2b --- /dev/null +++ b/app/lib/pages/onboarding/variants/yinka/widgets/onboarding_step_layout.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; + +import 'package:omi/ui/ui.dart'; + +/// Height of the navigation row (progress dots and back button) below the status bar: the dots' +/// top gap plus one tap target. +const double kOnboardingChromeHeight = OmiSpacing.md + kOmiMinTapTarget; + +/// The host owns space for navigation above steps that start at the top of the screen. +/// Bottom-card steps keep their navigation over the background artwork, but their content still +/// clears it: the step gets a [MediaQuery] whose top padding includes [kOnboardingChromeHeight], so +/// its own [SafeArea] starts below the navigation while backgrounds bleed to the top edge. +class OnboardingStepLayout extends StatelessWidget { + const OnboardingStepLayout({super.key, required this.child, required this.reserveHeader, this.progress, this.onBack}); + + final Widget child; + final bool reserveHeader; + final Widget? progress; + final VoidCallback? onBack; + + @override + Widget build(BuildContext context) { + final chrome = Stack( + children: [ + if (progress != null) + Padding( + // Same top inset as the back button so the progress line sits level with it; the bottom + // inset keeps the header row its full height, so content below does not move. + padding: const EdgeInsets.only(top: OmiSpacing.xxs, bottom: OmiSpacing.md - OmiSpacing.xxs), + child: progress!, + ), + if (onBack != null) + Padding( + padding: const EdgeInsets.only(left: OmiSpacing.xs, top: OmiSpacing.xxs), + child: Align( + alignment: Alignment.topLeft, + child: OmiBackButton(key: const Key('onboarding_back'), onPressed: onBack, color: OmiColors.textPrimary), + ), + ), + ], + ); + if (reserveHeader) { + return Column( + children: [ + SafeArea( + bottom: false, + child: SizedBox(height: kOnboardingChromeHeight, child: chrome), + ), + Expanded( + child: MediaQuery.removePadding(context: context, removeTop: true, child: child), + ), + ], + ); + } + if (progress == null && onBack == null) return child; + final media = MediaQuery.of(context); + return Stack( + children: [ + MediaQuery( + data: media.copyWith(padding: media.padding.copyWith(top: media.padding.top + kOnboardingChromeHeight)), + child: child, + ), + SafeArea(child: chrome), + ], + ); + } +} diff --git a/app/lib/pages/onboarding/variants/yinka/yinka_onboarding_flow.dart b/app/lib/pages/onboarding/variants/yinka/yinka_onboarding_flow.dart new file mode 100644 index 00000000000..007bc80287b --- /dev/null +++ b/app/lib/pages/onboarding/variants/yinka/yinka_onboarding_flow.dart @@ -0,0 +1,318 @@ +import 'dart:async'; + +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import 'package:omi/backend/preferences.dart'; +import 'package:omi/backend/schema/bt_device/bt_device.dart'; +import 'package:omi/pages/onboarding/interactive_device_onboarding/interactive_device_onboarding_wrapper.dart'; +import 'package:omi/pages/onboarding/variants/yinka/activation/activation_flow.dart'; +import 'package:omi/pages/onboarding/variants/yinka/activation/onboarding_plans_sheet.dart'; +import 'package:omi/pages/onboarding/variants/yinka/connect_omi_step.dart'; +import 'package:omi/pages/onboarding/variants/yinka/name_widget.dart'; +import 'package:omi/pages/onboarding/variants/yinka/permissions_widget.dart'; +import 'package:omi/pages/onboarding/variants/yinka/primary_language_widget.dart'; +import 'package:omi/pages/onboarding/variants/yinka/widgets/onboarding_step_layout.dart'; +import 'package:omi/pages/paywall/paywall_router.dart'; +import 'package:omi/providers/device_provider.dart'; +import 'package:omi/providers/onboarding_provider.dart'; +import 'package:omi/providers/usage_provider.dart'; +import 'package:omi/ui/ui.dart'; +import 'package:omi/utils/analytics/intercom.dart'; +import 'package:omi/utils/device.dart'; +import 'package:omi/utils/l10n_extensions.dart'; +import 'package:omi/utils/other/temp.dart'; +import 'package:omi/utils/platform/platform_manager.dart'; + +/// Yinka's first-run onboarding (PR #21445) as the `yinka` arm of `onboarding-variant`: Name → +/// Permissions → Language → Connect Omi → the activation (a first real conversation, its summary +/// and to-dos, the paywall, the rating ask). Runs after sign-in and consent, like the web +/// onboarding page; the native wrapper still owns completion through [onComplete], which saves, +/// routes home and answers false when the save failed (the activation then stays, so the reader +/// can tap again). +class YinkaOnboardingFlow extends StatefulWidget { + const YinkaOnboardingFlow({super.key, required this.onComplete}); + + /// Saves onboarding as done and opens Home. [paywallShown] says the activation already offered a + /// plan, so the end-of-onboarding paywall placement must not show a second one. + final Future Function({required bool paywallShown}) onComplete; + + @override + State createState() => _YinkaOnboardingFlowState(); +} + +class _YinkaOnboardingFlowState extends State { + static const int kNamePage = 0; + static const int kPermissionsPage = 1; + static const int kPrimaryLanguagePage = 2; + static const int kConnectOmiPage = 3; + static const int kActivationPage = 4; + + /// The steps the progress line counts, in order. The activation is a moment, not a form: it is + /// counted (so the line reads 4 of 5 on Connect Omi) but drawn without a line. + static const List kProgressSteps = [ + kNamePage, + kPermissionsPage, + kPrimaryLanguagePage, + kConnectOmiPage, + kActivationPage + ]; + + late int _index; + bool _forward = true; + bool _paywallShown = false; + OnboardingDeviceChoice? _deviceChoice; + + /// Per-account key so a different account signing in on this phone starts at the Name step. + static String resumeKey(String uid) => 'onboarding/yinka/resumeStep/$uid'; + + @override + void initState() { + super.initState(); + final saved = SharedPreferencesUtil().getInt(resumeKey(SharedPreferencesUtil().uid), defaultValue: kNamePage); + // The activation listens afresh: a reader killed there comes back to Connect Omi. + _index = saved >= kNamePage && saved <= kConnectOmiPage ? saved : kNamePage; + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; + // Start looking for the reader's Omi now, so "Connect your Omi" can show it at once. + unawaited(context.read().startQuietDeviceScan()); + }); + } + + void _show(int index) { + if (!mounted) return; + setState(() { + _forward = index >= _index; + _index = index; + }); + if (index <= kConnectOmiPage) SharedPreferencesUtil().saveInt(resumeKey(SharedPreferencesUtil().uid), index); + } + + void _goNext() => _show(_index + 1); + + /// The step before the current one, or null when there is nothing to go back to. Connect Omi is + /// the threshold into the experience: no way back into the setup questions (language and + /// permissions can be changed later in Settings); the activation is not a form either. + int? get _previousStep { + if (_index == kConnectOmiPage || _index == kActivationPage) return null; + final position = kProgressSteps.indexOf(_index); + if (position <= 0) return null; + return kProgressSteps[position - 1]; + } + + void _goBack() { + final previous = _previousStep; + if (previous == null) return; + OmiHaptics.selection(); + _show(previous); + } + + Future _leaveConnectOmi(OnboardingDeviceChoice choice) async { + _deviceChoice = choice; + PlatformManager.instance.analytics.onboardingStepCompleted('Connect Omi'); + // A reader who just connected an Omi learns its button now, before trying it; the tutorial + // marks itself done, so Home won't show it again. + if (choice == OnboardingDeviceChoice.connected && _needsDeviceTutorial()) { + await routeToPage(context, const InteractiveDeviceOnboardingWrapper()); + if (!mounted) return; + } + PlatformManager.instance.analytics.track('Onboarding Ending Shown', properties: {'ending': 'activation'}); + _show(kActivationPage); + } + + bool _needsDeviceTutorial() { + if (SharedPreferencesUtil().deviceOnboardingCompleted) return false; + final devices = context.read(); + final device = devices.connectedDevice; + if (device == null || device.type != DeviceType.omi) return false; + return DeviceUtils.isOmiCv1(modelNumber: devices.pairedDevice?.modelNumber, deviceName: device.name); + } + + /// The paywall inside the activation, between the celebration and the rating ask: the paywall + /// experiment's arm when the reader is in one; control keeps the onboarding sheet. + Future _showPaywall() async { + _paywallShown = true; + unawaited(context.read().fetchSubscription()); + if (await PaywallRouter.openVariant(context, source: 'onboarding')) return; + if (!mounted) return; + await showOmiSheet(context: context, padding: EdgeInsets.zero, builder: (_) => const OnboardingPlansSheet()); + } + + Future _finish() async { + PlatformManager.instance.analytics.onboardingStepCompleted('Activation'); + final saved = await widget.onComplete(paywallShown: _paywallShown); + if (saved) SharedPreferencesUtil().remove(resumeKey(SharedPreferencesUtil().uid)); + return saved; + } + + Widget _page(int index) { + switch (index) { + case kNamePage: + return NameWidget( + goNext: () { + _goNext(); + IntercomManager.instance.updateUser( + FirebaseAuth.instance.currentUser!.email, + FirebaseAuth.instance.currentUser!.displayName, + FirebaseAuth.instance.currentUser!.uid, + ); + PlatformManager.instance.analytics.onboardingStepCompleted('Name'); + }, + ); + case kPermissionsPage: + return PermissionsWidget( + goNext: () { + _goNext(); + PlatformManager.instance.analytics.onboardingStepCompleted('Permissions'); + }, + ); + case kPrimaryLanguagePage: + return PrimaryLanguageWidget( + goNext: () { + _goNext(); + PlatformManager.instance.analytics.onboardingStepCompleted('Primary Language'); + }, + ); + case kConnectOmiPage: + return ConnectOmiStep(onDone: _leaveConnectOmi); + default: + return ActivationFlow( + hasDevice: _deviceChoice == OnboardingDeviceChoice.connected || context.read().isConnected, + showPaywall: _showPaywall, + onFinished: _finish, + ); + } + } + + @override + Widget build(BuildContext context) { + final previous = _previousStep; + final onActivation = _index == kActivationPage; + return PopScope( + // The flow owns onboarding: system back and the iOS swipe step back one step, never out. + canPop: false, + onPopInvokedWithResult: (didPop, _) { + if (!didPop) _goBack(); + }, + child: GestureDetector( + onTap: () => FocusScope.of(context).unfocus(), + child: Scaffold( + backgroundColor: OmiColors.surface0, + body: OnboardingStepLayout( + reserveHeader: _index == kConnectOmiPage, + onBack: previous == null ? null : _goBack, + // The activation is a moment, not a form: no progress line over it. + progress: onActivation + ? null + : OnboardingProgressLine(current: kProgressSteps.indexOf(_index), total: kProgressSteps.length), + child: _StepTransition( + index: _index, + forward: _forward, + // Arriving at Connect Omi is a moment, not another form: a plain cross-fade lets the + // step's own entrance carry it. + fadeOnly: _index == kConnectOmiPage, + child: _page(_index), + ), + ), + ), + ), + ); + } +} + +/// Moves between steps: the new step slides in from the side the reader is heading and fades up, +/// the old one drifts the other way and fades out. Instant under Reduce Motion. +class _StepTransition extends StatelessWidget { + const _StepTransition({required this.index, required this.forward, required this.child, this.fadeOnly = false}); + + final int index; + final bool forward; + final bool fadeOnly; + final Widget child; + + static const double _shift = 0.08; + + @override + Widget build(BuildContext context) { + final reduce = OmiMotion.of(context).emphasized == Duration.zero; + return AnimatedSwitcher( + duration: reduce ? Duration.zero : const Duration(milliseconds: 420), + reverseDuration: reduce ? Duration.zero : const Duration(milliseconds: 260), + switchInCurve: Curves.easeOutCubic, + switchOutCurve: Curves.easeInCubic, + layoutBuilder: (current, previous) => Stack( + fit: StackFit.expand, + children: [...previous, if (current != null) current], + ), + transitionBuilder: (child, animation) { + final incoming = child.key == ValueKey(index); + // An outgoing child runs its animation backwards, ending at its begin offset. + final from = fadeOnly ? 0.0 : ((incoming == forward) ? _shift : -_shift); + return IgnorePointer( + ignoring: !incoming, + child: FadeTransition( + opacity: animation, + child: SlideTransition( + position: Tween(begin: Offset(from, 0), end: Offset.zero).animate(animation), + child: child, + ), + ), + ); + }, + child: KeyedSubtree(key: ValueKey(index), child: child), + ); + } +} + +/// The first-run progress: one thin line that fills as the reader moves through the real steps, +/// with a spoken "Step N of M". +class OnboardingProgressLine extends StatelessWidget { + const OnboardingProgressLine({super.key, required this.current, required this.total}); + + /// Zero-based position of the current step. + final int current; + final int total; + + @override + Widget build(BuildContext context) { + final motion = OmiMotion.of(context); + final fraction = total <= 0 ? 0.0 : ((current + 1) / total).clamp(0.0, 1.0); + return Semantics( + label: context.l10n.onboardingStepOf(current + 1, total), + excludeSemantics: true, + child: SizedBox( + height: kOmiMinTapTarget, + child: Center( + child: Padding( + // Clear of the back button on the left, balanced on the right. + padding: const EdgeInsets.symmetric(horizontal: kOmiMinTapTarget + OmiSpacing.md), + child: ClipRRect( + borderRadius: OmiRadius.pillAll, + child: SizedBox( + width: double.infinity, + height: 3, + child: Stack( + children: [ + Positioned.fill(child: ColoredBox(color: OmiColors.surface2)), + TweenAnimationBuilder( + tween: Tween(end: fraction), + duration: motion.standard, + curve: Curves.easeOutCubic, + builder: (context, value, _) => FractionallySizedBox( + widthFactor: value, + heightFactor: 1, + alignment: Alignment.centerLeft, + child: ColoredBox(color: OmiColors.textPrimary), + ), + ), + ], + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/app/lib/pages/onboarding/wrapper.dart b/app/lib/pages/onboarding/wrapper.dart index f1f280f0f65..68db725cf33 100644 --- a/app/lib/pages/onboarding/wrapper.dart +++ b/app/lib/pages/onboarding/wrapper.dart @@ -20,9 +20,10 @@ import 'package:omi/pages/onboarding/permissions/permissions_checker.dart'; import 'package:omi/pages/onboarding/permissions/permissions_widget.dart'; import 'package:omi/pages/onboarding/primary_language/primary_language_widget.dart'; import 'package:omi/pages/onboarding/complete_screen.dart'; +import 'package:omi/pages/onboarding/variants/yinka/yinka_onboarding_flow.dart'; import 'package:omi/pages/onboarding/web_onboarding_page.dart'; import 'package:omi/pages/paywall/paywall_router.dart'; -import 'package:omi/services/experiments/onboarding_webview_gate.dart'; +import 'package:omi/services/experiments/onboarding_variant_gate.dart'; import 'package:omi/services/experiments/paywall_experiment.dart'; import 'package:omi/pages/onboarding/setup_page.dart'; import 'package:omi/pages/onboarding/speech_profile_widget.dart'; @@ -201,7 +202,7 @@ class _OnboardingWrapperState extends State with TickerProvid // account on a fresh install — sees the consent screen at least // once before any AI processing begins. if (!SharedPreferencesUtil().aiConsentGiven) { - OnboardingWebviewGate.warm(); + OnboardingVariantGate.warm(); _controller!.animateTo(kAiConsentPage); } else if (SharedPreferencesUtil().onboardingCompleted && !_rerunForTesting) { await _routeWithPermissionsCheck(context); @@ -245,17 +246,19 @@ class _OnboardingWrapperState extends State with TickerProvid } } - /// Marks first-run onboarding complete and routes home. Shared by the native completion screen - /// and the web onboarding page, so only the wrapper ever owns completion. - Future _finishOnboarding() async { - if (_finishing) return; + /// Marks first-run onboarding complete and routes home. Shared by the native completion screen, + /// the web onboarding page and Yinka's flow, so only the wrapper ever owns completion. False when + /// the save failed (the step stays, so the reader can tap again). [paywallShown] skips the + /// end-of-onboarding paywall placement when the arm already offered a plan. + Future _finishOnboarding({bool paywallShown = false}) async { + if (_finishing) return false; _finishing = true; final saved = await OnboardingSyncRuntime.enqueue(completed: true); _finishing = false; - if (!mounted || !context.mounted) return; + if (!mounted || !context.mounted) return false; if (!saved) { OmiFeedback.error(context, context.l10n.somethingWentWrong); - return; + return false; } SharedPreferencesUtil().onboardingCompleted = true; SharedPreferencesUtil().permissionsCompleted = true; @@ -264,9 +267,10 @@ class _OnboardingWrapperState extends State with TickerProvid _completeOnboardingTelemetry(); PlatformManager.instance.analytics.onboardingCompleted(); PaintingBinding.instance.imageCache.clear(); - await _maybeShowOnboardingPaywall(); - if (!mounted) return; + if (!paywallShown) await _maybeShowOnboardingPaywall(); + if (!mounted) return true; routeToPage(context, const HomePageWrapper(), replace: true); + return true; } /// The experiment's end-of-onboarding placement: only arms whose payload asks for it, never @@ -277,21 +281,31 @@ class _OnboardingWrapperState extends State with TickerProvid await PaywallRouter.openVariant(context, source: 'onboarding', config: config); } - /// Enters the steps after sign-in and consent: the web onboarding page when its flag names one, - /// otherwise the native step the reader left off at. A page that falls back lands on that same - /// native step. + /// Enters the steps after sign-in and consent: the arm of `onboarding-variant` for this reader + /// (Reid's web page, Yinka's flow) or the native step the reader left off at. A web page that + /// falls back lands on that same native step. Future _enterSteps() async { - final url = await OnboardingWebviewGate.resolve(); + final arm = await OnboardingVariantGate.resolve(); if (!mounted) return; - if (url != null) { - const TypedEvents().emit(const OnboardingWebviewShown()); - final result = await routeToPage(context, WebOnboardingPage(url: url)); - if (!mounted) return; - if (result == WebOnboardingResult.completed) { - const TypedEvents().emit(const OnboardingWebviewCompleted()); - await _finishOnboarding(); + PlatformManager.instance.analytics.track('Onboarding Variant Shown', properties: { + 'variant': arm.variant.wireName, + '\$feature/${OnboardingVariantGate.enabledFlag}': arm.variant.wireName, + }); + switch (arm.variant) { + case OnboardingVariant.yinka: + await routeToPage(context, YinkaOnboardingFlow(onComplete: _finishOnboarding)); return; - } + case OnboardingVariant.reid: + const TypedEvents().emit(const OnboardingWebviewShown()); + final result = await routeToPage(context, WebOnboardingPage(url: arm.webUrl!)); + if (!mounted) return; + if (result == WebOnboardingResult.completed) { + const TypedEvents().emit(const OnboardingWebviewCompleted()); + await _finishOnboarding(); + return; + } + case OnboardingVariant.control: + break; } _controller!.animateTo(_resumeStep()); } @@ -431,7 +445,7 @@ class _OnboardingWrapperState extends State with TickerProvid /// "Use a Different Account" on the consent step: sign out and start again from the beginning. Future _useDifferentAccount() async { - OnboardingWebviewGate.invalidate(); + OnboardingVariantGate.invalidate(); _voiceController?.dispose(); _voiceController = null; final navigator = Navigator.of(context, rootNavigator: true); @@ -552,7 +566,7 @@ class _OnboardingWrapperState extends State with TickerProvid // state so a returning user signing in on a fresh install still // sees the consent screen before any AI processing begins. if (!SharedPreferencesUtil().aiConsentGiven) { - OnboardingWebviewGate.warm(); + OnboardingVariantGate.warm(); _controller!.animateTo(kAiConsentPage); } else if (SharedPreferencesUtil().onboardingCompleted) { await _routeWithPermissionsCheck(context); diff --git a/app/lib/pages/shop/omi_shop.dart b/app/lib/pages/shop/omi_shop.dart new file mode 100644 index 00000000000..05b07591c3f --- /dev/null +++ b/app/lib/pages/shop/omi_shop.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; + +import 'package:omi/pages/apps/widgets/omi_web_page.dart'; +import 'package:omi/utils/l10n_extensions.dart'; +import 'package:omi/utils/platform/platform_manager.dart'; + +/// The Omi store, tagged with where the reader came from so orders can be attributed. +Uri omiShopUrl(String source) => Uri.https('www.omi.me', '/', {'_ref': source}); + +/// Opens the Omi store inside the app (a clean in-app web page, not the browser), so buying the +/// device does not take the reader out of Omi. [source] names the entry point, e.g. +/// `onboarding_not_ordered`, and is sent as `_ref` and on the `Omi Shop Opened` event. Completes +/// when the reader closes the store. +Future openOmiShop(BuildContext context, {required String source}) async { + PlatformManager.instance.analytics.getOmiDeviceClicked(); + PlatformManager.instance.analytics.track('Omi Shop Opened', properties: {'source': source}); + final title = context.l10n.getOmiDevice; + // Presented as a sheet that slides up, with Close rather than Back: it is a detour, not a step. + await Navigator.of(context).push( + MaterialPageRoute( + fullscreenDialog: true, + builder: (_) => OmiWebPage(title: title, url: omiShopUrl(source), showClose: true), + ), + ); +} diff --git a/app/lib/providers/onboarding_provider.dart b/app/lib/providers/onboarding_provider.dart index eaa3bbf6887..d3836ab6cec 100644 --- a/app/lib/providers/onboarding_provider.dart +++ b/app/lib/providers/onboarding_provider.dart @@ -186,6 +186,40 @@ class OnboardingProvider extends BaseProvider with MessageNotifierMixin implemen notifyListeners(); } + /// Whether Omi may scan for and connect to Bluetooth devices, read without prompting. + Future readBluetoothPermission() async { + final granted = Platform.isIOS + ? await Permission.bluetooth.isGranted + : await Permission.bluetoothScan.isGranted && await Permission.bluetoothConnect.isGranted; + if (granted != hasBluetoothPermission) updateBluetoothPermission(granted); + return granted; + } + + /// Starts looking for nearby devices in the background, without prompts, when Bluetooth is + /// already allowed. "Connect your Omi" then shows the device the moment it opens. + Future startQuietDeviceScan() async { + if (_isDisposed || isConnected) return; + if (!await readBluetoothPermission()) return; + // Only when the radio is already on: an off adapter would raise the shared "Enable Bluetooth" + // guidance over an unrelated step. "Connect your Omi" handles that case itself. + try { + if (BluetoothReadiness.parse(await BleHostApi().getBluetoothState()) != BluetoothAdapterState.on) return; + } catch (_) { + return; + } + if (_isDisposed) return; + ServiceManager.instance().device.subscribe(this, this); + await deviceProvider?.initiateConnection('OnboardingEarlyScan'); + } + + /// Ends a search screen's session: stops scanning and clears "nothing found", so the next search + /// screen (onboarding's Connect Omi or the in-app pairing page, which share this provider) starts + /// fresh instead of opening on a stale "No Omi Found". + void endSearch() { + cancelActiveScan(); + enableInstructions = false; + } + Future askForNotificationPermissions() async { final attempt = ProductTelemetry.instance.start( ProductJourney.permission, diff --git a/app/lib/services/capture/capture_controller.dart b/app/lib/services/capture/capture_controller.dart index 6ea8042cc92..9f085c676f5 100644 --- a/app/lib/services/capture/capture_controller.dart +++ b/app/lib/services/capture/capture_controller.dart @@ -226,6 +226,17 @@ class CaptureController extends ChangeNotifier /// The conversation-capture recorder. Resolves an injected override first so /// deterministic scenarios can drive the real pipeline through the production /// decision path, then falls back to the shared service manager. + /// Receives a copy of the phone microphone's audio (16 kHz mono PCM16) as it arrives, for a caller + /// that needs the sound itself: onboarding enrolls the reader's voice from its first dictation. + void Function(List pcm)? phoneMicAudioTap; + + /// Receives each encoded audio packet from the pendant as it arrives (for a live level meter). + void Function(List packet)? deviceAudioTap; + + /// Set by a first-run step that explains a short or empty capture itself, so the generic + /// "talk for 30 seconds" notice stays away while it is on screen. + bool suppressShortCaptureNotice = false; + IMicRecorderService get _phoneMic => _phoneMicRecorderOverride ?? ServiceManager.instance().phoneMic; DateTime _now() => _nowOverride?.call() ?? DateTime.now(); @@ -496,6 +507,7 @@ class CaptureController extends ChangeNotifier await _phoneMic.start( onByteReceived: (bytes) { if (!_admitsCapture(revision)) return; + phoneMicAudioTap?.call(bytes); _recordingTelemetry.observeAudio(bytes.length); if (bytes.isNotEmpty) _livenessWatchdog.observeFrame(_now()); final frames = _activeSource?.processBytes(bytes) ?? []; @@ -2089,6 +2101,7 @@ class CaptureController extends ChangeNotifier if (_captureControllerDisposed || !_admitsCapture(revision) || _recordingDevice?.id != deviceId) return; final snapshot = List.from(value); if (snapshot.isEmpty || snapshot.length < 3) return; + deviceAudioTap?.call(snapshot); // Track bytes received from BLE _metrics.addBleBytes(snapshot.length); @@ -2830,6 +2843,7 @@ class CaptureController extends ChangeNotifier await _phoneMic.start( onByteReceived: (bytes) { if (!_admitsCapture(revision)) return; + phoneMicAudioTap?.call(bytes); _recordingTelemetry.observeAudio(bytes.length); if (bytes.isNotEmpty) _livenessWatchdog.observeFrame(_now()); // Process through AudioSource for frame splitting and sync key generation @@ -4040,7 +4054,7 @@ class CaptureController extends ChangeNotifier transcriptObserved: resolved?.transcriptObserved ?? false, reason: reason, ); - if (!showCopy) return; + if (!showCopy || suppressShortCaptureNotice) return; final ctx = globalNavigatorKey.currentContext; if (ctx != null) { AppSnackbar.showSnackbar( diff --git a/app/lib/services/experiments/onboarding_variant_gate.dart b/app/lib/services/experiments/onboarding_variant_gate.dart new file mode 100644 index 00000000000..4db4e03715e --- /dev/null +++ b/app/lib/services/experiments/onboarding_variant_gate.dart @@ -0,0 +1,207 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; + +import 'package:omi/utils/analytics/analytics_manager.dart'; + +/// The first-run onboarding arms of the `onboarding-variant` experiment. +enum OnboardingVariant { + /// The native steps exactly as shipped. + control('control'), + + /// Reid's device-first onboarding, rendered from a web page (OTA); the payload names it. + reid('reid'), + + /// Yinka's onboarding ending in a first conversation (PR #21445), native. + yinka('yinka'); + + const OnboardingVariant(this.wireName); + + /// The PostHog variant key. + final String wireName; + + static OnboardingVariant? fromWire(String? value) { + for (final variant in values) { + if (variant.wireName == value) return variant; + } + return null; + } +} + +/// One resolved arm: the variant and, for [OnboardingVariant.reid], the page to render. +class OnboardingArm { + const OnboardingArm(this.variant, {this.webUrl}); + + static const control = OnboardingArm(OnboardingVariant.control); + static const yinka = OnboardingArm(OnboardingVariant.yinka); + + final OnboardingVariant variant; + + /// Only for [OnboardingVariant.reid]: the https page. + final Uri? webUrl; + + bool get isControl => variant == OnboardingVariant.control; + + @override + bool operator ==(Object other) => other is OnboardingArm && other.variant == variant && other.webUrl == webUrl; + + @override + int get hashCode => Object.hash(variant, webUrl); + + @override + String toString() => 'OnboardingArm(${variant.wireName}${webUrl == null ? '' : ', $webUrl'})'; +} + +/// Gate for which first-run onboarding a reader gets after sign-in and consent: the native steps +/// (control), Reid's web page (`reid`) or Yinka's native flow (`yinka`). +/// +/// Evaluated on the client from the PostHog multivariate flag `onboarding-variant`; the `reid` +/// payload names the page: `{"url": "https://…"}`. Fail-closed: no analytics identity, a slow read, +/// a disabled flag, an unknown variant or a `reid` payload without an https URL all mean control. +abstract final class OnboardingVariantGate { + /// PostHog flag key (project 302298). Registered in `config/feature-flags.yaml`. + static const enabledFlag = 'onboarding-variant'; + + /// Debug-build override: `--dart-define=OMI_ONBOARDING_VARIANT=reid|yinka` + /// (`reid` also needs `OMI_ONBOARDING_WEBVIEW_URL=https://…`). + static const _debugVariant = String.fromEnvironment('OMI_ONBOARDING_VARIANT'); + static const _debugUrl = String.fromEnvironment('OMI_ONBOARDING_WEBVIEW_URL'); + + static OnboardingArm? get debugOverride { + if (!kDebugMode) return null; + return switch (OnboardingVariant.fromWire(_debugVariant)) { + OnboardingVariant.yinka => OnboardingArm.yinka, + OnboardingVariant.reid => parse(variant: 'reid', payload: {'url': _debugUrl}), + _ => null, + }; + } + + /// Parses one flag read (the variant and, for `reid`, a decoded map or JSON payload) into an arm. + /// Anything unexpected collapses to control. + static OnboardingArm parse({String? variant, Object? payload}) { + switch (OnboardingVariant.fromWire(variant?.trim())) { + case OnboardingVariant.yinka: + return OnboardingArm.yinka; + case OnboardingVariant.reid: + Object? value = payload; + if (value is String) { + try { + value = jsonDecode(value); + } on FormatException { + value = null; + } + } + final raw = value is Map ? value['url'] : null; + final url = raw is String ? Uri.tryParse(raw.trim()) : null; + if (url == null || url.scheme != 'https' || url.host.isEmpty) return OnboardingArm.control; + return OnboardingArm(OnboardingVariant.reid, webUrl: url); + case OnboardingVariant.control: + case null: + return OnboardingArm.control; + } + } + + /// Right after sign-in the analytics identity is still settling and the SDK's cached flags belong + /// to the anonymous id, where a per-user rollout never matches; so a read waits (at most + /// [identityWait]) for the identity, re-fetches flags for it, and only then reads. + static Future? _warmed; + static int _warmedEpoch = -1; + + /// Starts resolving early (while the reader is still on the consent screen) with a long identity + /// wait, so [resolve] at the tap usually finds the answer ready instead of starting the clock. + /// The warm-up is fenced to the analytics identity epoch it started in: an account change + /// ("use a different account") discards it, so one account never inherits another's decision. + static void warm({ + Future Function(String key)? readVariant, + Future Function(String key)? readPayload, + String? Function()? settledIdentity, + Future Function()? reload, + int Function()? identityEpoch, + Duration identityWait = const Duration(seconds: 20), + }) { + final epoch = (identityEpoch ?? _currentEpoch)(); + if (_warmed != null && _warmedEpoch == epoch) return; + _warmedEpoch = epoch; + _warmed = _resolve( + readVariant: readVariant, + readPayload: readPayload, + settledIdentity: settledIdentity, + reload: reload, + identityEpoch: identityEpoch, + identityWait: identityWait, + ); + } + + /// Drops a pending warm-up (the account is changing). + static void invalidate() { + _warmed = null; + _warmedEpoch = -1; + } + + static int _currentEpoch() => AnalyticsManager.identityChanges.value; + + @visibleForTesting + static void resetForTesting() => invalidate(); + + /// The arm to show. [readVariant] and [readPayload] default to the analytics manager's flag reads + /// and are injectable for tests. + static Future resolve({ + Future Function(String key)? readVariant, + Future Function(String key)? readPayload, + String? Function()? settledIdentity, + Future Function()? reload, + int Function()? identityEpoch, + Duration identityWait = const Duration(seconds: 3), + }) async { + final epoch = (identityEpoch ?? _currentEpoch)(); + final warmed = _warmed; + final warmedEpoch = _warmedEpoch; + invalidate(); + if (warmed != null && warmedEpoch == epoch) { + // Give a warm-up that is still waiting the same bounded patience as a cold read. + return warmed.timeout(identityWait + const Duration(seconds: 2), onTimeout: () => OnboardingArm.control); + } + return _resolve( + readVariant: readVariant, + readPayload: readPayload, + settledIdentity: settledIdentity, + reload: reload, + identityEpoch: identityEpoch, + identityWait: identityWait, + ); + } + + static Future _resolve({ + Future Function(String key)? readVariant, + Future Function(String key)? readPayload, + String? Function()? settledIdentity, + Future Function()? reload, + int Function()? identityEpoch, + required Duration identityWait, + }) async { + final override = debugOverride; + if (override != null) return override; + try { + final epoch = (identityEpoch ?? _currentEpoch)(); + final identity = settledIdentity ?? () => AnalyticsManager().settledDistinctId; + final deadline = DateTime.now().add(identityWait); + while (identity() == null && DateTime.now().isBefore(deadline)) { + await Future.delayed(const Duration(milliseconds: 100)); + } + if (identity() == null) return OnboardingArm.control; + // Stale flags must not decide: a reload that fails or times out is control. + if (!await (reload ?? AnalyticsManager().reloadFeatureFlags)()) return OnboardingArm.control; + final variant = await (readVariant ?? AnalyticsManager().getFeatureFlagVariant)(enabledFlag); + final arm = OnboardingVariant.fromWire(variant?.trim()); + if (arm == null || arm == OnboardingVariant.control) return OnboardingArm.control; + final payload = arm == OnboardingVariant.reid + ? await (readPayload ?? AnalyticsManager().getFeatureFlagPayload)(enabledFlag) + : null; + // The account changed while this was in flight: the answer belongs to someone else. + if ((identityEpoch ?? _currentEpoch)() != epoch) return OnboardingArm.control; + return parse(variant: variant, payload: payload); + } catch (_) { + return OnboardingArm.control; + } + } +} diff --git a/app/lib/services/experiments/onboarding_webview_gate.dart b/app/lib/services/experiments/onboarding_webview_gate.dart deleted file mode 100644 index 5fee7a9411f..00000000000 --- a/app/lib/services/experiments/onboarding_webview_gate.dart +++ /dev/null @@ -1,139 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/foundation.dart'; - -import 'package:omi/utils/analytics/analytics_manager.dart'; - -/// Gate for rendering first-run onboarding from a web page instead of the native steps, so the -/// flow can change over the air. Off (the default) leaves the native onboarding exactly as it is. -/// -/// Evaluated on the client from the PostHog flag `onboarding-webview`; the payload names the page: -/// `{"url": "https://…"}`. Fail-closed: no analytics identity, a slow read, a disabled flag or a -/// payload without an https URL all mean native onboarding. -abstract final class OnboardingWebviewGate { - /// PostHog flag key (project 302298). Registered in `config/feature-flags.yaml`. - static const enabledFlag = 'onboarding-webview'; - - /// Debug-build override: `--dart-define=OMI_ONBOARDING_WEBVIEW_URL=https://…`. - static const _debugUrl = String.fromEnvironment('OMI_ONBOARDING_WEBVIEW_URL'); - - static bool get debugOverrideActive => kDebugMode && _debugUrl.isNotEmpty; - - /// Parses a flag payload (a decoded map or a JSON string) into the page URL, or null. - static Uri? parse({required bool enabled, Object? payload}) { - if (!enabled) return null; - Object? value = payload; - if (value is String) { - try { - value = jsonDecode(value); - } on FormatException { - value = null; - } - } - final raw = value is Map ? value['url'] : null; - final url = raw is String ? Uri.tryParse(raw.trim()) : null; - if (url == null || url.scheme != 'https' || url.host.isEmpty) return null; - return url; - } - - /// The page to render, or null for native onboarding. [readFlag] and [readPayload] default to - /// the analytics manager's flag reads and are injectable for tests. - /// - /// Right after sign-in the analytics identity is still settling and the SDK's cached flags belong - /// to the anonymous id, where a per-user rollout never matches; so this waits (at most - /// [identityWait]) for the identity, re-fetches flags for it, and only then reads. - static Future? _warmed; - static int _warmedEpoch = -1; - - /// Starts resolving early (while the reader is still on the consent screen) with a long identity - /// wait, so [resolve] at the tap usually finds the answer ready instead of starting the clock. - /// The warm-up is fenced to the analytics identity epoch it started in: an account change - /// ("use a different account") discards it, so one account never inherits another's decision. - static void warm({ - Future Function(String key)? readFlag, - Future Function(String key)? readPayload, - String? Function()? settledIdentity, - Future Function()? reload, - int Function()? identityEpoch, - Duration identityWait = const Duration(seconds: 20), - }) { - final epoch = (identityEpoch ?? _currentEpoch)(); - if (_warmed != null && _warmedEpoch == epoch) return; - _warmedEpoch = epoch; - _warmed = _resolve( - readFlag: readFlag, - readPayload: readPayload, - settledIdentity: settledIdentity, - reload: reload, - identityEpoch: identityEpoch, - identityWait: identityWait, - ); - } - - /// Drops a pending warm-up (the account is changing). - static void invalidate() { - _warmed = null; - _warmedEpoch = -1; - } - - static int _currentEpoch() => AnalyticsManager.identityChanges.value; - - @visibleForTesting - static void resetForTesting() => invalidate(); - - static Future resolve({ - Future Function(String key)? readFlag, - Future Function(String key)? readPayload, - String? Function()? settledIdentity, - Future Function()? reload, - int Function()? identityEpoch, - Duration identityWait = const Duration(seconds: 3), - }) async { - final epoch = (identityEpoch ?? _currentEpoch)(); - final warmed = _warmed; - final warmedEpoch = _warmedEpoch; - invalidate(); - if (warmed != null && warmedEpoch == epoch) { - // Give a warm-up that is still waiting the same bounded patience as a cold read. - return warmed.timeout(identityWait + const Duration(seconds: 2), onTimeout: () => null); - } - return _resolve( - readFlag: readFlag, - readPayload: readPayload, - settledIdentity: settledIdentity, - reload: reload, - identityEpoch: identityEpoch, - identityWait: identityWait, - ); - } - - static Future _resolve({ - Future Function(String key)? readFlag, - Future Function(String key)? readPayload, - String? Function()? settledIdentity, - Future Function()? reload, - int Function()? identityEpoch, - required Duration identityWait, - }) async { - if (debugOverrideActive) return parse(enabled: true, payload: {'url': _debugUrl}); - try { - final epoch = (identityEpoch ?? _currentEpoch)(); - final identity = settledIdentity ?? () => AnalyticsManager().settledDistinctId; - final deadline = DateTime.now().add(identityWait); - while (identity() == null && DateTime.now().isBefore(deadline)) { - await Future.delayed(const Duration(milliseconds: 100)); - } - if (identity() == null) return null; - // Stale flags must not decide: a reload that fails or times out is native onboarding. - if (!await (reload ?? AnalyticsManager().reloadFeatureFlags)()) return null; - final enabled = await (readFlag ?? AnalyticsManager().isFeatureEnabled)(enabledFlag); - if (!enabled) return null; - final payload = await (readPayload ?? AnalyticsManager().getFeatureFlagPayload)(enabledFlag); - // The account changed while this was in flight: the answer belongs to someone else. - if ((identityEpoch ?? _currentEpoch)() != epoch) return null; - return parse(enabled: true, payload: payload); - } catch (_) { - return null; - } - } -} diff --git a/app/lib/widgets/scanning_ripple.dart b/app/lib/widgets/scanning_ripple.dart index 3e10282e88a..b0a471d82f4 100644 --- a/app/lib/widgets/scanning_ripple.dart +++ b/app/lib/widgets/scanning_ripple.dart @@ -4,7 +4,10 @@ class ScanningRippleWidget extends StatefulWidget { final bool isScanning; final double size; - const ScanningRippleWidget({super.key, required this.isScanning, this.size = 300}); + /// Ring colour; white by default (the dark pairing page). Light screens pass the text colour. + final Color color; + + const ScanningRippleWidget({super.key, required this.isScanning, this.size = 300, this.color = Colors.white}); @override State createState() => _ScanningRippleWidgetState(); @@ -44,7 +47,7 @@ class _ScanningRippleWidgetState extends State with Single builder: (context, child) { return CustomPaint( size: Size(widget.size, widget.size), - painter: _RipplePainter(progress: _controller.value, color: Colors.white), + painter: _RipplePainter(progress: _controller.value, color: widget.color), ); }, ), diff --git a/app/test/unit/onboarding_variant_gate_test.dart b/app/test/unit/onboarding_variant_gate_test.dart new file mode 100644 index 00000000000..83f648150b2 --- /dev/null +++ b/app/test/unit/onboarding_variant_gate_test.dart @@ -0,0 +1,212 @@ +import 'dart:async'; + +import 'package:flutter_test/flutter_test.dart'; + +import 'package:omi/services/experiments/onboarding_variant_gate.dart'; + +// The onboarding variant gate is fail-closed: off, slow, erroring, an unknown variant, or a reid +// payload without an https URL all mean the native onboarding exactly as before. + +void main() { + setUp(OnboardingVariantGate.resetForTesting); + + test('flag key is the registered PostHog key', () { + expect(OnboardingVariantGate.enabledFlag, 'onboarding-variant'); + expect(OnboardingVariantGate.debugOverride, isNull); + }); + + test('parse: yinka is native, reid needs an https URL from a map or a JSON string', () { + expect(OnboardingVariantGate.parse(variant: 'yinka'), OnboardingArm.yinka); + expect(OnboardingVariantGate.parse(variant: ' yinka ', payload: 'ignored'), OnboardingArm.yinka); + expect(OnboardingVariantGate.parse(variant: 'reid', payload: {'url': 'https://x.example/onboarding'}).variant, + OnboardingVariant.reid); + expect(OnboardingVariantGate.parse(variant: 'reid', payload: {'url': 'https://x.example/onboarding'}).webUrl, + Uri.parse('https://x.example/onboarding')); + expect(OnboardingVariantGate.parse(variant: 'reid', payload: '{"url":"https://x.example/o"}').webUrl, + Uri.parse('https://x.example/o')); + expect(OnboardingVariantGate.parse(variant: 'reid', payload: {'url': 'http://x.example'}), OnboardingArm.control); + expect(OnboardingVariantGate.parse(variant: 'reid', payload: {}), OnboardingArm.control); + expect(OnboardingVariantGate.parse(variant: 'reid', payload: 'garbage'), OnboardingArm.control); + expect( + OnboardingVariantGate.parse(variant: 'control', payload: {'url': 'https://x.example'}), OnboardingArm.control); + expect(OnboardingVariantGate.parse(variant: 'something-else'), OnboardingArm.control); + expect(OnboardingVariantGate.parse(variant: null), OnboardingArm.control); + }); + + test('control or an unread flag -> native, without reading the payload', () async { + var payloadReads = 0; + for (final variant in [null, 'control', 'unknown']) { + final arm = await OnboardingVariantGate.resolve( + settledIdentity: () => 'u', + reload: () async => true, + readVariant: (_) async => variant, + readPayload: (_) async { + payloadReads++; + return {'url': 'https://x.example'}; + }, + ); + expect(arm, OnboardingArm.control, reason: 'variant $variant'); + } + expect(payloadReads, 0); + }); + + test('yinka -> the native flow, without reading the payload', () async { + var payloadReads = 0; + final arm = await OnboardingVariantGate.resolve( + settledIdentity: () => 'u', + reload: () async => true, + readVariant: (_) async => 'yinka', + readPayload: (_) async { + payloadReads++; + return null; + }, + ); + expect(arm, OnboardingArm.yinka); + expect(payloadReads, 0); + }); + + test('reid -> the payload URL', () async { + final keys = []; + final arm = await OnboardingVariantGate.resolve( + settledIdentity: () => 'u', + reload: () async => true, + readVariant: (key) async { + keys.add(key); + return 'reid'; + }, + readPayload: (_) async => {'url': 'https://x.example/onboarding'}, + ); + expect(arm.variant, OnboardingVariant.reid); + expect(arm.webUrl, Uri.parse('https://x.example/onboarding')); + expect(keys, ['onboarding-variant']); + }); + + test('flag read error -> native (fail closed)', () async { + expect( + await OnboardingVariantGate.resolve( + settledIdentity: () => 'u', + reload: () async => true, + readVariant: (_) => Future.error(StateError('no sdk'))), + OnboardingArm.control); + }); + + test('waits for the identity to settle and reloads flags before the first read', () async { + final order = []; + var polls = 0; + final arm = await OnboardingVariantGate.resolve( + settledIdentity: () => ++polls < 4 ? null : 'u', + reload: () async { + order.add('reload'); + return true; + }, + readVariant: (_) async { + order.add('read'); + return 'yinka'; + }, + ); + expect(arm, OnboardingArm.yinka); + expect(order, ['reload', 'read']); + expect(polls, greaterThanOrEqualTo(4)); + }); + + test('no identity within the wait -> native, without reading anything', () async { + var reads = 0; + final arm = await OnboardingVariantGate.resolve( + settledIdentity: () => null, + identityWait: const Duration(milliseconds: 200), + reload: () async { + reads++; + return true; + }, + readVariant: (_) async { + reads++; + return 'yinka'; + }, + ); + expect(arm, OnboardingArm.control); + expect(reads, 0); + }); + + test('a warm-up started on the consent screen answers the tap without restarting the wait', () async { + var polls = 0; + var reads = 0; + OnboardingVariantGate.warm( + settledIdentity: () => ++polls < 6 ? null : 'u', + reload: () async => true, + readVariant: (_) async { + reads++; + return 'yinka'; + }, + ); + final arm = await OnboardingVariantGate.resolve(settledIdentity: () => null, identityWait: Duration.zero); + expect(arm, OnboardingArm.yinka); + expect(reads, 1); + // The warm-up is consumed: the next resolve is a cold read again. + expect(await OnboardingVariantGate.resolve(settledIdentity: () => null, identityWait: Duration.zero), + OnboardingArm.control); + }); + + test('a warm-up for account A is discarded when the account changes before the tap', () async { + var epoch = 1; + var warmReads = 0; + OnboardingVariantGate.warm( + settledIdentity: () => 'a', + reload: () async => true, + identityEpoch: () => epoch, + readVariant: (_) async { + warmReads++; + return 'yinka'; + }, + ); + epoch = 2; // "use a different account": the analytics identity moved on + var coldReads = 0; + final arm = await OnboardingVariantGate.resolve( + settledIdentity: () => 'b', + reload: () async => true, + identityEpoch: () => epoch, + readVariant: (_) async { + coldReads++; + return 'control'; + }, + ); + expect(arm, OnboardingArm.control, reason: 'B gets its own (control) answer, never A\'s warmed arm'); + expect(coldReads, 1); + expect(warmReads, 1); + }); + + test('an account change while a read is in flight yields native', () async { + var epoch = 1; + final arm = await OnboardingVariantGate.resolve( + settledIdentity: () => 'a', + reload: () async => true, + identityEpoch: () => epoch, + readVariant: (_) async { + epoch = 2; + return 'yinka'; + }, + ); + expect(arm, OnboardingArm.control); + }); + + test('a failed or timed-out flag reload yields native even when the stale flag says on', () async { + var reads = 0; + final arm = await OnboardingVariantGate.resolve( + settledIdentity: () => 'u', + reload: () async => false, + readVariant: (_) async { + reads++; + return 'yinka'; + }, + ); + expect(arm, OnboardingArm.control); + expect(reads, 0, reason: 'nothing is read on top of stale flags'); + expect( + await OnboardingVariantGate.resolve( + settledIdentity: () => 'u', + reload: () => Future.error(TimeoutException('flags')), + readVariant: (_) async => 'yinka', + ), + OnboardingArm.control, + ); + }); +} diff --git a/app/test/unit/onboarding_webview_gate_test.dart b/app/test/unit/onboarding_webview_gate_test.dart deleted file mode 100644 index 66d5954615e..00000000000 --- a/app/test/unit/onboarding_webview_gate_test.dart +++ /dev/null @@ -1,190 +0,0 @@ -import 'dart:async'; - -import 'package:flutter_test/flutter_test.dart'; - -import 'package:omi/services/experiments/onboarding_webview_gate.dart'; - -// Web onboarding is fail-closed: off, slow, erroring, or a payload without an https URL all mean -// the native onboarding exactly as before. - -void main() { - setUp(OnboardingWebviewGate.resetForTesting); - - test('flag key is the registered PostHog key', () { - expect(OnboardingWebviewGate.enabledFlag, 'onboarding-webview'); - expect(OnboardingWebviewGate.debugOverrideActive, isFalse); - }); - - test('parse accepts only an https URL from a map or a JSON string', () { - expect( - OnboardingWebviewGate.parse(enabled: true, payload: {'url': 'https://omi-paywall.vercel.app/onboarding.html'}), - Uri.parse('https://omi-paywall.vercel.app/onboarding.html')); - expect(OnboardingWebviewGate.parse(enabled: true, payload: '{"url":"https://x.example/o"}'), - Uri.parse('https://x.example/o')); - expect(OnboardingWebviewGate.parse(enabled: true, payload: {'url': 'http://x.example'}), isNull); - expect(OnboardingWebviewGate.parse(enabled: true, payload: {}), isNull); - expect(OnboardingWebviewGate.parse(enabled: true, payload: 'garbage'), isNull); - expect(OnboardingWebviewGate.parse(enabled: false, payload: {'url': 'https://x.example'}), isNull); - }); - - test('flag off -> native, without reading the payload', () async { - var payloadReads = 0; - final url = await OnboardingWebviewGate.resolve( - settledIdentity: () => 'u', - reload: () async => true, - readFlag: (_) async => false, - readPayload: (_) async { - payloadReads++; - return {'url': 'https://x.example'}; - }, - ); - expect(url, isNull); - expect(payloadReads, 0); - }); - - test('flag on -> the payload URL', () async { - final keys = []; - final url = await OnboardingWebviewGate.resolve( - settledIdentity: () => 'u', - reload: () async => true, - readFlag: (key) async { - keys.add(key); - return true; - }, - readPayload: (_) async => {'url': 'https://x.example/onboarding'}, - ); - expect(url, Uri.parse('https://x.example/onboarding')); - expect(keys, ['onboarding-webview']); - }); - - test('flag read error -> native (fail closed)', () async { - expect( - await OnboardingWebviewGate.resolve( - settledIdentity: () => 'u', reload: () async => true, readFlag: (_) => Future.error(StateError('no sdk'))), - isNull); - }); - - test('waits for the identity to settle and reloads flags before the first read', () async { - final order = []; - var polls = 0; - final url = await OnboardingWebviewGate.resolve( - settledIdentity: () => ++polls < 4 ? null : 'u', - reload: () async { - order.add('reload'); - return true; - }, - readFlag: (_) async { - order.add('read'); - return true; - }, - readPayload: (_) async => {'url': 'https://x.example/onboarding'}, - ); - expect(url, Uri.parse('https://x.example/onboarding')); - expect(order, ['reload', 'read']); - expect(polls, greaterThanOrEqualTo(4)); - }); - - test('no identity within the wait -> native, without reading anything', () async { - var reads = 0; - final url = await OnboardingWebviewGate.resolve( - settledIdentity: () => null, - identityWait: const Duration(milliseconds: 200), - reload: () async { - reads++; - return true; - }, - readFlag: (_) async { - reads++; - return true; - }, - ); - expect(url, isNull); - expect(reads, 0); - }); - - test('a warm-up started on the consent screen answers the tap without restarting the wait', () async { - var polls = 0; - var reads = 0; - OnboardingWebviewGate.warm( - settledIdentity: () => ++polls < 6 ? null : 'u', - reload: () async => true, - readFlag: (_) async { - reads++; - return true; - }, - readPayload: (_) async => {'url': 'https://x.example/onboarding'}, - ); - final url = await OnboardingWebviewGate.resolve(settledIdentity: () => null, identityWait: Duration.zero); - expect(url, Uri.parse('https://x.example/onboarding')); - expect(reads, 1); - // The warm-up is consumed: the next resolve is a cold read again. - expect(await OnboardingWebviewGate.resolve(settledIdentity: () => null, identityWait: Duration.zero), isNull); - }); - - test('a warm-up for account A is discarded when the account changes before the tap', () async { - var epoch = 1; - var warmReads = 0; - OnboardingWebviewGate.warm( - settledIdentity: () => 'a', - reload: () async => true, - identityEpoch: () => epoch, - readFlag: (_) async { - warmReads++; - return true; - }, - readPayload: (_) async => {'url': 'https://x.example/for-a'}, - ); - epoch = 2; // "use a different account": the analytics identity moved on - var coldReads = 0; - final url = await OnboardingWebviewGate.resolve( - settledIdentity: () => 'b', - reload: () async => true, - identityEpoch: () => epoch, - readFlag: (_) async { - coldReads++; - return false; - }, - ); - expect(url, isNull, reason: 'B gets its own (off) answer, never A\'s warmed URL'); - expect(coldReads, 1); - expect(warmReads, 1); - }); - - test('an account change while a read is in flight yields native', () async { - var epoch = 1; - final url = await OnboardingWebviewGate.resolve( - settledIdentity: () => 'a', - reload: () async => true, - identityEpoch: () => epoch, - readFlag: (_) async { - epoch = 2; - return true; - }, - readPayload: (_) async => {'url': 'https://x.example/for-a'}, - ); - expect(url, isNull); - }); - - test('a failed or timed-out flag reload yields native even when the stale flag says on', () async { - var reads = 0; - final url = await OnboardingWebviewGate.resolve( - settledIdentity: () => 'u', - reload: () async => false, - readFlag: (_) async { - reads++; - return true; - }, - readPayload: (_) async => {'url': 'https://x.example/onboarding'}, - ); - expect(url, isNull); - expect(reads, 0, reason: 'nothing is read on top of stale flags'); - expect( - await OnboardingWebviewGate.resolve( - settledIdentity: () => 'u', - reload: () => Future.error(TimeoutException('flags')), - readFlag: (_) async => true, - ), - isNull, - ); - }); -} diff --git a/app/test/widgets/variants/yinka_onboarding_activation_flow_test.dart b/app/test/widgets/variants/yinka_onboarding_activation_flow_test.dart new file mode 100644 index 00000000000..5808b83127c --- /dev/null +++ b/app/test/widgets/variants/yinka_onboarding_activation_flow_test.dart @@ -0,0 +1,328 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:omi/backend/preferences.dart'; +import 'package:omi/l10n/app_localizations.dart'; +import 'package:omi/pages/onboarding/variants/yinka/activation/activation_capture.dart'; +import 'package:omi/pages/onboarding/variants/yinka/activation/activation_flow.dart'; + +class _FakeCapture implements ActivationCapture { + _FakeCapture(this.result, {this.startsAs = ActivationStart.listening, this.device = false}); + + final ActivationResult result; + final ActivationStart startsAs; + final bool device; + final ValueNotifier live = ValueNotifier(''); + bool cancelled = false; + + @override + bool get usesDevice => device; + + @override + ValueListenable get liveText => live; + + final ValueNotifier levelNotifier = ValueNotifier(0); + + @override + ValueListenable get level => levelNotifier; + + @override + Future start() async => startsAs; + + @override + Future finish({Duration timeout = const Duration(seconds: 30)}) async => result; + + @override + Future cancel() async => cancelled = true; +} + +/// Omi never finishes its notes (a slow network or a slow server). +class _SlowCapture extends _FakeCapture { + _SlowCapture() : super(const ActivationResult(transcript: '')); + + @override + Future finish({Duration timeout = const Duration(seconds: 30)}) => + Completer().future; +} + +void main() { + setUp(() async { + SharedPreferences.setMockInitialValues({}); + await SharedPreferencesUtil.init(); + }); + + const notes = ActivationResult( + transcript: 'Remind me to send the deck', + emoji: '🥗', + title: 'Lunch with Sam', + overview: 'Sam is moving to Lisbon in June.', + tasks: ['Send the deck to the team by Friday', 'Grab milk on the way home'], + ); + + Future<({_FakeCapture capture, List events})> pump( + WidgetTester tester, { + bool hasDevice = false, + ActivationResult result = notes, + Completer? paywall, + _FakeCapture Function()? capture, + Future Function()? requestMicrophone, + Future Function()? finish, + }) async { + final captures = <_FakeCapture>[]; + _FakeCapture next() { + final made = (captures.isEmpty ? capture?.call() : null) ?? _FakeCapture(result); + captures.add(made); + return made; + } + + final first = next(); + var used = false; + final events = []; + await tester.pumpWidget( + MaterialApp( + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: const [Locale('en')], + home: Scaffold( + body: ActivationFlow( + hasDevice: hasDevice, + capture: (_) { + if (!used) { + used = true; + return first; + } + return next(); + }, + microphoneGranted: () async => true, + requestMicrophone: requestMicrophone, + requestReview: () async => events.add('review'), + showPaywall: () { + events.add('paywall'); + return paywall?.future ?? Future.value(); + }, + onFinished: () async { + events.add('finished'); + return finish == null ? true : await finish(); + }, + ), + ), + ), + ); + return (capture: first, events: events); + } + + Future tapKey(WidgetTester tester, String key) async { + await tester.tap(find.byKey(Key(key))); + await tester.pump(const Duration(seconds: 5)); + } + + testWidgets('the full path: intro, dictation, notes, tasks, hooray, rating, paywall, Home', (tester) async { + final paywall = Completer(); + final run = await pump(tester, paywall: paywall); + + expect(find.text("Let's see Omi at work."), findsNothing, reason: 'words appear one by one'); + await tester.pump(const Duration(seconds: 4)); // Moves on by itself, straight to the dictation. + + // Dictation: the check stays off until Omi has heard something. + expect(find.text('Tell Omi about your day'), findsOneWidget); + await tester.tap(find.byKey(const Key('activation_dictation_done'))); + await tester.pump(const Duration(seconds: 1)); + expect(find.text('Tell Omi about your day'), findsOneWidget, reason: 'nothing heard yet'); + run.capture.live.value = 'Remind me to send the deck'; + await tester.pump(); + await tapKey(tester, 'activation_dictation_done'); + + expect(find.text('Your day, summarized'), findsOneWidget); + expect(find.text('Lunch with Sam'), findsOneWidget); + expect(find.text('Just now'), findsOneWidget); + await tapKey(tester, 'activation_continue_summary'); + + expect(find.text('Omi caught your to-dos'), findsOneWidget); + expect(find.text('Grab milk on the way home'), findsOneWidget); + expect(find.text('Saved to your tasks'), findsOneWidget); + await tapKey(tester, 'activation_continue_tasks'); + + // Hooray, then the rating ask, then the paywall. + expect(find.byKey(const Key('activation_continue_congrats')), findsOneWidget); + expect(run.events, isEmpty); + await tapKey(tester, 'activation_continue_congrats'); + await tester.pump(const Duration(seconds: 2)); // The pop-up waits for the stars. + expect(find.text('Enjoying Omi so far?'), findsOneWidget); + expect(run.events, ['review'], reason: 'the App Store pop-up opens by itself'); + // The stars sit right under the title, above where the pop-up opens. + final titleBottom = tester.getBottomLeft(find.text('Enjoying Omi so far?')).dy; + final starsTop = tester.getTopLeft(find.byIcon(Icons.star_rounded).first).dy; + expect(starsTop - titleBottom, lessThan(40)); + + // The pop-up makes the app inactive; its closing opens the paywall, and closing that goes Home. + tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.inactive); + tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.resumed); + await tester.pump(); + expect(run.events, ['review', 'paywall']); + paywall.complete(); + await tester.pump(); + expect(run.events, ['review', 'paywall', 'finished']); + }); + + testWidgets('rating: once the App Store pop-up is up, it moves on to the paywall by itself', (tester) async { + final run = await pump(tester); + await tester.pump(const Duration(seconds: 4)); + run.capture.live.value = 'Remind me to send the deck'; + await tester.pump(); + await tapKey(tester, 'activation_dictation_done'); + await tapKey(tester, 'activation_continue_summary'); + await tapKey(tester, 'activation_continue_tasks'); + await tapKey(tester, 'activation_continue_congrats'); + + // A tap before the pop-up has been asked for stays on the rating screen. + await tester.tapAt(tester.getCenter(find.text('Enjoying Omi so far?'))); + await tester.pump(); + expect(run.events, isEmpty); + + await tester.pump(const Duration(seconds: 2)); + expect(run.events, ['review'], reason: 'the pop-up is asked for, not yet on screen'); + + // iOS gives no cue when its pop-up closes: the paywall opens underneath it instead. + await tester.pump(const Duration(seconds: 1)); + expect(run.events, ['review', 'paywall', 'finished']); + }); + + testWidgets('Omi owners go straight to the intro (how to wear it is the button tutorial before it)', (tester) async { + await pump(tester, hasDevice: true); + await tester.pump(const Duration(seconds: 1)); + expect(find.text('Wear your Omi'), findsNothing); + await tester.pump(const Duration(seconds: 5)); + }); + + testWidgets('when the Omi is not streaming, the microphone is asked for before the dictation', (tester) async { + var asked = false; + await pump( + tester, + hasDevice: true, + capture: () => _FakeCapture(notes, startsAs: ActivationStart.needsMicrophone), + requestMicrophone: () async => asked = true, + ); + await tester.pump(const Duration(seconds: 4)); + await tester.pump(const Duration(seconds: 1)); + expect(find.text("Your Omi isn't sending audio yet. Use your phone for now."), findsOneWidget); + await tapKey(tester, 'activation_continue_microphone'); + expect(asked, isTrue); + expect(find.text('Tell Omi about your day'), findsOneWidget, reason: 'listening again, through the phone'); + await tester.pumpWidget(const SizedBox()); + await tester.pump(const Duration(seconds: 13)); + }); + + testWidgets('after a quiet stretch the dictation says it cannot hear the Omi', (tester) async { + await pump(tester, hasDevice: true, capture: () => _FakeCapture(notes, device: true)); + await tester.pump(const Duration(seconds: 4)); + await tester.pump(const Duration(seconds: 13)); + expect(find.text("Can't hear your Omi. Make sure it's on and close by."), findsOneWidget); + await tester.pumpWidget(const SizedBox()); + await tester.pump(const Duration(seconds: 1)); + }); + + testWidgets('skipping the dictation leaves a welcome card for Home and goes to the hooray', (tester) async { + final run = await pump(tester); + await tester.pump(const Duration(seconds: 4)); + await tapKey(tester, 'activation_dictation_skip'); + + expect(run.capture.cancelled, isTrue); + expect(SharedPreferencesUtil().getBool(ActivationFlow.welcomeCardKey), isTrue); + expect(find.byKey(const Key('activation_continue_congrats')), findsOneWidget); + await tester.pump(const Duration(seconds: 3)); // Lets the celebration finish. + }); + + testWidgets('when Omi could not make notes, the reader still sees their own words', (tester) async { + final run = await pump(tester, result: const ActivationResult(transcript: 'Just testing this out')); + await tester.pump(const Duration(seconds: 4)); + run.capture.live.value = 'Just testing this out'; + await tester.pump(); + await tapKey(tester, 'activation_dictation_done'); + + expect(find.text("Here's what you said"), findsOneWidget); + await tapKey(tester, 'activation_continue_summary'); + expect(find.byKey(const Key('activation_continue_congrats')), findsOneWidget, reason: 'no tasks step'); + await tester.pump(const Duration(seconds: 3)); // Lets the celebration finish. + }); + + testWidgets('slow notes: after the deadline the reader moves on with their words, told notes are coming', + (tester) async { + final run = await pump(tester, capture: _SlowCapture.new); + await tester.pump(const Duration(seconds: 4)); + run.capture.live.value = 'Lunch with Sam today'; + await tester.pump(); + await tester.tap(find.byKey(const Key('activation_dictation_done'))); + await tester.pump(const Duration(seconds: 5)); + expect(find.text('Summarizing…'), findsOneWidget, reason: 'still within the deadline'); + + await tester.pump(const Duration(seconds: 8)); // Past the 12 s deadline. + await tester.pump(const Duration(seconds: 5)); + expect(find.text("Here's what you said"), findsOneWidget); + expect(find.text('Lunch with Sam today'), findsOneWidget); + expect(find.textContaining('still writing'), findsNothing, reason: 'no apology line; the notes reach Home'); + expect(find.text("Omi remembers so you don't have to"), findsOneWidget); + await tapKey(tester, 'activation_continue_summary'); + expect(find.byKey(const Key('activation_continue_congrats')), findsOneWidget); + await tester.pump(const Duration(seconds: 3)); + }); + + testWidgets('when listening cannot start, Continue moves on instead of a dead end', (tester) async { + await pump(tester, capture: () => _FakeCapture(notes, startsAs: ActivationStart.failed)); + await tester.pump(const Duration(seconds: 4)); + await tester.pump(); + expect(find.byKey(const Key('activation_dictation_status')), findsOneWidget); + await tapKey(tester, 'activation_continue_dictation'); + expect(find.byKey(const Key('activation_continue_congrats')), findsOneWidget); + await tester.pump(const Duration(seconds: 3)); + }); + + testWidgets('when nothing is heard for a while, Continue appears; late words bring the check back', (tester) async { + final run = await pump(tester); + await tester.pump(const Duration(seconds: 4)); + expect(find.byKey(const Key('activation_continue_dictation')), findsNothing); + await tester.pump(const Duration(seconds: 26)); + expect(find.byKey(const Key('activation_continue_dictation')), findsOneWidget); + + run.capture.live.value = 'Finally some words'; + await tester.pump(); + expect(find.byKey(const Key('activation_continue_dictation')), findsNothing); + expect(find.byKey(const Key('activation_dictation_done')), findsOneWidget); + }); + + testWidgets('a failed final save keeps Continue working; the paywall is not shown twice', (tester) async { + var attempts = 0; + final run = await pump(tester, finish: () async => ++attempts > 1); + await tester.pump(const Duration(seconds: 4)); + await tester.tap(find.text('Skip')); + await tester.pump(const Duration(seconds: 5)); + await tapKey(tester, 'activation_continue_congrats'); + await tester.pump(const Duration(seconds: 2)); + await tester.tap(find.byKey(const Key('activation_continue_rating')), warnIfMissed: false); + await tester.pump(); + expect(run.events, ['review'], reason: 'Continue waits for the App Store prompt'); + await tester.pump(const Duration(seconds: 2)); + expect(run.events, ['review', 'paywall', 'finished'], reason: 'moves on by itself; the first save fails'); + + await tapKey(tester, 'activation_continue_rating'); + expect(run.events, ['review', 'paywall', 'finished', 'finished'], reason: 'retried without a second paywall'); + expect(attempts, 2); + }); + + testWidgets('Omi visibly hears the person: the voice ring and meter follow the level', (tester) async { + final run = await pump(tester); + await tester.pump(const Duration(seconds: 4)); + await tester.pump(); + final ring = find.byKey(const Key('activation_voice_ring')); + expect(ring, findsOneWidget); + expect(find.byKey(const Key('activation_level_meter')), findsOneWidget); + final quiet = tester.getSize(ring).width; + + run.capture.levelNotifier.value = 1; + await tester.pump(); + await tester.pump(const Duration(milliseconds: 200)); + expect(tester.getSize(ring).width, greaterThan(quiet + 40)); + }); +} diff --git a/app/test/widgets/variants/yinka_onboarding_connect_omi_test.dart b/app/test/widgets/variants/yinka_onboarding_connect_omi_test.dart new file mode 100644 index 00000000000..613b15c795e --- /dev/null +++ b/app/test/widgets/variants/yinka_onboarding_connect_omi_test.dart @@ -0,0 +1,187 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:connectivity_plus_platform_interface/connectivity_plus_platform_interface.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:omi/backend/preferences.dart'; +import 'package:omi/backend/schema/bt_device/bt_device.dart'; +import 'package:omi/l10n/app_localizations.dart'; +import 'package:omi/pages/onboarding/variants/yinka/connect_omi_step.dart'; +import 'package:omi/providers/device_provider.dart'; +import 'package:omi/providers/onboarding_provider.dart'; +import 'package:omi/services/services.dart'; + +/// The shared pairing provider with the radio calls replaced: records what the step asks of it. +class _FakeOnboarding extends OnboardingProvider { + int scans = 0; + int cancels = 0; + + @override + Future scanDevices({required VoidCallback onShowDialog, VoidCallback? onShowLocationDialog}) async { + scans++; + } + + @override + void cancelActiveScan() => cancels++; + + /// Connecting succeeds at once, as `handleTap` does when the radio answers. + @override + Future handleTap({required BtDevice device, required bool isFromOnboarding, VoidCallback? goNext}) async { + isClicked = true; + deviceId = device.id; + deviceName = device.name; + deviceType = device.type; + isConnected = true; + notifyListeners(); + goNext?.call(); + } +} + +class _FakeDevice extends DeviceProvider { + _FakeDevice() : super(chargingConnectionLoader: (_) async => null); + + @override + Future initiateConnection(String caller, {bool boundDeviceOnly = false}) async {} +} + +class _NoConnectivity extends ConnectivityPlatform { + @override + Future> checkConnectivity() async => [ConnectivityResult.none]; + + @override + Stream> get onConnectivityChanged => const Stream.empty(); +} + +void main() { + setUpAll(() async { + SharedPreferences.setMockInitialValues({}); + await SharedPreferencesUtil.init(); + ConnectivityPlatform.instance = _NoConnectivity(); + try { + await ServiceManager.init(); + } catch (_) { + // Already initialised by another test in this isolate. + } + }); + + // The providers are app-wide singletons in production: hand them in by value so the test owns + // their lifetime, as the app does. + Future pump(WidgetTester tester, _FakeOnboarding onboarding, + {required void Function(OnboardingDeviceChoice) onDone, + Future Function(BuildContext, {required String source})? openShop}) { + return tester.pumpWidget( + MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: _FakeDevice()), + ChangeNotifierProvider.value(value: onboarding), + ], + child: MaterialApp( + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: const [Locale('en')], + home: Scaffold( + body: + openShop == null ? ConnectOmiStep(onDone: onDone) : ConnectOmiStep(onDone: onDone, openShop: openShop), + ), + ), + ), + ); + } + + testWidgets('says "Connect Omi" and searches only after the arrival has played', (tester) async { + final onboarding = _FakeOnboarding(); + await pump(tester, onboarding, onDone: (_) {}); + + expect(find.text('Connect Omi'), findsOneWidget); + await tester.pump(const Duration(milliseconds: 500)); + expect(onboarding.scans, 0, reason: 'no Bluetooth guidance may cover the arrival'); + await tester.pump(const Duration(seconds: 2)); // The search rings never settle. + expect(onboarding.scans, 1); + }); + + testWidgets('"I\'ve ordered mine" leaves once with that answer', (tester) async { + final choices = []; + await pump(tester, _FakeOnboarding(), onDone: choices.add); + await tester.pump(const Duration(seconds: 2)); // The search rings never settle. + + await tester.tap(find.byKey(const Key('connect_omi_ordered'))); + await tester.tap(find.byKey(const Key('connect_omi_ordered')), warnIfMissed: false); + await tester.pump(); + + expect(choices, [OnboardingDeviceChoice.ordered]); + }); + + testWidgets('"Not ordered yet" opens the Omi store, and onboarding carries on once it is closed', (tester) async { + final choices = []; + final sources = []; + final closed = Completer(); + await pump( + tester, + _FakeOnboarding(), + onDone: choices.add, + openShop: (_, {required source}) { + sources.add(source); + return closed.future; + }, + ); + await tester.pump(const Duration(seconds: 2)); + + await tester.tap(find.byKey(const Key('connect_omi_not_ordered'))); + await tester.tap(find.byKey(const Key('connect_omi_not_ordered')), warnIfMissed: false); + await tester.pump(); + expect(sources, ['onboarding_not_ordered'], reason: 'one store, tagged with where it came from'); + expect(choices, isEmpty, reason: 'still in the store'); + + closed.complete(); + await tester.pump(); + expect(choices, [OnboardingDeviceChoice.notOrdered]); + }); + + testWidgets('a nearby device is shown as a row to connect', (tester) async { + final device = BtDevice(name: 'Omi', id: 'AA:BB', type: DeviceType.omi, rssi: -50); + final onboarding = _FakeOnboarding() + ..deviceList = [device] + ..foundDevicesMap = {device.id: device}; + await pump(tester, onboarding, onDone: (_) {}); + await tester.pump(const Duration(seconds: 2)); // The search rings never settle. + + expect(find.text('Omi'), findsOneWidget); + expect(find.text('Tap your Omi to connect.'), findsOneWidget); + }); + + testWidgets('leaving stops the search and clears "nothing found" for the in-app pairing page', (tester) async { + final onboarding = _FakeOnboarding(); + await pump(tester, onboarding, onDone: (_) {}); + await tester.pump(const Duration(seconds: 2)); // The search rings never settle. + onboarding.enableInstructions = true; // The search came up empty here. + + await tester.pumpWidget(const SizedBox()); + + expect(onboarding.cancels, greaterThan(0)); + expect(onboarding.enableInstructions, isFalse); + }); + + testWidgets('connecting says Connected with a ✓, then moves on once the moment has played', (tester) async { + final device = BtDevice(name: 'Omi', id: 'AA:BB', type: DeviceType.omi, rssi: -50); + final onboarding = _FakeOnboarding() + ..deviceList = [device] + ..foundDevicesMap = {device.id: device}; + final choices = []; + await pump(tester, onboarding, onDone: choices.add); + await tester.pump(const Duration(seconds: 2)); // The search rings never settle. + + await tester.tap(find.text('Omi')); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 900)); + + expect(find.text('Connected'), findsOneWidget); + expect(find.byKey(const Key('connect_omi_connected_badge')), findsOneWidget); + expect(choices, isEmpty, reason: 'the reader sees Connected before the tutorial opens'); + + await tester.pump(const Duration(milliseconds: 300)); // The ring and ✓ finish. + await tester.pump(const Duration(seconds: 1)); // Then a beat to read it. + expect(choices, [OnboardingDeviceChoice.connected]); + }); +} diff --git a/app/test/widgets/variants/yinka_onboarding_summary_text_test.dart b/app/test/widgets/variants/yinka_onboarding_summary_text_test.dart new file mode 100644 index 00000000000..33a167044e1 --- /dev/null +++ b/app/test/widgets/variants/yinka_onboarding_summary_text_test.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:omi/pages/onboarding/variants/yinka/activation/activation_motion.dart'; + +// Omi's notes arrive as markdown; the onboarding card shows them as clean text and never grows +// past its box, however long the notes are. + +const _realNotes = '''## Lunch Update on Sam + +- One speaker mentioned having lunch with Sam, noting that she plans to move to Lisbon in June. + +## Action Item Reminders + +- A speaker requested reminders to **send the deck** to their team by Friday and to pick up milk on the way.'''; + +void main() { + test('markdown becomes headings, bullets and plain text', () { + final lines = summaryLines(_realNotes); + expect(lines.map((l) => l.text), [ + 'Lunch Update on Sam', + 'One speaker mentioned having lunch with Sam, noting that she plans to move to Lisbon in June.', + 'Action Item Reminders', + 'A speaker requested reminders to send the deck to their team by Friday and to pick up milk on the way.', + ]); + expect(lines.map((l) => l.heading), [true, false, true, false]); + expect(lines.map((l) => l.bullet), [false, true, false, true]); + }); + + test('plain prose stays one line', () { + final lines = summaryLines('Lunch with Sam, who is moving to Lisbon in June.'); + expect(lines, hasLength(1)); + expect(lines.single.heading || lines.single.bullet, isFalse); + }); + + testWidgets('long notes stay inside the box and show no markdown', (tester) async { + tester.view.physicalSize = const Size(402 * 3, 874 * 3); + tester.view.devicePixelRatio = 3; + addTearDown(tester.view.reset); + final long = List.filled(4, _realNotes).join('\n\n'); + await tester.pumpWidget(MaterialApp( + home: Scaffold(body: Center(child: SummaryText(text: long, t: 1, style: const TextStyle(fontSize: 17)))), + )); + expect(tester.takeException(), isNull); + expect(tester.getSize(find.byType(SummaryText)).height, lessThanOrEqualTo(190)); + expect(find.textContaining('##', findRichText: true), findsNothing); + expect(find.textContaining('**', findRichText: true), findsNothing); + }); +} diff --git a/config/feature-flags.yaml b/config/feature-flags.yaml index f9b985bbc8e..386718276cf 100644 --- a/config/feature-flags.yaml +++ b/config/feature-flags.yaml @@ -11,7 +11,7 @@ flags: - {"key":"OMI_MESSAGING_RETENTION_DAYS","aliases":[],"kind":"env","lifecycle":"config_switch","surfaces":["backend"],"summary":"Hourly messaging maintenance retains done inbox markers and unknown identities for 30 days by default; minimum seven; does not send.","fail":"closed","owner":"dazheng","created":"2026-10-14","decision":"keep"} - {"key":"OWNER_IDENTITY_RETRY_COALESCE_ENABLED","aliases":[],"kind":"env","lifecycle":"rollout","surfaces":["backend"],"summary":"Default false; bounded per-UID late identity coalescing, 16 cohorts, 128 candidates and eight eligible passes per cohort; two workers unchanged","fail":"closed","owner":"dazheng","created":"2026-10-14","review_by":"2026-11-13","decision":"pending"} - {"key":"paywall-variant","prereg":"product/experiments/paywall-variant.md","aliases":[],"kind":"posthog","lifecycle":"experiment","surfaces":["mobile"],"summary":"Mobile paywall A/B test: control (current PlansSheet) / outline (PR #21412 soft paywall, native) / web (webview paywall whose page is the variant). Client-evaluated multivariate flag; control collapses to the shipped sheet, paid readers and App Review builds never see a variant","fail":"closed","owner":"nik","created":"2026-10-13","review_by":"2026-11-12","decision":"pending","posthog":{"row":"expected","role":"exposure"},"notes":"PostHog id 957521, control 100% until the build is in the stores; the split is the experiment. Payload per variant: {\"url\": \"https://…\" (web only, https), \"onboarding_paywall\": true|false}. Debug builds: --dart-define=OMI_PAYWALL_VARIANT=outline|web [OMI_PAYWALL_WEB_URL=…]."} - - {"key":"onboarding-webview","aliases":[],"kind":"posthog","lifecycle":"rollout","surfaces":["mobile"],"summary":"Mobile first-run onboarding rendered from a web page after sign-in and consent (OTA); off = native steps unchanged; the page never owns completion","fail":"closed","owner":"nik","created":"2026-10-13","review_by":"2026-11-12","decision":"pending","posthog":{"row":"expected","role":"enable"},"notes":"PostHog id 957522 at 0%. Payload: {\"url\": \"https://…\"} (https). A page that errors or never reports ready within 10s falls back to native onboarding. Debug builds: --dart-define=OMI_ONBOARDING_WEBVIEW_URL=https://…"} + - {"key":"onboarding-variant","aliases":["onboarding-webview"],"kind":"posthog","lifecycle":"experiment","surfaces":["mobile"],"summary":"Mobile first-run onboarding A/B/C after sign-in and consent: control (native steps unchanged) / reid (device-first onboarding rendered from a web page, OTA; payload names it) / yinka (onboarding ending in a first conversation, PR #21445, native). Client-evaluated multivariate flag; an unread flag, an unknown variant or a reid payload without an https URL is control; the page never owns completion","fail":"closed","owner":"nik","created":"2026-10-14","review_by":"2026-11-13","decision":"pending","posthog":{"row":"expected","role":"exposure"},"notes":"Replaces onboarding-webview (PostHog 957522, test-account only). Payload for reid: {\"url\": \"https://…\"} (https). A page that errors or never reports ready within 10s falls back to native onboarding. Debug builds: --dart-define=OMI_ONBOARDING_VARIANT=reid|yinka [OMI_ONBOARDING_WEBVIEW_URL=https://…]."} - {"key":"DIARIZATION_V2_ENABLED","aliases":[],"kind":"env","lifecycle":"config_switch","surfaces":["backend"],"summary":"Enable the separate batch-only diarizer app; default false; startup loads models before readiness","fail":"closed","owner":"dazheng","created":"2026-10-14","decision":"keep"} - {"key":"DREAM_AGENT_FULL_SAMPLES","aliases":[],"kind":"env","lifecycle":"config_switch","surfaces":["backend"],"summary":"Concurrent independent full Dream reasoning samples; default 3, bounded to 1 through 8; strict majority among returned samples; reserve one JSON retry per sample","fail":"closed","owner":"dazheng","created":"2026-10-13","decision":"pending"} - {"key":"DREAM_AGENT_CONTEXT_MODE","aliases":[],"kind":"env","lifecycle":"rollout","surfaces":["backend"],"summary":"Dream context mode bounded (default) or full; full requires the separate UID allowlist","fail":"closed","owner":"dazheng","created":"2026-10-13","decision":"pending","review_by":"2026-11-12"} From 651f6264ae33a998e01da972a11556ea92b07749 Mon Sep 17 00:00:00 2001 From: Nik Shevchenko Date: Wed, 14 Oct 2026 19:43:42 -0400 Subject: [PATCH 2/2] docs: pre-register the onboarding-variant experiment --- .../yinka/activation/activation_motion.dart | 19 +++++----- .../services/capture/capture_controller.dart | 27 ++++---------- backend/docs/feature-flag-registry.md | 3 +- config/feature-flags.yaml | 2 +- product/experiments/onboarding-variant.md | 36 +++++++++++++++++++ 5 files changed, 54 insertions(+), 33 deletions(-) create mode 100644 product/experiments/onboarding-variant.md diff --git a/app/lib/pages/onboarding/variants/yinka/activation/activation_motion.dart b/app/lib/pages/onboarding/variants/yinka/activation/activation_motion.dart index a843756ae19..15ef78d8ec6 100644 --- a/app/lib/pages/onboarding/variants/yinka/activation/activation_motion.dart +++ b/app/lib/pages/onboarding/variants/yinka/activation/activation_motion.dart @@ -600,16 +600,15 @@ class _SummaryRevealState extends State with SingleTickerProvider final v = _c.value; final appear = Curves.easeOutCubic.transform((v / 0.45).clamp(0.0, 1.0)); final sweep = ((v - 0.55) / 0.45).clamp(0.0, 1.0); - Widget out = Opacity( - opacity: appear, - child: Transform.scale( - scale: 0.94 + 0.06 * appear, - child: ImageFiltered( - imageFilter: ImageFilter.blur(sigmaX: 12 * (1 - appear), sigmaY: 12 * (1 - appear)), - child: child, - ), - ), - ); + // The blur only runs during the 450 ms entrance; once settled the card is drawn plain. + final Widget settled = appear >= 1 + ? child + : ImageFiltered( + // omi-perf-allow: blur-filter -- one-shot entrance of a single card, gone when appear == 1; never in a list + imageFilter: ImageFilter.blur(sigmaX: 12 * (1 - appear), sigmaY: 12 * (1 - appear)), + child: child, + ); + Widget out = Opacity(opacity: appear, child: Transform.scale(scale: 0.94 + 0.06 * appear, child: settled)); if (sweep > 0 && sweep < 1) { final x = -1.5 + 3.0 * Curves.easeInOut.transform(sweep); out = ShaderMask( diff --git a/app/lib/services/capture/capture_controller.dart b/app/lib/services/capture/capture_controller.dart index 9f085c676f5..caa7342163d 100644 --- a/app/lib/services/capture/capture_controller.dart +++ b/app/lib/services/capture/capture_controller.dart @@ -223,20 +223,17 @@ class CaptureController extends ChangeNotifier IWalService get _wal => _walServiceOverride ?? ServiceManager.instance().wal; - /// The conversation-capture recorder. Resolves an injected override first so - /// deterministic scenarios can drive the real pipeline through the production - /// decision path, then falls back to the shared service manager. - /// Receives a copy of the phone microphone's audio (16 kHz mono PCM16) as it arrives, for a caller - /// that needs the sound itself: onboarding enrolls the reader's voice from its first dictation. + /// Audio taps for a caller that needs the sound itself (onboarding's dictation enrolls the voice + /// and meters the level): phone-mic PCM16 16 kHz mono, and the pendant's encoded packets. void Function(List pcm)? phoneMicAudioTap; - - /// Receives each encoded audio packet from the pendant as it arrives (for a live level meter). void Function(List packet)? deviceAudioTap; - /// Set by a first-run step that explains a short or empty capture itself, so the generic - /// "talk for 30 seconds" notice stays away while it is on screen. + /// Set by a first-run step that explains a short capture itself: holds the generic notice. bool suppressShortCaptureNotice = false; + /// The conversation-capture recorder. Resolves an injected override first so + /// deterministic scenarios can drive the real pipeline through the production + /// decision path, then falls back to the shared service manager. IMicRecorderService get _phoneMic => _phoneMicRecorderOverride ?? ServiceManager.instance().phoneMic; DateTime _now() => _nowOverride?.call() ?? DateTime.now(); @@ -1393,7 +1390,6 @@ class CaptureController extends ChangeNotifier return; } - // Handle phone mic recording if (recordingState == RecordingState.record || (_phoneMicPaused && !_phoneMicBatchActive)) { await _socket?.stop(reason: 'transcription settings changed'); await _initiateWebsocket( @@ -1605,7 +1601,6 @@ class CaptureController extends ChangeNotifier Logger.debug('is ws null: ${_socket == null}'); Logger.debug('Initiating WebSocket with: codec=$codec, sampleRate=$sampleRate, channels=$channels, isPcm=$isPcm'); - // Get language and custom STT config String language = _preferences.hasSetPrimaryLanguage ? _preferences.userPrimaryLanguage : "multi"; final customSttConfig = _preferences.customSttConfig; final sessionToken = _sessionOwner?.token; @@ -2019,7 +2014,6 @@ class CaptureController extends ChangeNotifier if (!_starOngoingConversation) { markConversationForStarring(); PlatformManager.instance.analytics.omiDoubleTap(feature: 'star_conversation'); - // Haptic feedback to confirm HapticFeedback.mediumImpact(); } else { // Toggle off if already marked @@ -2062,7 +2056,6 @@ class CaptureController extends ChangeNotifier _startVoiceCommandTimeout(deviceId); _playSpeakerHaptic(deviceId, 1); } else { - // End on second tap debugPrint("Ending voice question session (toggle mode)"); _endVoiceCommandSession(deviceId); } @@ -2103,7 +2096,6 @@ class CaptureController extends ChangeNotifier if (snapshot.isEmpty || snapshot.length < 3) return; deviceAudioTap?.call(snapshot); - // Track bytes received from BLE _metrics.addBleBytes(snapshot.length); _recordingTelemetry.observeAudio(snapshot.length); @@ -2113,7 +2105,6 @@ class CaptureController extends ChangeNotifier // buffering can share these owned bytes without another full copy. final blePayload = source is BleDeviceSource && frames.length == 1 ? frames.single.payload : null; - // Command button triggered bool voiceCommandSupported = _recordingDevice != null ? (_recordingDevice?.type == DeviceType.omi || _recordingDevice?.type == DeviceType.openglass) : false; @@ -2170,7 +2161,6 @@ class CaptureController extends ChangeNotifier } } - // Send WS if (_socket?.state == SocketServiceState.connected) { final socketPayload = blePayload ?? source?.getSocketPayload(snapshot) ?? snapshot; if (positionedFrame?.captureRoot != null && positionedFrame!.payload.length == socketPayload.length) { @@ -2184,7 +2174,6 @@ class CaptureController extends ChangeNotifier _wedgeMonitor.onSocketBytesSent(wedgeSession.handle, socketPayload.length); } - // Track bytes sent to websocket _metrics.addSocketBytes(socketPayload.length); _recordingTelemetry.observeSent(socketPayload.length); @@ -2359,7 +2348,6 @@ class CaptureController extends ChangeNotifier }) : null; - // Create audio source for BLE device final pd = await device.getDeviceInfo(connection); if (_deviceIdentityStale(deviceRevision)) return; final deviceModel = pd.modelNumber.isNotEmpty ? pd.modelNumber : "Omi"; @@ -2828,17 +2816,14 @@ class CaptureController extends ChangeNotifier return const CaptureStageFailure('permission_denied'); } - // prepare await changeAudioRecordProfile(audioCodec: BleAudioCodec.pcm16, sampleRate: 16000); - // Initialize WAL for phone mic recording _activeSource = PhoneMicSource(); _phoneMicWalActive = true; await _wal.getSyncs().phone.onAudioCodecChanged(BleAudioCodec.pcm16); _wal.getSyncs().phone.setDeviceInfo('phone-mic', 'Phone Microphone'); setIsWalSupported(true); - // record try { await _phoneMic.start( onByteReceived: (bytes) { diff --git a/backend/docs/feature-flag-registry.md b/backend/docs/feature-flag-registry.md index f63e751997f..af008f85863 100644 --- a/backend/docs/feature-flag-registry.md +++ b/backend/docs/feature-flag-registry.md @@ -109,6 +109,7 @@ entries are exempt: they are queued for removal, not running. | `MEMORY_OWNER_JEV_SHADOW_PERCENT` | dazheng | [backend/docs/experiments/EXP-004-jev-relevance-owner-ramp.md](../../backend/docs/experiments/EXP-004-jev-relevance-owner-ramp.md) | 2026-10-21 | | `SPEAKER_GROUPING_SHADOW` | dazheng | [backend/docs/listen_pusher_pipeline.mdx](../../backend/docs/listen_pusher_pipeline.mdx) | 2026-11-08 | | `exp-002-desktop-identity-v1` | unowned | [backend/docs/experiments/EXP-002-desktop-identity-memory-v1.md](../../backend/docs/experiments/EXP-002-desktop-identity-memory-v1.md) | 2026-10-26 | +| `onboarding-variant` | nik | [product/experiments/onboarding-variant.md](../../product/experiments/onboarding-variant.md) | 2026-11-13 | | `paywall-variant` | nik | [product/experiments/paywall-variant.md](../../product/experiments/paywall-variant.md) | 2026-11-12 | ## Overdue for a decision @@ -152,6 +153,7 @@ and an explicit empty literal renders as `''`. | `MEMORY_OWNER_JEV_SHADOW_PERCENT` | Candidate-hash percentage for advisory owner measurement | backend | env | closed | — | 0 (cloud_run/backend-sync, cloud_run/backend-sync-backfill); 100 (backend-listen (chart), cloud_run/backend, gke/backend-listen, gke/pusher, pusher (chart)) | 100 (backend-listen (chart), cloud_run/backend, cloud_run/backend-sync, cloud_run/backend-sync-backfill, gke/backend-listen, gke/pusher, pusher (chart)) | — | pending | 2026-10-21 | dazheng | | `SPEAKER_GROUPING_SHADOW` | off\|owner\|cohort; default off; prod cached-evidence comparison at 10 percent; never changes transcript labels | backend | env | closed | — | — | cohort (backend-listen (chart), pusher (chart)); {value: 'cohort', category: rollout} (cloud_run/backend, cloud_run/backend-integration, cloud_run/backend-sync, cloud_run/backend-sync-backfill, gke/backend-listen, gke/pusher) | — | pending | 2026-11-08 | dazheng | | `exp-002-desktop-identity-v1` | EXP-002 memory_v1 desktop identity arm enrollment | backend, macos | posthog | closed | — | — | — | expected (enable) | pending | 2026-10-26 | unowned | +| `onboarding-variant` | Mobile first-run onboarding A/B/C after sign-in and consent: control (native steps unchanged) / reid (device-first onboarding rendered from a web page, OTA; payload names it) / yinka (onboarding ending in a first conversation, PR #21445, native). Client-evaluated multivariate flag; an unread flag, an unknown variant or a reid payload without an https URL is control; the page never owns completion | mobile | posthog | closed | — | — | — | expected (exposure) | pending | 2026-11-13 | nik | | `paywall-variant` | Mobile paywall A/B test: control (current PlansSheet) / outline (PR #21412 soft paywall, native) / web (webview paywall whose page is the variant). Client-evaluated multivariate flag; control collapses to the shipped sheet, paid readers and App Review builds never see a variant | mobile | posthog | closed | — | — | — | expected (exposure) | pending | 2026-11-12 | nik | ### rollout @@ -285,7 +287,6 @@ and an explicit empty literal renders as `''`. | `on_device_meeting_identity` | Enable on-device meeting identity on stable | macos | posthog | closed | — | — | — | absent (enable) | graduate | 2026-10-23 | dazheng | | `on_device_meeting_identity_kill` | Beta on-device meeting identity stop | macos | posthog | inverted | — | — | — | expected (kill) | pending | 2026-10-23 | unowned | | `onboarding-setup-rating-prompt` | Mobile onboarding Setting up your Omi page with the store-rating pre-prompt before the completion screen; client-evaluated, default off so the flow is unchanged and store review never sees it | mobile | posthog | closed | — | — | — | expected (enable) | pending | 2026-11-05 | nik | -| `onboarding-webview` | Mobile first-run onboarding rendered from a web page after sign-in and consent (OTA); off = native steps unchanged; the page never owns completion | mobile | posthog | closed | — | — | — | expected (enable) | pending | 2026-11-12 | nik | | `proactivity_v2` | Server v2 proactivity admission; absent or unknown denies | backend, llm-gateway | posthog | closed | — | — | — | expected (enable) | pending | 2026-11-03 | dazheng | | `screen_activity_lossless_sync` | Enable lossless screen sync on stable | macos | posthog | closed | — | — | — | absent (enable) | pending | 2026-10-22 | unowned | | `screen_activity_lossless_sync_kill` | Beta lossless screen sync stop | macos | posthog | inverted | — | — | — | expected (kill) | pending | 2026-10-22 | unowned | diff --git a/config/feature-flags.yaml b/config/feature-flags.yaml index 386718276cf..ae619aa73c9 100644 --- a/config/feature-flags.yaml +++ b/config/feature-flags.yaml @@ -11,7 +11,7 @@ flags: - {"key":"OMI_MESSAGING_RETENTION_DAYS","aliases":[],"kind":"env","lifecycle":"config_switch","surfaces":["backend"],"summary":"Hourly messaging maintenance retains done inbox markers and unknown identities for 30 days by default; minimum seven; does not send.","fail":"closed","owner":"dazheng","created":"2026-10-14","decision":"keep"} - {"key":"OWNER_IDENTITY_RETRY_COALESCE_ENABLED","aliases":[],"kind":"env","lifecycle":"rollout","surfaces":["backend"],"summary":"Default false; bounded per-UID late identity coalescing, 16 cohorts, 128 candidates and eight eligible passes per cohort; two workers unchanged","fail":"closed","owner":"dazheng","created":"2026-10-14","review_by":"2026-11-13","decision":"pending"} - {"key":"paywall-variant","prereg":"product/experiments/paywall-variant.md","aliases":[],"kind":"posthog","lifecycle":"experiment","surfaces":["mobile"],"summary":"Mobile paywall A/B test: control (current PlansSheet) / outline (PR #21412 soft paywall, native) / web (webview paywall whose page is the variant). Client-evaluated multivariate flag; control collapses to the shipped sheet, paid readers and App Review builds never see a variant","fail":"closed","owner":"nik","created":"2026-10-13","review_by":"2026-11-12","decision":"pending","posthog":{"row":"expected","role":"exposure"},"notes":"PostHog id 957521, control 100% until the build is in the stores; the split is the experiment. Payload per variant: {\"url\": \"https://…\" (web only, https), \"onboarding_paywall\": true|false}. Debug builds: --dart-define=OMI_PAYWALL_VARIANT=outline|web [OMI_PAYWALL_WEB_URL=…]."} - - {"key":"onboarding-variant","aliases":["onboarding-webview"],"kind":"posthog","lifecycle":"experiment","surfaces":["mobile"],"summary":"Mobile first-run onboarding A/B/C after sign-in and consent: control (native steps unchanged) / reid (device-first onboarding rendered from a web page, OTA; payload names it) / yinka (onboarding ending in a first conversation, PR #21445, native). Client-evaluated multivariate flag; an unread flag, an unknown variant or a reid payload without an https URL is control; the page never owns completion","fail":"closed","owner":"nik","created":"2026-10-14","review_by":"2026-11-13","decision":"pending","posthog":{"row":"expected","role":"exposure"},"notes":"Replaces onboarding-webview (PostHog 957522, test-account only). Payload for reid: {\"url\": \"https://…\"} (https). A page that errors or never reports ready within 10s falls back to native onboarding. Debug builds: --dart-define=OMI_ONBOARDING_VARIANT=reid|yinka [OMI_ONBOARDING_WEBVIEW_URL=https://…]."} + - {"key":"onboarding-variant","prereg":"product/experiments/onboarding-variant.md","aliases":[],"kind":"posthog","lifecycle":"experiment","surfaces":["mobile"],"summary":"Mobile first-run onboarding A/B/C after sign-in and consent: control (native steps unchanged) / reid (device-first onboarding rendered from a web page, OTA; payload names it) / yinka (onboarding ending in a first conversation, PR #21445, native). Client-evaluated multivariate flag; an unread flag, an unknown variant or a reid payload without an https URL is control; the page never owns completion","fail":"closed","owner":"nik","created":"2026-10-14","review_by":"2026-11-13","decision":"pending","posthog":{"row":"expected","role":"exposure"},"notes":"Replaces onboarding-webview (PostHog 957522, test-account only). Payload for reid: {\"url\": \"https://…\"} (https). A page that errors or never reports ready within 10s falls back to native onboarding. Debug builds: --dart-define=OMI_ONBOARDING_VARIANT=reid|yinka [OMI_ONBOARDING_WEBVIEW_URL=https://…]."} - {"key":"DIARIZATION_V2_ENABLED","aliases":[],"kind":"env","lifecycle":"config_switch","surfaces":["backend"],"summary":"Enable the separate batch-only diarizer app; default false; startup loads models before readiness","fail":"closed","owner":"dazheng","created":"2026-10-14","decision":"keep"} - {"key":"DREAM_AGENT_FULL_SAMPLES","aliases":[],"kind":"env","lifecycle":"config_switch","surfaces":["backend"],"summary":"Concurrent independent full Dream reasoning samples; default 3, bounded to 1 through 8; strict majority among returned samples; reserve one JSON retry per sample","fail":"closed","owner":"dazheng","created":"2026-10-13","decision":"pending"} - {"key":"DREAM_AGENT_CONTEXT_MODE","aliases":[],"kind":"env","lifecycle":"rollout","surfaces":["backend"],"summary":"Dream context mode bounded (default) or full; full requires the separate UID allowlist","fail":"closed","owner":"dazheng","created":"2026-10-13","decision":"pending","review_by":"2026-11-12"} diff --git a/product/experiments/onboarding-variant.md b/product/experiments/onboarding-variant.md new file mode 100644 index 00000000000..da1e342f40f --- /dev/null +++ b/product/experiments/onboarding-variant.md @@ -0,0 +1,36 @@ +# Pre-registration: mobile first-run onboarding A/B/C (`onboarding-variant`) + +**Question.** Which first-run onboarding gets more new mobile readers to finish onboarding and to +pay: today's steps, a device-first onboarding, or one that ends in a first real conversation? + +**Arms** (PostHog multivariate flag `onboarding-variant`, id 961069, experience continuity on): +- `control` — the shipped native steps, unchanged. +- `reid` — Reid's device-first onboarding (PR #21412 prototype) rendered from a web page after + sign-in and consent (payload `url`); a page that errors or never reports ready falls back to + control. +- `yinka` — Yinka's onboarding (PR #21445): Name → Permissions → Language → Connect Omi → a first + real conversation with its summary and to-dos, the paywall and a rating ask, native. +New designs join as new arms; the split is set in PostHog, never in code. + +**Unit.** A signed-in reader who reaches the steps after consent. Exposure = `Onboarding Variant +Shown` (property `$feature/onboarding-variant`), emitted once by the onboarding wrapper at the +moment the arm is entered; an unread flag counts as control. + +**Primary metrics.** `Onboarding Completed` per exposed reader (funnel, 7-day window) and +`Upgrade Succeeded` per exposed reader (funnel, 7-day window). + +**Secondary / guardrails.** `Paywall Variant Shown` per reader; new MRR (`Billing Subscription +Started`, sum `mrr_cents`); `Conversation Processing Completed` per reader (first conversation); +`Product Error` per reader (crash proxy). Dashboard 2212642 breaks the funnel down by +`$feature/onboarding-variant`; experiment 484222 holds the significance stats. + +**Decision rule.** Minimum detectable effect 10% relative on the primary metrics at PostHog's +default confidence; an arm whose Product Error rate is worse than control by more than 20% +relative, or whose completion is worse than control by more than 20% relative, is switched to 0% +regardless of conversion. Run until each arm has at least 2,000 exposed readers or 14 days, +whichever is later. + +**Kill switch.** Set the flag's split to control 100% (or deactivate the flag); every build +falls back to the shipped steps without a release. + +**Owner.** nik. Created 2026-10-14. Review by 2026-11-13.