diff --git a/.gitignore b/.gitignore index 8a9a164dd..410aab404 100644 --- a/.gitignore +++ b/.gitignore @@ -85,3 +85,4 @@ liblantern_amd64.dylib lib/generated_bindings.dart *.env +ExportOptions.plist diff --git a/.tx/config b/.tx/config index 2f23ce963..445d9b939 100644 --- a/.tx/config +++ b/.tx/config @@ -12,9 +12,8 @@ lang_map = zh-Hant: zh-rHK, bn_IN: bn-rIN, bg_BG: bg-rBG, ku_IQ: ku-rIQ, pl_ [o:lantern-1:p:lantern:r:lantern-flutter] file_filter = assets/locales/.po -source_file = assets/locales/en.po +source_file = assets/locales/en-us.po source_lang = en_US type = PO minimum_perc = 80 -lang_map = hi_IN: hi-in, zh_CN: zh-cn, zh_HK: zh-hk, ru_RU: ru-ru, fa_IR: fa-ir, es_CU: es-cu, es_EU: es-es, fr_FR: fr-fr, fr_CA: fr-ca - +lang_map = hi_IN: hi-in, zh_CN: zh-cn, zh_HK: zh-hk, ru_RU: ru-ru, fa_IR: fa-ir, es_CU: es-cu, es: es-es, fr_FR: fr-fr, fr_CA: fr-ca, ar: ar-eg, ur: ur-in, vi: vi-vn, tr: tr-tr, th: th-th, bn: bn-bd, my: my-mm, ms: ms-my \ No newline at end of file diff --git a/Makefile b/Makefile index 6d973cb53..1c2620da9 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,9 @@ internalsdk/protos/%.pb.go: protos_shared/%.proto internalsdk/protos/vpn.pb.go: protos_shared/vpn.proto @protoc --go_out=internalsdk protos_shared/vpn.proto +internalsdk/protos/auth.pb.go: protos_shared/auth.proto + @protoc --go_out=internalsdk protos_shared/auth.proto + # Compiles autorouter routes routes: lib/core/router/router.gr.dart @@ -462,6 +465,10 @@ set-version: ios-release:set-version guard-SENTRY_AUTH_TOKEN guard-SENTRY_ORG guard-SENTRY_PROJECT_IOS build-framework + @echo "Flutter Clean" + flutter clean + @echo "Flutter pub get" + flutter pub get @echo "Creating the Flutter iOS build..." flutter build ipa --flavor prod --release --export-options-plist ./ExportOptions.plist @echo "Uploading debug symbols to Sentry..." @@ -692,6 +699,21 @@ build-framework: assert-go-version install-gomobile mkdir -p $(INTERNALSDK_FRAMEWORK_DIR) mv ./$(INTERNALSDK_FRAMEWORK_NAME) $(INTERNALSDK_FRAMEWORK_DIR)/$(INTERNALSDK_FRAMEWORK_NAME) +build-release-framework: assert-go-version install-gomobile + @echo "Nuking $(INTERNALSDK_FRAMEWORK_DIR) and $(MINISQL_FRAMEWORK_DIR)" + rm -Rf $(INTERNALSDK_FRAMEWORK_DIR) $(MINISQL_FRAMEWORK_DIR) + @echo "generating Ios.xcFramework" + go env -w 'GOPRIVATE=github.com/getlantern/*' && \ + gomobile init && \ + gomobile bind -target=ios \ + -tags='headless lantern ios netgo' \ + -ldflags="$(LDFLAGS)" \ + $(GOMOBILE_EXTRA_BUILD_FLAGS) \ + github.com/getlantern/lantern-client/internalsdk github.com/getlantern/pathdb/testsupport github.com/getlantern/pathdb/minisql github.com/getlantern/lantern-client/internalsdk/ios + @echo "moving framework" + mkdir -p $(INTERNALSDK_FRAMEWORK_DIR) + mv ./$(INTERNALSDK_FRAMEWORK_NAME) $(INTERNALSDK_FRAMEWORK_DIR)/$(INTERNALSDK_FRAMEWORK_NAME) + install-gomobile: @echo "installing gomobile" && \ diff --git a/android/app/src/main/java/org/getlantern/lantern/model/ProPlan.java b/android/app/src/main/java/org/getlantern/lantern/model/ProPlan.java index 8e6e92ecc..fd5ac1c88 100644 --- a/android/app/src/main/java/org/getlantern/lantern/model/ProPlan.java +++ b/android/app/src/main/java/org/getlantern/lantern/model/ProPlan.java @@ -292,7 +292,7 @@ public String getFormattedPrice(Map price) { return getFormattedPrice(price, false); } - private String getFormattedPrice(Map price, boolean formatFloat) { + private String getFormattedPrice(Map price, boolean formatFloat) { final String formattedPrice; Long currencyPrice = price.get(currencyCode); if (currencyPrice == null) { diff --git a/assets/images/add_account_ illustration.svg b/assets/images/add_account_ illustration.svg new file mode 100644 index 000000000..8e71b4c65 --- /dev/null +++ b/assets/images/add_account_ illustration.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/empty_check.svg b/assets/images/empty_check.svg new file mode 100644 index 000000000..717a634d8 --- /dev/null +++ b/assets/images/empty_check.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/images/eye.svg b/assets/images/eye.svg new file mode 100644 index 000000000..cca0b9faf --- /dev/null +++ b/assets/images/eye.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/images/eye_cross.svg b/assets/images/eye_cross.svg new file mode 100644 index 000000000..8e2397e83 --- /dev/null +++ b/assets/images/eye_cross.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/images/lock_filled.svg b/assets/images/lock_filled.svg new file mode 100644 index 000000000..1a69a8d9f --- /dev/null +++ b/assets/images/lock_filled.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/images/sign_in.svg b/assets/images/sign_in.svg new file mode 100644 index 000000000..45d1cd409 --- /dev/null +++ b/assets/images/sign_in.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/images/sign_out.svg b/assets/images/sign_out.svg new file mode 100644 index 000000000..baf7f492c --- /dev/null +++ b/assets/images/sign_out.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/images/warning.svg b/assets/images/warning.svg new file mode 100644 index 000000000..801231310 --- /dev/null +++ b/assets/images/warning.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/locales/ar-eg.po b/assets/locales/ar-eg.po index a4029b3ae..0566f362a 100644 --- a/assets/locales/ar-eg.po +++ b/assets/locales/ar-eg.po @@ -3,23 +3,24 @@ # Abdelaziz “Azhou” Houcini , 2022 # محيي الدين , 2022 # Layla Taha , 2022 -# Derek F , 2022 -# lamine Kacimi , 2023 # ouss , 2023 # Mhawesh, 2023 -# e2f , 2023 -# Amin Jobran, 2023 -# Rima Sghaier, 2023 # Abdulaziz AlObaili , 2023 # Lantern, 2023 # dc64c51d94429ef6835e68b9062d8713_f0bbcb2 , 2023 -# 160565ee57b988ee904d03fec374ff93_4f79b8c <73ec7ace4de62e6bb6454cb54e990a4d_721149>, 2023 -# 6ad90dadbe5d08991a98500e1efbdbb4_9d79615 <7392e61add82b364ab0393a04573af48_721156>, 2023 -# Abed Abedd , 2023 +# 160565ee57b988ee904d03fec374ff93_4f79b8c <73ec7ace4de62e6bb6454cb54e990a4d_721149>, 2024 +# Rima Sghaier, 2024 +# Amin Jobran, 2024 +# lamine Kacimi , 2024 +# Abed Abedd , 2024 +# 6ad90dadbe5d08991a98500e1efbdbb4_9d79615 <7392e61add82b364ab0393a04573af48_721156>, 2024 +# Derek F , 2024 +# e2f , 2024 +# tx_e2f_ar_r5 tx_e2f_ar_r5, 2024 # msgid "" msgstr "" -"Last-Translator: Abed Abedd , 2023\n" +"Last-Translator: tx_e2f_ar_r5 tx_e2f_ar_r5, 2024\n" "Language-Team: Arabic (https://app.transifex.com/lantern-1/teams/94371/ar/)\n" "Language: ar\n" "Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" @@ -41,6 +42,98 @@ msgstr "ربما لاحقًا" msgid "try" msgstr "جربه" +msgid "sign_in" +msgstr "تسجيل الدخول" + +msgid "enter_email" +msgstr "أدخل البريد الإلكتروني" + +msgid "enter_password" +msgstr "أدخل كلمة المرور" + +msgid "forgot_password" +msgstr "نسيت كلمة المرور" + +msgid "forgot_your_password" +msgstr "هل نسيت كلمة المرور؟" + +msgid "click_here" +msgstr "انقر هنا" + +msgid "reset_password" +msgstr "إعادة تعيين كلمة المرور" + +msgid "lantern_pro_email" +msgstr "بريد إلكتروني Lantern Pro" + +msgid "return_to_sign_in" +msgstr "العودة إلى تسجيل الدخول" + +msgid "enter_confirmation_code" +msgstr "أدخل رمز التأكيد" + +msgid "confirmation_code_msg" +msgstr "" +"تم إرسال رمز تأكيد إلى XX. أدخله هنا لتأمين حسابك وتمكين استرداد كلمة " +"المرور." + +msgid "resend_confirmation_code" +msgstr "إعادة إرسال بريد إلكتروني بالتأكيد" + +msgid "email_already_exists" +msgstr "البريد الإلكتروني موجود بالفعل" + +msgid "email_already_exists_msg" +msgstr "" +"هذا البريد الإلكتروني مسجّل بالفعل، إذا كان لديك حساب بهذا البريد " +"الإلكتروني، يمكنك استرداده. إذا لم يكن لديك حساب، يرجى العودة واختيار عنوان " +"بريد إلكتروني مختلف." + +msgid "recover_account" +msgstr "استرداد الحساب" + +msgid "back" +msgstr "الرجوع" + +msgid "change_email" +msgstr "تغيير البريد الإلكتروني" + +msgid "new_password" +msgstr "كلمة مرور جديدة" + +msgid "confirm_new_password" +msgstr "تأكيد كلمة المرور الجديدة" + +msgid "already_have_an_account" +msgstr "هل لديك حساب بالفعل؟" + +msgid "create_account" +msgstr "إنشاء حساب" + +msgid "create_password" +msgstr "إنشاء كلمة السر" + +msgid "by_creating_an_account" +msgstr "عند إنشاء حساب، فإنك توافق على" + +msgid "confirm_email" +msgstr "أكّد البريد الاكتروني" + +msgid "update_pro_account" +msgstr "تحديث حساب Pro" + +msgid "update_pro_account_message" +msgstr "" +"تطرأ حاليًا تغييرات على Lantern Pro! الآن، ستحتاج إلى بريد إلكتروني وكلمة " +"مرور لتسجيل الدخول على جميع الأجهزة، وللوصول إلى إدارة الحساب. يرجى تخصيص " +"بضع لحظات لإضافة هذه البيانات إلى حسابك." + +msgid "not_now" +msgstr "ليس الأن" + +msgid "update_account" +msgstr "تحديث الحساب" + msgid "new" msgstr "جديد" @@ -50,6 +143,12 @@ msgstr "الاستخدام اليومي للبيانات" msgid "is" msgstr "يتم الآن" +msgid "connect" +msgstr "اتصال" + +msgid "disconnect" +msgstr "قطع الاتصال" + msgid "connected" msgstr "موصول" @@ -83,7 +182,7 @@ msgid "Go Pro Title" msgstr "الترقية إلى النسخة الاحترافية!" msgid "Go Pro Description" -msgstr "" +msgstr "افتح سرعات أسرع، تجربة خالية من الإعلانات، وبيانات غير محدودة! " msgid "Server Location" msgstr "موقع الخادم" @@ -116,7 +215,7 @@ msgstr "أؤكد رغبتي في حذف بيانات الدردشة" msgid "Pro Account Management" msgstr "إدارة حساب برو" -msgid "Link Device" +msgid "link_device" msgstr "ربط الجهاز" msgid "Upgrade to Lantern Pro" @@ -164,12 +263,6 @@ msgstr "عام" msgid "about" msgstr "حول" -msgid "privacy_policy" -msgstr "سياسة الخصوصية" - -msgid "terms_of_service" -msgstr "شروط الخدمة" - msgid "proxy_all" msgstr "كل شيء على البروكسي" @@ -192,7 +285,7 @@ msgid "select_an_issue" msgstr "يرجى اختيار مشكلة" msgid "enter_description" -msgstr "" +msgstr "يرجى وصف مشكلتك " msgid "cannot_access_blocked_sites" msgstr "لا يمكن الوصول الى المواقع المحظورة" @@ -200,17 +293,35 @@ msgstr "لا يمكن الوصول الى المواقع المحظورة" msgid "cannot_complete_purchase" msgstr "لا يمكن إكمال عملية الشراء" +msgid "discover_not_working" +msgstr "ميزة الاكتشاف لا تعمل " + msgid "cannot_sign_in" msgstr "لا يُمكن تسجيل الدخول" msgid "spinner_loads_endlessly" msgstr "العجلة تدور بلا نهاية" +msgid "slow" +msgstr "بطئ" + +msgid "cannot_link_devices" +msgstr "لا يمكن ربط الأجهزة" + +msgid "wrong_device_linking_code" +msgstr "رامز ربط الجهاز خاطئ أو غير صالح" + +msgid "cannot_remove_device" +msgstr "لا يمكن إزالة الجهاز " + +msgid "application_crashes" +msgstr "تعطل التطبيق" + msgid "other" msgstr "أخرى" msgid "issue_description" -msgstr "وصف المشكلة" +msgstr "وصف مشكلتك " msgid "check_for_updates" msgstr "تحقق من وجود تحديثات" @@ -252,10 +363,10 @@ msgid "device_linking_pin" msgstr "رقم التعريف الشخصي لربط الجهاز" msgid "link_device_step_one" -msgstr "" +msgstr "افتح عنصر القائمة \"إضافة جهاز\" على جهاز Pro الخاص بك. " msgid "link_device_step_two" -msgstr "" +msgstr "أدخل رقم PIN لربط الجهاز واضغط على إرسال " msgid "ensure_most_recent_version_lantern" msgstr "* تأكد من أن كلا الجهازين يعملان بأحدث إصدار من Lantern" @@ -272,14 +383,14 @@ msgstr "يحتاج إلى الوصول إلى البريد الإلكتروني msgid "Link via Email" msgstr "ربط عبر البريد الإلكتروني" -msgid "lantern_pro_email" -msgstr "البريد الإلكتروني الخاص بـ Lantern Pro" - msgid "lantern_desktop" msgstr "لانترن للكمبيوتر المكتبي" +msgid "report_description" +msgstr "وصف المشكلة" + msgid "report_issue" -msgstr "" +msgstr "الإبلاغ عن مشكلة " msgid "report_an_issue" msgstr "ابلاغ عن مشكلة" @@ -300,7 +411,7 @@ msgstr "" "الرابط أعلاه." msgid "share_link" -msgstr "" +msgstr "الإبلاغ عن مشكلة " msgid "share_title" msgstr "مشاركة رابط تنزيل لانترن" @@ -332,8 +443,8 @@ msgstr "الشهر" msgid "months" msgstr "الشهور" -msgid "enter_email" -msgstr "أدخل البريد الإلكتروني" +msgid "enter_email_to_complete_purchase" +msgstr "أدخل البريد الإلكتروني لإكمال الشراء " msgid "error_fetching_plans" msgstr "حدث خطأ أثناء إحضار الخطط، تُرجى إعادة تشغيل Lantern وإعادة المحاولة." @@ -363,10 +474,12 @@ msgid "referral_code" msgstr "رمز الإحالة" msgid "share_referral_code" -msgstr "" +msgstr "مشاركة كود الإحالة " msgid "share_message_referral_code" msgstr "" +"للحصول على شهر مجاني من Lantern Pro، استخدم كود الإحالة هذا: %s ; يمكنك " +"تنزيله من هنا: https://lantern.io/download " msgid "add_referral_code" msgstr "أضف رمز الإحالة" @@ -435,7 +548,7 @@ msgstr "" "يصل إلى ثلاثة أجهزة بحسابك. " msgid "by_clicking_tos" -msgstr "بالضغط على %s، فإنك بذلك توافق على " +msgstr "عند النقر على %s، فإنك توافق على" msgid "tos" msgstr "شروط الخدمة" @@ -485,6 +598,18 @@ msgstr "نعم" msgid "No" msgstr "لا" +msgid "exit" +msgstr "خروج " + +msgid "show" +msgstr "عرض " + +msgid "status_on" +msgstr "الحالة: تشغيل " + +msgid "status_off" +msgstr "الحالة: إيقاف " + msgid "Enter or paste linking code" msgstr "أدخل أو الصق رمز الربط" @@ -523,6 +648,9 @@ msgstr "تسجيل الخروج" msgid "confirm_remove_device" msgstr "هل أنت متأكد من أنك تريد فك ربط هذا الجهاز مع حساب برو؟" +msgid "confirm_close_window" +msgstr "هل أنت متأكد أنك تريد إغلاق Lantern؟ " + msgid "unlimited_data" msgstr "بيانات غير محدودة" @@ -535,6 +663,9 @@ msgstr "بدون إعلانات" msgid "choose_plan" msgstr "اختر الخطة" +msgid "billed_one_time" +msgstr "تمت الفوترة مرة واحدة " + msgid "connect_up_to_3_devices" msgstr "توصيل ما يصل إلى 3 أجهزة" @@ -730,9 +861,6 @@ msgstr "لن تختفي جميع الرسائل بالنسبة لك و%s." msgid "message_disappearing_description" msgstr "ستختفي جميع الرسائل بعد مرور %s بالنسبة لك و%s." -msgid "delete" -msgstr "حذف" - msgid "view_contact_info" msgstr "عرض معلومات جهة الاتصال" @@ -1535,3 +1663,234 @@ msgstr "أسئلة متكررة" msgid "add_device" msgstr "أضف جهاز" + +msgid "search_apps" +msgstr "البحث عن التطبيقات " + +msgid "user_not_found" +msgstr "" +"المستخدم غير معروف. يرجى التحقق من بيانات اعتمادك وحاول مرة أخرى. إذا مازلت " +"تواجه مشكلات، يرجى التواصل مع فريق الدعم للحصول على المساعدة." + +msgid "email_resend_message" +msgstr "تم إرسال رمز التأكيد الجديد الخاص بك" + +msgid "signup_error" +msgstr "" +"يبدو أن هناك خطأ ما قد حدث أثناء التسجيل. يرجى المحاولة مرة أخرى، وفي حال " +"استمرت المشكلة، يرجى التواصل مع فريق الدعم لدينا." + +msgid "signup_error_user_exists" +msgstr "" +"يبدو أن هناك خطأ ما قد حدث أثناء التسجيل. يرجى المحاولة مرة أخرى عن طريق " +"إعادة تعيين كلمة المرور الخاصة بك، وفي حال استمرت المشكلة، يرجى التواصل مع " +"فريق الدعم لدينا." + +msgid "confirm_email_error" +msgstr "يرجى تأكيد بريدك الإلكتروني" + +msgid "password_cannot_be_empty" +msgstr "لا يمكن أن يكون حقل كلمة المرور فارغًا" + +msgid "password_must_be_at_least_8_characters" +msgstr "يجب ألا يقل طول كلمة المرور عن 8 أحرف أو أكثر" + +msgid "sign_out" +msgstr "تسجيل الخروج" + +msgid "sign_out_message" +msgstr "" +"سيؤدي تسجيل الخروج إلى تحويل Lantern إلى الإصدار المجاني. للوصول إلى حسابك " +"مرة أخرى، سيتعين عليك تسجيل الدخول مرة أخرى." + +msgid "change_password" +msgstr "تغيير كلمة المرور" + +msgid "password" +msgstr "كلمة المرور" + +msgid "new_email" +msgstr "عنوان بريد إلكتروني جديد" + +msgid "new_email_same_as_old_email" +msgstr "" +"لا يمكن أن يكون عنوان البريد الإلكتروني الجديد مطابقًا لعنوان بريدك " +"الإلكتروني القديم" + +msgid "danger_zone" +msgstr "منطقة الخطر" + +msgid "delete_account" +msgstr "حذف الحساب" + +msgid "delete" +msgstr "حذف" + +msgid "delete_account_message" +msgstr "" +"لا يمكن التراجع عن حذف الحساب. سوف تفقد إمكانية الوصول إلى جميع الأجهزة " +"وتتنازل عن أي وقت متبقٍ في حساب Pro دون استرداد." + +msgid "delete_your_account" +msgstr "هل تريد حذف حسابك؟" + +msgid "confirm_deletion" +msgstr "تأكيد الحذف" + +msgid "account_deleted" +msgstr "تم حذف الحساب" + +msgid "account_deleted_message" +msgstr "تم حذف حسابك بشكل دائم." + +msgid "password_has_been_updated" +msgstr "تم تحديث كلمة المرور" + +msgid "password_has_been_updated_message" +msgstr "تم إعادة تعيين كلمة المرور الخاصة بك بنجاح" + +msgid "invalid_code" +msgstr "" +"رمز غير صحيح أو قد يكون منتهي الصلاحية. يرجى المحاولة مرة أخرى عن طريق إعادة" +" الإرسال" + +msgid "get_lantern_pro" +msgstr "إحصل على لانترن برو" + +msgid "try_lantern_pro" +msgstr "جرّب Lantern مجانًا!" + +msgid "continue_for_free" +msgstr "المتابعة مجانًا" + +msgid "recovery_not_found" +msgstr "الجهاز غير مرتبط بهذا المستخدم" + +msgid "device_added" +msgstr "الجهاز غير مرتبط بهذا المستخدم" + +msgid "device_added_message" +msgstr "" +"تمت إضافة جهازك بنجاح. إذا لم تر الجهاز متصلاً، يرجى إعادة تشغيل التطبيق على" +" كلا الجهازين " + +msgid "wrong_link_code" +msgstr "الرمز الذي أدخلته غير صحيح. يرجى التحقق والمحاولة مرة أخرى." + +msgid "recovery_code_sent" +msgstr "تم إرسال رمز الاسترداد" + +msgid "device_added_msg" +msgstr "تم ربط هذا الجهاز بـ %s" + +msgid "new_to_lantern" +msgstr "هل أنت جديد في Lantern؟" + +msgid "email_has_been_updated" +msgstr "تم تحديث البريد الإلكتروني" + +msgid "email_has_been_updated_message" +msgstr "لقد تم تغيير بريدك الإلكتروني بنجاح" + +msgid "email_has_been_verified" +msgstr "تم التحقق من صحة البريد الإلكتروني" + +msgid "email_has_been_verified_message" +msgstr "تم التحقق من صحة بريدك الإلكتروني بنجاح" + +msgid "device_limit_reached" +msgstr "تم الوصول إلى الحد الأقصى للأجهزة" + +msgid "device_limit_reached_message" +msgstr "" +"أنت مسجل الدخول حاليًا على العدد الأقصى من الأجهزة (3). لتسجيل الدخول على " +"هذا الجهاز، يرجى اختيار وإزالة أحد أجهزتك الأخرى أدناه." + +msgid "enter_activation_code" +msgstr "أدخل رمز التفعيل" + +msgid "check_your_email" +msgstr "تحقق من عنوان بريدك الإلكتروني" + +msgid "please_verify_email" +msgstr "يرجى التحقق من أن عنوان البريد الإلكتروني الذي أدخلته صحيح." + +msgid "verify" +msgstr "تحقق من الصحة" + +msgid "cannot_login" +msgstr "لا يمكن تسجيل الدخول " + +msgid "privacy_policy" +msgstr "سياسة الخصوصية" + +msgid "terms_of_service" +msgstr "شروط الخدمة" + +msgid "invalid_verification_code" +msgstr "كود تحقق غير صحيح " + +msgid "wrong_seller_code" +msgstr "كود البائع غير صحيح. يرجى التحقق والمحاولة مرة أخرى. " + +msgid "follow_us" +msgstr "تابعنا " + +msgid "follow_us_telegram" +msgstr "تابعنا على Telegram " + +msgid "follow_us_instagram" +msgstr "تابعنا على Instagram " + +msgid "follow_us_facebook" +msgstr "تابعنا على Facebook " + +msgid "follow_us_x" +msgstr "تابعنا على X " + +msgid "report_issue_error" +msgstr "نواجه صعوبات تقنية. حاول مرة أخرى لاحقًا. " + +msgid "we_are_experiencing_technical_difficulties" +msgstr "نواجه صعوبات تقنية. حاول مرة أخرى لاحقًا. " + +msgid "a_temporary_error_occurred" +msgstr "حدث خطأ مؤقت " + +msgid "sorry_we_are_unable_to_load_that_page" +msgstr "" +"عذرًا، لا يمكن تحميل هذه الصفحة في الوقت الحالي، يرجى الضغط على \"تحديث\" " +"للمحاولة مرة أخرى. " + +msgid "refresh" +msgstr "تحديث" + +msgid "check_your_internet_connection" +msgstr "تحقق من اتصال الإنترنت لديك " + +msgid "please_try" +msgstr "يرجى المحاولة مرة أخرى " + +msgid "turning_off_airplane_mode" +msgstr "إيقاف وضع الطيران " + +msgid "turning_on_mobile_data_or_wifi" +msgstr "تشغيل البيانات المحمولة أو الواي فاي " + +msgid "check_the_signal_in_your_area" +msgstr "تحقق من الإشارة في منطقتك " + +msgid "got_it" +msgstr "فهمت" + +msgid "fetching_configuration" +msgstr "جلب التكوين " + +msgid "establish_connection_to_server" +msgstr "إقامة اتصال بالخادم " + +msgid "file_size_limit_title" +msgstr "تم الوصول إلى الحد " + +msgid "file_size_limit_description" +msgstr "يجب أن تكون أحجام المرفقات أقل من 100 ميجابايت" diff --git a/assets/locales/bn-bd.po b/assets/locales/bn-bd.po index 753b07c41..0d72c7421 100644 --- a/assets/locales/bn-bd.po +++ b/assets/locales/bn-bd.po @@ -1,13 +1,14 @@ # # Translators: -# Al Shahrior Hasan Sagor , 2021 # Lantern, 2023 -# tx_e2f_bn c3 , 2023 -# code smite , 2023 +# Al Shahrior Hasan Sagor , 2024 +# code smite , 2024 +# tx_e2f_bn c3 , 2024 +# Derek F , 2024 # msgid "" msgstr "" -"Last-Translator: code smite , 2023\n" +"Last-Translator: Derek F , 2024\n" "Language-Team: Bengali (https://app.transifex.com/lantern-1/teams/94371/bn/)\n" "Language: bn\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" @@ -29,6 +30,99 @@ msgstr "সম্ভবত পরে" msgid "try" msgstr "এটি ব্যবহার করে দেখুন" +msgid "sign_in" +msgstr "সাইন ইন করুন" + +msgid "enter_email" +msgstr "ইমেল লিখুন" + +msgid "enter_password" +msgstr "পাসওয়ার্ড লিখুন " + +msgid "forgot_password" +msgstr "পাসওয়ার্ড ভুলে গেছেন " + +msgid "forgot_your_password" +msgstr "আপনি কি আপনার পাসওয়ার্ড ভুলে গেছেন? " + +msgid "click_here" +msgstr "এখানে ক্লিক করুন " + +msgid "reset_password" +msgstr "পাসওয়ার্ড রিসেট করুন" + +msgid "lantern_pro_email" +msgstr "ল্যান্টার্ন প্রো ইমেইল " + +msgid "return_to_sign_in" +msgstr "প্রবেশে ফিরে যান " + +msgid "enter_confirmation_code" +msgstr "নিশ্চিতকরণ কোড লিখুন " + +msgid "confirmation_code_msg" +msgstr "" +"একটি নিশ্চিতকরণ কোড XX এ পাঠানো হয়েছে। এটি এখানে লিখুন আপনার অ্যাকাউন্ট " +"নিরাপদ করতে এবং পাসওয়ার্ড পুনরুদ্ধার সক্ষম করতে। " + +msgid "resend_confirmation_code" +msgstr "নিশ্চিতকরণ ইমেইল পুনরায় পাঠান " + +msgid "email_already_exists" +msgstr "ইমেইল ইতিমধ্যে বিদ্যমান " + +msgid "email_already_exists_msg" +msgstr "" +"এই ইমেইলটি নেওয়া হয়েছে, যদি আপনার এই ইমেইল সহ একটি অ্যাকাউন্ট থাকে আপনি " +"এটি পুনরুদ্ধার করতে পারেন। যদি না হয়, অনুগ্রহ করে ফিরে যান এবং একটি ভিন্ন " +"ইমেইল ঠিকানা নির্বাচন করুন। " + +msgid "recover_account" +msgstr "অ্যাকাউন্ট পুনরুদ্ধার করুন " + +msgid "back" +msgstr "ফিরে যান " + +msgid "change_email" +msgstr "ইমেইল পরিবর্তন করুন " + +msgid "new_password" +msgstr "নতুন পাসওয়ার্ড" + +msgid "confirm_new_password" +msgstr "নতুন পাসওয়ার্ড নিশ্চিত করুন" + +msgid "already_have_an_account" +msgstr "ইতিমধ্যে একটি অ্যাকাউন্ট আছে? " + +msgid "create_account" +msgstr "অ্যাকাউন্ট তৈরি করুন" + +msgid "create_password" +msgstr "পাসওয়ার্ড তৈরি করুন " + +msgid "by_creating_an_account" +msgstr "একটি অ্যাকাউন্ট তৈরি করে, আপনি আমাদের সাথে সম্মত হন " + +msgid "confirm_email" +msgstr "ইমেইল নিশ্চিত করুন" + +msgid "update_pro_account" +msgstr "প্রো অ্যাকাউন্ট আপডেট করুন " + +msgid "update_pro_account_message" +msgstr "" +"ল্যান্টার্ন প্রোতে পরিবর্তন আসছে! এখন, আপনি সমস্ত ডিভাইসে লগ ইন করতে এবং " +"অ্যাকাউন্ট ব্যবস্থাপনায় অ্যাক্সেস করতে একটি ইমেইল এবং পাসওয়ার্ড প্রয়োজন " +"হবে। অনুগ্রহ করে এই প্রমাণপত্রগুলি আপনার অ্যাকাউন্টে যোগ করার জন্য কয়েক " +"মুহূর্ত নিন। " + +msgid "not_now" +msgstr "এখন নয় " + +msgid "update_account" +msgstr "অ্যাকাউন্ট আপডেট করুন " + msgid "new" msgstr "নতুন" @@ -38,6 +132,12 @@ msgstr "প্রাত্যহিক ডেটা ব্যবহারের msgid "is" msgstr "সংযোগ" +msgid "connect" +msgstr "সংযোগ" + +msgid "disconnect" +msgstr "বিচ্ছিন্ন" + msgid "connected" msgstr "সংযুক্ত" @@ -72,7 +172,7 @@ msgid "Go Pro Title" msgstr "প্রো-তে যান!" msgid "Go Pro Description" -msgstr "" +msgstr "আরও দ্রুত গতি, বিজ্ঞাপনমুক্ত অভিজ্ঞতা, এবং সীমাহীন ডেটা আনলক করুন! " msgid "Server Location" msgstr "সার্ভারের অবস্থান" @@ -106,7 +206,7 @@ msgstr "আমি নিশ্চিত করছি যে আমি আমা msgid "Pro Account Management" msgstr "Pro একাউন্ট ব্যবস্থাপনা" -msgid "Link Device" +msgid "link_device" msgstr "ডিভাইস লিঙ্ক করুন" msgid "Upgrade to Lantern Pro" @@ -155,12 +255,6 @@ msgstr "সাধারণ" msgid "about" msgstr "সম্পর্কে" -msgid "privacy_policy" -msgstr "গোপনীয়তা নীতি" - -msgid "terms_of_service" -msgstr "সেবা পাবার শর্ত" - msgid "proxy_all" msgstr "সমস্ত কিছু প্রক্সি করুন" @@ -183,7 +277,7 @@ msgid "select_an_issue" msgstr "একটি সমস্যা নির্বাচন করুন" msgid "enter_description" -msgstr "" +msgstr "আপনার সমস্যাটি বর্ণনা করুন " msgid "cannot_access_blocked_sites" msgstr "ব্লক সাইটগুলোতে যেতে পারছিনা" @@ -191,17 +285,35 @@ msgstr "ব্লক সাইটগুলোতে যেতে পারছি msgid "cannot_complete_purchase" msgstr "কেনা সম্পূর্ণ করা যাচ্ছে না" +msgid "discover_not_working" +msgstr "ডিসকভার কাজ করছে না " + msgid "cannot_sign_in" msgstr "সাইন ইন করা যাচ্ছে না" msgid "spinner_loads_endlessly" msgstr "স্পিনার অবিরাম লোড হয়" +msgid "slow" +msgstr "ধীরগতি" + +msgid "cannot_link_devices" +msgstr "ডিভাইস লিংক করতে পারছিনা" + +msgid "wrong_device_linking_code" +msgstr "ভুল বা অবৈধ ডিভাইস লিঙ্কিং কোড" + +msgid "cannot_remove_device" +msgstr "ডিভাইসটি সরানো যাচ্ছে না " + +msgid "application_crashes" +msgstr "অ্যাপ্লিকেশন ক্র্যাশ করেছে" + msgid "other" msgstr "অন্যান্য" msgid "issue_description" -msgstr "সমস্যার বিবরণ" +msgstr "আপনার সমস্যাটি বর্ণনা করুন " msgid "check_for_updates" msgstr "নতুন আপডেটের জন্য চেক করুন." @@ -243,10 +355,10 @@ msgid "device_linking_pin" msgstr "ডিভাইস লিঙ্ক করার পিন" msgid "link_device_step_one" -msgstr "" +msgstr "আপনার প্রো ডিভাইসে 'ডিভাইস যোগ করুন' মেনু আইটেমটি খুলুন। " msgid "link_device_step_two" -msgstr "" +msgstr "ডিভাইস লিঙ্কিং পিন প্রবেশ করান এবং জমা দিন " msgid "ensure_most_recent_version_lantern" msgstr "" @@ -265,14 +377,14 @@ msgstr "" msgid "Link via Email" msgstr "ইমেলের মাধ্যমে লিঙ্ক করুন" -msgid "lantern_pro_email" -msgstr "Lantern প্রো ইমেল" - msgid "lantern_desktop" msgstr "Lantern Desktop" +msgid "report_description" +msgstr "সমস্যার বিবরণ" + msgid "report_issue" -msgstr "" +msgstr "সমস্যা রিপোর্ট করুন " msgid "report_an_issue" msgstr "একটি সমস্যা রিপোর্ট করুন" @@ -296,7 +408,7 @@ msgstr "" "উপরের লিঙ্কটিতে দেখা যেতে পারে।" msgid "share_link" -msgstr "" +msgstr "লিঙ্ক শেয়ার করুন " msgid "share_title" msgstr "Lantern ডাউনলোড লিঙ্ক শেয়ার করুন" @@ -328,8 +440,8 @@ msgstr "মাস" msgid "months" msgstr "মাস" -msgid "enter_email" -msgstr "ইমেল লিখুন" +msgid "enter_email_to_complete_purchase" +msgstr "ক্রয় সম্পূর্ণ করতে ইমেইল প্রবেশ করুন " msgid "error_fetching_plans" msgstr "" @@ -361,10 +473,12 @@ msgid "referral_code" msgstr "রেফারেল কোড" msgid "share_referral_code" -msgstr "" +msgstr "রেফারেল কোড শেয়ার করুন " msgid "share_message_referral_code" msgstr "" +"এক মাসের জন্য বিনামূল্যে Lantern Pro পেতে, এই রেফারেল কোডটি ব্যবহার করুন: %s" +" ; এটি এখান থেকে ডাউনলোড করতে পারেন: https://lantern.io/download " msgid "add_referral_code" msgstr "রেফারেল কোড যোগ করুন" @@ -435,7 +549,7 @@ msgstr "" "যেতে পারে।" msgid "by_clicking_tos" -msgstr "%sএ ক্লিক করে, আপনি সম্মত হন আমাদের" +msgstr "%s এ ক্লিক করে, আপনি আমাদের সাথে সম্মত হন " msgid "tos" msgstr "সেবা পাবার শর্ত" @@ -485,6 +599,18 @@ msgstr "হ্যা" msgid "No" msgstr "না" +msgid "exit" +msgstr "প্রস্থান করুন " + +msgid "show" +msgstr "দেখান" + +msgid "status_on" +msgstr "স্থিতি: চালু " + +msgid "status_off" +msgstr "স্থিতি: বন্ধ " + msgid "Enter or paste linking code" msgstr "লিঙ্ক করার কোডটি লিখুন বা পেস্ট করুন" @@ -523,6 +649,9 @@ msgstr "লগ আউট করুন" msgid "confirm_remove_device" msgstr "আপনি কি আপনার Pro অ্যাকাউন্ট থেকে এই ডিভাইসটি অপসারন করতে চান?" +msgid "confirm_close_window" +msgstr "আপনি কি নিশ্চিত যে আপনি Lantern বন্ধ করতে চান? " + msgid "unlimited_data" msgstr "অসীম ডাটা" @@ -535,6 +664,9 @@ msgstr "কোন বিজ্ঞাপণ নেই" msgid "choose_plan" msgstr "প্ল্যান বাছুন" +msgid "billed_one_time" +msgstr "একবার বিল করা হয়েছে " + msgid "connect_up_to_3_devices" msgstr "সর্বাধিক 3টি ডিভাইসে সংযোগ করুন" @@ -734,9 +866,6 @@ msgstr "আপনার এবং %s এর জন্য সমস্ত বা msgid "message_disappearing_description" msgstr "আপনার এবং %s-এর জন্য %s-এর পরে সমস্ত বার্তা অদৃশ্য হয়ে যাবে।" -msgid "delete" -msgstr "মুছে ফেলুন" - msgid "view_contact_info" msgstr "যোগাযোগের তথ্য দেখুন" @@ -1543,3 +1672,238 @@ msgstr "FAQ" msgid "add_device" msgstr "ডিভাইস যোগ করুন" + +msgid "search_apps" +msgstr "অ্যাপ অনুসন্ধান করুন " + +msgid "user_not_found" +msgstr "" +"ব্যবহারকারী স্বীকৃত নয়। অনুগ্রহ করে আপনার প্রমাণপত্রগুলি পরীক্ষা করুন এবং " +"আবার চেষ্টা করুন। আপনি যদি সমস্যাগুলি অব্যাহত রাখেন, অনুগ্রহ করে সহায়তার " +"জন্য আমাদের সহায়তা দলকে যোগাযোগ করুন। " + +msgid "email_resend_message" +msgstr "আপনার নতুন নিশ্চিতকরণ কোড পাঠানো হয়েছে " + +msgid "signup_error" +msgstr "" +"দেখা যাচ্ছে সাইন-আপের সাথে কিছু ভুল হয়েছে। অনুগ্রহ করে আবার চেষ্টা করুন, " +"এবং যদি সমস্যা অব্যাহত থাকে, আমাদের সহায়তা দলকে যোগাযোগ করুন। " + +msgid "signup_error_user_exists" +msgstr "" +"দেখা যাচ্ছে সাইন-আপের সাথে কিছু ভুল হয়েছে। অনুগ্রহ করে আপনার পাসওয়ার্ড " +"পুনরায় সেট করার চেষ্টা করুন, এবং যদি সমস্যা অব্যাহত থাকে, আমাদের সহায়তা " +"দলকে যোগাযোগ করুন। " + +msgid "confirm_email_error" +msgstr "অনুগ্রহ করে আপনার ইমেইল নিশ্চিত করুন " + +msgid "password_cannot_be_empty" +msgstr "পাসওয়ার্ড খালি থাকতে পারবে না " + +msgid "password_must_be_at_least_8_characters" +msgstr "পাসওয়ার্ড অন্তত ৮ বা তার বেশি অক্ষরের হতে হবে " + +msgid "sign_out" +msgstr "সাইন আউট " + +msgid "sign_out_message" +msgstr "" +"লগ আউট করা ল্যান্টার্নকে বিনামূল্যের সংস্করণে পরিবর্তন করবে। আপনার " +"অ্যাকাউন্টে পুনরায় অ্যাক্সেস করতে, আপনাকে পুনরায় সাইন ইন করতে হবে। " + +msgid "change_password" +msgstr "পাসওয়ার্ড পরিবর্তন করুন" + +msgid "password" +msgstr "পাসওয়ার্ড" + +msgid "new_email" +msgstr "নতুন ইমেইল ঠিকানা " + +msgid "new_email_same_as_old_email" +msgstr "নতুন ইমেইল ঠিকানা আপনার পুরানো ইমেইল ঠিকানার মতো হতে পারে না " + +msgid "danger_zone" +msgstr "বিপদ অঞ্চল " + +msgid "delete_account" +msgstr "অ্যাকাউন্ট মুছুন " + +msgid "delete" +msgstr "মুছে ফেলুন" + +msgid "delete_account_message" +msgstr "" +"আপনার অ্যাকাউন্ট মুছে ফেলা পূর্বাবস্থায় ফেরানো যাবে না। আপনি সমস্ত ডিভাইস " +"অ্যাক্সেস হারাবেন এবং কোনও ফেরতের ছাড়াই প্রো অ্যাকাউন্টের সময় পরিত্যাগ " +"করবেন। " + +msgid "delete_your_account" +msgstr "আপনার অ্যাকাউন্ট মুছবেন? " + +msgid "confirm_deletion" +msgstr "মুছে ফেলা নিশ্চিত করুন " + +msgid "account_deleted" +msgstr "অ্যাকাউন্ট মুছে ফেলা হয়েছে " + +msgid "account_deleted_message" +msgstr "আপনার অ্যাকাউন্ট স্থায়ীভাবে মুছে ফেলা হয়েছে। " + +msgid "password_has_been_updated" +msgstr "পাসওয়ার্ড আপডেট করা হয়েছে " + +msgid "password_has_been_updated_message" +msgstr "আপনার পাসওয়ার্ড সফলভাবে পুনরায় সেট করা হয়েছে " + +msgid "invalid_code" +msgstr "" +"ভুল কোড বা এটি মেয়াদোত্তীর্ণ হতে পারে। অনুগ্রহ করে পুনরায় পাঠানোর চেষ্টা " +"করুন " + +msgid "get_lantern_pro" +msgstr "Lantern Pro নিন" + +msgid "try_lantern_pro" +msgstr "ল্যান্টার্ন বিনামূল্যে চেষ্টা করুন! " + +msgid "continue_for_free" +msgstr "বিনামূল্যে চালিয়ে যান " + +msgid "recovery_not_found" +msgstr "ডিভাইসটি এই ব্যবহারকারী সাথে যুক্ত না" + +msgid "device_added" +msgstr "ডিভাইসটি এই ব্যবহারকারী সাথে যুক্ত না" + +msgid "device_added_message" +msgstr "" +"আপনার ডিভাইস সফলভাবে যোগ করা হয়েছে। যদি আপনি ডিভাইসটি সংযুক্ত না দেখেন, তবে" +" উভয় ডিভাইসে অ্যাপটি পুনরায় চালু করুন " + +msgid "wrong_link_code" +msgstr "" +"আপনি যে কোডটি প্রবেশ করেছেন তা ভুল। অনুগ্রহ করে পরীক্ষা করুন এবং আবার চেষ্টা" +" করুন। " + +msgid "recovery_code_sent" +msgstr "পুনরুদ্ধার কোড পাঠানো হয়েছে " + +msgid "device_added_msg" +msgstr "এই ডিভাইসটি %s এর সাথে লিঙ্ক করা হয়েছে " + +msgid "new_to_lantern" +msgstr "ল্যান্টার্নে নতুন? " + +msgid "email_has_been_updated" +msgstr "ইমেইল আপডেট করা হয়েছে " + +msgid "email_has_been_updated_message" +msgstr "আপনার ইমেইল সফলভাবে পরিবর্তন করা হয়েছে " + +msgid "email_has_been_verified" +msgstr "ইমেইল যাচাই করা হয়েছে " + +msgid "email_has_been_verified_message" +msgstr "আপনার ইমেইল সফলভাবে যাচাই করা হয়েছে " + +msgid "device_limit_reached" +msgstr "ডিভাইস সীমা পৌঁছেছে " + +msgid "device_limit_reached_message" +msgstr "" +"আপনি বর্তমানে সর্বাধিক সংখ্যক ডিভাইসে (৩) সাইন ইন করেছেন। এই ডিভাইসে সাইন ইন" +" করতে, অনুগ্রহ করে নিচের আপনার অন্য ডিভাইসগুলির একটি নির্বাচন করুন এবং সরান।" +" " + +msgid "enter_activation_code" +msgstr "সক্রিয়করণ কোড লিখুন " + +msgid "check_your_email" +msgstr "আপনার ইমেইল ঠিকানা পরীক্ষা করুন " + +msgid "please_verify_email" +msgstr "" +"অনুগ্রহ করে নিশ্চিত করুন যে আপনি যে ইমেইল ঠিকানাটি প্রবেশ করেছেন তা সঠিক। " + +msgid "verify" +msgstr "যাচাই করুন" + +msgid "cannot_login" +msgstr "প্রবেশ করতে পারছেন না " + +msgid "privacy_policy" +msgstr "গোপনীয়তা নীতি" + +msgid "terms_of_service" +msgstr "সেবা পাবার শর্ত" + +msgid "invalid_verification_code" +msgstr "অবৈধ যাচাই কোড" + +msgid "wrong_seller_code" +msgstr "ভুল রিসেলার কোড। দয়া করে যাচাই করুন এবং আবার চেষ্টা করুন। " + +msgid "follow_us" +msgstr "আমাদের অনুসরণ করুন " + +msgid "follow_us_telegram" +msgstr "আমাদের অনুসরণ করুন Telegram-এ " + +msgid "follow_us_instagram" +msgstr "আমাদের অনুসরণ করুন Instagram-এ " + +msgid "follow_us_facebook" +msgstr "আমাদের অনুসরণ করুন Facebook-এ " + +msgid "follow_us_x" +msgstr "আমাদের অনুসরণ করুন X-এ " + +msgid "report_issue_error" +msgstr "আমরা প্রযুক্তিগত সমস্যার সম্মুখীন হচ্ছি। পরে আবার চেষ্টা করুন। " + +msgid "we_are_experiencing_technical_difficulties" +msgstr "আমরা প্রযুক্তিগত সমস্যার সম্মুখীন হচ্ছি। পরে আবার চেষ্টা করুন। " + +msgid "a_temporary_error_occurred" +msgstr "একটি সাময়িক ত্রুটি ঘটেছে " + +msgid "sorry_we_are_unable_to_load_that_page" +msgstr "" +"দুঃখিত, আমরা এই মুহূর্তে সেই পৃষ্ঠাটি লোড করতে অক্ষম, দয়া করে আবার চেষ্টা " +"করতে 'রিফ্রেশ' টিপুন। " + +msgid "refresh" +msgstr "রিফ্রেশ" + +msgid "check_your_internet_connection" +msgstr "আপনার ইন্টারনেট সংযোগ পরীক্ষা করুন " + +msgid "please_try" +msgstr "দয়া করে চেষ্টা করুন " + +msgid "turning_off_airplane_mode" +msgstr "এয়ারপ্লেন মোড বন্ধ করুন " + +msgid "turning_on_mobile_data_or_wifi" +msgstr "মোবাইল ডেটা বা ওয়াইফাই চালু করুন " + +msgid "check_the_signal_in_your_area" +msgstr "আপনার এলাকায় সিগন্যাল পরীক্ষা করুন " + +msgid "got_it" +msgstr "পেয়েছি" + +msgid "fetching_configuration" +msgstr "কনফিগারেশন আনছে " + +msgid "establish_connection_to_server" +msgstr "সার্ভারের সাথে সংযোগ স্থাপন করুন " + +msgid "file_size_limit_title" +msgstr "সীমা পৌঁছেছে " + +msgid "file_size_limit_description" +msgstr "সংযুক্তি আকার 100 এমবি-এর নিচে হওয়া উচিত" diff --git a/assets/locales/en-us.po b/assets/locales/en-us.po index dc77f8a23..944b86498 100644 --- a/assets/locales/en-us.po +++ b/assets/locales/en-us.po @@ -18,6 +18,91 @@ msgstr "Maybe Later" msgid "try" msgstr "Try it out" +msgid "sign_in" +msgstr "Sign In" + +msgid "enter_email" +msgstr "Enter Email" + +msgid "enter_password" +msgstr "Enter Password" + +msgid "forgot_password" +msgstr "Forgot Password" + +msgid "forgot_your_password" +msgstr "Forgot your password? " + +msgid "click_here" +msgstr "Click here" + +msgid "reset_password" +msgstr "Reset Password" + +msgid "lantern_pro_email" +msgstr "Lantern Pro Email " + +msgid "return_to_sign_in" +msgstr "Return to Sign in" + +msgid "enter_confirmation_code" +msgstr "Enter Confirmation Code" + +msgid "confirmation_code_msg" +msgstr "A confirmation code has been sent to XX. Enter it here to secure your account and enable password recovery." + +msgid "resend_confirmation_code" +msgstr "Resend Confirmation Email" + +msgid "email_already_exists" +msgstr "Email Already Exists" + +msgid "email_already_exists_msg" +msgstr "This email is taken, if you already have an account with this email you can recover. If not please go back and select a different email address." + +msgid "recover_account" +msgstr "Recover Account" + +msgid "back" +msgstr "back" + +msgid "change_email" +msgstr "Change Email" + +msgid "new_password" +msgstr "New Password" + +msgid "confirm_new_password" +msgstr "Confirm New Password" + +msgid "already_have_an_account" +msgstr "Already have an account? " + +msgid "create_account" +msgstr "Create Account" + +msgid "create_password" +msgstr "Create Password" + +msgid "by_creating_an_account" +msgstr "By Creating an Account, you agree to our" + +msgid "confirm_email" +msgstr "Confirm Email" + +msgid "update_pro_account" +msgstr "Update Pro Account" + +msgid "update_pro_account_message" +msgstr "Changes are coming to Lantern Pro! Now, you will need an email and password to log in on all devices, and to accesss Account Management. Please take a few moments to add these credentials to your account. " + +msgid "not_now" +msgstr "Not now" + +msgid "update_account" +msgstr "Update Account" + + msgid "new" msgstr "New" @@ -93,7 +178,7 @@ msgstr "I confirm that I want to delete my chat data" msgid "Pro Account Management" msgstr "Pro Account Management" -msgid "Link Device" +msgid "link_device" msgstr "Link Device" msgid "Upgrade to Lantern Pro" @@ -126,11 +211,6 @@ msgstr "General" msgid "about" msgstr "About" -msgid "privacy_policy" -msgstr "Privacy Policy" - -msgid "terms_of_service" -msgstr "Terms of Service" msgid "proxy_all" msgstr "Proxy everything" @@ -252,9 +332,6 @@ msgstr "Requires access to the email you used to buy Lantern Pro" msgid "Link via Email" msgstr "Link via Email" -msgid "lantern_pro_email" -msgstr "Lantern Pro Email" - msgid "lantern_desktop" msgstr "Lantern Desktop" @@ -312,9 +389,6 @@ msgstr "month" msgid "months" msgstr "months" -msgid "enter_email" -msgstr "Enter Email" - msgid "enter_email_to_complete_purchase" msgstr "Enter email to complete purchase" @@ -409,7 +483,7 @@ msgid "pro_purchase_success_descripion" msgstr "You now have unlimited access using our fastest data centers. Up to three devices can be connected to your account." msgid "by_clicking_tos" -msgstr "By clicking %s, you agree to our" +msgstr "By clicking %s, you agree to our " msgid "tos" msgstr "Terms of Service" @@ -702,9 +776,6 @@ msgstr "All messages will not disappear for you and %s." msgid "message_disappearing_description" msgstr "All messages will disappear after %s for you and %s." -msgid "delete" -msgstr "Delete" - msgid "view_contact_info" msgstr "View Contact Info" @@ -1470,17 +1541,141 @@ msgstr "FAQ" msgid "add_device" msgstr "Add Device" -msgid "recovery_code_sent" -msgstr "Recovery Code Sent" +msgid "search_apps" +msgstr "Search Apps" + +msgid "user_not_found" +msgstr "User not recognized. Please check your credentials and try again. If you continue to experience issues, please reach out to our support team for assistance." + +msgid "email_resend_message" +msgstr "Your new confirmation code has been sent" + +msgid "signup_error" +msgstr "It looks like something went wrong with the sign-up. Please try again, and if the issue continues, reach out to our support team" + +msgid "signup_error_user_exists" +msgstr "It looks like something went wrong with the sign-up. Please try resetting your password, and if the issue continues, reach out to our support team" + +msgid "confirm_email_error" +msgstr "Please confirm your email" + +msgid "password_cannot_be_empty" +msgstr "Password cannot be empty" + +msgid "password_must_be_at_least_8_characters" +msgstr "Password must be at least 8 or more characters" + +msgid "sign_out" +msgstr "Sign out" + +msgid "sign_out_message" +msgstr "Logging out will switch Lantern to the free version. To access your account again, you will need to sign back in." + +msgid "change_password" +msgstr "Change Password" + +msgid "password" +msgstr "Password" + +msgid "new_email" +msgstr "New Email Address" + +msgid "new_email_same_as_old_email" +msgstr "New email address cannot be the same as your old email address" + +msgid "danger_zone" +msgstr "Danger zone" + +msgid "delete_account" +msgstr "Delete Account" + +msgid "delete" +msgstr "Delete" + +msgid "delete_account_message" +msgstr "Deleting your account cannot be undone. You'll lose all device access and forfeit any Pro account time without refund." + +msgid "delete_your_account" +msgstr "Delete your account?" + +msgid "confirm_deletion" +msgstr "Confirm Deletion" + +msgid "account_deleted" +msgstr "Account Deleted" + +msgid "account_deleted_message" +msgstr "Your account has been permanently deleted." + +msgid "password_has_been_updated" +msgstr "Password has been updated" + +msgid "password_has_been_updated_message" +msgstr "Your password has been reset successfully" + +msgid "invalid_code" +msgstr "Incorrect code or it might have expired. Please try resending again" + +msgid "get_lantern_pro" +msgstr "Get Lantern Pro" + +msgid "try_lantern_pro" +msgstr "Try Lantern for free! " + +msgid "continue_for_free" +msgstr "Continue for Free " + +msgid "recovery_not_found" +msgstr "Device not associated to this user" msgid "device_added" -msgstr "Device Added" +msgstr "Device not associated to this user" msgid "device_added_message" msgstr "Your device has been successfully added. If you do not see the device connected, please restart the app on both devices" -msgid "search_apps" -msgstr "Search Apps" +msgid "wrong_link_code" +msgstr "The code you entered is incorrect. Please check and try again." + +msgid "recovery_code_sent" +msgstr "Recovery code has been sent" + + +msgid "device_added_msg" +msgstr "This device has been Linked to %s" + +msgid "new_to_lantern" +msgstr "New to Lantern? " + +msgid "email_has_been_updated" +msgstr "Email has been updated" + +msgid "email_has_been_updated_message" +msgstr "Your Email has been changed successfully" + +msgid "email_has_been_verified" +msgstr "Email has been verified" + +msgid "email_has_been_verified_message" +msgstr "Your Email has been verified successfully" + +msgid "device_limit_reached" +msgstr "Device Limit Reached" + +msgid "device_limit_reached_message" +msgstr "You are currently signed in on the maximum number of devices (3). To sign in on this device, please select and remove one of your other devices below." + +msgid "enter_activation_code" +msgstr "Enter Activation Code" + +msgid "check_your_email" +msgstr "Check Your Email Address" + +msgid "please_verify_email" +msgstr "Please verify that the email address you entered is correct." + +msgid "verify" +msgstr "Verify" msgid "cannot_login" msgstr "Cannot login" @@ -1551,7 +1746,6 @@ msgstr "Fetching Configuration" msgid "establish_connection_to_server" msgstr "Establish connection to server" - msgid "file_size_limit_title" msgstr "Limit Reached" diff --git a/assets/locales/es-es.po b/assets/locales/es-es.po index 3ea121571..13f20c50a 100644 --- a/assets/locales/es-es.po +++ b/assets/locales/es-es.po @@ -4,14 +4,14 @@ # e2f_es r3 , 2022 # Emerson Castaneda, 2023 # fffw , 2023 -# tx_e2f_es t1 , 2023 # Lantern, 2023 -# tx_e2f_es t35 , 2023 # e2f_es r1 , 2023 # Aimara pena, 2024 # e2f_es r2 , 2024 # e2f , 2024 +# tx_e2f_es t35 , 2024 # strel, 2024 +# tx_e2f_es t1 , 2024 # Derek F , 2024 # msgid "" @@ -38,6 +38,100 @@ msgstr "Mejor en otro momento" msgid "try" msgstr "Probar" +msgid "sign_in" +msgstr "Iniciar sesión" + +msgid "enter_email" +msgstr "Introducir correo electrónico" + +msgid "enter_password" +msgstr "Ingrese la Contraseña" + +msgid "forgot_password" +msgstr "¿Olvidó la Contraseña?" + +msgid "forgot_your_password" +msgstr "¿Olvidaste tu contraseña?" + +msgid "click_here" +msgstr "Haga clic aquí" + +msgid "reset_password" +msgstr "Restablecer contraseña" + +msgid "lantern_pro_email" +msgstr "Correo Electrónico de Lantern Pro" + +msgid "return_to_sign_in" +msgstr "Volver a Iniciar Sesión" + +msgid "enter_confirmation_code" +msgstr "Ingrese el Código de Confirmación" + +msgid "confirmation_code_msg" +msgstr "" +"Este correo está tomado, si ya tienes una cuenta con este correo, puedes " +"recuperarla. Si no, por favor regresa y selecciona una dirección de correo " +"diferente. " + +msgid "resend_confirmation_code" +msgstr "Recuperar Cuenta" + +msgid "email_already_exists" +msgstr "El Correo Electrónico Ya Existe" + +msgid "email_already_exists_msg" +msgstr "" +"Este correo está tomado, si ya tienes una cuenta con este correo, puedes " +"recuperarla. Si no, por favor regresa y selecciona una dirección de correo " +"diferente. " + +msgid "recover_account" +msgstr "Recuperar Cuenta " + +msgid "back" +msgstr "volver" + +msgid "change_email" +msgstr "Cambiar Correo Electrónico " + +msgid "new_password" +msgstr "Nueva contraseña" + +msgid "confirm_new_password" +msgstr "Confirmar nueva contraseña" + +msgid "already_have_an_account" +msgstr "¿Ya tienes una cuenta? " + +msgid "create_account" +msgstr "Crear cuenta" + +msgid "create_password" +msgstr "Crear contraseña" + +msgid "by_creating_an_account" +msgstr "Al Crear una Cuenta, aceptas nuestros " + +msgid "confirm_email" +msgstr "Correo de confirmación" + +msgid "update_pro_account" +msgstr "Actualizar Cuenta Pro " + +msgid "update_pro_account_message" +msgstr "" +"¡Se avecinan cambios a Lantern Pro! Ahora, necesitarás un correo electrónico" +" y contraseña para iniciar sesión en todos los dispositivos y acceder a la " +"Gestión de la Cuenta. Por favor, toma unos momentos para agregar estas " +"credenciales a tu cuenta. " + +msgid "not_now" +msgstr "Ahora no" + +msgid "update_account" +msgstr "Actualizar Cuenta " + msgid "new" msgstr "Novedad" @@ -121,7 +215,7 @@ msgstr "Confirmo que deseo eliminar mis datos de chat" msgid "Pro Account Management" msgstr "Gestión de cuenta Pro" -msgid "Link Device" +msgid "link_device" msgstr "Vincular dispositivo" msgid "Upgrade to Lantern Pro" @@ -172,12 +266,6 @@ msgstr "General" msgid "about" msgstr "Acerca de" -msgid "privacy_policy" -msgstr "Política de privacidad" - -msgid "terms_of_service" -msgstr "Condiciones del servicio" - msgid "proxy_all" msgstr "Proxyficar todo" @@ -302,9 +390,6 @@ msgstr "" msgid "Link via Email" msgstr "Vincular con correo electrónico" -msgid "lantern_pro_email" -msgstr "Correo electrónico de Lantern Pro" - msgid "lantern_desktop" msgstr "Lantern de Escritorio" @@ -366,9 +451,6 @@ msgstr "mes" msgid "months" msgstr "meses" -msgid "enter_email" -msgstr "Introducir correo electrónico" - msgid "enter_email_to_complete_purchase" msgstr "Introduce el correo electrónico para completar la compra" @@ -472,7 +554,7 @@ msgstr "" " Puede vincular hasta tres dispositivos a su cuenta." msgid "by_clicking_tos" -msgstr "Al hacer clic en %s, acepta nuestras" +msgstr "Al hacer clic en %s, aceptas nuestros " msgid "tos" msgstr "Condiciones del servicio" @@ -791,9 +873,6 @@ msgstr "No desaparecerán todos los mensajes entre usted y %s." msgid "message_disappearing_description" msgstr "Todos los mensajes entre usted y %s desaparecerán al cabo de %s." -msgid "delete" -msgstr "Borrar" - msgid "view_contact_info" msgstr "Ver información de contacto" @@ -1615,23 +1694,175 @@ msgstr "Preguntas frecuentes (FAQ)" msgid "add_device" msgstr "Añadir dispositivo" -msgid "recovery_code_sent" -msgstr "Código de recuperación enviado" +msgid "search_apps" +msgstr "Buscar aplicaciones" + +msgid "user_not_found" +msgstr "" +"Usuario no reconocido. Por favor, revisa tus credenciales e inténtalo de " +"nuevo. Si continúas experimentando problemas, comunícate con nuestro equipo " +"de soporte para obtener ayuda. " + +msgid "email_resend_message" +msgstr "Tu nuevo código de confirmación ha sido enviado " + +msgid "signup_error" +msgstr "" +"Parece que algo salió mal con el registro. Por favor, inténtalo de nuevo, y " +"si el problema persiste, comunícate con nuestro equipo de soporte. " + +msgid "signup_error_user_exists" +msgstr "" +"Parece que algo salió mal con el registro. Por favor, intenta restablecer tu" +" contraseña, y si el problema persiste, comunícate con nuestro equipo de " +"soporte. " + +msgid "confirm_email_error" +msgstr "Por favor, confirma tu correo electrónico " + +msgid "password_cannot_be_empty" +msgstr "La contraseña no puede estar vacía " + +msgid "password_must_be_at_least_8_characters" +msgstr "La contraseña debe tener al menos 8 caracteres o más " + +msgid "sign_out" +msgstr "Cerrar sesión " + +msgid "sign_out_message" +msgstr "" +"Cerrar sesión cambiará Lantern a la versión gratuita. Para acceder a tu " +"cuenta nuevamente, deberás iniciar sesión de nuevo. " + +msgid "change_password" +msgstr "Cambiar contraseña" + +msgid "password" +msgstr "Contraseña" + +msgid "new_email" +msgstr "Nueva Dirección de Correo Electrónico " + +msgid "new_email_same_as_old_email" +msgstr "" +"La nueva dirección de correo electrónico no puede ser la misma que tu " +"antigua dirección de correo electrónico " + +msgid "danger_zone" +msgstr "Zona de peligro " + +msgid "delete_account" +msgstr "Eliminar Cuenta " + +msgid "delete" +msgstr "Borrar" + +msgid "delete_account_message" +msgstr "" +"Eliminar tu cuenta no se puede deshacer. Perderás el acceso a todos los " +"dispositivos y renunciarás a cualquier tiempo de cuenta Pro sin reembolso. " + +msgid "delete_your_account" +msgstr "¿Eliminar tu cuenta? " + +msgid "confirm_deletion" +msgstr "Confirmar Eliminación " + +msgid "account_deleted" +msgstr "Cuenta Eliminada " + +msgid "account_deleted_message" +msgstr "Tu cuenta ha sido eliminada permanentemente. " + +msgid "password_has_been_updated" +msgstr "La contraseña ha sido actualizada " + +msgid "password_has_been_updated_message" +msgstr "Tu contraseña ha sido restablecida exitosamente " + +msgid "invalid_code" +msgstr "" +"Código incorrecto o puede haber expirado. Por favor, intenta reenviar " +"nuevamente " + +msgid "get_lantern_pro" +msgstr "Consiga Lantern Pro" + +msgid "try_lantern_pro" +msgstr "¡Prueba Lantern gratis! " + +msgid "continue_for_free" +msgstr "Continuar Gratis " + +msgid "recovery_not_found" +msgstr "Dispositivo no asociado a este usuario" msgid "device_added" -msgstr "Dispositivo añadido" +msgstr "Dispositivo no asociado a este usuario" msgid "device_added_message" msgstr "" "Tu dispositivo se ha añadido correctamente. Si no ves el dispositivo " "conectado, reinicia la aplicación en ambos dispositivos." -msgid "search_apps" -msgstr "Buscar aplicaciones" +msgid "wrong_link_code" +msgstr "" +"El código que ingresaste es incorrecto. Por favor, revisa e inténtalo de " +"nuevo. " + +msgid "recovery_code_sent" +msgstr "El código de recuperación ha sido enviado " + +msgid "device_added_msg" +msgstr "Este dispositivo ha sido Vinculado a %s " + +msgid "new_to_lantern" +msgstr "¿Nuevo en Lantern? " + +msgid "email_has_been_updated" +msgstr "El correo electrónico ha sido actualizado " + +msgid "email_has_been_updated_message" +msgstr "Tu correo electrónico ha sido cambiado exitosamente " + +msgid "email_has_been_verified" +msgstr "El correo electrónico ha sido verificado " + +msgid "email_has_been_verified_message" +msgstr "Tu correo electrónico ha sido verificado exitosamente " + +msgid "device_limit_reached" +msgstr "Límite de Dispositivos Alcanzado " + +msgid "device_limit_reached_message" +msgstr "" +"Actualmente has iniciado sesión en el número máximo de dispositivos (3). " +"Para iniciar sesión en este dispositivo, por favor selecciona y elimina uno " +"de tus otros dispositivos a continuación. " + +msgid "enter_activation_code" +msgstr "Ingrese el Código de Activación " + +msgid "check_your_email" +msgstr "Revisa tu Dirección de Correo Electrónico " + +msgid "please_verify_email" +msgstr "" +"Por favor, verifica que la dirección de correo electrónico que ingresaste " +"sea correcta. " + +msgid "verify" +msgstr "Verificar" msgid "cannot_login" msgstr "No puedo iniciar sesión" +msgid "privacy_policy" +msgstr "Política de privacidad" + +msgid "terms_of_service" +msgstr "Condiciones del servicio" + msgid "invalid_verification_code" msgstr "Código de verificación no válido" diff --git a/assets/locales/ms-my.po b/assets/locales/ms-my.po index 7a3f1258e..8c92c2656 100644 --- a/assets/locales/ms-my.po +++ b/assets/locales/ms-my.po @@ -2,16 +2,17 @@ # Translators: # abuyop , 2022 # Ahmed Noor Kader Mustajir Md Eusoff (kader) , 2023 -# Lantern, 2023 -# e2f_my c2 , 2023 # tx_e2f_my t1 , 2023 -# e2f , 2023 -# tx_e2f_ms t9 , 2023 -# Alyssa Chung , 2023 +# tx_e2f_ms t9 , 2024 +# Alyssa Chung , 2024 +# Lantern, 2024 +# e2f , 2024 +# e2f_my c2 , 2024 +# Derek F , 2024 # msgid "" msgstr "" -"Last-Translator: Alyssa Chung , 2023\n" +"Last-Translator: Derek F , 2024\n" "Language-Team: Malay (https://app.transifex.com/lantern-1/teams/94371/ms/)\n" "Language: ms\n" "Plural-Forms: nplurals=1; plural=0;\n" @@ -34,6 +35,99 @@ msgstr "Mungkin Kemudian" msgid "try" msgstr "Cubalah" +msgid "sign_in" +msgstr "Sign In" + +msgid "enter_email" +msgstr "Masukkan E-mel" + +msgid "enter_password" +msgstr "" + +msgid "forgot_password" +msgstr "Terlupa Kata Laluan " + +msgid "forgot_your_password" +msgstr "Terlupa kata laluan anda? " + +msgid "click_here" +msgstr "Klik di sini " + +msgid "reset_password" +msgstr "Tetapkan Semula Kata Laluan" + +msgid "lantern_pro_email" +msgstr "Email Lantern Pro " + +msgid "return_to_sign_in" +msgstr "Kembali ke Log Masuk " + +msgid "enter_confirmation_code" +msgstr "Masukkan Kod Pengesahan " + +msgid "confirmation_code_msg" +msgstr "" +"Kod pengesahan telah dihantar ke XX. Masukkan di sini untuk mengamankan " +"akaun anda dan mengaktifkan pemulihan kata laluan. " + +msgid "resend_confirmation_code" +msgstr "Hantar Semula Email Pengesahan " + +msgid "email_already_exists" +msgstr "Email Sudah Wujud " + +msgid "email_already_exists_msg" +msgstr "" +"Email ini telah diambil, jika anda sudah mempunyai akaun dengan email ini, " +"anda boleh memulihkannya. Jika tidak, sila kembali dan pilih alamat email " +"yang berbeza. " + +msgid "recover_account" +msgstr "Pulihkan Akaun " + +msgid "back" +msgstr "kembali " + +msgid "change_email" +msgstr "Tukar Email " + +msgid "new_password" +msgstr "Kata Laluan Baharu" + +msgid "confirm_new_password" +msgstr "Sahkan Kata Laluan Baharu" + +msgid "already_have_an_account" +msgstr "Sudah mempunyai akaun? " + +msgid "create_account" +msgstr "Cipta Akaun" + +msgid "create_password" +msgstr "Buat Kata Laluan " + +msgid "by_creating_an_account" +msgstr "Dengan Membuat Akaun, anda bersetuju dengan " + +msgid "confirm_email" +msgstr "Sahkan Emel" + +msgid "update_pro_account" +msgstr "Kemas Kini Akaun Pro " + +msgid "update_pro_account_message" +msgstr "" +"Perubahan akan datang ke Lantern Pro! Sekarang, anda memerlukan email dan " +"kata laluan untuk log masuk pada semua peranti, dan untuk mengakses " +"Pengurusan Akaun. Sila luangkan beberapa saat untuk menambah maklumat ini ke" +" akaun anda. " + +msgid "not_now" +msgstr "Tidak sekarang " + +msgid "update_account" +msgstr "Kemas Kini Akaun " + msgid "new" msgstr "Baharu" @@ -43,6 +137,12 @@ msgstr "Penggunaan Data Harian" msgid "is" msgstr "is" +msgid "connect" +msgstr "Sambuungan" + +msgid "disconnect" +msgstr "Putuskan sambungan" + msgid "connected" msgstr "Telah disambungkan" @@ -77,6 +177,8 @@ msgstr "Go Pro!" msgid "Go Pro Description" msgstr "" +"Buka kelajuan yang lebih pantas, pengalaman tanpa iklan, dan data tanpa had!" +" " msgid "Server Location" msgstr "Lokasi Server" @@ -110,7 +212,7 @@ msgstr "Saya mengesahkan bahawa saya ingin memadamkan data sembang saya" msgid "Pro Account Management" msgstr "Akaun Pengurusan Pro" -msgid "Link Device" +msgid "link_device" msgstr "Pautkan Peranti" msgid "Upgrade to Lantern Pro" @@ -159,12 +261,6 @@ msgstr "Am" msgid "about" msgstr "Mengenai" -msgid "privacy_policy" -msgstr "Polisi Privasi" - -msgid "terms_of_service" -msgstr "Terma Perkhidmatan" - msgid "proxy_all" msgstr "Proksi semua" @@ -187,7 +283,7 @@ msgid "select_an_issue" msgstr "Sila Pilih satu Isu" msgid "enter_description" -msgstr "" +msgstr "Sila jelaskan masalah anda " msgid "cannot_access_blocked_sites" msgstr "Tidak boleh akses laman-laman disekat" @@ -195,17 +291,35 @@ msgstr "Tidak boleh akses laman-laman disekat" msgid "cannot_complete_purchase" msgstr "Tidak dapat melengkapkan pembelian" +msgid "discover_not_working" +msgstr "Fungsi 'Discover' tidak berfungsi " + msgid "cannot_sign_in" msgstr "Tidak boleh mendaftar masuk" msgid "spinner_loads_endlessly" msgstr "Pemutar dimuatkan tanpa henti" +msgid "slow" +msgstr "Lambat" + +msgid "cannot_link_devices" +msgstr "Tidak boleh pautkan peranti" + +msgid "wrong_device_linking_code" +msgstr "Kod pautan alatan salah atau tidak sah " + +msgid "cannot_remove_device" +msgstr "Tidak boleh mengeluarkan peranti " + +msgid "application_crashes" +msgstr "Aplikasi ranap" + msgid "other" msgstr "Lain-lain" msgid "issue_description" -msgstr "Penerangan Isu" +msgstr "Jelaskan masalah anda " msgid "check_for_updates" msgstr "Semak kemas kini" @@ -247,10 +361,10 @@ msgid "device_linking_pin" msgstr "Pin pemautan peranti" msgid "link_device_step_one" -msgstr "" +msgstr "Buka item menu 'Tambah Peranti' pada peranti Pro anda. " msgid "link_device_step_two" -msgstr "" +msgstr "Masukkan PIN pautan peranti dan klik hantar " msgid "ensure_most_recent_version_lantern" msgstr "* Pastikan kedua-dua peranti menjalankan versi terbaru Lantern" @@ -268,14 +382,14 @@ msgstr "" msgid "Link via Email" msgstr "Pautkan melalui E-mel" -msgid "lantern_pro_email" -msgstr "E-mel Pro Lantern" - msgid "lantern_desktop" msgstr "Desktop Lantern" +msgid "report_description" +msgstr "Penerangan Isu" + msgid "report_issue" -msgstr "" +msgstr "Lapor Masalah " msgid "report_an_issue" msgstr "Melapor suatu Isu" @@ -297,7 +411,7 @@ msgstr "" "didapati di pautan di atas." msgid "share_link" -msgstr "" +msgstr "Kongsi Pautan " msgid "share_title" msgstr "Kongsikan pautan muat turun Lantern" @@ -329,8 +443,8 @@ msgstr "bulan" msgid "months" msgstr "bulan" -msgid "enter_email" -msgstr "Masukkan E-mel" +msgid "enter_email_to_complete_purchase" +msgstr "Masukkan e-mel untuk melengkapkan pembelian " msgid "error_fetching_plans" msgstr "Ralat mendapatkan pelan, sila mulakan semula Lantern dan cuba lagi." @@ -360,10 +474,12 @@ msgid "referral_code" msgstr "Kod rujukan" msgid "share_referral_code" -msgstr "" +msgstr "Kongsi Kod Rujukan " msgid "share_message_referral_code" msgstr "" +"Untuk sebulan percuma Lantern Pro, gunakan kod rujukan ini: %s ; Anda boleh " +"memuat turunnya di sini: https://lantern.io/download " msgid "add_referral_code" msgstr "Tambah kod Rujukan" @@ -433,7 +549,7 @@ msgstr "" "Maksimum tiga peranti boleh disambungkan dengan akaun anda." msgid "by_clicking_tos" -msgstr "Dengan mengklik %s, anda bersetuju dengan" +msgstr "Dengan mengklik %s, anda bersetuju dengan " msgid "tos" msgstr "Terma Perkhidmatan" @@ -483,6 +599,18 @@ msgstr "Ya" msgid "No" msgstr "Tidak" +msgid "exit" +msgstr "Keluar " + +msgid "show" +msgstr "Keluar " + +msgid "status_on" +msgstr "Status: Hidup " + +msgid "status_off" +msgstr "Status: Mati " + msgid "Enter or paste linking code" msgstr "Masukkan atau tampal kod pemautan" @@ -522,6 +650,9 @@ msgid "confirm_remove_device" msgstr "" "Adakah anda pasti ingin mengeluarkan pautan peranti dari account Pro anda?" +msgid "confirm_close_window" +msgstr "Adakah anda pasti mahu menutup Lantern? " + msgid "unlimited_data" msgstr "Data Tidak Terbatas" @@ -534,6 +665,9 @@ msgstr "Tiada Iklan" msgid "choose_plan" msgstr "Pilih Pelan" +msgid "billed_one_time" +msgstr "dibilkan sekali sahaja " + msgid "connect_up_to_3_devices" msgstr "Sambungkan sehingga 3 peranti" @@ -735,9 +869,6 @@ msgstr "Semua mesej tidak akan hilang untuk anda dan %s." msgid "message_disappearing_description" msgstr "Semua mesej akan hilang selepas %s untuk anda dan %s." -msgid "delete" -msgstr "Padam" - msgid "view_contact_info" msgstr "Lihat Maklumat Kenalan" @@ -1553,3 +1684,232 @@ msgstr "FAQ" msgid "add_device" msgstr "Tambah Peranti" + +msgid "search_apps" +msgstr "Cari Apl " + +msgid "user_not_found" +msgstr "" +"Pengguna tidak dikenali. Sila semak maklumat anda dan cuba lagi. Jika anda " +"terus mengalami masalah, sila hubungi pasukan sokongan kami untuk bantuan. " + +msgid "email_resend_message" +msgstr "Kod pengesahan baru anda telah dihantar " + +msgid "signup_error" +msgstr "" +"Nampaknya ada sesuatu yang tidak kena dengan pendaftaran. Sila cuba lagi, " +"dan jika masalah berterusan, hubungi pasukan sokongan kami. " + +msgid "signup_error_user_exists" +msgstr "" +"Nampaknya ada sesuatu yang tidak kena dengan pendaftaran. Sila cuba " +"menetapkan semula kata laluan anda, dan jika masalah berterusan, hubungi " +"pasukan sokongan kami. " + +msgid "confirm_email_error" +msgstr "Sila sahkan email anda " + +msgid "password_cannot_be_empty" +msgstr "Kata laluan tidak boleh kosong " + +msgid "password_must_be_at_least_8_characters" +msgstr "Kata laluan mesti sekurang-kurangnya 8 atau lebih aksara " + +msgid "sign_out" +msgstr "Log keluar " + +msgid "sign_out_message" +msgstr "" +"Log keluar akan menukar Lantern ke versi percuma. Untuk mengakses akaun anda" +" lagi, anda perlu log masuk semula. " + +msgid "change_password" +msgstr "Ubah Kata Laluan" + +msgid "password" +msgstr "Kata Laluan" + +msgid "new_email" +msgstr "Alamat Email Baru " + +msgid "new_email_same_as_old_email" +msgstr "Alamat email baru tidak boleh sama dengan alamat email lama anda " + +msgid "danger_zone" +msgstr "Zon bahaya " + +msgid "delete_account" +msgstr "Padam Akaun " + +msgid "delete" +msgstr "Padam" + +msgid "delete_account_message" +msgstr "" +"Memadam akaun anda tidak boleh dibatalkan. Anda akan kehilangan semua akses " +"peranti dan melepaskan sebarang masa akaun Pro tanpa bayaran balik. " + +msgid "delete_your_account" +msgstr "Padam akaun anda? " + +msgid "confirm_deletion" +msgstr "Sahkan Pemadaman " + +msgid "account_deleted" +msgstr "Akaun Dipadamkan " + +msgid "account_deleted_message" +msgstr "Akaun anda telah dipadamkan secara kekal. " + +msgid "password_has_been_updated" +msgstr "Kata laluan telah dikemas kini " + +msgid "password_has_been_updated_message" +msgstr "Kata laluan anda telah berjaya ditetapkan semula " + +msgid "invalid_code" +msgstr "" +"Kod salah atau mungkin telah tamat tempoh. Sila cuba hantar semula lagi " + +msgid "get_lantern_pro" +msgstr "Dapatkan Lantern Pro" + +msgid "try_lantern_pro" +msgstr "Cuba Lantern secara percuma! " + +msgid "continue_for_free" +msgstr "Teruskan secara Percuma " + +msgid "recovery_not_found" +msgstr "Peranti tidak dikaitkan dengan pengguna ini" + +msgid "device_added" +msgstr "Peranti tidak dikaitkan dengan pengguna ini" + +msgid "device_added_message" +msgstr "" +"Peranti anda telah berjaya ditambah. Jika anda tidak melihat peranti yang " +"disambungkan, sila mulakan semula aplikasi pada kedua-dua peranti " + +msgid "wrong_link_code" +msgstr "" + +msgid "recovery_code_sent" +msgstr "Kod pemulihan telah dihantar " + +msgid "device_added_msg" +msgstr "Peranti ini telah Dipautkan ke %s " + +msgid "new_to_lantern" +msgstr "Baru dengan Lantern? " + +msgid "email_has_been_updated" +msgstr "Email telah dikemas kini " + +msgid "email_has_been_updated_message" +msgstr "Email anda telah berjaya diubah " + +msgid "email_has_been_verified" +msgstr "Email telah disahkan " + +msgid "email_has_been_verified_message" +msgstr "Email anda telah disahkan berjaya " + +msgid "device_limit_reached" +msgstr "Had Peranti Dicapai " + +msgid "device_limit_reached_message" +msgstr "" +"Anda kini log masuk pada bilangan maksimum peranti (3). Untuk log masuk pada" +" peranti ini, sila pilih dan keluarkan salah satu peranti lain anda di " +"bawah. " + +msgid "enter_activation_code" +msgstr "Masukkan Kod Pengaktifan " + +msgid "check_your_email" +msgstr "Semak Alamat Email Anda " + +msgid "please_verify_email" +msgstr "Sila sahkan bahawa alamat email yang anda masukkan adalah betul. " + +msgid "verify" +msgstr "Sahkan" + +msgid "cannot_login" +msgstr "Tidak boleh log masuk " + +msgid "privacy_policy" +msgstr "Polisi Privasi" + +msgid "terms_of_service" +msgstr "Terma Perkhidmatan" + +msgid "invalid_verification_code" +msgstr "Kod pengessahan yang tidah sah" + +msgid "wrong_seller_code" +msgstr "Kod penjual semula tidak betul. Sila sahkan dan cuba lagi. " + +msgid "follow_us" +msgstr "Ikuti Kami " + +msgid "follow_us_telegram" +msgstr "Ikuti kami di Telegram " + +msgid "follow_us_instagram" +msgstr "Ikuti kami di Instagram " + +msgid "follow_us_facebook" +msgstr "Ikuti kami di Facebook " + +msgid "follow_us_x" +msgstr "Ikuti kami di X " + +msgid "report_issue_error" +msgstr "Kami mengalami kesukaran teknikal. Cuba lagi nanti. " + +msgid "we_are_experiencing_technical_difficulties" +msgstr "Kami mengalami kesukaran teknikal. Cuba lagi nanti. " + +msgid "a_temporary_error_occurred" +msgstr "Ralat sementara berlaku " + +msgid "sorry_we_are_unable_to_load_that_page" +msgstr "" +"Maaf, kami tidak dapat memuatkan halaman itu pada masa ini, sila tekan " +"'Refresh' untuk mencuba lagi. " + +msgid "refresh" +msgstr "Muat semula" + +msgid "check_your_internet_connection" +msgstr "Semak sambungan internet anda " + +msgid "please_try" +msgstr "Sila cuba " + +msgid "turning_off_airplane_mode" +msgstr "Matikan mod kapal terbang " + +msgid "turning_on_mobile_data_or_wifi" +msgstr "Hidupkan data mudah alih atau wifi " + +msgid "check_the_signal_in_your_area" +msgstr "Semak isyarat di kawasan anda " + +msgid "got_it" +msgstr "Faham" + +msgid "fetching_configuration" +msgstr "Mengambil Konfigurasi " + +msgid "establish_connection_to_server" +msgstr "Menjalin sambungan ke pelayan " + +msgid "file_size_limit_title" +msgstr "Had Tercapai " + +msgid "file_size_limit_description" +msgstr "Saiz lampiran hendaklah di bawah 100 MB" diff --git a/assets/locales/my-mm.po b/assets/locales/my-mm.po index 331ced4ae..367deb5eb 100644 --- a/assets/locales/my-mm.po +++ b/assets/locales/my-mm.po @@ -1,13 +1,15 @@ # # Translators: # tx_e2f_bu r4 , 2022 -# tx_e2f_bu r2 , 2023 -# tx_e2f_my t1 , 2023 -# e2f , 2023 +# Lantern, 2023 +# Derek F , 2024 +# e2f , 2024 +# tx_e2f_my t1 , 2024 +# tx_e2f_bu r2 , 2024 # msgid "" msgstr "" -"Last-Translator: e2f , 2023\n" +"Last-Translator: tx_e2f_bu r2 , 2024\n" "Language-Team: Burmese (https://app.transifex.com/lantern-1/teams/94371/my/)\n" "Language: my\n" "Plural-Forms: nplurals=1; plural=0;\n" @@ -30,6 +32,101 @@ msgstr "နောက်မှ ပြုလုပ်မည်" msgid "try" msgstr "ကြိုးစားကြည့်ပါ" +msgid "sign_in" +msgstr "ဆိုင်းအင်လုပ်ခြင်း" + +msgid "enter_email" +msgstr "အီးမေးလ်ထည့်ပါ" + +msgid "enter_password" +msgstr "စကားဝှက် ထည့်ပါ" + +msgid "forgot_password" +msgstr "စကားဝှက် မေ့သွားသည်" + +msgid "forgot_your_password" +msgstr "သင့်စကားဝှက်ကို မေ့သွားပါသလား။" + +msgid "click_here" +msgstr "ဤနေရာတွင် နှိပ်ပါ" + +msgid "reset_password" +msgstr "စကားဝှက်အား မူလအတိုင်းပြန်ထားပါ" + +msgid "lantern_pro_email" +msgstr "Lantern Pro အီးမေးလ် " + +msgid "return_to_sign_in" +msgstr "ဝင်ရောက်ရန် နောက်သို့ပြန်သွားပါ" + +msgid "enter_confirmation_code" +msgstr "အတည်ပြုကုဒ် ထည့်ပါ" + +msgid "confirmation_code_msg" +msgstr "" +"အတည်ပြုကုဒ်ကို XX သို့ ပို့ပြီးပါပြီ။ သင့်အကောင့်ကို လုံခြုံအောင်ထားရန်နှင့်" +" စကားဝှက် ပြန်လည်ရယူခြင်းကို ဖွင့်ရန် ၎င်းကို ဤနေရာတွင် ထည့်ပါ။" + +msgid "resend_confirmation_code" +msgstr "အတည်ပြုအီးမေးလ် ပြန်ပို့ရန်" + +msgid "email_already_exists" +msgstr "အီးမေးလ် ရှိပြီးသားဖြစ်သည်" + +msgid "email_already_exists_msg" +msgstr "" +"ဤအီးမေးလ်ကို ယူထားသည်၊ သင့်တွင် ဤအီးမေးလ်ဖြင့် အကောင့်တစ်ခု " +"ရှိပြီးသားဖြစ်ပါက ပြန်လည်ရယူနိုင်သည်။ မရှိလျှင် ပြန်သွားပြီး " +"အခြားအီးမေးလ်လိပ်စာကို ရွေးချယ်ပါ။" + +msgid "recover_account" +msgstr "အကောင့် ပြန်လည်ရယူရန်" + +msgid "back" +msgstr "‌နောက်သို့" + +msgid "change_email" +msgstr "အီးမေးလ် ပြောင်းရန်" + +msgid "new_password" +msgstr "စကားဝှက်အသစ်" + +msgid "confirm_new_password" +msgstr "စကားဝှက်အသစ်ကိုအတည်ပြုပါ" + +msgid "already_have_an_account" +msgstr "အကောင့်ရှိပြီးသားလား။ " + +msgid "create_account" +msgstr "အကောင့်ပြုလုပ်ပါ" + +msgid "create_password" +msgstr "စကားဝှက် ဖန်တီးရန်" + +msgid "by_creating_an_account" +msgstr "" +"အကောင့်တစ်ခုဖွင့်ခြင်းဖြင့် သင်သည် ကျွန်ုပ်တို့၏ ဖော်ပြပါကို " +"သဘောတူရာရောက်ပါသည်" + +msgid "confirm_email" +msgstr "အီးမေးကိုအတည်ပြုပါ" + +msgid "update_pro_account" +msgstr "Pro အကောင့်ကို အပ်ဒိတ်လုပ်ရန်" + +msgid "update_pro_account_message" +msgstr "" +"Lantern Pro တွင် ပြောင်းလဲမှုများ ရောက်လာပါပြီ။ ယခု သင်သည် စက်အားလုံးတွင် " +"လော့ဂ်အင်ဝင်ရန်နှင့် အကောင့် စီမံခန့်ခွဲမှုကို ဝင်ရောက်သုံးစွဲရန် " +"အီးမေးလ်နှင့် စကားဝှက် လိုအပ်ပါမည်။ အချိန်အနည်းငယ်ယူပြီး ဤအထောက်အထားများကို" +" သင့်အကောင့်တွင်ထည့်ပါ။ " + +msgid "not_now" +msgstr "ယခုမဟုတ်ပါ" + +msgid "update_account" +msgstr "အကောင့် အပ်ဒိတ်လုပ်ရန်" + msgid "new" msgstr "အသစ်" @@ -39,6 +136,12 @@ msgstr "နေ့စဉ် ဒေတာသုံးစွဲမှု" msgid "is" msgstr "သည်" +msgid "connect" +msgstr "ချိတ်ဆက်ပါ" + +msgid "disconnect" +msgstr "ချိတ်ဆက်မှုဖြတ်ပါ" + msgid "connected" msgstr "ချိတ်ဆက်ထားသည်" @@ -73,7 +176,8 @@ msgstr "Pro သို့ သွားပါ။" msgid "Go Pro Description" msgstr "" -"ပိုမိုမြန်ဆန်သော အမြန်နှုန်းများနှင့် အကန့်အသတ်မရှိသော ဒေတာကို လော့ဖွင့်ပါ!" +"ပိုမိုမြန်ဆန်သောနှုန်း, ကြော်ငြာမပါဘဲ အတွေ့အကြုံ၊ နှင့် " +"အကန့်အသတ်မရှိသောဒေတာကို ဖွင့်ပါ! " msgid "Server Location" msgstr "ဆာဗာတည်နေရာ" @@ -108,7 +212,7 @@ msgstr "ကျွန်ုပ်၏ ချက်ဒေတာကို ဖျက msgid "Pro Account Management" msgstr "Pro အကောင့်စီမံခန့်ခွဲမှု" -msgid "Link Device" +msgid "link_device" msgstr "ချိတ်ဆက်မှုကိရိယာ" msgid "Upgrade to Lantern Pro" @@ -117,6 +221,37 @@ msgstr "Lantern Pro ကိုအဆင့်မြှင့်တင်ပါ" msgid "Invite Friends" msgstr "သူငယ်ချင်းများကိုဖိတ်ကြားသည်" +msgid "share_lantern_pro" +msgstr "" +"Lantern Pro ကိုမိတ်ဆွေတစ်ယောက်နှင့်မျှဝေပါ! သင့်မိတ်ဆွေသည်၎င်းတို့၏ Lantern " +"Pro ဝယ်ယူမှုအတွင်း သင့်ရဲ့ ဖော်ပြချက်ကုဒ်ကိုအသုံးပြုပါက, သင်နှင့်သင့်မိတ်ဆွေ" +" နှစ်ယောက်စလုံး Lantern Pro ဝန်ဆောင်မှုကို တစ်လအခမဲ့ရရှိပါမည်! " + +msgid "privacy_disclosure_title" +msgstr "Lantern Privacy ထုတ်ဖော်" + +msgid "privacy_disclosure_body" +msgstr "" +"Lantern သည် သင်၏ ကိုယ်ရေးလုံခြုံမှုကို ကာကွယ်ရန် ကတိကဝတ်ပြုထားသည်။ " +"ကျွန်ုပ်တို့မည်သည့်သတင်းအချက်အလက်များကိုကျွန်ုပ်တို့စုဆောင်းသည်၊ " +"ကျွန်ုပ်တို့မစုဆောင်းမိသောအရာများနှင့်ကျွန်ုပ်တို့မည်သို့စုဆောင်းအသုံးပြုသည်၊" +" သိုလှောင်ထားသည့်သတင်းအချက်အလက်များကိုသင့်အားနားလည်စေလိုသည်။ " +"သင်၏ငွေပေးချေမှုဆိုင်ရာသတင်းအချက်အလက်များကိုသင်၏ Lantern Pro " +"အကောင့်နှင့်ဆက်သွယ်ခြင်းမပြုပါ။ သင်၏လုပ်ဆောင်မှုမှတ်တမ်းများ၊ browsing " +"history၊ traffic များ၊ အချက်အလက်အကြောင်းအရာများ (သို့) DNS " +"မေးမြန်းခြင်းများမပါဝင်ပါ။ " +"ဆက်သွယ်မှုမှတ်တမ်းများကိုလည်းမည်သည့်အခါမျှသိမ်းဆည်းမည်မဟုတ်ပါ။ " +"ဆိုလိုသည်မှာသင်၏ IP လိပ်စာ၊ log outan Lantern server IP connection " +"connection timestamp နှင့် session session ကြာလိမ့်မည်မဟုတ်ပါ။ တိကျတဲ့ " +"Lantern Pro အကောင့်တစ်ခုအရ Lantern သည် Lantern " +"အကောင့်ကိုအသုံးပြုသူ၏မည်သူမည်ဝါဖြစ်ကြောင်းနှင့်နည်းပညာပိုင်းအရမချိတ်ဆက်နိုင်ပါ။" +" Lantern အိုင်ပီလိပ်စာတစ်ခုနှင့် တိကျသောအချိန်တံဆိပ်ခေါင်းများအရ Lantern သည်" +" ဤ IP လိပ်စာကို Lantern အသုံးပြုသူသို့မဟုတ် Lantern " +"အသုံးပြုသူအိုင်ပီလိပ်စာအားသတ်မှတ်ရန် နည်းပညာပိုင်းအရမစွမ်းဆောင်နိုင်ပါ။" + +msgid "privacy_disclosure_accept" +msgstr "ကျွန်တော်လက်ခံသည်" + msgid "desktop_version" msgstr "Desktop ဗားရှင်း" @@ -129,12 +264,6 @@ msgstr "အထွေထွေ" msgid "about" msgstr "အကြောင်း" -msgid "privacy_policy" -msgstr "ကိုယ်ရေးအချက်အလက်မူဝါဒ" - -msgid "terms_of_service" -msgstr "ဝန်ဆောင်မှု၏စည်းကမ်းချက်များ" - msgid "proxy_all" msgstr "အရာအားလုံး ကိုယ်စား" @@ -153,8 +282,47 @@ msgstr "SDK %s" msgid "language" msgstr "ဘာသာစကား" -msgid "report_issue" -msgstr "ပြဿနာတစ်ခုကို သတင်းပို့ပါ" +msgid "select_an_issue" +msgstr "ပြဿနာကိုရွေးချယ်ပါ" + +msgid "enter_description" +msgstr "ကျေးဇူးပြု၍ သင့်ပြဿနာကိုဖော်ပြပါ " + +msgid "cannot_access_blocked_sites" +msgstr "ပိတ်ထားသောဆိုဒ်များကို ကြည့်၍မရပါ" + +msgid "cannot_complete_purchase" +msgstr "ဝယ်ယူမှုကိုပြီးမြောက်စေမရနိုင် " + +msgid "discover_not_working" +msgstr "ရှာဖွေမှုလုပ်ငန်းမလုပ်နိုင် " + +msgid "cannot_sign_in" +msgstr "လက်မှတ်ထိုးမရနိုင် " + +msgid "spinner_loads_endlessly" +msgstr "အဆုံးမရှိသော လည်ပတ်ခြင်း " + +msgid "slow" +msgstr "နှေးနှေး" + +msgid "cannot_link_devices" +msgstr "ကိရိယာများကို ချိတ်ဆက်၍ မရပါ" + +msgid "wrong_device_linking_code" +msgstr "မှားယွင်းသော သို့မဟုတ် မမှန်ကန်သော လင့်ခ်ချိတ်သည့်ကုဒ်" + +msgid "cannot_remove_device" +msgstr "စက်ကိရိယာကိုဖယ်ရှားမရနိုင် " + +msgid "application_crashes" +msgstr "အပလီကေးရှင်းမကြာခဏကျွေး " + +msgid "other" +msgstr "အခြား" + +msgid "issue_description" +msgstr "သင့်ပြဿနာကိုဖော်ပြပါ " msgid "check_for_updates" msgstr "အပ်ဒိတ်များကိုစစ်ဆေးပါ" @@ -192,6 +360,20 @@ msgstr "PIN ဖြင့်လင့်ခ်ချိတ်ပါ" msgid "OR" msgstr "သို့မဟုတ်" +msgid "device_linking_pin" +msgstr "ကိရိယာချိတ်ဆက်မှု ပင်နံပါတ်" + +msgid "link_device_step_one" +msgstr "သင့် Pro စက်ကိရိယာတွင် 'စက်ကိရိယာထည့်ရန်' မီနူးအချက်အလက်ကိုဖွင့်ပါ။ " + +msgid "link_device_step_two" +msgstr "စက်ကိရိယာဆက်စပ် PIN ကိုထည့်ပြီးတင်ပါ " + +msgid "ensure_most_recent_version_lantern" +msgstr "" +"* ကိရိယာနှစ်ခုစလုံးသည် Lantern ၏ နောက်ဆုံးထွက်ဗားရှင်းကို " +"အသုံးပြုထားကြောင်းသေချာပါစေ" + msgid "Authorize Device via Email" msgstr "ကိရိယာကို အီးမေးလ်မှတစ်ဆင့် ခွင့်ပြုပါ" @@ -205,10 +387,41 @@ msgstr "" msgid "Link via Email" msgstr "အီးမေးလ်မှတဆင့်လင့်ခ်ချိတ်ပါ" -msgid "lantern_pro_email" -msgstr "Lantern Pro အီးမေးလ်" +msgid "lantern_desktop" +msgstr "Lantern Desktop" + +msgid "report_description" +msgstr "ပြဿနာရှင်းလင်းဖော်ပြချက်" + +msgid "report_issue" +msgstr "ပြဿနာကိုအကြောင်းပြန်ကြားပါ " + +msgid "report_an_issue" +msgstr "ပြဿနာတစ်ခုသတင်းပို့ပါ" + +msgid "report_sent" +msgstr "ပို့ပြီးပါပြီ" + +msgid "thank_you_for_reporting_your_issue" +msgstr "" +"သင်၏ပြဿနာကို တင်ပြသည့်အတွက် ကျေးဇူးတင်ပါသည်။ " +"ကျွန်ုပ်တို့တတ်နိုင်သမျှအမြန်ဆုံး အီးမေးလ်ဖြင့်ပြန်ပို့ပါမည်" + +msgid "send_report" +msgstr "အစီရင်ခံစာပို့ရန်" + +msgid "most_recent_lantern_apps" +msgstr "" +"ဒပ်စတော့နှင့် အခြားပလက်ဖောင်းများအားလုံးအတွက် လတ်တလော Lantern အက်ပ်များကို " +"အပေါ်က လင့်ခ်တွင် တွေ့နိုင်ပါသည်။" -msgid "Email" +msgid "share_link" +msgstr "လင့်ခ်မျှဝေပါ " + +msgid "share_title" +msgstr "Lantern ဒေါင်းလုဒ်လင့်ခ်ကို မျှဝေပါ" + +msgid "email" msgstr "အီးမေးလ်" msgid "Submit" @@ -217,9 +430,172 @@ msgstr "တင်သွင်းပါ" msgid "save" msgstr "သိမ်းဆည်းမည်" -msgid "Please enter a valid email address" +msgid "please_enter_a_valid_email_address" msgstr "မှန်ကန်သော အီးမေးလ်လိပ်စာထည့်ပါ" +msgid "bonus" +msgstr "အပိုဆု " + +msgid "referral_bonus" +msgstr "ရည်ညွှန်းအပိုဆု " + +msgid "total" +msgstr "စုစုပေါင်း " + +msgid "month" +msgstr "လ" + +msgid "months" +msgstr "လများ " + +msgid "enter_email_to_complete_purchase" +msgstr "ဝယ်ယူမှုကိုပြီးမြောက်စေရန် အီးမေးလ်ထည့်ပါ " + +msgid "error_fetching_plans" +msgstr "" +"အစီအစဉ်များကိုရယူရာတွင်အမှားအယွင်းတစ်ခုဖြစ်ပွားခဲ့သည်၊ ကျေးဇူးပြု၍ Lantern " +"ကိုပြန်လည်စတင်ပြီး ထပ်မံကြိုးစားပါ။ " + +msgid "apply" +msgstr "လျှောက်ထားပါ" + +msgid "choose_payment_method" +msgstr "ငွေပေးချေမှုနည်းလမ်းရွေးပါ " + +msgid "pro_plan" +msgstr "Pro အစီအစဉ် " + +msgid "one_year_plan" +msgstr "၁ နှစ်အစီအစဉ် " + +msgid "two_year_plan" +msgstr "၂ နှစ်အစီအစဉ် " + +msgid "plan_discount" +msgstr "%d%% လွတ်မြောက် " + +msgid "enter_email_and_activation_code" +msgstr "အီးမေးလ်နှင့်အတည်ပြုကုဒ်ထည့်ပါ " + +msgid "referral_code" +msgstr "ရည်ညွှန်းကုဒ်" + +msgid "share_referral_code" +msgstr "ရည်ညွှန်းကုဒ်မျှဝေပါ " + +msgid "share_message_referral_code" +msgstr "" +"တစ်လအခမဲ့သော Lantern Pro ရရန်, ဤရည်ညွှန်းကုဒ်ကိုအသုံးပြုပါ: %s; သင်ဤနေရာမှ " +"ဒေါင်းလုပ်လုပ်နိုင်သည်: https://lantern.io/download " + +msgid "add_referral_code" +msgstr "ရည်ညွှန်းကုဒ်ထည့်ပါ " + +msgid "Activation Code" +msgstr "Activation ကုဒ်" + +msgid "invalid_or_incomplete_referral_code" +msgstr "မမှန်ကန်သော သို့မဟုတ် မပြည့်စုံသော ရည်ညွှန်းကုဒ် " + +msgid "register_for_pro" +msgstr "Pro အတွက်မှတ်ပုံတင်ပါ" + +msgid "Something went wrong while applying your referral code." +msgstr "သင့်ရဲ့ရည်ညွှန်းကုဒ်ကိုအသုံးပြုရာတွင် အမှားတစ်ခုဖြစ်ပွားခဲ့သည်။ " + +msgid "your_activation_code_is_invalid" +msgstr "သင့်အတည်ပြုကုဒ်သည်မမှန်ကန်ပါ။ " + +msgid "update_cache_plans_timeout" +msgstr "ရရှိနိုင်သောအစီအစဉ်များကိုရယူရာတွင် အမှားတစ်ခုဖြစ်ပွားခဲ့သည်။ " + +msgid "bitcoin_timeout" +msgstr "BTCPay တောင်းဆိုမှုသည်အချိန်ကုန်ဆုံးခဲ့သည်။ " + +msgid "BTCPay is unavailable" +msgstr "BTCPay မရရှိနိုင်ပါ " + +msgid "update_cache_user_timeout" +msgstr "သင့်အကောင့်ဒေတာကိုရယူရာတွင် အမှားတစ်ခုဖြစ်ပွားခဲ့သည်။ " + +msgid "reseller_timeout" +msgstr "သင်၏ စဖွင့်သုံးကုဒ်ကို အတည်ပြုနေစဉ် တစ်ခုခုမှားယွင်းသွားသည်။" + +msgid "reseller_success" +msgstr "အောင်မြင်စွာအတည်ပြုကုဒ်ကိုရရှိပါပြီ။ " + +msgid "stripe_timeout" +msgstr "" +"သင့် Stripe ငွေပေးချေမှုသည်အချိန်ကုန်ဆုံးခဲ့သည်။ ကျေးဇူးပြု၍ Lantern " +"ကိုပြန်လည်စတင်ပြီး ထပ်မံကြိုးစားပါ။ " + +msgid "stripe_success" +msgstr "Stripe ဝယ်ယူမှုအောင်မြင်သည်။ " + +msgid "renewal_success" +msgstr "အသက်ဆက် အောင်မြင်သည်။ " + +msgid "pro_renewal_success_description" +msgstr "" +"သင့်ရဲ့ 蓝灯 Pro အကောင့်အားအသက်ဆက်ပေးသည့်အတွက် ကျေးဇူးတင်ပါတယ်။ သင်အခုမှ " +"မြန်ဆန်ဆုံးဒေတာစင်တာများကို အသုံးပြုပြီး အကန့်အသတ်မရှိသုံးနိုင်သည်။ " +"အကောင့်နှင့်ဆက်သွယ်ပြီး တက်ဘလက် အပါအဝင် သုံးခုအထိ တည်ဆက်ထားနိုင်သည်။ " + +msgid "pro_purchase_success" +msgstr "Lantern Pro မှ ကြိုဆိုပါသည် " + +msgid "platinum_purchase_success_descripion" +msgstr "" +"သင်သည် ယခု အမြန်ဆုံးဒေတာစင်တာများနှင့် အထူးလိုင်းကို အသုံးပြု၍ အကန့်အသတ်မရှိ" +" ဝင်ရောက်အသုံးပြုနိုင်ပါသည်။ သင်၏အကောင့်တွင် အထိ သုံးခုထိကိရိယာများကို " +"ချိတ်ဆက်နိုင်ပါသည်။ " + +msgid "pro_purchase_success_descripion" +msgstr "" +"သင်သည် ယခု အမြန်ဆုံးဒေတာစင်တာများကို အသုံးပြု၍ အကန့်အသတ်မရှိ " +"ဝင်ရောက်အသုံးပြုနိုင်ပါသည်။ သင်၏အကောင့်တွင် အထိ သုံးခုထိကိရိယာများကို " +"ချိတ်ဆက်နိုင်ပါသည်။ " + +msgid "by_clicking_tos" +msgstr "" +"%s ကိုနှိပ်ခြင်းဖြင့် သင်သည် ကျွန်ုပ်တို့၏ ဖော်ပြပါကို သဘောတူရာရောက်ပါသည် " + +msgid "tos" +msgstr "ဝန်ဆောင်မှု၏စည်းကမ်းချက်များ" + +msgid "step_1" +msgstr "အဆင့် 1" + +msgid "step_2" +msgstr "အဆင့် ၂" + +msgid "step_3" +msgstr "အဆင့် 3" + +msgid "invalid_email" +msgstr "မှန်ကန်သော အီးမေးလ်လိပ်စာမဟုတ်ပါ။ " + +msgid "invalid_card" +msgstr "မှန်ကန်သော ခရက်ဒစ်ကတ်နံပါတ်မဟုတ်ပါ။ " + +msgid "invalid_expiration" +msgstr "မမှန်ကန်သော သက်တမ်းကုန်ဆုံးရက်" + +msgid "cc_number" +msgstr "အကြွေးဝယ်ကဒ်" + +msgid "card_number" +msgstr "ကတ်နံပါတ်" + +msgid "card_expiration" +msgstr "လ / နှစ်" + +msgid "invalid_cvc" +msgstr "မမှန်ကန်သော CVC" + +msgid "complete_purchase" +msgstr "ဝယ်ယူမှုအပြီး" + msgid "Re-send Email" msgstr "အီးမေးလ်ပြန်ပို့ပါ" @@ -232,6 +608,18 @@ msgstr "ဟုတ်တယ်" msgid "No" msgstr "မဟုတ်ဘူး" +msgid "exit" +msgstr "ထွက်ရန် " + +msgid "show" +msgstr "ပြရန် " + +msgid "status_on" +msgstr "အခြေအနေ: အဖွင့် " + +msgid "status_off" +msgstr "အခြေအနေ: အဖွင့် " + msgid "Enter or paste linking code" msgstr "လင့်ခ်ကုဒ်ကို ထည့်သွင်းပါ သို့မဟုတ် ကူးယူပါ" @@ -271,6 +659,60 @@ msgstr "လော့အောက်ထွက်ပါ" msgid "confirm_remove_device" msgstr "ဤပစ္စည်းအားသင်၏ Pro အကောင့်မှသင်ပြန်ဖွင့်လိုသည်မှာသေချာပါသလား။" +msgid "confirm_close_window" +msgstr "သင်သည် Lantern ကိုပိတ်ရန် အတည်ပြုပါသလား? " + +msgid "unlimited_data" +msgstr "Unlimited Data" + +msgid "no_logs" +msgstr "မှတ်တမ်းများ မရှိပါ" + +msgid "no_ads" +msgstr "ကြော်ငြာမရှိပါ" + +msgid "choose_plan" +msgstr "Plan ကိုရွေးချယ်ပါ" + +msgid "billed_one_time" +msgstr "တစ်ကြိမ်ပေးဆောင်သည်။ " + +msgid "connect_up_to_3_devices" +msgstr "ကိရိယာ ၃ ခုအထိချိတ်ဆက်ပါ" + +msgid "faster_data_centers" +msgstr "ပိုမိုမြန်ဆန်သော ဒေတာစင်တာများ" + +msgid "dedicated_line" +msgstr "အထူးလိုင်း " + +msgid "increased_reliability" +msgstr "ယုံကြည်စိတ်ချရမှု မြင့်မားခြင်း " + +msgid "checkout" +msgstr "ထွက်ခွာသည်" + +msgid "credit_card" +msgstr "အကြွေးဝယ်ကဒ်" + +msgid "lantern_pro_checkout" +msgstr "Lantern Pro Checkout" + +msgid "1y_pricing" +msgstr "၁ နှစ်စျေးနှုန်း " + +msgid "2y_pricing" +msgstr "၂ နှစ်စျေးနှုန်း " + +msgid "plan_type" +msgstr "အစီအစဉ်အမျိုးအစား " + +msgid "most_popular" +msgstr "အများဆုံး နှစ်သက်သည်။ " + +msgid "free" +msgstr "အခမဲ့" + msgid "n/a" msgstr "n/a" @@ -335,8 +777,11 @@ msgstr "ပြင်ဆင်မည်" msgid "save_error" msgstr "သိမ်းဆည်းနေစဉ် တစ်စုံတစ်ခု မှားသွားသည်။" +msgid "fewer_options" +msgstr "ရွေးချယ်မှုများနည်းသည် " + msgid "more_options" -msgstr "နောက်ထပ် ရွေးချယ်စရာများ" +msgstr "ရွေးချယ်မှုများပိုမိုရှိသည် " msgid "block_user" msgstr "အသုံးပြုသူကို ပိတ်ဆို့မည်" @@ -405,22 +850,30 @@ msgid "start_chat" msgstr "ချက် စတင်မည်" msgid "your_installed_apps" -msgstr "" +msgstr "သင့်တပ်ဆင်ထားသော အက်ပ်များ " msgid "apps_routed_through_lantern" -msgstr "" +msgstr "Lantern ဖြင့် လမ်းကြောင်းသွားသော အက်ပ်များ " msgid "apps_to_unblock" msgstr "" +"သင်ဖွင့်လိုသော အက်ပ်များကို ရွေးချယ်ပါ။ ဆက်သွယ်ပါက အောက်ပါ အက်ပ်များသာ " +"Lantern ဖြင့် လမ်းကြောင်းသွားပါမည်။ " msgid "split_tunneling" -msgstr "" +msgstr "အပိုတန်းနယ်လမ်းကြောင်းခွဲခြားခြင်း " msgid "split_tunneling_info" msgstr "" +"အပိုတန်းနယ်လမ်းကြောင်းခွဲခြားခြင်းကို ဖွင့်ထားပါက ရွေးချယ်ထားသော " +"အက်ပ်များသည် Lantern ဖြင့် လမ်းကြောင်းသွားပါမည်။ ဒါက " +"တိုးတက်မှုမြင့်မားစေပြီး သတ်မှတ်ထားသော အက်ပ်များကိုသာ အခက်အခဲကင်းစွာ " +"ဖြေရှင်းပေးနိုင်သည်။ " msgid "applied_next_time" msgstr "" +"အပိုတန်းနယ်လမ်းကြောင်းခွဲခြားခြင်း အသစ်ကို နောက်တစ်ကြိမ်ဆက်သွယ်ပါက " +"ဆက်သွယ်မှုတွင် အသုံးပြုမည်။ " msgid "message_disappearing" msgstr "မက်ဆေ့ချ်အားလုံးသည် သင်နှင့် %s အတွက် ပျောက်သွားမည်မဟုတ်ပါ။" @@ -428,9 +881,6 @@ msgstr "မက်ဆေ့ချ်အားလုံးသည် သင်န msgid "message_disappearing_description" msgstr "မက်ဆေ့ချ်အားလုံးသည် သင်နှင့် %s အတွက် %s အကြာတွင် ပျောက်သွားပါမည်။" -msgid "delete" -msgstr "ဖျက်ပါ" - msgid "view_contact_info" msgstr "အဆက်အသွယ် အချက်အလက်ကို ကြည့်မည်" @@ -443,6 +893,9 @@ msgstr "ဖွင့်" msgid "continue" msgstr "ဆက်သွားပါ" +msgid "continue_to_pro" +msgstr "Pro ကိုဆက်သွားပါ" + msgid "are_you_sure_you_want_to_open" msgstr "%s ကို သင်ဖွင့်လိုသည်မှာ သေချာပါသလား။" @@ -1243,13 +1696,247 @@ msgstr "" "ပြင်ပအဖွဲ့အစည်းများထံ မရောက်ရှိစေရန် ကာကွယ်ပေးသည်။" msgid "support" -msgstr "" +msgstr "အထောက်အပံ့ " msgid "lantern_user_forum" -msgstr "" +msgstr "Lantern အသုံးပြုသူဖိုရမ် " msgid "faq" -msgstr "" +msgstr "အမြဲမေးလေ့ရှိသောမေးခွန်းများ " msgid "add_device" msgstr "ကိရိယာထည့်ပါ" + +msgid "search_apps" +msgstr "အက်ပ်များကိုရှာဖွေပါ " + +msgid "user_not_found" +msgstr "" +"သုံးစွဲသူကို မသိရှိပါ။ သင့်အထောက်အထားများကို စစ်ဆေးပြီး ထပ်မံကြိုးစားပါ။ " +"ပြဿနာများကို ဆက်လက်တွေ့ကြုံပါက ကျွန်ုပ်တို့၏ ကူညီပံ့ပိုးရေးအဖွဲ့ကို ဆက်သွယ်၍" +" အကူအညီရယူပါ။" + +msgid "email_resend_message" +msgstr "သင်၏ အတည်ပြုကုဒ်အသစ်ကို ပို့ပြီးပါပြီ" + +msgid "signup_error" +msgstr "" +"အကောင့်ဖွင့်ရာတွင် တစ်ခုခုမှားယွင်းသွားပုံရသည်။ ထပ်မံကြိုးစားပါ၊ ပြဿနာ " +"ဆက်ဖြစ်နေပါက ကျွန်ုပ်တို့၏ ကူညီပံ့ပိုးရေးအဖွဲ့ကို ဆက်သွယ်ပါ" + +msgid "signup_error_user_exists" +msgstr "" +"အကောင့်ဖွင့်ရာတွင် တစ်ခုခုမှားယွင်းသွားပုံရသည်။ သင့်စကားဝှက်ကို " +"ပြန်သတ်မှတ်ကြည့်ပြီး ပြဿနာ ဆက်ရှိနေပါက ကျွန်ုပ်တို့၏ ကူညီပံ့ပိုးရေးအဖွဲ့ကို " +"ဆက်သွယ်ပါ" + +msgid "confirm_email_error" +msgstr "သင့်အီးမေးလ်ကို အတည်ပြုပါ" + +msgid "password_cannot_be_empty" +msgstr "စကားဝှက်ကို ကွက်လပ်မထားရပါ" + +msgid "password_must_be_at_least_8_characters" +msgstr "စကားဝှက်သည် အနည်းဆုံးအက္ခရာ 8 လုံးနှင့်အထက် ဖြစ်ရမည်" + +msgid "sign_out" +msgstr "ထွက်ရန်" + +msgid "sign_out_message" +msgstr "" +"ထွက်ပါက Lantern ကို အခမဲ့ဗားရှင်းသို့ ပြောင်းပါမည်။ သင့်အကောင့်ကို " +"ထပ်မံဝင်ရောက်သုံးစွဲရန် သင့်အနေဖြင့် ပြန်လည်ဝင်ရောက်ရပါမည်။" + +msgid "change_password" +msgstr "စကားဝှက်ကိုပြောင်းရန်" + +msgid "password" +msgstr "စကားဝှက်" + +msgid "new_email" +msgstr "အီးမေးလ်လိပ်စာအသစ်" + +msgid "new_email_same_as_old_email" +msgstr "အီးမေးလ်လိပ်စာအသစ်သည် အီးမေးလ်လိပ်စာအဟောင်းနှင့် မတူရပါ" + +msgid "danger_zone" +msgstr "အန္တရာယ်ဇုန်" + +msgid "delete_account" +msgstr "အကောင့်ဖျက်ရန်" + +msgid "delete" +msgstr "ဖျက်ပါ" + +msgid "delete_account_message" +msgstr "" +"သင့်အကောင့်ကို ဖျက်ပြီးသွားလျှင် ပြန်ပြင်၍မရပါ။ သင်သည် " +"စက်ဝင်သုံးခွင့်အားလုံးကို ဆုံးရှုံးမည့်အပြင် Pro " +"အကောင့်သုံးခွင့်ကျန်ချိန်မှန်သမျှကို ပြန်အမ်းငွေမရရှိဘဲ လက်လွှတ်ရမည်။" + +msgid "delete_your_account" +msgstr "သင့်အကောင့်ကို ဖျက်မလား။" + +msgid "confirm_deletion" +msgstr "ဖျက်ခြင်းကို အတည်ပြုရန်" + +msgid "account_deleted" +msgstr "အကောင့် ဖျက်လိုက်ပြီ" + +msgid "account_deleted_message" +msgstr "သင့်အကောင့်ကို အပြီးတိုင်ဖျက်လိုက်ပါပြီ။" + +msgid "password_has_been_updated" +msgstr "စကားဝှက်ကို အပ်ဒိတ်လုပ်ပြီးပါပြီ" + +msgid "password_has_been_updated_message" +msgstr "သင့်စကားဝှက်ကို အောင်မြင်စွာ ပြန်သတ်မှတ်ပြီးပါပြီ" + +msgid "invalid_code" +msgstr "" +"ကုဒ် မှားနေသည် သို့မဟုတ် သက်တမ်းကုန်သွားခြင်း ဖြစ်နိုင်သည်။ ထပ်ပြီး " +"ပြန်ပို့ကြည့်ပါ" + +msgid "get_lantern_pro" +msgstr "Lantern Pro ကိုရယူပါ" + +msgid "try_lantern_pro" +msgstr "Lantern ကို အခမဲ့စမ်းသုံးကြည့်ပါ။ " + +msgid "continue_for_free" +msgstr "အခမဲ့ ဆက်သုံးရန် " + +msgid "recovery_not_found" +msgstr "စက်ကို ဤအသုံးပြုသူနှင့် တွဲဖက်ထားခြင်းမရှိပါ" + +msgid "device_added" +msgstr "စက်ကို ဤအသုံးပြုသူနှင့် တွဲဖက်ထားခြင်းမရှိပါ" + +msgid "device_added_message" +msgstr "ပြန်လည်ရယူရန်ကုဒ် ပေးပို့ပြီးပြီ " + +msgid "wrong_link_code" +msgstr "သင်ထည့်ထားသည့်ကုဒ်သည် မှားနေသည်။ စစ်ဆေးပြီး ထပ်မံကြိုးစားပါ။" + +msgid "recovery_code_sent" +msgstr "ပြန်လည်ရယူကုဒ်ကို ပို့ပြီးပါပြီ" + +msgid "device_added_msg" +msgstr "ဤစက်ကို %s နှင့် ချိတ်ဆက်ထားသည်" + +msgid "new_to_lantern" +msgstr "Lantern ကို ယခုမှစသုံးဖူးတာလား။ " + +msgid "email_has_been_updated" +msgstr "အီးမေးလ်ကို အပ်ဒိတ်လုပ်ပြီးပြီ" + +msgid "email_has_been_updated_message" +msgstr "သင့်အီးမေးလ်ကို အောင်မြင်စွာပြောင်းပြီးပါပြီ" + +msgid "email_has_been_verified" +msgstr "အီးမေးလ်ကို အတည်ပြုပြီးပြီ" + +msgid "email_has_been_verified_message" +msgstr "သင့်အီးမေးလ်ကို အောင်မြင်စွာအတည်ပြုပြီးပါပြီ" + +msgid "device_limit_reached" +msgstr "စက်ကန့်သတ်ချက်သို့ ရောက်ပါပြီ" + +msgid "device_limit_reached_message" +msgstr "" +"အများဆုံးစက်အရေအတွက် (3) လုံးတွင် သင် လက်ရှိ ဝင်ရောက်ထားသည်။ ဤစက်တွင် " +"ဝင်ရောက်ရန် အောက်ရှိသင်၏အခြားစက်များမှ တစ်ခုကို ရွေးချယ်ပြီး ဖယ်ရှားပါ။" + +msgid "enter_activation_code" +msgstr "စဖွင့်သုံးကုဒ် ထည့်ရန်" + +msgid "check_your_email" +msgstr "သင့်အီးမေးလ်လိပ်စာကို စစ်ဆေးပါ" + +msgid "please_verify_email" +msgstr "သင်ထည့်ထားသည့် အီးမေးလ်လိပ်စာသည် မှန်ကန်ကြောင်း အတည်ပြုပါ။" + +msgid "verify" +msgstr "အတည်ပြုရန်" + +msgid "cannot_login" +msgstr "လော့ဂ်အင်ဝင်လို့မရပါ " + +msgid "privacy_policy" +msgstr "ကိုယ်ရေးအချက်အလက်မူဝါဒ" + +msgid "terms_of_service" +msgstr "ဝန်ဆောင်မှု၏စည်းကမ်းချက်များ" + +msgid "invalid_verification_code" +msgstr "မှန်ကန်မှုမရှိသောအတည်ပြုနံပါတ်" + +msgid "wrong_seller_code" +msgstr "" +"အရောင်းကိုယ်စားလှယ်ကုဒ်မှားနေသည်။ ကျေးဇူးပြု၍ စစ်ဆေးပြီး ထပ်မံကြိုးစားပါ။ " + +msgid "follow_us" +msgstr "ကျွန်ုပ်တို့ကိုလိုက်နာပါ " + +msgid "follow_us_telegram" +msgstr "ကျွန်ုပ်တို့ကို Telegram ပေါ်တွင်လိုက်နာပါ " + +msgid "follow_us_instagram" +msgstr "ကျွန်ုပ်တို့ကို Instagram ပေါ်တွင်လိုက်နာပါ " + +msgid "follow_us_facebook" +msgstr "ကျွန်ုပ်တို့ကို Facebook ပေါ်တွင်လိုက်နာပါ " + +msgid "follow_us_x" +msgstr "ကျွန်ုပ်တို့ကို X ပေါ်တွင်လိုက်နာပါ " + +msgid "report_issue_error" +msgstr "" +"ကျွန်ုပ်တို့သည် နည်းပညာပိုင်းဆိုင်ရာ အခက်အခဲများနှင့် ကြုံနေရပါသည်။ နောက်မှ " +"ထပ်ကြိုးစားပါ။ " + +msgid "we_are_experiencing_technical_difficulties" +msgstr "" +"ကျွန်ုပ်တို့သည် နည်းပညာပိုင်းဆိုင်ရာ အခက်အခဲများနှင့် ကြုံနေရပါသည်။ နောက်မှ " +"ထပ်ကြိုးစားပါ။ " + +msgid "a_temporary_error_occurred" +msgstr "ယာယီအမှားတစ်ခု ဖြစ်ပွားခဲ့သည် " + +msgid "sorry_we_are_unable_to_load_that_page" +msgstr "" +"ဝမ်းနည်းပါသည်၊ ကျွန်ုပ်တို့သည် ယခုအချိန်တွင် အဲဒီစာမျက်နှာကို " +"ပြန်လည်သတ်မှတ်၍ မရပါ၊ ထပ်ကြိုးစားရန် ‘Refresh’ ကိုနှိပ်ပါ။ " + +msgid "refresh" +msgstr "Refresh" + +msgid "check_your_internet_connection" +msgstr "သင့်အင်တာနက်ဆက်သွယ်မှုကို စစ်ဆေးပါ " + +msgid "please_try" +msgstr "ကျေးဇူးပြု၍ ထပ်မံကြိုးစားပါ " + +msgid "turning_off_airplane_mode" +msgstr "လေယာဉ်ပြေးလမ်းအချိန်ကို ပိတ်ပါ " + +msgid "turning_on_mobile_data_or_wifi" +msgstr "မိုဘိုင်းဒေတာ သို့မဟုတ် wifi ကိုဖွင့်ပါ " + +msgid "check_the_signal_in_your_area" +msgstr "သင့်နေရာအတွက် အချက်ပြချက်ကိုစစ်ဆေးပါ " + +msgid "got_it" +msgstr "နားလည်ပြီ" + +msgid "fetching_configuration" +msgstr "ဖွဲ့စည်းမှုကိုရယူနေသည် " + +msgid "establish_connection_to_server" +msgstr "ဆာဗာနှင့် ချိတ်ဆက်မှုကို အတည်ပြုသည် " + +msgid "file_size_limit_title" +msgstr "ကန့်သတ်ချက်ရောက်ရှိပြီးပြီ " + +msgid "file_size_limit_description" +msgstr "ပူးတွဲထားသောဖိုင်အရွယ်အစားသည် 100 MB ထက်ငယ်သင့်သည်" diff --git a/assets/locales/th-th.po b/assets/locales/th-th.po new file mode 100644 index 000000000..86f3f9bd7 --- /dev/null +++ b/assets/locales/th-th.po @@ -0,0 +1,1878 @@ +# +# Translators: +# Acclaro Thailand , 2024 +# Derek F , 2024 +# Thai Localization, 2024 +# Amazing Nev , 2024 +# Lantern, 2024 +# e2f , 2024 +# tx_e2f_th c5 , 2024 +# +msgid "" +msgstr "" +"Last-Translator: tx_e2f_th c5 , 2024\n" +"Language-Team: Thai (https://app.transifex.com/lantern-1/teams/94371/th/)\n" +"Language: th\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +msgid "app_name" +msgstr "Lantern" + +msgid "introducing" +msgstr "แนะนำแชท" + +msgid "introducing_des" +msgstr "" +"แชทกับเพื่อนอย่างปลอดภัยด้วยการต้านทานต่อการปิดกั้นแบบใหม่และแชทเข้ารหัสที่สร้างมาโดยเฉพาะสำหรับ" +" Lantern!" + +msgid "maybe_later" +msgstr "ไว้ครั้งหน้า" + +msgid "try" +msgstr "ทดลองใช้งาน" + +msgid "sign_in" +msgstr "Sign In" + +msgid "enter_email" +msgstr "ป้อนอีเมล" + +msgid "enter_password" +msgstr "ใส่รหัสผ่าน" + +msgid "forgot_password" +msgstr "ลืมรหัสผ่าน" + +msgid "forgot_your_password" +msgstr "ลืมรหัสผ่านของคุณใช่ไหม" + +msgid "click_here" +msgstr "คลิกที่นี่" + +msgid "reset_password" +msgstr "ตั้งรหัสผ่านใหม่" + +msgid "lantern_pro_email" +msgstr "อีเมล Lantern Pro" + +msgid "return_to_sign_in" +msgstr "ย้อนกลับไปที่หน้าเข้าสู่ระบบ" + +msgid "enter_confirmation_code" +msgstr "ใส่รหัสยืนยัน" + +msgid "confirmation_code_msg" +msgstr "" +"รหัสยืนยันได้ถูกส่งไปที่ XX " +"กรอกรหัสยืนยันที่นี่เพื่อทำให้บัญชีของคุณปลอดภัยและเปิดใช้งานการกู้คืนรหัสผ่าน" + +msgid "resend_confirmation_code" +msgstr "ส่งอีเมลยืนยันอีกครั้ง" + +msgid "email_already_exists" +msgstr "อีเมลนี้มีอยู่แล้ว" + +msgid "email_already_exists_msg" +msgstr "" +"อีเมลนี้ถูกใช้ไปแล้ว " +"หากคุณมีบัญชีที่ใช้อีเมลนี้อยู่แล้วคุณสามารถทำการกู้คืนได้ " +"หากยังไม่มีโปรดกดย้อนกลับและเลือกที่อยู่อีเมลอื่น" + +msgid "recover_account" +msgstr "กู้คืนบัญชี" + +msgid "back" +msgstr "ย้อนกลับ" + +msgid "change_email" +msgstr "เปลี่ยนอีเมล" + +msgid "new_password" +msgstr "รหัสผ่านใหม่" + +msgid "confirm_new_password" +msgstr "ยืนยันรหัสผ่านใหม่" + +msgid "already_have_an_account" +msgstr "มีบัญชีอยู่แล้วเหรอ" + +msgid "create_account" +msgstr "สร้างบัญชี" + +msgid "create_password" +msgstr "สร้างรหัสผ่าน" + +msgid "by_creating_an_account" +msgstr "ในการสร้างบัญชี เท่ากับคุณยอมรับว่า" + +msgid "confirm_email" +msgstr "ยืนยันอีเมล" + +msgid "update_pro_account" +msgstr "อัปเดตบัญชี Pro" + +msgid "update_pro_account_message" +msgstr "" +"ความเปลี่ยนแปลงกำลังจะมาใน Lantern Pro! " +"ในตอนนี้คุณจะต้องใช้อีเมลและรหัสผ่านเพื่อเข้าถึงการจัดการบัญชีและเข้าสู่ระบบบนอุปกรณ์ทั้งหมด" +" โปรดสละเวลาสักครู่เพื่อเพิ่มข้อมูลประจำตัวเหล่านี้ลงในบัญชีของคุณ" + +msgid "not_now" +msgstr "ไม่ใช่ตอนนี้" + +msgid "update_account" +msgstr "อัปเดตบัญชี" + +msgid "new" +msgstr "ใหม่" + +msgid "Daily Data Usage" +msgstr "การใช้งานข้อมูลประจำวัน" + +msgid "is" +msgstr "คือ" + +msgid "connect" +msgstr "เชื่อมต่อ" + +msgid "disconnect" +msgstr "ตัดการเชื่อมต่อ" + +msgid "connected" +msgstr "เชื่อมต่อแล้ว" + +msgid "Disconnected" +msgstr "ตัดการเชื่อมต่อ" + +msgid "Disconnecting" +msgstr "ตัดการเชื่อมต่อ..." + +msgid "Connecting" +msgstr "กำลังเชื่อมต่อ..." + +msgid "no_network_connection" +msgstr "ไม่มีการเชื่อมต่อเครือข่าย" + +msgid "connection_error" +msgstr "การเชื่อมต่อล้มเหลว" + +msgid "connection_error_des" +msgstr "" +"Lantern พบปัญหาในการเชื่อมต่อกับเซิร์ฟเวอร์ของเรา โปรดรีสตาร์ทแอป " +"ในกรณีที่ยังเกิดปัญหานี้อยู่ โปรดติดต่อฝ่ายสนับสนุน" + +msgid "connection_error_button" +msgstr "ติดต่อฝ่ายสนับสนุน" + +msgid "Calling" +msgstr "โทร..." + +msgid "Go Pro Title" +msgstr "สมัคร Pro!" + +msgid "Go Pro Description" +msgstr "ปลดล็อคความเร็วที่สูงขึ้น ประสบการณ์ไม่มีโฆษณา และข้อมูลไม่จำกัด! " + +msgid "Server Location" +msgstr "ตำแหน่งที่ตั้งของเซิร์ฟเวอร์" + +msgid "Server Location Info" +msgstr "" +"Lantern " +"ใช้การเลือกเซิร์ฟเวอร์อัจฉริยะเพื่อเชื่อมต่อคุณกับศูนย์ข้อมูลที่ดีที่สุดของเราโดยอัตโนมัติ" + +msgid "OK" +msgstr "ตกลง" + +msgid "Done" +msgstr "เสร็จสิ้น" + +msgid "backup_recovery_key" +msgstr "คีย์การกู้คืนข้อมูลสำรอง" + +msgid "delete_chat_data" +msgstr "ลบข้อมูลแชททั้งหมด" + +msgid "delete_chat_data_description" +msgstr "" +"จะเป็นการลบหมายเลขแชท รายชื่อผู้ติดต่อ และข้อความของคุณออกจากอุปกรณ์นี้ " +"คุณจะสูญเสียการเข้าถึงหมายเลขแชทในกรณีที่ไม่มีคีย์การกู้คืนข้อมูลสำรอง" + +msgid "delete_chat_data_confirmation" +msgstr "ฉันยืนยันว่าต้องการที่จะลบข้อมูลแชทของฉัน" + +msgid "Pro Account Management" +msgstr "การจัดการบัญชี Pro" + +msgid "link_device" +msgstr "เชื่อมโยงอุปกรณ์" + +msgid "Upgrade to Lantern Pro" +msgstr "อัปเกรดเป็น Lantern Pro" + +msgid "Invite Friends" +msgstr "เชิญเพื่อน" + +msgid "share_lantern_pro" +msgstr "" +"แชร์ Lantern Pro กับเพื่อน! เมื่อเพื่อนของคุณใช้รหัสแนะนำระหว่างการซื้อ " +"Lantern Pro คุณทั้งสองคนจะได้รับ Lantern Pro ฟรีหนึ่งเดือน!" + +msgid "privacy_disclosure_title" +msgstr "การเปิดเผยความเป็นส่วนตัวของ Lantern" + +msgid "privacy_disclosure_body" +msgstr "" +"Lantern มุ่งมั่นที่จะปกป้องความเป็นส่วนตัวของคุณ " +"เราต้องการให้คุณเข้าใจว่ามีข้อมูลใดบ้างที่เรารวบรวมและไม่ได้รวบรวม " +"รวมถึงวิธีการที่เรารวบรวม ใช้ และจัดเก็บข้อมูล " +"เราแยกข้อมูลการชำระเงินออกจากบัญชี Lantern Pro ของคุณ " +"เราไม่มีการเก็บรวบรวมบันทึกกิจกรรม ซึ่งรวมถึงการบันทึกประวัติการเข้าชม " +"ปลายทางในการรับส่งข้อมูล เนื้อหาข้อมูล หรือ DNS query นอกจากนี้ " +"เราไม่มีการจัดเก็บบันทึกการเชื่อมต่อ จึงเท่ากับว่าไม่มีบันทึกที่อยู่ IP, " +"ที่อยู่ IP ของเซิร์ฟเวอร์ Lantern " +"ขาออกที่เชื่อมโยงกับการประทับตราเวลาการเชื่อมต่อ หรือระยะเวลาเซสชัน " +"โดยหลักการแล้ว Lantern ไม่สามารถเชื่อมโยงบัญชี Lantern " +"กับตัวตนที่แท้จริงของผู้ใช้ได้จากบัญชี Lantern Pro บัญชีใดบัญชีหนึ่งโดยเฉพาะ" +" และแม้จะมีข้อมูลที่อยู่ IP ของ Lantern และการประทับตราเวลาโดยละเอียด " +"แต่ตามหลักการแล้ว Lantern ก็ไม่อาจระบุได้ว่าที่อยู่ IP นี้เป็นของผู้ใช้ " +"Lantern หรือที่อยู่ IP ของผู้ใช้ Lantern รายใด" + +msgid "privacy_disclosure_accept" +msgstr "ฉันยอมรับ" + +msgid "desktop_version" +msgstr "เวอร์ชันเดสก์ท็อป" + +msgid "settings" +msgstr "การตั้งค่า" + +msgid "general" +msgstr "ทั่วไป" + +msgid "about" +msgstr "เกี่ยวกับ" + +msgid "proxy_all" +msgstr "พร็อกซีทุกอย่าง" + +msgid "proxy_everything_is" +msgstr "พร็อกซีทุกอย่าง %s" + +msgid "version_number" +msgstr "เวอร์ชัน %s" + +msgid "build_number" +msgstr "สร้าง %s" + +msgid "sdk_version" +msgstr "SDK %s" + +msgid "language" +msgstr "ภาษา" + +msgid "select_an_issue" +msgstr "โปรดเลือกปัญหา" + +msgid "enter_description" +msgstr "กรุณาอธิบายปัญหาของคุณ " + +msgid "cannot_access_blocked_sites" +msgstr "ไม่สามารถเข้าถึงไซต์ที่ถูกปิดกั้น" + +msgid "cannot_complete_purchase" +msgstr "ไม่สามารถดำเนินการซื้อได้เสร็จสิ้น" + +msgid "discover_not_working" +msgstr "การค้นหาไม่ทำงาน " + +msgid "cannot_sign_in" +msgstr "ไม่สามารถเข้าสู่ระบบ" + +msgid "spinner_loads_endlessly" +msgstr "Spinner ยังโหลดต่อไป" + +msgid "slow" +msgstr "ช้า" + +msgid "cannot_link_devices" +msgstr "ไม่สามารถเชื่อมโยงอุปกรณ์" + +msgid "wrong_device_linking_code" +msgstr "รหัสเชื่อมโยงอุปกรณ์ที่ไม่ถูกต้องหรือไม่มีอยู่" + +msgid "cannot_remove_device" +msgstr "ไม่สามารถลบอุปกรณ์ได้ " + +msgid "application_crashes" +msgstr "แอปพลิเคชันขัดข้อง" + +msgid "other" +msgstr "อื่นๆ" + +msgid "issue_description" +msgstr "อธิบายปัญหาของคุณ " + +msgid "check_for_updates" +msgstr "ตรวจสอบการอัพเดต" + +msgid "blocked_users" +msgstr "ผู้ใช้ที่ถูกบล็อก" + +msgid "description_proxy_all_dialog" +msgstr "" +"ในกรณีที่เปิดใช้งาน การรับส่งข้อมูลจะถูกส่งผ่าน Lantern (ปลอดภัยกว่า) " +"ในกรณีที่ปิดใช้งาน การรับส่งข้อมูลที่ถูกบล็อกเท่านั้นจะถูกส่งผ่าน Lantern " +"(เร็วกว่า)" + +msgid "Account" +msgstr "บัญชี" + +msgid "VPN Status" +msgstr "สถานะ VPN" + +msgid "VPN" +msgstr "VPN" + +msgid "Authorize Device for Pro" +msgstr "อนุญาตอุปกรณ์สำหรับ Pro" + +msgid "Authorize with Device Linking Pin" +msgstr "ให้สิทธิ์ด้วยพินการเชื่อมโยงอุปกรณ์" + +msgid "Requires physical access to a Lantern Pro Device" +msgstr "ต้องการการเข้าถึงทางกายภาพไปยังอุปกรณ์ Lantern Pro" + +msgid "Link with PIN" +msgstr "เชื่อมต่อผ่านพิน" + +msgid "OR" +msgstr "หรือ" + +msgid "device_linking_pin" +msgstr "พินเชื่อมโยงอุปกรณ์" + +msgid "link_device_step_one" +msgstr "เปิดเมนู 'เพิ่มอุปกรณ์' ในอุปกรณ์ Pro ของคุณ " + +msgid "link_device_step_two" +msgstr "ป้อน PIN การเชื่อมโยงอุปกรณ์และคลิกส่ง " + +msgid "ensure_most_recent_version_lantern" +msgstr "*ตรวจสอบให้แน่ใจว่าอุปกรณ์ทั้งสองใช้ Lantern เวอร์ชันล่าสุด" + +msgid "Authorize Device via Email" +msgstr "ให้สิทธิ์อุปกรณ์ผ่านอีเมล" + +msgid "auth_email_helper_text" +msgstr "ป้อนอีเมล์ที่เชื่อมกับบัญชี Pro ของคุณ" + +msgid "Requires access to the email you used to buy Lantern Pro" +msgstr "ต้องการเข้าถึงอีเมลที่คุณเคยใช้ซื้อ Lantern Pro" + +msgid "Link via Email" +msgstr "เชื่อมผ่านอีเมล" + +msgid "lantern_desktop" +msgstr "Lantern Desktop" + +msgid "report_description" +msgstr "คำอธิบายปัญหา" + +msgid "report_issue" +msgstr "รายงานปัญหา " + +msgid "report_an_issue" +msgstr "รายงานปัญหา" + +msgid "report_sent" +msgstr "ส่งรายงานแล้ว" + +msgid "thank_you_for_reporting_your_issue" +msgstr "ขอบคุณสำหรับการรายงานปัญหา แล้วเราจะติดต่อกลับทางอีเมลโดยเร็วที่สุด" + +msgid "send_report" +msgstr "ส่งรายงาน" + +msgid "most_recent_lantern_apps" +msgstr "" +"สามารถพบแอป Lantern ล่าสุดสำหรับคอมพิวเตอร์และแพลตฟอร์มอื่นๆ " +"ได้ที่ลิงก์ด้านบน" + +msgid "share_link" +msgstr "แชร์ลิงก์ " + +msgid "share_title" +msgstr "แชร์ลิงก์ดาวน์โหลด Lantern" + +msgid "email" +msgstr "อีเมล" + +msgid "Submit" +msgstr "ส่ง" + +msgid "save" +msgstr "บันทึก" + +msgid "please_enter_a_valid_email_address" +msgstr "โปรดป้อนที่อยู่อีเมลที่ถูกต้อง" + +msgid "bonus" +msgstr "โบนัส" + +msgid "referral_bonus" +msgstr "โบนัสจากการแนะนำ" + +msgid "total" +msgstr "รวม" + +msgid "month" +msgstr "เดือน" + +msgid "months" +msgstr "เดือน" + +msgid "enter_email_to_complete_purchase" +msgstr "ป้อนอีเมลเพื่อทำการซื้อให้เสร็จสมบูรณ์ " + +msgid "error_fetching_plans" +msgstr "เกิดข้อผิดพลาดในการดึงแผน โปรดรีสตาร์ท Lantern และลองอีกครั้ง" + +msgid "apply" +msgstr "ใช้" + +msgid "choose_payment_method" +msgstr "เลือกวิธีการชำระเงิน" + +msgid "pro_plan" +msgstr "แผน Pro" + +msgid "one_year_plan" +msgstr "แผน 1 ปี" + +msgid "two_year_plan" +msgstr "แผน 2 ปี" + +msgid "plan_discount" +msgstr "ประหยัด %d%%" + +msgid "enter_email_and_activation_code" +msgstr "ป้อนอีเมลและรหัสเปิดใช้งาน" + +msgid "referral_code" +msgstr "รหัสแนะนำ" + +msgid "share_referral_code" +msgstr "แชร์รหัสแนะนำ " + +msgid "share_message_referral_code" +msgstr "" +"สำหรับเดือนฟรีของ Lantern Pro ใช้รหัสแนะนำนี้: %s ; " +"คุณสามารถดาวน์โหลดได้ที่นี่: https://lantern.io/download " + +msgid "add_referral_code" +msgstr "เพิ่มรหัสแนะนำ" + +msgid "Activation Code" +msgstr "รหัสเปิดใช้งาน" + +msgid "invalid_or_incomplete_referral_code" +msgstr "รหัสแนะนำไม่ถูกต้องหรือไม่สมบูรณ์" + +msgid "register_for_pro" +msgstr "ลงทะเบียนใช้งาน Pro" + +msgid "Something went wrong while applying your referral code." +msgstr "เกิดข้อผิดพลาดบางอย่างระหว่างการใส่รหัสอ้างอิงของคุณ" + +msgid "your_activation_code_is_invalid" +msgstr "รหัสเปิดใช้งานของคุณไม่ถูกต้อง" + +msgid "update_cache_plans_timeout" +msgstr "เกิดข้อผิดพลาดบางอย่างระหว่างการดึงแผนที่มีอยู่" + +msgid "bitcoin_timeout" +msgstr "คำขอ BTCPay หมดเวลาแล้ว" + +msgid "BTCPay is unavailable" +msgstr "ไม่รองรับ BTCPay" + +msgid "update_cache_user_timeout" +msgstr "เกิดข้อผิดพลาดบางอย่างระหว่างการดึงข้อมูลบัญชีของคุณ" + +msgid "reseller_timeout" +msgstr "เกิดข้อผิดพลาดบางอย่างระหว่างการตรวจสอบรหัสเปิดใช้งานของคุณ" + +msgid "reseller_success" +msgstr "แลกรับรหัสเปิดใช้งานเรียบร้อยแล้ว" + +msgid "stripe_timeout" +msgstr "" +"การชำระผ่าน Stripe ของคุณหมดเวลาแล้ว โปรดรีสตาร์ท Lantern และลองอีกครั้ง" + +msgid "stripe_success" +msgstr "ดำเนินการซื้อผ่าน Stripe สำเร็จ" + +msgid "renewal_success" +msgstr "ต่ออายุสำเร็จ" + +msgid "pro_renewal_success_description" +msgstr "" +"ขอบคุณที่ต่ออายุ Lantern Pro ของคุณ " +"คุณสามารถเข้าถึงได้ไม่จำกัดโดยใช้ศูนย์ข้อมูลที่เร็วที่สุดของเรา " +"ซึ่งสามารถเชื่อมต่อกับบัญชีของคุณได้ถึงสามอุปกรณ์" + +msgid "pro_purchase_success" +msgstr "ยินดีต้อนรับสู่ Lantern Pro" + +msgid "platinum_purchase_success_descripion" +msgstr "" +"คุณสามารถเข้าถึงได้ไม่จำกัดโดยใช้ศูนย์ข้อมูลที่เร็วที่สุดและช่องทางเฉพาะของเรา" +" ซึ่งสามารถเชื่อมต่อกับบัญชีของคุณได้ถึงสามอุปกรณ์" + +msgid "pro_purchase_success_descripion" +msgstr "" +"คุณสามารถเข้าถึงได้ไม่จำกัดโดยใช้ศูนย์ข้อมูลที่เร็วที่สุดของเรา " +"ซึ่งสามารถเชื่อมต่อกับบัญชีของคุณได้ถึงสามอุปกรณ์" + +msgid "by_clicking_tos" +msgstr "โดยการคลิก %s เท่ากับคุณยอมรับว่า" + +msgid "tos" +msgstr "เงื่อนไขการให้บริการ" + +msgid "step_1" +msgstr "ขั้นตอนที่ 1" + +msgid "step_2" +msgstr "ขั้นตอนที่ 2" + +msgid "step_3" +msgstr "ขั้นตอนที่ 3" + +msgid "invalid_email" +msgstr "ที่อยู่อีเมลไม่ถูกต้อง" + +msgid "invalid_card" +msgstr "หมายเลขบัตรไม่ถูกต้อง" + +msgid "invalid_expiration" +msgstr "วันหมดอายุไม่ถูกต้อง" + +msgid "cc_number" +msgstr "บัตรเครดิต" + +msgid "card_number" +msgstr "หมายเลขบัตร" + +msgid "card_expiration" +msgstr "ดด/ปป" + +msgid "invalid_cvc" +msgstr "CVC ไม่ถูกต้อง" + +msgid "complete_purchase" +msgstr "ดำเนินการซื้อให้สมบูรณ์" + +msgid "Re-send Email" +msgstr "ส่งอีเมลใหม่" + +msgid "Paste from clipboard?" +msgstr "วางจากคลิปบอร์ดไหม?" + +msgid "Yes" +msgstr "ใช่" + +msgid "No" +msgstr "ไม่" + +msgid "exit" +msgstr "ออก" + +msgid "show" +msgstr "แสดง" + +msgid "status_on" +msgstr "สถานะ: เปิด " + +msgid "status_off" +msgstr "สถานะ: ปิด " + +msgid "Enter or paste linking code" +msgstr "ป้อนหรือวางรหัสการเชื่อมโยง" + +msgid "recovery_email_sent" +msgstr "" +"พินเชื่อมโยงถูกส่งไปยัง %1$s แล้ว หากมีบัญชีนี้อยู่ " +"ให้ใส่พินจากอีเมลที่ได้รับข้างต้น " +"อาจจะใช้เวลาหลายชั่วโมงกว่าคุณจะได้รับอีเมลนี้" + +msgid "Add Device" +msgstr "เพิ่มอุปกรณ์" + +msgid "approve_device_step_1" +msgstr "" +"เปิด 'ให้สิทธิ์อุปกรณ์สำหรับ Pro' จากเมนูบนอุปกรณ์ที่คุณต้องการจะเชื่อมต่อ " +"จากนั้นเลือก 'เชื่อมต่อด้วยพิน'" + +msgid "approve_device_step_2" +msgstr "ป้อนรหัสเชื่อมโยงที่ได้รับจากขั้นตอนที่ 1" + +msgid "Pro Account Expiration" +msgstr "การหมดอายุของบัญชี Pro" + +msgid "Renew" +msgstr "ต่ออายุ" + +msgid "pro_devices_header" +msgstr "อุปกรณ์ Lantern Pro (สูงสุด 3 เครื่อง)" + +msgid "Remove" +msgstr "เอาออก" + +msgid "Log Out" +msgstr "ออกจากระบบ" + +msgid "confirm_remove_device" +msgstr "คุณแน่ใจหรือไม่ว่าต้องการเลิกเชื่อมโยงอุปกรณ์นี้จากบัญชี Pro ของคุณ" + +msgid "confirm_close_window" +msgstr "คุณแน่ใจหรือว่าต้องการปิด Lantern? " + +msgid "unlimited_data" +msgstr "ข้อมูลไม่จำกัด" + +msgid "no_logs" +msgstr "ไม่มีบันทึก" + +msgid "no_ads" +msgstr "ไม่มีโฆษณา" + +msgid "choose_plan" +msgstr "เลือกแผน" + +msgid "billed_one_time" +msgstr "เรียกเก็บเงินครั้งเดียว " + +msgid "connect_up_to_3_devices" +msgstr "เชื่อมต่ออุปกรณ์ได้สูงสุด 3 เครื่อง" + +msgid "faster_data_centers" +msgstr "ศูนย์ข้อมูลที่เร็วขึ้น" + +msgid "dedicated_line" +msgstr "ช่องทางเฉพาะ" + +msgid "increased_reliability" +msgstr "เพิ่มความน่าเชื่อถือ" + +msgid "checkout" +msgstr "ชำระเงิน" + +msgid "credit_card" +msgstr "บัตรเครดิต" + +msgid "lantern_pro_checkout" +msgstr "ชำระเงิน Lantern Pro" + +msgid "1y_pricing" +msgstr "ราคาต่อ 1 ปี" + +msgid "2y_pricing" +msgstr "ราคาต่อ 2 ปี" + +msgid "plan_type" +msgstr "ประเภทแผน" + +msgid "most_popular" +msgstr "เป็นที่นิยมมากที่สุด" + +msgid "free" +msgstr "ฟรี" + +msgid "n/a" +msgstr "ไม่ระบุ" + +msgid "Developer Settings" +msgstr "การตั้งค่าผู้พัฒนา" + +msgid "Developer" +msgstr "ผู้พัฒนา" + +msgid "dev_settings" +msgstr "" +"การตั้งค่าเหล่านี้ใช้สำหรับการพัฒนาเท่านั้น " +"ในกรณีที่มีการเปลี่ยนแปลงการตั้งค่าใดๆ แอปจะหยุดและรีสตาร์ทอัตโนมัติ" + +msgid "dev_payment_mode" +msgstr "" +"ขณะเปิดใช้งานโหมดทดสอบการชำระเงิน แอปทำงานโดยผู้ใช้ที่เข้ารหัสตายตัวรายเดียว" +" คุณสามารถสร้างการชำระเงิน ผ่าน Stripe โดยใช้หมายเลข 4242 4242 4242 4242 " +"วันที่หมดอายุในอนาคต และ CVV ใดก็ได้" + +msgid "Payment Test Mode" +msgstr "โหมดทดสอบการชำระเงิน" + +msgid "Play Version" +msgstr "เวอร์ชันเล่น" + +msgid "Force Country" +msgstr "ประเทศที่บังคับ" + +msgid "Error" +msgstr "ข้อผิดพลาด" + +msgid "accept" +msgstr "ยอมรับ" + +msgid "cancel" +msgstr "ยกเลิก" + +msgid "reject" +msgstr "ปฏิเสธ" + +msgid "copied" +msgstr "คัดลอกแล้ว!" + +msgid "saved" +msgstr "บันทึกแล้ว!" + +msgid "new_chat" +msgstr "แชทใหม่" + +msgid "your_contact_info" +msgstr "ข้อมูลการติดต่อของคุณ" + +msgid "display_name" +msgstr "ชื่อที่แสดง" + +msgid "edit" +msgstr "แก้ไข" + +msgid "save_error" +msgstr "เกิดข้อผิดพลาดบางอย่างระหว่างการบันทึก" + +msgid "fewer_options" +msgstr "ดูตัวเลือกลดลง" + +msgid "more_options" +msgstr "ดูตัวเลือกเพิ่มเติม" + +msgid "block_user" +msgstr "บล็อกผู้ใช้" + +msgid "unblock_user" +msgstr "ปลดบล็อกผู้ใช้" + +msgid "block" +msgstr "บล็อก" + +msgid "unblock" +msgstr "ปลดบล็อก" + +msgid "block_info_title" +msgstr "บล็อก %s ไหม?" + +msgid "no_blocked_users" +msgstr "ไม่มีผู้ใช้ที่ถูกบล็อก" + +msgid "unblock_info_title" +msgstr "ปลดบล็อก %s ไหม?" + +msgid "block_info_description" +msgstr "" +"การบล็อกผู้ใช้นี้จะเป็นการลบประวัติการแชทและปิดกั้นการติดต่อกับคุณในอนาคต " +"คุณสามารถปลดบล็อกผู้ใช้ได้จากเมนูการตั้งค่า" + +msgid "unblock_info_description" +msgstr "" +"%s สามารถส่งข้อความหาคุณได้แล้ว " +"พวกเขาจะไม่ได้รับแจ้งเตือนว่าคุณได้ทำการปลดบล็อก" + +msgid "block_info_checkbox" +msgstr "ฉันเข้าใจว่าประวัติการแชททั้งหมดจะถูกลบ" + +msgid "unblock_info_checkbox" +msgstr "ฉันยืนยันที่จะปลดบล็อกผู้ติดต่อนี้" + +msgid "delete_permanently" +msgstr "ลบผู้ใช้ถาวร" + +msgid "delete_info_description" +msgstr "" +"เมื่อลบแล้ว " +"คุณจะต้องทำการเพิ่มข้อมูลผู้ติดต่อในกรณีที่คุณต้องการแชทกับพวกเขาอีกครั้ง" + +msgid "add_new_contact" +msgstr "เพิ่มผู้ติดต่อใหม่" + +msgid "add_contact" +msgstr "เพิ่มผู้ติดต่อ" + +msgid "share_your_chat_number" +msgstr "แชร์ Chat Number ของคุณ" + +msgid "scan_qr_code" +msgstr "สแกน QR Code" + +msgid "add_contact_in_person" +msgstr "เพิ่มผู้ติดต่อด้วยตัวเอง" + +msgid "start_chat" +msgstr " เริ่มแชท" + +msgid "your_installed_apps" +msgstr "แอปที่คุณติดตั้งแล้ว" + +msgid "apps_routed_through_lantern" +msgstr "แอปส่งผ่าน Lantern" + +msgid "apps_to_unblock" +msgstr "" +"เลือกแอปที่คุณต้องการจะปลดบล็อก " +"เมื่อเชื่อมต่อแล้วจะมีแต่แอปที่เลือกไว้ด้านล่างเท่านั้นที่จะส่งผ่าน Lantern" + +msgid "split_tunneling" +msgstr "Split Tunneling" + +msgid "split_tunneling_info" +msgstr "" +"เมื่อเปิดใช้ Split Tunnelling แอปที่เลือกไว้จะถูกส่งผ่าน Lantern " +"ซี่งจะปรับปรุงประสิทธิภาพและประหยัดแบนด์วิธเพียงแค่ปลดบล็อกแอปที่ระบุเท่านั้น" + +msgid "applied_next_time" +msgstr "" +"การตั้งค่า Split Tunnelling ใหม่จะเปิดใช้ในครั้งหน้าที่คุณเชื่อมต่อใหม่" + +msgid "message_disappearing" +msgstr "ข้อความทั้งหมดจะไม่หายไปสำหรับคุณและ %s" + +msgid "message_disappearing_description" +msgstr "ข้อความทั้งหมดจะหายไปหลังจาก %s สำหรับคุณและ %s" + +msgid "view_contact_info" +msgstr "ดูข้อมูลการติดต่อ" + +msgid "off" +msgstr "ปิด" + +msgid "on" +msgstr "เปิด" + +msgid "continue" +msgstr "ดำเนินการต่อ" + +msgid "continue_to_pro" +msgstr "ดำเนินการต่อไปยัง Pro" + +msgid "are_you_sure_you_want_to_open" +msgstr "คุณแน่ใจหรือไม่ว่าคุณต้องการที่จะเปิด %s?" + +msgid "set" +msgstr "ชุด" + +msgid "menu" +msgstr "เมนู" + +msgid "send_error" +msgstr "เกิดข้อผิดพลาดบางอย่างระหว่างการส่งข้อความของคุณ" + +msgid "share_media_error" +msgstr "เกิดข้อผิดพลาดบางอย่างระหว่างการแชร์ไฟล์มีเดีย" + +msgid "no_recents" +msgstr "ไม่มีล่าสุด" + +msgid "set_display_name" +msgstr "ตั้งชื่อที่แสดง" + +msgid "introduce_contact" +msgstr "แนะนำผู้ติดต่อ" + +msgid "introduce_contacts" +msgstr "แนะนำผู้ติดต่อ" + +msgid "introduce_contacts_with_count" +msgstr "แนะนำผู้ติดต่อ (%s)" + +msgid "need_two_contacts_to_introduce" +msgstr "เมื่อคุณมีผู้ติดต่อมากกว่าสองคน คุณจะสามารถสร้างการแนะนำได้" + +msgid "introduce_contacts_select" +msgstr "" +"เลือกแนะนำผู้ติดต่อสองคนหรือมากกว่า " +"พวกเขาจะได้รับการแนะนำการเริ่มต้นส่งข้อความถึงกัน" + +msgid "introduce_single_contact" +msgstr "" +"เลือกผู้ติดต่อเพื่อแนะนำให้กับ %s เมื่อพวกเขาได้ยอมรับการแนะนำแล้ว " +"พวกเขาจะสามารถส่งข้อความถึงกันได้" + +msgid "send_introductions" +msgstr "ส่งการแนะนำ" + +msgid "introductions_sent" +msgstr "ส่งการแนะนำแล้ว!" + +msgid "no_contacts_yet" +msgstr "ยังไม่มีผู้ติดต่อ" + +msgid "contact_verification" +msgstr "การยืนยันผู้ติดต่อ" + +msgid "verification" +msgstr "การยืนยัน" + +msgid "contact_verification_description" +msgstr "ยืนยันผู้ติดต่อได้เสมอจากเมนูข้อความด้านขวาบน" + +msgid "qr_error_description" +msgstr "เกิดข้อผิดพลาดบางอย่างระหว่างการสแกน QR Code" + +msgid "qr_info_verification_title" +msgstr "การยืนยันผู้ติดต่อด้วยตนเอง" + +msgid "qr_info_f2f_title" +msgstr "สแกน QR Code" + +msgid "qr_info_verification_des" +msgstr "" +"สแกน QR Code ของกันและกันเพื่อยืนยันผู้ติดต่อของคุณ\n" +"\n" +"กระบวนการนี้รับประกันความเป็นส่วนตัวและมีการเข้ารหัสการสนทนาระหว่างคู่ของคุณ" + +msgid "qr_info_f2f_des" +msgstr "" +"สแกน QR Code เพื่อเพิ่มเพื่อนของคุณเข้ารายชื่อผู้ติดต่อ\n" +"\n" +"กระบวนการนี้รับประกันความเป็นส่วนตัวและมีการเข้ารหัสการสนทนาระหว่างคู่ของคุณ" + +msgid "qr_info_verification_scan" +msgstr "สแกน QR Code ของกันและกันเพื่อยืนยัน" + +msgid "qr_info_f2f_scan" +msgstr "สแกน QR Code ของกันและกันเพื่อเพิ่มรายชื่อผู้ติดต่อ" + +msgid "qr_success_snackbar" +msgstr "%s คือผู้ติดต่อ" + +msgid "qr_info_waiting_qr" +msgstr "กำลังรอเพื่อนสแกน QR Code ของคุณ..." + +msgid "scan_complete" +msgstr "สแกนสำเร็จ" + +msgid "qr_for_your_contact" +msgstr "QR Code ของคุณ" + +msgid "add_via_chat_number" +msgstr "เพิ่มผ่าน Chat Number" + +msgid "qr_info_waiting_id" +msgstr "กำลังรอเพื่อนป้อน messenger ID ของคุณหรือสแกน QR Code ของคุณ..." + +msgid "chat_number_not_found" +msgstr "" +"ไม่พบ Chat Number\n" +"โปรดตรวจสอบหมายเลขที่คุณป้อนและลองใหม่อีกครั้ง" + +msgid "unable_to_add_contact" +msgstr "" +"ไม่พบผู้ติดต่อ\n" +"ตรวจสอบหมายเลขที่คุณป้อนและลองใหม่อีกครั้ง" + +msgid "self_adding" +msgstr "Chat Number ที่คุณป้อนเป็นของคุณ" + +msgid "chat_number_type" +msgstr "พิมพ์ Chat Number" + +msgid "letter_and_numbers_only" +msgstr "ตัวอักษรและตัวเลขเท่านั้น" + +msgid "name_your_contact" +msgstr "ตั้งชื่อผู้ติดต่อ" + +msgid "messenger_id_invalid" +msgstr "Messenger ID ไม่ถูกต้อง" + +msgid "username_not_found" +msgstr "ไม่พบชื่อผู้ใช้" + +msgid "contact_id_your_id" +msgstr "Messenger ID ของคุณ" + +msgid "introductions" +msgstr "การแนะนำ" + +msgid "introduced" +msgstr "แนะนำโดย %s" + +msgid "introductions_title" +msgstr "ยอมรับการแนะนำให้ %s" + +msgid "introductions_info" +msgstr "" +"คู่สนทนาต้องยอมรับการแนะนำการแชทของกันและกัน การแนะนำจะหายไปหลังจาก 7 วัน " +"ในกรณีที่ไม่มีการดำเนินการใดๆ" + +msgid "introduction_reject_title" +msgstr "ปฏิเสธการแนะนำ" + +msgid "introduction_reject_content" +msgstr "คุณจะไม่สามารถส่งข้อความถึงผู้ติดต่อนี้ได้ในกรณีที่คุณปฏิเสธการแนะนำ" + +msgid "introductions_error_description_rejecting" +msgstr "เกิดข้อผิดพลาดบางอย่างระหว่างการปฏิเสธคำขอการเชื่อมต่อนี้" + +msgid "introductions_error_description_accepting" +msgstr "เกิดข้อผิดพลาดบางอย่างระหว่างการตอบรับคำขอการเชื่อมต่อนี้" + +msgid "no_introductions" +msgstr "ไม่มีการแนะนำที่อยู่ระหว่างการดำเนินการ" + +msgid "introduction_approved" +msgstr "แนะนำให้กับ %s สำเร็จ!" + +msgid "delete_contact_name" +msgstr "ลบ %s" + +msgid "delete_contact" +msgstr "ลบผู้ติดต่อ" + +msgid "error" +msgstr "ข้อผิดพลาด" + +msgid "error_delete_contact" +msgstr "เกิดข้อผิดพลาดบางอย่างระหว่างการลบผู้ติดต่อนี้" + +msgid "unnamed_contact" +msgstr "ผู้ติดต่อที่ไม่ได้ระบุชื่อ" + +msgid "me" +msgstr "ฉัน" + +msgid "disappearing_messages" +msgstr "ข้อความที่กำลังจะหายไป" + +msgid "start_of_your_history" +msgstr "แชทและการโทรด้วยเสียงกับ %s เป็นการเข้ารหัสระหว่างคู่สนทนา" + +msgid "banner_unaccepted" +msgstr "" +"%s ไม่ได้อยู่ในรายชื่อผู้ติดต่อของคุณ " +"คุณต้องการที่จะตอบรับคำขอการแชทหรือไม่?" + +msgid "banner_messages_persist" +msgstr "ข้อความใหม่จะไม่หายไป" + +msgid "banner_message_retention" +msgstr "การจัดเก็บข้อความได้ตั้งเป็น %s" + +msgid "longform_seconds" +msgstr "%s วินาที" + +msgid "shortform_seconds" +msgstr "%sวินาที" + +msgid "longform_minute" +msgstr "%s นาที" + +msgid "shortform_minutes" +msgstr "%sนาที" + +msgid "longform_minutes" +msgstr "%s นาที" + +msgid "longform_hour" +msgstr "%s ชั่วโมง" + +msgid "shortform_hours" +msgstr "%sชั่วโมง" + +msgid "longform_hours" +msgstr "%s ชั่วโมง" + +msgid "longform_day" +msgstr "%s วัน" + +msgid "shortform_days" +msgstr "%sวัน" + +msgid "longform_days" +msgstr "%s วัน" + +msgid "longform_week" +msgstr "%s สัปดาห์" + +msgid "shortform_weeks" +msgstr "%sสัปดาห์" + +msgid "longform_weeks" +msgstr "%s สัปดาห์" + +msgid "unknown" +msgstr "ไม่ทราบ" + +msgid "banner_source_qr" +msgstr "QR Code" + +msgid "banner_source_id" +msgstr "Messenger ID" + +msgid "banner_source_intro" +msgstr "การแนะนำ" + +msgid "message_will_disappear_at" +msgstr "%s หายไป" + +msgid "change_display_name" +msgstr "เปลี่ยนชื่อที่แสดง" + +msgid "yesterday" +msgstr "เมื่อวาน" + +msgid "tomorrow" +msgstr "พรุ่งนี้" + +msgid "just_now" +msgstr "เมื่อสักครู่" + +msgid "within_one_minute" +msgstr "ในหนึ่งนาที" + +msgid "on_date" +msgstr "บน %s" + +msgid "at_date" +msgstr "ที่ %s" + +msgid "error_fetching_message_preview" +msgstr "เกิดข้อผิดพลาดในการดึงตัวอย่างข้อความ" + +msgid "message_was_deleted" +msgstr "ข้อความถูกลบ" + +msgid "contact_was_deleted" +msgstr "%s ถูกลบออกจากผู้ติดต่อของคุณ" + +msgid "contact_was_blocked" +msgstr "%s ถูกบล็อก" + +msgid "contact_was_unblocked" +msgstr "%s ถูกปลดบล็อก" + +msgid "dismiss" +msgstr "Dismiss" + +msgid "copy_text" +msgstr "คัดลอกตัวหนังสือ" + +msgid "message" +msgstr "ข้อความ" + +msgid "messages" +msgstr "ข้อความ" + +msgid "chats" +msgstr "แชท" + +msgid "new_requests" +msgstr "คำขอใหม่ %s" + +msgid "chat" +msgstr "แชท" + +msgid "lantern_chat" +msgstr "Lantern Chat" + +msgid "welcome_title" +msgstr "ยินดีต้อนรับสู่ Lantern Chat!" + +msgid "welcome_text" +msgstr "" +"แชทเข้ารหัสเพื่อปกป้องความเป็นส่วนตัวของคุณจับคู่กับเทคโนโลยีต้านทานต่อการปิดกั้นที่ดีที่สุด" + +msgid "get_started" +msgstr "เริ่มต้น" + +msgid "want_to_recover" +msgstr "มี Chat Number หรือยัง?" + +msgid "recover" +msgstr "กู้คืน" + +msgid "chat_number" +msgstr "Chat Number" + +msgid "your_chat_number" +msgstr "แชร์ Chat Number ของคุณ" + +msgid "chat_number_recovery" +msgstr "กู้คืน Chat Number" + +msgid "recovery_helper_text" +msgstr "โปรดป้อนคีย์กู้คืนที่ถูกต้อง" + +msgid "recovery_button" +msgstr "ป้อนคีย์กู้คืนเพื่อกู้ Chat Number ของคุณ" + +msgid "recovery_label" +msgstr "ป้อนคีย์กู้คืนของคุณ" + +msgid "recovery_key" +msgstr "คีย์กู้คืน" + +msgid "secure_text_explanation" +msgstr "" +"Chat Number ของคุณทำงานเหมือนเบอร์โทรศัพท์ที่เพื่อนของคุณใช้ติดต่อคุณบน " +"Lantern" + +msgid "secure_text_explanation_account" +msgstr "" +"นี่คือเวอร์ชันเต็มของ Chat Number ของคุณ การแชร์ตัวเลขหลายหลักนี้อาจจะปลอดภัยมากขึ้น โดยเฉพาะอย่างยิ่งในกรณีที่คุณมีช่องการเข้ารหัสคู่สนทนาอื่นที่จะแบ่งปัน\n" +"\n" +"Lantern มี Chat Number แบบย่อที่ทำให้การแชร์ข้อมูลผู้ติดต่อสะดวกยิ่งขึ้น" + +msgid "recovery_key_account_explanation" +msgstr "" +"เก็บคีย์ในที่ปลอดภัยและไม่แชร์ให้ใคร นี่คือวิธีเดียวที่จะกู้คืน Chat Number " +"ของคุณได้ในกรณีที่คุณทำโทรศัพท์หาย เปลี่ยนอุปกรณ์ " +"หรือตั้งค่าระบบการปฏิบัติการใหม่" + +msgid "recovery_error" +msgstr "ไม่สามารถกู้คืนบัญชีของคุณได้" + +msgid "recovery_input_error" +msgstr "ตรวจสอบว่าคุณป้อนคีย์การกู้คืนที่ถูกต้อง" + +msgid "recovery_retrieval_error" +msgstr "เกิดข้อผิดพลาดบางอย่างระหว่างการกู้คืนบัญชีของคุณ" + +msgid "recovery_success" +msgstr "คุณได้กู้คืน Chat Number ของคุณสำเร็จแล้ว" + +msgid "empty_chats_text" +msgstr "แชร์ Chat Number หรือเพิ่มผู้ติดต่อที่นี่!" + +msgid "next" +msgstr "ถัดไป" + +msgid "automated_backup" +msgstr "สำรองข้อมูลอัตโนมัติ" + +msgid "backup_explanation" +msgstr "" +"Lantern มีตัวเลือกการเข้ารหัสและสำรองข้อมูลผู้ติดต่อของคุณไปยังเซิร์ฟเวอร์ที่ปลอดภัยของเรา ในกรณีที่คุณลืมสำรองข้อมูล คุณจะไม่สามารถกู้คืนผู้ติดต่อโดยใช้คีย์กู้คืนของคุณได้\n" +"\n" +" Lantern ไม่สามารถอ่านรายชื่อผู้ติดต่อของคุณได้เมื่อคุณเลือกที่จะสำรองข้อมูล" + +msgid "backup_contacts" +msgstr "สำรองข้อมูลผู้ติดต่อ" + +msgid "copy_recovery_key" +msgstr "คัดลอกคีย์การกู้คืน" + +msgid "search" +msgstr "Search" + +msgid "account_management" +msgstr "การจัดการบัญชี" + +msgid "add_via_username" +msgstr "เพิ่มผ่านชื่อผู้ใช้" + +msgid "username" +msgstr "ชื่อผู้ใช้" + +msgid "enter_a_username_to_start_a_conversation" +msgstr "ป้อนชื่อผู้ใช้เพื่อเริ่มการสนทนาผ่านข้อความ" + +msgid "start_message" +msgstr "เริ่มส่งข้อความ" + +msgid "please_enter_a_valid_username" +msgstr "โปรดป้อนชื่อผู้ใช้ที่ถูกต้อง" + +msgid "open_url" +msgstr "เปิด URL" + +msgid "message_deleted" +msgstr "%s ลบข้อความนี้" + +msgid "never" +msgstr "ไม่" + +msgid "delete_for_me" +msgstr "ลบให้ฉัน" + +msgid "delete_for_me_explanation" +msgstr "จะลบข้อความให้คุณเท่านั้น คนอื่นยังคงมองเห็นได้" + +msgid "delete_for_everyone" +msgstr "ลบให้ทุกคน" + +msgid "delete_for_everyone_explanation" +msgstr "จะลบข้อความให้กับทุกคน" + +msgid "reply" +msgstr "ตอบกลับ" + +msgid "replying_to" +msgstr "ตอบกลับถึง %s" + +msgid "attachment" +msgstr "แนบ" + +msgid "error_rendering_message" +msgstr "เกิดข้อผิดพลาดในการแสดงข้อความ" + +msgid "error_fetching_preview" +msgstr "เกิดข้อผิดพลาดในการดึงตัวอย่างข้อความ" + +msgid "audio" +msgstr "เสียง" + +msgid "image" +msgstr "รูปภาพ" + +msgid "images" +msgstr "รูปภาพ" + +msgid "video" +msgstr "วิดีโอ" + +msgid "videos" +msgstr "วิดีโอ" + +msgid "app" +msgstr "แอป" + +msgid "document" +msgstr "เอกสาร" + +msgid "search_chars_min" +msgstr "พิมพ์อย่างน้อยสามอักขระ" + +msgid "search_no_results" +msgstr "ขออภัย เราไม่พบผลลัพธ์ที่ตรงกับการค้นหานี้" + +msgid "search_error" +msgstr "ขออภัย เกิดข้อผิดพลาดในการดึงผลการค้นหา" + +msgid "search_contacts" +msgstr "ผู้ติดต่อ (%s)" + +msgid "search_chats" +msgstr "แชท (%s)" + +msgid "search_in_contacts" +msgstr "ค้นหาในผู้ติดต่อ" + +msgid "search_in_chats" +msgstr "ค้นหาในแชทและผู้ติดต่อ" + +msgid "could_not_render_title" +msgstr "ไม่สามารถประมวลชื่อไฟล์ได้" + +msgid "could_not_render_type" +msgstr "ไม่สามารถประมวลประเภทไฟล์ได้" + +msgid "speaker" +msgstr "ลำโพง" + +msgid "speaker_on" +msgstr "เปิดลำโพง" + +msgid "mute" +msgstr "ปิดเสียง" + +msgid "muted" +msgstr "ปิดเสียงแล้ว" + +msgid "end_call" +msgstr "จบการโทร" + +msgid "verify_contact" +msgstr "ยืนยัน %s" + +msgid "verified" +msgstr "ยืนยันแล้ว" + +msgid "pending_verification" +msgstr "รอการยืนยัน" + +msgid "verify_description" +msgstr "" +"สำหรับแนวทางปฏิบัติด้านความปลอดภัยที่ดีที่สุด เราขอแนะนำให้ดูว่าหมายเลขยืนยันของ %s ตรงกันกับของคุณ\n" +"\n" +"คุณต้องการที่จะยืนยันด้วยตนเองหรือผ่านการสนทนาทางโทรศัพท์?" + +msgid "verify_in_person" +msgstr "ยืนยันด้วยตนเอง" + +msgid "verify_via_call" +msgstr "ยืนยันผ่านการสนทนาทางโทรศัพท์" + +msgid "dismiss_notification" +msgstr "ปิดการแจ้งเตือน" + +msgid "call_contact" +msgstr "โทร %s ?" + +msgid "call" +msgstr "โทร" + +msgid "incoming_call" +msgstr "สายเข้า" + +msgid "call_from" +msgstr "จาก %s" + +msgid "in_call" +msgstr "สายโทรออก" + +msgid "in_call_des" +msgstr "การแจ้งเตือนสายโทรออก" + +msgid "incoming_call_from" +msgstr "สายโทรเข้าจาก %s" + +msgid "touch_here_to_open_call" +msgstr "แตะที่นี่เพื่อเปิดการโทร" + +msgid "in_call_with" +msgstr "อยู่ในสายกับ %s" + +msgid "undo_verification" +msgstr "ยกเลิกการยืนยัน" + +msgid "mark_as_verified" +msgstr "ทำเครื่องหมายว่ายืนยันแล้ว" + +msgid "verification_panel_success" +msgstr "การยืนยันสำเร็จ! ข้อความกับ %s ของคุณปลอดภัย" + +msgid "verification_panel_pending" +msgstr "" +"นี่คือหมายเลขยืนยันของคุณ ตรวจดูว่าหมายเลขของคุณตรงกันกับของ %s " +"ก่อนที่จะทำเครื่องหมายว่ายืนยัน" + +msgid "replica_upload_confirmation_title" +msgstr "สำคัญ!" + +msgid "replica_upload_confirmation_body" +msgstr "" +"ไฟล์ที่อัปโหลดเข้าเครือข่าย Lantern จะสามารถเข้าถึงได้สาธารณะ ไม่มีใครลบได้" +" โปรดอย่าใส่ข้อมูลส่วนตัวหรือระบุตัวตนลงในเนื้อหาไฟล์หรือชื่อไฟล์ของคุณ" + +msgid "replica_upload_confirmation_agree" +msgstr "อัปโหลดต่อ" + +msgid "dont_show_me_this_again" +msgstr "ไม่ต้องแสดงรายการนี้ให้ฉันเห็นอีก" + +msgid "download" +msgstr "ดาวน์โหลด" + +msgid "download_started" +msgstr "เริ่มการดาวน์โหลดแล้ว" + +msgid "uploader" +msgstr "ผู้อัปโหลด" + +msgid "upload_started" +msgstr "เริ่มการอัปโหลดแล้ว" + +msgid "upload_in_progress" +msgstr "กำลังอัปโหลด" + +msgid "upload_complete" +msgstr "เผยแพร่ไฟล์ของคุณเรียบร้อยแล้ว" + +msgid "upload_failed" +msgstr "การอัปโหลดล้มเหลว" + +msgid "upload_cancelled" +msgstr "การอัปโหลดถูกยกเลิก" + +msgid "upload_unknown_error" +msgstr "เกิดข้อผิดพลาดบางอย่างระหว่างการเผยแพร่" + +msgid "download_unknown_error" +msgstr "เกิดข้อผิดพลาดบางอย่างระหว่างการดาวน์โหลด %s" + +msgid "replica_link_fetcher" +msgstr "สำเนาการดึงการเชื่อมต่อ" + +msgid "no_preview_for_this_type_of_file" +msgstr "ไม่มีการแสดงตัวอย่างสำหรับไฟล์ประเภทนี้" + +msgid "app_unknown" +msgstr "แอป/ไม่ปรากฏ" + +msgid "audio_unknown" +msgstr "เสียง/ไม่ปรากฏ" + +msgid "document_unknown" +msgstr "เอกสาร/ไม่ปรากฏ" + +msgid "no_search_result_found" +msgstr "ขออภัย เราไม่พบผลลัพธ์ที่ตรงกับการค้นหานี้" + +msgid "search_result_error" +msgstr "เกิดข้อผิดพลาดในการดึงผลลัพธ์ โปรดลองอีกครั้งในภายหลัง" + +msgid "video_stream_error" +msgstr "เกิดข้อผิดพลาดในการประมวลผลวิดีโอ โปรดลองอีกครั้งในภายหลัง" + +msgid "untitled" +msgstr "ไม่มีชื่อ" + +msgid "preview_not_available" +msgstr "ไม่มีการแสดงตัวอย่าง" + +msgid "download_to_view" +msgstr "ดาวน์โหลดไฟล์เพื่อดู" + +msgid "failed_to_init_replica" +msgstr "เกิดข้อผิดพลาด: ไม่สามารถทำสำเนาได้" + +msgid "awaiting_result" +msgstr "รอผลลัพธ์..." + +msgid "discover" +msgstr "ค้นพบ" + +msgid "replica_search_intro" +msgstr "" +"ค้นหาเนื้อหาที่อัปโหลดโดยผู้ใช้บนเครือข่าย Lantern " +"หรืออัปโหลดเนื้อหาของคุณเองให้คนอื่นได้ค้นพบ" + +msgid "replica_new_discover" +msgstr "หมวดหมู่การค้นพบใหม่" + +msgid "replica_new_discover_news" +msgstr "การค้นพบใหม่ช่วยให้คุณได้ค้นหาบทความข่าว!" + +msgid "replica_check_it_out" +msgstr "ลองดู" + +msgid "news" +msgstr "ข่าว" + +msgid "share" +msgstr "แบ่งปัน" + +msgid "upload_file_screen" +msgstr "โพสต์" + +msgid "edit_title" +msgstr "แก้ไขชื่อเรื่อง" + +msgid "filenames_cannot_be_edited" +msgstr "เมื่อเผยแพร่แล้วจะแก้ไขชื่อไฟล์ไม่ได้" + +msgid "add_description" +msgstr "เพิ่มคำอธิบาย" + +msgid "no_description" +msgstr "ไม่ได้ระบุคำอธิบาย" + +msgid "read_details" +msgstr "อ่าน PDF" + +msgid "description_initial_value" +msgstr "เมื่อเผยแพร่แล้วจะแก้ไขคำอธิบายไม่ได้" + +msgid "name_your_file" +msgstr "ชื่อไฟล์ (จำเป็น)" + +msgid "upload" +msgstr "อัปโหลด" + +msgid "review" +msgstr "ทบทวน" + +msgid "skip" +msgstr "ข้าม" + +msgid "replica_layout_filetype" +msgstr "ประเภทไฟล์: %s" + +msgid "replica_layout_creation_date" +msgstr "สร้างเมื่อ %s" + +msgid "empty_description" +msgstr "ไม่ได้ระบุคำอธิบาย" + +msgid "publish" +msgstr "ใช่ เผยแพร่" + +msgid "important" +msgstr "สำคัญ!" + +msgid "upload_tos_agree" +msgstr "ฉันเข้าใจ" + +msgid "test_text" +msgstr "ฉันอยากกินพิซซ่าจัง 🍕 😊" + +msgid "test_hello" +msgstr "สวัสดี สบายดีมั้ย?" + +msgid "test_reply" +msgstr "ไม่ค่อยเลย ฉันว่าวันนี้ฉันมีอารมณ์อยากกินซูชิแหละ!" + +msgid "test_disappearing_messages_1" +msgstr "ในเบื้องต้นทุกข้อความจะหายไปหลังจากผ่านไปแล้ว 24 ชั่วโมง" + +msgid "test_disappearing_messages_2" +msgstr "ข้อความนี้ก็จะหายไปเร็วๆ นี้!" + +msgid "test_replica_search" +msgstr "แมว" + +msgid "discover_disclaimer" +msgstr "Lantern ไม่เปิดเผยการค้นหาของคุณและป้องกันการค้นหาจากบุคคลที่สาม" + +msgid "support" +msgstr "สนับสนุน" + +msgid "lantern_user_forum" +msgstr "กระดานสนทนาผู้ใช้ Lantern" + +msgid "faq" +msgstr "คำถามที่พบบ่อย" + +msgid "add_device" +msgstr "เพิ่มอุปกรณ์" + +msgid "search_apps" +msgstr "ค้นหาแอป " + +msgid "user_not_found" +msgstr "" +"ไม่รู้จักผู้ใช้นี้ โปรดตรวจสอบข้อมูลประจำตัวของคุณแล้วลองอีกครั้ง " +"หากคุณยังคงประสบปัญหาอยู่ โปรดติดต่อทีมสนับสนุนของเราเพื่อขอความช่วยเหลือ" + +msgid "email_resend_message" +msgstr "รหัสยืนยันใหม่ของคุณถูกส่งไปแล้ว" + +msgid "signup_error" +msgstr "" +"ดูเหมือนว่าจะเกิดข้อผิดพลาดบางอย่างขึ้นกับการลงทะเบียน โปรดลองอีกครั้ง " +"และหากยังคงมีปัญหานี้อยู่ โปรดติดต่อทีมสนับสนุนของเรา" + +msgid "signup_error_user_exists" +msgstr "" +"ดูเหมือนว่าจะเกิดข้อผิดพลาดบางอย่างขึ้นกับการลงทะเบียน โปรดลองรีเซ็ตรหัสผ่าน" +" และหากยังคงมีปัญหานี้อยู่ โปรดติดต่อทีมสนับสนุนของเรา" + +msgid "confirm_email_error" +msgstr "โปรดยืนยันอีเมลของคุณ" + +msgid "password_cannot_be_empty" +msgstr "รหัสผ่านไม่สามารถเว้นว่างได้" + +msgid "password_must_be_at_least_8_characters" +msgstr "รหัสผ่านต้องมีอักขระอย่างน้อย 8 ตัว" + +msgid "sign_out" +msgstr "ออกจากระบบ" + +msgid "sign_out_message" +msgstr "" +"การออกจากระบบจะสลับ Lantern ไปเป็นเวอร์ชันฟรี " +"หากต้องการเข้าถึงบัญชีของคุณอีกครั้ง คุณจะต้องลงชื่อเข้าใช้อีกครั้ง" + +msgid "change_password" +msgstr "เปลี่ยนรหัสผ่าน" + +msgid "password" +msgstr "รหัสผ่าน" + +msgid "new_email" +msgstr "ที่อยู่อีเมลใหม่" + +msgid "new_email_same_as_old_email" +msgstr "ที่อยู่อีเมลใหม่จะต้องไม่ซ้ำกับที่อยู่อีเมลเก่าของคุณ" + +msgid "danger_zone" +msgstr "โซนอันตราย" + +msgid "delete_account" +msgstr "ลบบัญชี" + +msgid "delete" +msgstr "ลบ" + +msgid "delete_account_message" +msgstr "" +"การลบบัญชีของคุณจะไม่สามารถยกเลิกได้ " +"คุณจะสูญเสียสิทธิ์การเข้าถึงอุปกรณ์ทั้งหมดและสูญเสียเวลาของบัญชี Pro " +"โดยไม่มีการคืนเงิน" + +msgid "delete_your_account" +msgstr "ต้องการลบบัญชีหรือไม่" + +msgid "confirm_deletion" +msgstr "ยืนยันการลบ" + +msgid "account_deleted" +msgstr "บัญชีถูกลบแล้ว" + +msgid "account_deleted_message" +msgstr "บัญชีของคุณถูกลบอย่างถาวรแล้ว" + +msgid "password_has_been_updated" +msgstr "รหัสผ่านถูกอัปเดตแล้ว" + +msgid "password_has_been_updated_message" +msgstr "รหัสผ่านของคุณถูกรีเซ็ตเรียบร้อยแล้ว" + +msgid "invalid_code" +msgstr "รหัสไม่ถูกต้องหรืออาจจะหมดอายุแล้ว โปรดลองส่งรหัสใหม่อีกครั้ง" + +msgid "get_lantern_pro" +msgstr "รับ Lantern Pro" + +msgid "try_lantern_pro" +msgstr "ลองใช้ Lantern ฟรี!" + +msgid "continue_for_free" +msgstr "ดำเนินการต่อได้ฟรี" + +msgid "recovery_not_found" +msgstr "อุปกรณ์ที่ไม่เชื่อมโยงกับผู้ใช้รายนี้" + +msgid "device_added" +msgstr "อุปกรณ์ที่ไม่เชื่อมโยงกับผู้ใช้รายนี้" + +msgid "device_added_message" +msgstr "" +"อุปกรณ์ของคุณถูกเพิ่มเรียบร้อยแล้ว หากคุณไม่เห็นอุปกรณ์เชื่อมต่อ " +"โปรดรีสตาร์ทแอปบนทั้งสองอุปกรณ์ " + +msgid "wrong_link_code" +msgstr "รหัสที่คุณใส่ไม่ถูกต้อง โปรดตรวจสอบรหัสและลองอีกครั้ง" + +msgid "recovery_code_sent" +msgstr "รหัสกู้คืนถูกส่งไปแล้ว" + +msgid "device_added_msg" +msgstr "อุปกรณ์นี้เชื่อมโยงกับ %s" + +msgid "new_to_lantern" +msgstr "ยังไม่เคยใช้ Lantern เหรอ" + +msgid "email_has_been_updated" +msgstr "อีเมลถูกอัปเดตแล้ว" + +msgid "email_has_been_updated_message" +msgstr "อีเมลของคุณถูกเปลี่ยนเรียบร้อยแล้ว" + +msgid "email_has_been_verified" +msgstr "อีเมลได้รับการยืนยันแล้ว" + +msgid "email_has_been_verified_message" +msgstr "อีเมลของคุณได้รับการยืนยันเรียบร้อยแล้ว" + +msgid "device_limit_reached" +msgstr "ถึงขีดจำกัดอุปกรณ์แล้ว" + +msgid "device_limit_reached_message" +msgstr "" +"ขณะนี้คุณลงชื่อเข้าใช้ในอุปกรณ์จำนวนสูงสุด (3) เครื่องแล้ว " +"หากต้องการลงชื่อเข้าใช้บนอุปกรณ์นี้ " +"โปรดเลือกและลบอุปกรณ์เครื่องใดก็ได้ของคุณที่อยู่ด้านล่าง" + +msgid "enter_activation_code" +msgstr "ใส่รหัสเปิดใช้งาน" + +msgid "check_your_email" +msgstr "ตรวจสอบที่อยู่อีเมลของคุณ" + +msgid "please_verify_email" +msgstr "โปรดยืนยันที่อยู่อีเมลที่คุณใส่มาว่าถูกต้องหรือไม่" + +msgid "verify" +msgstr "ยืนยัน" + +msgid "cannot_login" +msgstr "ไม่สามารถเข้าสู่ระบบได้ " + +msgid "privacy_policy" +msgstr "นโยบายความเป็นส่วนตัว" + +msgid "terms_of_service" +msgstr "เงื่อนไขการให้บริการ" + +msgid "invalid_verification_code" +msgstr "รหัสตรวจสอบไม่ถูกต้อง" + +msgid "wrong_seller_code" +msgstr "รหัสผู้ขายไม่ถูกต้อง กรุณาตรวจสอบและลองอีกครั้ง " + +msgid "follow_us" +msgstr "ติดตามเรา " + +msgid "follow_us_telegram" +msgstr "ติดตามเราบน Telegram " + +msgid "follow_us_instagram" +msgstr "ติดตามเราบน Instagram " + +msgid "follow_us_facebook" +msgstr "ติดตามเราบน Facebook " + +msgid "follow_us_x" +msgstr "ติดตามเราบน X " + +msgid "report_issue_error" +msgstr "เรากำลังพบปัญหาทางเทคนิค กรุณาลองใหม่ภายหลัง " + +msgid "we_are_experiencing_technical_difficulties" +msgstr "เรากำลังพบปัญหาทางเทคนิค กรุณาลองใหม่ภายหลัง " + +msgid "a_temporary_error_occurred" +msgstr "เกิดข้อผิดพลาดชั่วคราว " + +msgid "sorry_we_are_unable_to_load_that_page" +msgstr "" +"ขออภัย เราไม่สามารถโหลดหน้านั้นได้ในขณะนี้ กรุณากด 'รีเฟรช' " +"เพื่อลองใหม่อีกครั้ง " + +msgid "refresh" +msgstr "รีเฟรช" + +msgid "check_your_internet_connection" +msgstr "ตรวจสอบการเชื่อมต่ออินเทอร์เน็ตของคุณ " + +msgid "please_try" +msgstr "กรุณาลองใหม่ " + +msgid "turning_off_airplane_mode" +msgstr "ปิดโหมดเครื่องบิน " + +msgid "turning_on_mobile_data_or_wifi" +msgstr "เปิดข้อมูลมือถือหรือ wifi " + +msgid "check_the_signal_in_your_area" +msgstr "ตรวจสอบสัญญาณในพื้นที่ของคุณ " + +msgid "got_it" +msgstr "เข้าใจแล้ว" + +msgid "fetching_configuration" +msgstr "กำลังดึงข้อมูลการกำหนดค่า " + +msgid "establish_connection_to_server" +msgstr "เชื่อมต่อกับเซิร์ฟเวอร์ " + +msgid "file_size_limit_title" +msgstr "ถึงขีดจำกัดแล้ว " + +msgid "file_size_limit_description" +msgstr "ขนาดไฟล์แนบต้องต่ำกว่า 100 MB" diff --git a/assets/locales/tr-tr.po b/assets/locales/tr-tr.po index 6eeb79ca3..18a8cb12f 100644 --- a/assets/locales/tr-tr.po +++ b/assets/locales/tr-tr.po @@ -2,23 +2,23 @@ # Translators: # Kudret , 2022 # Zeynel Cebeci , 2022 -# Aşkın Özgür , 2022 -# Derek F , 2023 # M. Utkun Uyar, 2023 # Emre Deniz, 2023 -# Volkan Gezer , 2023 # Alexander B. , 2023 # fffw , 2023 # Gökhan Kalayci , 2023 # Lantern, 2023 -# efe kerem sözeri , 2023 -# e2f , 2023 -# e2f_tr c1 , 2023 -# Teo Westbrook, 2023 +# Teo Westbrook, 2024 +# efe kerem sözeri , 2024 +# Volkan Gezer , 2024 +# Aşkın Özgür , 2024 +# Derek F , 2024 +# e2f , 2024 +# e2f_tr c1 , 2024 # msgid "" msgstr "" -"Last-Translator: Teo Westbrook, 2023\n" +"Last-Translator: e2f_tr c1 , 2024\n" "Language-Team: Turkish (https://app.transifex.com/lantern-1/teams/94371/tr/)\n" "Language: tr\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" @@ -41,6 +41,98 @@ msgstr "Belki Daha Sonra" msgid "try" msgstr "Bunu deneyin" +msgid "sign_in" +msgstr "Giriş" + +msgid "enter_email" +msgstr "E-posta Gir" + +msgid "enter_password" +msgstr "Parolayı Girin" + +msgid "forgot_password" +msgstr "Parolamı Unuttum" + +msgid "forgot_your_password" +msgstr "Parolanızı mı unuttunuz?" + +msgid "click_here" +msgstr "Buraya tıklayın" + +msgid "reset_password" +msgstr "Parolayı Sıfırla" + +msgid "lantern_pro_email" +msgstr "Lantern Pro E-postası " + +msgid "return_to_sign_in" +msgstr "Girişe Geri Dön" + +msgid "enter_confirmation_code" +msgstr "Onay Kodunu Girin" + +msgid "confirmation_code_msg" +msgstr "" +"XX adresine onay kodu gönderildi. Hesabınızın güvenliğini sağlamak ve parola" +" kurtarma işlevini etkinleştirmek için kodu buraya girin." + +msgid "resend_confirmation_code" +msgstr "Onay E-postasını Yeniden Gönder" + +msgid "email_already_exists" +msgstr "E-posta Zaten Kullanılıyor" + +msgid "email_already_exists_msg" +msgstr "" +"Bu e-posta zaten alınmış, bu e-postayla önceden hesap açmışsanız bu hesabı " +"kurtarabilirsiniz. Bu e-postayla hesap açmadıysanız lütfen geri dönüp farklı" +" bir e-posta adresi seçin." + +msgid "recover_account" +msgstr "Hesabı Kurtar" + +msgid "back" +msgstr "geri" + +msgid "change_email" +msgstr "E-postayı Değiştir" + +msgid "new_password" +msgstr "Yeni Parola" + +msgid "confirm_new_password" +msgstr "Yeni Parolayı Onayla" + +msgid "already_have_an_account" +msgstr "Zaten hesabınız var mı?" + +msgid "create_account" +msgstr "Hesap Oluştur" + +msgid "create_password" +msgstr "Parola Oluştur" + +msgid "by_creating_an_account" +msgstr "Hesap Oluşturduğunuzda şunu kabul etmiş olursunuz:" + +msgid "confirm_email" +msgstr "E-posta Adresini Doğrulayın" + +msgid "update_pro_account" +msgstr "Pro Hesabını Güncelle" + +msgid "update_pro_account_message" +msgstr "" +"Lantern Pro'da değişiklikler yapıyoruz! Artık tüm cihazlardan giriş yapmak " +"ve Hesap Yönetimine erişmek için bir e-posta ve parolaya ihtiyacınız olacak." +" Lütfen biraz zaman ayırıp bu kimlik bilgilerini hesabınıza ekleyin. " + +msgid "not_now" +msgstr "Şimdi değil" + +msgid "update_account" +msgstr "Hesabı Güncelle" + msgid "new" msgstr "Yeni" @@ -50,6 +142,12 @@ msgstr "Günlük Veri Kullanımı" msgid "is" msgstr ":" +msgid "connect" +msgstr "Bağlan" + +msgid "disconnect" +msgstr "Bağlantıyı kes" + msgid "connected" msgstr "Bağlandı" @@ -84,6 +182,7 @@ msgstr "Pro'ya Yükseltin!" msgid "Go Pro Description" msgstr "" +"Daha hızlı hızların, reklamsız deneyimin ve sınırsız verinin kilidini açın! " msgid "Server Location" msgstr "Sunucu Konumu" @@ -117,7 +216,7 @@ msgstr "Sohbet verilerimi silmek istediğimi onaylıyorum" msgid "Pro Account Management" msgstr "Pro Hesap Yönetimi" -msgid "Link Device" +msgid "link_device" msgstr "Cihaz Bağla" msgid "Upgrade to Lantern Pro" @@ -166,12 +265,6 @@ msgstr "Genel" msgid "about" msgstr "Hakkında" -msgid "privacy_policy" -msgstr "Gizlilik Politikası" - -msgid "terms_of_service" -msgstr "Hizmet Şartları" - msgid "proxy_all" msgstr "Her şeyi proxy'le" @@ -194,7 +287,7 @@ msgid "select_an_issue" msgstr "Bir hata seçiniz" msgid "enter_description" -msgstr "" +msgstr "Lütfen sorununuzu açıklayın " msgid "cannot_access_blocked_sites" msgstr "Engellenen sitelere erişilemiyor" @@ -202,17 +295,35 @@ msgstr "Engellenen sitelere erişilemiyor" msgid "cannot_complete_purchase" msgstr "Satın alma tamamlanamıyor" +msgid "discover_not_working" +msgstr "Keşfet çalışmıyor " + msgid "cannot_sign_in" msgstr "Giriş yapılamadı" msgid "spinner_loads_endlessly" msgstr "Yükleme ekranı sürekli dönüyor" +msgid "slow" +msgstr "Yavaş" + +msgid "cannot_link_devices" +msgstr "Cihazlar ilişkilendirilemiyor" + +msgid "wrong_device_linking_code" +msgstr "Yanlış veya geçersiz cihaz bağlama kodu" + +msgid "cannot_remove_device" +msgstr "Cihaz kaldırılamıyor " + +msgid "application_crashes" +msgstr "Uygulama çökmeleri" + msgid "other" msgstr "Diğer" msgid "issue_description" -msgstr "Sorun Açıklaması" +msgstr "Sorununuzu açıklayın " msgid "check_for_updates" msgstr "Güncellemeleri Kontrol Et" @@ -254,10 +365,10 @@ msgid "device_linking_pin" msgstr "Cihaz bağlama pini" msgid "link_device_step_one" -msgstr "" +msgstr "Pro cihazınızda 'Cihaz Ekle' menü öğesini açın. " msgid "link_device_step_two" -msgstr "" +msgstr "Cihaz bağlantı PIN kodunu girin ve gönder'e tıklayın " msgid "ensure_most_recent_version_lantern" msgstr "" @@ -276,14 +387,14 @@ msgstr "" msgid "Link via Email" msgstr "E-Posta Aracılığıyla Bağla" -msgid "lantern_pro_email" -msgstr "Lantern Pro E-Postası" - msgid "lantern_desktop" msgstr "Lantern Masaüstü" +msgid "report_description" +msgstr "Sorun Açıklaması" + msgid "report_issue" -msgstr "" +msgstr "Sorunu Bildir " msgid "report_an_issue" msgstr "Hata Bildir" @@ -305,7 +416,7 @@ msgstr "" "yukarıdaki bağlantıda bulunabilir." msgid "share_link" -msgstr "" +msgstr "Bağlantıyı Paylaş " msgid "share_title" msgstr "Lantern indirme bağlantısı paylaş" @@ -337,8 +448,8 @@ msgstr "ay" msgid "months" msgstr "ay" -msgid "enter_email" -msgstr "E-posta Gir" +msgid "enter_email_to_complete_purchase" +msgstr "Satın almayı tamamlamak için e-posta girin " msgid "error_fetching_plans" msgstr "" @@ -370,10 +481,12 @@ msgid "referral_code" msgstr "Referans kodu" msgid "share_referral_code" -msgstr "" +msgstr "Referans Kodunu Paylaş " msgid "share_message_referral_code" msgstr "" +"Bir ay ücretsiz Lantern Pro için, bu referans kodunu kullanın: %s ; Buradan " +"indirebilirsiniz: https://lantern.io/download " msgid "add_referral_code" msgstr "Referans kodu ekleyin" @@ -442,7 +555,7 @@ msgstr "" "sahipsiniz. Hesabınıza en fazla üç cihaz bağlanabilir." msgid "by_clicking_tos" -msgstr "%s ögesine tıklayarak şunu kabul etmiş olursunuz:" +msgstr "%s ögesine tıklayarak şunu kabul etmiş olursunuz: " msgid "tos" msgstr "Hizmet Şartları" @@ -492,6 +605,18 @@ msgstr "Evet" msgid "No" msgstr "Hayır" +msgid "exit" +msgstr "Çıkış " + +msgid "show" +msgstr "Göster " + +msgid "status_on" +msgstr "Durum: Açık " + +msgid "status_off" +msgstr "Durum: Kapalı " + msgid "Enter or paste linking code" msgstr "Bağlama kodunu girin veya yapıştırın" @@ -529,6 +654,9 @@ msgstr "Oturumu Kapat" msgid "confirm_remove_device" msgstr "Bu cihazı Pro hesabınızdan çıkartmak istediğinize emin misiniz?" +msgid "confirm_close_window" +msgstr "Lantern'ı kapatmak istediğinizden emin misiniz? " + msgid "unlimited_data" msgstr "Sınırsız Veri" @@ -541,6 +669,9 @@ msgstr "Reklamsız" msgid "choose_plan" msgstr "Plan Seç" +msgid "billed_one_time" +msgstr "bir kerelik faturalandırma " + msgid "connect_up_to_3_devices" msgstr "3 cihaza kadar bağlayın" @@ -740,9 +871,6 @@ msgstr "Siz ve %s için tüm mesajlar kaybolmayacaktır." msgid "message_disappearing_description" msgstr "%s sonrasında siz ve %s için tüm mesajlar kaybolacaktır." -msgid "delete" -msgstr "Sil" - msgid "view_contact_info" msgstr "İletişim Bilgilerini Görüntüle" @@ -1547,3 +1675,236 @@ msgstr "SSS" msgid "add_device" msgstr "Hesap Ekle" + +msgid "search_apps" +msgstr "Uygulamaları Ara " + +msgid "user_not_found" +msgstr "" +"Kullanıcı tanınmıyor. Lütfen kimlik bilgilerinizi kontrol edip tekrar " +"deneyin. Sorun yaşamaya devam ederseniz yardım için lütfen destek ekibimize " +"ulaşın." + +msgid "email_resend_message" +msgstr "Yeni onay kodunuz gönderildi" + +msgid "signup_error" +msgstr "" +"Kayıt sırasında bir sorun oluşmuş gibi görünüyor. Lütfen tekrar deneyin ve " +"sorun devam ederse destek ekibimize ulaşın" + +msgid "signup_error_user_exists" +msgstr "" +"Kayıt sırasında bir sorun oluşmuş gibi görünüyor. Lütfen parolanızı " +"sıfırlamayı deneyin ve sorun devam ederse destek ekibimize ulaşın" + +msgid "confirm_email_error" +msgstr "Lütfen e-postanızı onaylayın" + +msgid "password_cannot_be_empty" +msgstr "Parola boş bırakılamaz" + +msgid "password_must_be_at_least_8_characters" +msgstr "Parola en az 8 karakter uzunluğunda olmalıdır" + +msgid "sign_out" +msgstr "Çıkış yap" + +msgid "sign_out_message" +msgstr "" +"Oturumunuzu kapattığınızda Lantern ücretsiz sürüme geçer. Hesabınıza tekrar " +"erişmek için yeniden giriş yapmanız gerekir." + +msgid "change_password" +msgstr "Parolayı Değiştir" + +msgid "password" +msgstr "Şifre" + +msgid "new_email" +msgstr "Yeni E-posta Adresi" + +msgid "new_email_same_as_old_email" +msgstr "Yeni e-posta adresi, eski e-posta adresinizin aynısı olamaz" + +msgid "danger_zone" +msgstr "Tehlikeli bölge" + +msgid "delete_account" +msgstr "Hesabı Sil" + +msgid "delete" +msgstr "Sil" + +msgid "delete_account_message" +msgstr "" +"Hesabı silme işlemi geri alınamaz. Tüm cihazlarınızdaki erişimi kaybeder ve " +"Pro hesabınızın daha kullanım süresi varsa herhangi bir ücret iadesi olmadan" +" bu süreden feragat etmiş olursunuz." + +msgid "delete_your_account" +msgstr "Hesabınız silinsin mi?" + +msgid "confirm_deletion" +msgstr "Silme İşlemini Onayla" + +msgid "account_deleted" +msgstr "Hesap Silindi" + +msgid "account_deleted_message" +msgstr "Hesabınız kalıcı olarak silindi." + +msgid "password_has_been_updated" +msgstr "Parola güncellendi" + +msgid "password_has_been_updated_message" +msgstr "Parolanız başarıyla sıfırlandı" + +msgid "invalid_code" +msgstr "Kod yanlış ya da süresi geçmiş. Lütfen yeniden göndermeyi deneyin" + +msgid "get_lantern_pro" +msgstr "Lantern Pro'ya Geçin" + +msgid "try_lantern_pro" +msgstr "Lantern'i ücretsiz deneyin! " + +msgid "continue_for_free" +msgstr "Ücretsiz Olarak Devam Et" + +msgid "recovery_not_found" +msgstr "Cihaz bu kullancı ile ilişkilendirilmemiş" + +msgid "device_added" +msgstr "Cihaz bu kullancı ile ilişkilendirilmemiş" + +msgid "device_added_message" +msgstr "" +"Cihazınız başarıyla eklendi. Bağlı cihazı görmüyorsanız, lütfen her iki " +"cihazda uygulamayı yeniden başlatın " + +msgid "wrong_link_code" +msgstr "Girdiğiniz kod yanlış. Lütfen kontrol edip tekrar deneyin." + +msgid "recovery_code_sent" +msgstr "Kurtarma kodu gönderildi" + +msgid "device_added_msg" +msgstr "Bu cihaz şuna Bağlandı: %s" + +msgid "new_to_lantern" +msgstr "Lantern'de yeni misiniz? " + +msgid "email_has_been_updated" +msgstr "E-posta güncellendi" + +msgid "email_has_been_updated_message" +msgstr "E-postanız başarıyla değiştirildi" + +msgid "email_has_been_verified" +msgstr "E-posta doğrulandı" + +msgid "email_has_been_verified_message" +msgstr "E-postanız başarıyla doğrulandı" + +msgid "device_limit_reached" +msgstr "Cihaz Sınırına Ulaşıldı" + +msgid "device_limit_reached_message" +msgstr "" +"Şu anda mümkün olan en fazla sayıda cihazdan (3) giriş yapmış durumdasınız. " +"Bu cihazdan giriş yapmak için lütfen aşağıda görülen diğer cihazlarınızdan " +"birini seçip kaldırın." + +msgid "enter_activation_code" +msgstr "Aktivasyon Kodunu Girin" + +msgid "check_your_email" +msgstr "E-posta Adresinizi Kontrol Edin" + +msgid "please_verify_email" +msgstr "Girdiğiniz e-posta adresinin doğru olduğunu lütfen onaylayın." + +msgid "verify" +msgstr "Doğrula" + +msgid "cannot_login" +msgstr "Giriş yapılamıyor " + +msgid "privacy_policy" +msgstr "Gizlilik Politikası" + +msgid "terms_of_service" +msgstr "Hizmet Şartları" + +msgid "invalid_verification_code" +msgstr "Geçersiz doğrulama kodu" + +msgid "wrong_seller_code" +msgstr "Yanlış satıcı kodu. Lütfen doğrulayın ve tekrar deneyin. " + +msgid "follow_us" +msgstr "Bizi Takip Edin " + +msgid "follow_us_telegram" +msgstr "Telegram'da bizi takip edin " + +msgid "follow_us_instagram" +msgstr "Instagram'da bizi takip edin " + +msgid "follow_us_facebook" +msgstr "Facebook'ta bizi takip edin " + +msgid "follow_us_x" +msgstr "X'te bizi takip edin " + +msgid "report_issue_error" +msgstr "Teknik zorluklar yaşıyoruz. Daha sonra tekrar deneyin. " + +msgid "we_are_experiencing_technical_difficulties" +msgstr "Teknik zorluklar yaşıyoruz. Daha sonra tekrar deneyin. " + +msgid "a_temporary_error_occurred" +msgstr "Geçici bir hata oluştu " + +msgid "sorry_we_are_unable_to_load_that_page" +msgstr "" +"Üzgünüz, şu anda o sayfayı yükleyemiyoruz, lütfen tekrar denemek için " +"'Yenile'ye dokunun. " + +msgid "refresh" +msgstr "Yenile" + +msgid "check_your_internet_connection" +msgstr "İnternet bağlantınızı kontrol edin " + +msgid "please_try" +msgstr "Lütfen deneyin " + +msgid "turning_off_airplane_mode" +msgstr "Uçak modunu kapatma " + +msgid "turning_on_mobile_data_or_wifi" +msgstr "Mobil veri veya wifi açma " + +msgid "check_the_signal_in_your_area" +msgstr "Bölgenizdeki sinyali kontrol etme " + +msgid "got_it" +msgstr "Tamam" + +msgid "fetching_configuration" +msgstr "Yapılandırma Alınıyor " + +msgid "establish_connection_to_server" +msgstr "Sunucuya bağlantı kuruluyor " + +msgid "file_size_limit_title" +msgstr "Limit Aşıldı " + +msgid "file_size_limit_description" +msgstr "" +"Ek boyutları 100 MB'dan küçük olmalıdır\n" +"\n" +" \n" +" " diff --git a/assets/locales/ur-in.po b/assets/locales/ur-in.po index 7101d8d10..083b5b49d 100644 --- a/assets/locales/ur-in.po +++ b/assets/locales/ur-in.po @@ -1,13 +1,14 @@ # # Translators: # tx_e2f_pk r6 , 2023 -# e2f , 2023 -# tx_e2f_ur c1 , 2023 -# Diversity83 83, 2023 +# Derek F , 2024 +# Diversity83 83, 2024 +# e2f , 2024 +# tx_e2f_ur c1 , 2024 # msgid "" msgstr "" -"Last-Translator: Diversity83 83, 2023\n" +"Last-Translator: tx_e2f_ur c1 , 2024\n" "Language-Team: Urdu (https://app.transifex.com/lantern-1/teams/94371/ur/)\n" "Language: ur\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" @@ -29,6 +30,98 @@ msgstr "شاید بعد میں" msgid "try" msgstr "اسے آزمائیں" +msgid "sign_in" +msgstr "سائن ان کریں" + +msgid "enter_email" +msgstr "ای میل درج کریں" + +msgid "enter_password" +msgstr "پاس ورڈ درج کریں" + +msgid "forgot_password" +msgstr "پاس ورڈ بھول گئے" + +msgid "forgot_your_password" +msgstr "اپنا پاس ورڈ بھول گئے؟ " + +msgid "click_here" +msgstr "یہاں کلک کریں" + +msgid "reset_password" +msgstr "پاس ورڈ ری سیٹ کریں" + +msgid "lantern_pro_email" +msgstr "Lantern Pro ای میل " + +msgid "return_to_sign_in" +msgstr "سائن ان پر واپس جائیں" + +msgid "enter_confirmation_code" +msgstr "تصدیقی کوڈ درج کریں" + +msgid "confirmation_code_msg" +msgstr "" +"تصدیقی کوڈ XX پر بھیج دیا گیا ہے۔ اپنا اکاؤنٹ محفوظ کرنے اور پاس ورڈ کی " +"بحالی فعال کرنے کے لیے اسے یہاں درج کریں۔" + +msgid "resend_confirmation_code" +msgstr "تصدیقی ای میل دوبارہ بھیجیں" + +msgid "email_already_exists" +msgstr "ای میل پہلے سے موجود ہے" + +msgid "email_already_exists_msg" +msgstr "" +"یہ ای میل لے لی گئی، اگر آپ کے پاس پہلے سے اس ای میل کے ساتھ اکاؤںٹ موجود ہے" +" تو آپ بحال کر سکتے ہیں۔ اگر نہیں تو براہ کرم واپس جائیں اور مختلف ای میل " +"ایڈریس منتخب کریں۔" + +msgid "recover_account" +msgstr "اکاؤنٹ بحال کریں" + +msgid "back" +msgstr "واپس جائیں" + +msgid "change_email" +msgstr "ای میل تبدیل کریں" + +msgid "new_password" +msgstr "نیا پاس ورڈ" + +msgid "confirm_new_password" +msgstr "نئے پاس ورڈ کی تصدیق کریں" + +msgid "already_have_an_account" +msgstr "پہلے سے اکاؤنٹ موجود ہے؟ " + +msgid "create_account" +msgstr "اکاؤنٹ تخلیق کریں" + +msgid "create_password" +msgstr "پاس ورڈ تخلیق کریں" + +msgid "by_creating_an_account" +msgstr "اکاؤنٹ تخلیق کر کے، آپ ہماری سے متفق ہوتے ہیں" + +msgid "confirm_email" +msgstr "ای میل کی تصدیق کیجئے" + +msgid "update_pro_account" +msgstr "Pro اکاؤنٹ اپ ڈیٹ کریں" + +msgid "update_pro_account_message" +msgstr "" +"Lantern Pro میں تبدیلیاں آ رہی ہیں! اب، آپ کو تمام ڈیوائسز میں لاگ ان کرنے، " +"اور اکاؤنٹ مینیجمنٹ تک رسائی حاصل کرنے کے لیے ای میل اور پاس ورڈ درکار ہو " +"گا۔ براہ کرم اپنے اکاؤنٹ میں ان اسناد کو شامل کرنے کے لیے چند لمحے دیں۔ " + +msgid "not_now" +msgstr "ابھی نہیں" + +msgid "update_account" +msgstr "اکاؤنٹ اپ ڈیٹ کریں" + msgid "new" msgstr "نیا" @@ -38,6 +131,12 @@ msgstr "ڈیٹا کا روزانہ کا استعمال " msgid "is" msgstr "ہے" +msgid "connect" +msgstr "کنیکٹ" + +msgid "disconnect" +msgstr "منقطع" + msgid "connected" msgstr "کنیکٹ ہو چکا ہے" @@ -72,7 +171,7 @@ msgid "Go Pro Title" msgstr "پرو اپنائیں!" msgid "Go Pro Description" -msgstr "" +msgstr "تیز رفتار، اشتہار سے پاک تجربہ، اور لا محدود ڈیٹا کو ان لاک کریں! " msgid "Server Location" msgstr "سرور لوکیشن" @@ -107,7 +206,7 @@ msgstr "" msgid "Pro Account Management" msgstr "پرو اکاؤنٹ مینجمنٹ" -msgid "Link Device" +msgid "link_device" msgstr "ڈیوائس لنک کریں" msgid "Upgrade to Lantern Pro" @@ -155,12 +254,6 @@ msgstr "جنرل" msgid "about" msgstr "متعلق" -msgid "privacy_policy" -msgstr "پرائیوسی پالیسی" - -msgid "terms_of_service" -msgstr "سروس کی شرائط" - msgid "proxy_all" msgstr "پراکسی سب کُجھ" @@ -183,7 +276,7 @@ msgid "select_an_issue" msgstr "برائے مہربانی ایک مسئلے کا انتخاب کیجئے" msgid "enter_description" -msgstr "" +msgstr "براہ کرم اپنی مسئلہ کی وضاحت کریں " msgid "cannot_access_blocked_sites" msgstr "بلاک سائٹس تک رسائی ممکن نہیں " @@ -191,17 +284,35 @@ msgstr "بلاک سائٹس تک رسائی ممکن نہیں " msgid "cannot_complete_purchase" msgstr "خریداری مکمل نہیں کر سکتے" +msgid "discover_not_working" +msgstr "دریافت کام نہیں کر رہا " + msgid "cannot_sign_in" msgstr "سائن ان نہیں کر سکتے" msgid "spinner_loads_endlessly" msgstr "اسپنر لا محدود طور پر لوڈ کرتا ہے" +msgid "slow" +msgstr "آہستہ" + +msgid "cannot_link_devices" +msgstr "ڈیوائس منسلک نہیں ہو سکتی" + +msgid "wrong_device_linking_code" +msgstr "غیر درست ڈیوائس منسلک کوڈ" + +msgid "cannot_remove_device" +msgstr "ڈیوائس کو ہٹایا نہیں جا سکتا " + +msgid "application_crashes" +msgstr "اپیلیکیشن کریش ہوتی ہے" + msgid "other" msgstr "دوسرے" msgid "issue_description" -msgstr "مسئلہ کی تفصیل" +msgstr "اپنی مسئلہ کی وضاحت کریں " msgid "check_for_updates" msgstr "اپ ڈیٹس کے لیے پڑتال کریں" @@ -242,10 +353,10 @@ msgid "device_linking_pin" msgstr "ڈیوائس لنکنگ پن" msgid "link_device_step_one" -msgstr "" +msgstr "اپنے پرو ڈیوائس پر 'ڈیوائس شامل کریں' مینو آئٹم کو کھولیں۔ " msgid "link_device_step_two" -msgstr "" +msgstr "ڈیوائس لنکنگ PIN درج کریں اور جمع کرائیں " msgid "ensure_most_recent_version_lantern" msgstr "" @@ -265,14 +376,14 @@ msgstr "" msgid "Link via Email" msgstr "ای میل کے ذریعہ لنک" -msgid "lantern_pro_email" -msgstr "Lantern پرو ای میل" - msgid "lantern_desktop" msgstr "لینٹرن ڈیسک ٹاپ" +msgid "report_description" +msgstr "مسئلہ کی تفصیل" + msgid "report_issue" -msgstr "" +msgstr "مسئلہ رپورٹ کریں " msgid "report_an_issue" msgstr "مسئلہ رپورٹ کیجئے" @@ -295,7 +406,7 @@ msgstr "" "پر مل سکتی ہیں۔" msgid "share_link" -msgstr "" +msgstr "لنک شیئر کریں " msgid "share_title" msgstr "لینٹرن ڈاؤن لوڈ کا لنک شیئر کریں" @@ -327,8 +438,8 @@ msgstr "ماہ" msgid "months" msgstr "مہینے" -msgid "enter_email" -msgstr "ای میل درج کریں" +msgid "enter_email_to_complete_purchase" +msgstr "خریداری مکمل کرنے کے لیے ای میل درج کریں " msgid "error_fetching_plans" msgstr "" @@ -360,10 +471,12 @@ msgid "referral_code" msgstr "حوالہ کوڈ" msgid "share_referral_code" -msgstr "" +msgstr "ریفرل کوڈ شیئر کریں " msgid "share_message_referral_code" msgstr "" +"ایک مہینے کے مفت Lantern Pro کے لیے، یہ ریفرل کوڈ استعمال کریں: %s ; آپ اسے " +"یہاں سے ڈاؤن لوڈ کر سکتے ہیں: https://lantern.io/download " msgid "add_referral_code" msgstr "ریفرل کوڈ شامل کریں" @@ -432,7 +545,7 @@ msgstr "" " کے اکاؤنٹ سے تین تک ڈیوائسز مربوط ہو سکتی ہیں۔" msgid "by_clicking_tos" -msgstr "%s پر کلک کر کے، آپ ہمارے درج ذیل سے متفق ہیں" +msgstr "%s پر کلک کر کے، آپ ہماری سے متفق ہوتے ہیں " msgid "tos" msgstr "سروس کی شرائط" @@ -482,6 +595,18 @@ msgstr "جی ہاں" msgid "No" msgstr "نہیں" +msgid "exit" +msgstr "خروج " + +msgid "show" +msgstr "دکھائیں " + +msgid "status_on" +msgstr "حالت: چل رہا ہے " + +msgid "status_off" +msgstr "حالت: بند ہے " + msgid "Enter or paste linking code" msgstr "لنکنگ کوڈ درج یا پیسٹ کریں" @@ -519,6 +644,9 @@ msgstr "لاگ آؤٹ کریں" msgid "confirm_remove_device" msgstr "کیا آپ واقعی اس ڈیوائس کو اپنے پرو اکاؤنٹ سے منقطع کرنا چاہتے ہیں ؟" +msgid "confirm_close_window" +msgstr "کیا آپ یقیناً Lantern کو بند کرنا چاہتے ہیں؟ " + msgid "unlimited_data" msgstr "لامحدود ڈیٹا" @@ -531,6 +659,9 @@ msgstr "کوئی اشتہار نہیں" msgid "choose_plan" msgstr "پلان چنیں" +msgid "billed_one_time" +msgstr "ایک بار بل کیا گیا " + msgid "connect_up_to_3_devices" msgstr "3 تک آلات کو جوڑیں" @@ -731,9 +862,6 @@ msgstr "آپ اور %s کے لیے تمام پیغامات غائب نہیں ہ msgid "message_disappearing_description" msgstr "%s کے بعد تمام پیغامات آپ اور %s کے لیے غائب ہو جائیں گے۔" -msgid "delete" -msgstr "حذف کریں" - msgid "view_contact_info" msgstr "رابطے کی معلومات دیکھیں" @@ -1544,3 +1672,238 @@ msgstr "اکثر پوچھے جانے والے سوالات" msgid "add_device" msgstr "ڈیوائس شامل کیجئے" + +msgid "search_apps" +msgstr "ایپس تلاش کریں " + +msgid "user_not_found" +msgstr "" +"صارف کی شناخت نہیں ہوئی۔ براہ کرم اپنی اسناد کی پڑتال کریں اور دوبارہ کوشش " +"کریں۔ اگر آپ کو مسائل کا سامنا کرنا جاری رہتا ہے، تو براہ کرم معاونت کے لیے " +"ہماری معاونتی ٹیم سے رابطہ کریں۔" + +msgid "email_resend_message" +msgstr "آپ کا نیا تصدیقی کوڈ بھیج دیا گیا ہے" + +msgid "signup_error" +msgstr "" +"ایسا لگتا ہے کہ سائن اپ کے ساتھ کچھ غلط ہو گیا تھا۔ براہ کرم دوبارہ کوشش " +"کریں، اور اگر مسئلہ جاری رہتا ہے، تو ہماری معاونتی ٹیم سے رابطہ کریں" + +msgid "signup_error_user_exists" +msgstr "" +"ایسا لگتا ہے کہ سائن اپ کے ساتھ کچھ غلط ہو گیا تھا۔ براہ کرم اپنا پاس ورڈ ری" +" سیٹ کرنے کی کوشش کریں، اور اگر مسئلہ جاری رہتا ہے، تو ہماری معاونتی ٹیم سے " +"رابطہ کریں" + +msgid "confirm_email_error" +msgstr "براہ کرم اپنی ای میل کی تصدیق کریں" + +msgid "password_cannot_be_empty" +msgstr "پاس ورڈ خالی نہیں ہو سکتا" + +msgid "password_must_be_at_least_8_characters" +msgstr "پاس ورڈ میں کم از کم 8 یا زائد حروف ہونے چاہیئے" + +msgid "sign_out" +msgstr "سائن آؤٹ کریں" + +msgid "sign_out_message" +msgstr "" +"لاگ آؤٹ کرنے سے Lantern مفت ورژن پر سوئچ ہو جائے گا۔ اپنے اکاؤنٹ تک دوبارہ " +"رسائی حاصل کرنے کے لیے، آپ کو واپس سائن ان کرنے کی ضرورت ہو گی۔" + +msgid "change_password" +msgstr "پاس ورڈ تبدیل کریں" + +msgid "password" +msgstr "پاس ورڈ" + +msgid "new_email" +msgstr "نیا ای میل ایڈریس" + +msgid "new_email_same_as_old_email" +msgstr "نیا ای میل ایڈریس آپ کے پرانے ای میل ایڈریس سے یکساں نہیں ہو سکتا" + +msgid "danger_zone" +msgstr "خطرے کا علاقہ" + +msgid "delete_account" +msgstr "اکاؤنٹ حذف کریں" + +msgid "delete" +msgstr "حذف کریں" + +msgid "delete_account_message" +msgstr "" +"آپ کے اکاؤنٹ کو حذف کرنا کالعدم نہیں کیا جا سکتا۔ آپ تمام ڈیوائس تک رسائی سے" +" محروم ہو جائیں گے اور ریفنڈ کے بغیر کسی بھی Pro اکاؤنٹ کا وقت ضائع کریں گے۔" + +msgid "delete_your_account" +msgstr "آپ کا اکاؤنٹ حذف کریں؟" + +msgid "confirm_deletion" +msgstr "حذف کاری کی تصدیق کریں" + +msgid "account_deleted" +msgstr "اکاؤنٹ حذف ہو گیا" + +msgid "account_deleted_message" +msgstr "آپ کا اکاؤنٹ مستقل طور پر حذف کر دیا گیا ہے۔" + +msgid "password_has_been_updated" +msgstr "پاس ورڈ اپ ڈیٹ کر دیا گیا ہے" + +msgid "password_has_been_updated_message" +msgstr "آپ کا پاس ورڈ کامیابی سے ری سیٹ کر دیا گیا ہے" + +msgid "invalid_code" +msgstr "" +"کوڈ غلط ہے یا شاید یہ زائد المیعاد ہو گیا ہے۔ براہ کرم دوبارہ بھیجنے کی کوشش" +" کریں" + +msgid "get_lantern_pro" +msgstr "لینٹرن پرو حاصل کیجئے" + +msgid "try_lantern_pro" +msgstr "Lantern مفت آزمائیں! " + +msgid "continue_for_free" +msgstr "مفت جاری رکھیں " + +msgid "recovery_not_found" +msgstr "ڈیوائس اس یوزر سے منسلک نہیں " + +msgid "device_added" +msgstr "ڈیوائس اس یوزر سے منسلک نہیں " + +msgid "device_added_message" +msgstr "" +"آپ کا ڈیوائس کامیابی سے شامل کر دیا گیا ہے۔ اگر آپ ڈیوائس کو منسلک نہیں " +"دیکھتے ہیں، تو براہ کرم دونوں ڈیوائسز پر ایپ کو دوبارہ شروع کریں " + +msgid "wrong_link_code" +msgstr "آپ کا درج کردہ کوڈ غلط ہے۔ براہ کرم پڑتال کریں اور دوبارہ کوشش کریں۔" + +msgid "recovery_code_sent" +msgstr "بحالی کا کوڈ بھیج دیا گیا ہے" + +msgid "device_added_msg" +msgstr "یہ ڈیوائس %s سے لنک کر دی گئی ہے" + +msgid "new_to_lantern" +msgstr "Lantern پر نئے ہیں؟ " + +msgid "email_has_been_updated" +msgstr "ای میل اپ ڈیٹ کر دی گئی ہے" + +msgid "email_has_been_updated_message" +msgstr "آپ کی ای میل کامیابی سے تبدیل کر دی گئی ہے" + +msgid "email_has_been_verified" +msgstr "ای میل کی تصدیق ہو گئی ہے" + +msgid "email_has_been_verified_message" +msgstr "آپ کی ای میل کی کامیابی سے تصدیق کر دی گئی ہے" + +msgid "device_limit_reached" +msgstr "ڈیوائس کی حد تک پہنچ گئے" + +msgid "device_limit_reached_message" +msgstr "" +"آپ حالیہ طور پر ڈیوائسز کی زیادہ سے زیادہ تعداد (3) پر سائن ان ہو گئے ہیں۔ " +"اس ڈیوائس پر سائن ان کرنے کے لیے، براہ کرم ذیل میں موجود اپنی دیگر ڈیوائسز " +"میں سے ایک منتخب کریں اور ہٹائیں۔" + +msgid "enter_activation_code" +msgstr "فعالیت کا کوڈ درج کریں" + +msgid "check_your_email" +msgstr "اپنے ای میل ایڈریس کی پڑتال کریں" + +msgid "please_verify_email" +msgstr "براہ کرم تصدیق کریں کہ آپ کا درج کردہ ای میل ایڈریس درست ہے۔" + +msgid "verify" +msgstr "تصدیق کریں" + +msgid "cannot_login" +msgstr "لاگ ان نہیں ہو سکتا " + +msgid "privacy_policy" +msgstr "پرائیوسی پالیسی" + +msgid "terms_of_service" +msgstr "سروس کی شرائط" + +msgid "invalid_verification_code" +msgstr "غیر درست تصدیقی کوڈ" + +msgid "wrong_seller_code" +msgstr "غلط ری سیلر کوڈ۔ براہ کرم تصدیق کریں اور دوبارہ کوشش کریں۔ " + +msgid "follow_us" +msgstr "ہمیں فالو کریں " + +msgid "follow_us_telegram" +msgstr "ہمیں فالو کریں Telegram پر " + +msgid "follow_us_instagram" +msgstr "ہمیں فالو کریں Instagram پر " + +msgid "follow_us_facebook" +msgstr "ہمیں فالو کریں Facebook پر " + +msgid "follow_us_x" +msgstr "ہمیں فالو کریں X پر " + +msgid "report_issue_error" +msgstr "ہمیں تکنیکی مشکلات کا سامنا ہے۔ بعد میں دوبارہ کوشش کریں۔ " + +msgid "we_are_experiencing_technical_difficulties" +msgstr "ہمیں تکنیکی مشکلات کا سامنا ہے۔ بعد میں دوبارہ کوشش کریں۔ " + +msgid "a_temporary_error_occurred" +msgstr "ایک عارضی غلطی واقع ہوئی " + +msgid "sorry_we_are_unable_to_load_that_page" +msgstr "" +"معذرت، ہم اس وقت اس صفحے کو لوڈ نہیں کر سکتے، براہ کرم دوبارہ کوشش کرنے کے " +"لیے 'ریفریش' پر کلک کریں۔ " + +msgid "refresh" +msgstr "ریفریش" + +msgid "check_your_internet_connection" +msgstr "اپنے انٹرنیٹ کنکشن کی جانچ کریں " + +msgid "please_try" +msgstr "براہ کرم کوشش کریں " + +msgid "turning_off_airplane_mode" +msgstr "ایئرپلین موڈ بند کریں " + +msgid "turning_on_mobile_data_or_wifi" +msgstr "موبائل ڈیٹا یا وائی فائی کو آن کریں " + +msgid "check_the_signal_in_your_area" +msgstr "اپنے علاقے میں سگنل چیک کریں " + +msgid "got_it" +msgstr "سمجھ گئے" + +msgid "fetching_configuration" +msgstr "تشکیل حاصل کی جا رہی ہے " + +msgid "establish_connection_to_server" +msgstr "سرور سے کنکشن قائم کریں " + +msgid "file_size_limit_title" +msgstr "حد پوری ہو گئی " + +msgid "file_size_limit_description" +msgstr "" +"منسلکات کا سائز 100 MB سے کم ہونا چاہئے\n" +"\n" +" \n" +" " diff --git a/assets/locales/vi-vn.po b/assets/locales/vi-vn.po index 948ce86de..0571283d8 100644 --- a/assets/locales/vi-vn.po +++ b/assets/locales/vi-vn.po @@ -1,16 +1,17 @@ # # Translators: -# Anh Phan , 2023 -# Yen Le Van , 2023 # Lantern, 2023 -# Nathan Tran, 2023 -# e2f , 2023 -# e2f_vi r1 , 2023 -# Ngoc Hoang , 2023 +# Yen Le Van , 2024 +# Ngoc Hoang , 2024 +# e2f_vi r1 , 2024 +# Nathan Tran, 2024 +# Anh Phan , 2024 +# e2f , 2024 +# Derek F , 2024 # msgid "" msgstr "" -"Last-Translator: Ngoc Hoang , 2023\n" +"Last-Translator: Derek F , 2024\n" "Language-Team: Vietnamese (https://app.transifex.com/lantern-1/teams/94371/vi/)\n" "Language: vi\n" "Plural-Forms: nplurals=1; plural=0;\n" @@ -32,6 +33,98 @@ msgstr "Có lẽ để sau" msgid "try" msgstr "Dùng thử" +msgid "sign_in" +msgstr "Đăng nhập" + +msgid "enter_email" +msgstr "Nhập Email" + +msgid "enter_password" +msgstr "Nhập mật khẩu " + +msgid "forgot_password" +msgstr "Quên mật khẩu " + +msgid "forgot_your_password" +msgstr "Bạn quên mật khẩu? " + +msgid "click_here" +msgstr "Bạn quên mật khẩu? " + +msgid "reset_password" +msgstr "Đặt lại mật khẩu" + +msgid "lantern_pro_email" +msgstr "Email Lantern Pro " + +msgid "return_to_sign_in" +msgstr "Quay lại Đăng nhập " + +msgid "enter_confirmation_code" +msgstr "Nhập mã xác nhận " + +msgid "confirmation_code_msg" +msgstr "" +"Một mã xác nhận đã được gửi đến XX. Nhập nó ở đây để bảo vệ tài khoản của " +"bạn và kích hoạt khôi phục mật khẩu. " + +msgid "resend_confirmation_code" +msgstr "Gửi lại Email Xác nhận " + +msgid "email_already_exists" +msgstr "Email đã tồn tại " + +msgid "email_already_exists_msg" +msgstr "" +"Email này đã được sử dụng, nếu bạn đã có tài khoản với email này, bạn có thể" +" khôi phục. Nếu không, vui lòng quay lại và chọn một địa chỉ email khác. " + +msgid "recover_account" +msgstr "Khôi phục Tài khoản " + +msgid "back" +msgstr "trở lại " + +msgid "change_email" +msgstr "Thay đổi Email " + +msgid "new_password" +msgstr "Mật khẩu mới" + +msgid "confirm_new_password" +msgstr "Xác nhận mật khẩu mới" + +msgid "already_have_an_account" +msgstr "Đã có tài khoản? " + +msgid "create_account" +msgstr "Tạo tài khoản" + +msgid "create_password" +msgstr "Tạo mật khẩu" + +msgid "by_creating_an_account" +msgstr "Bằng cách Tạo tài khoản, bạn đồng ý với " + +msgid "confirm_email" +msgstr "Xác Nhận Email" + +msgid "update_pro_account" +msgstr "Cập nhật Tài khoản Pro " + +msgid "update_pro_account_message" +msgstr "" +"Những thay đổi sắp đến với Lantern Pro! Bây giờ, bạn sẽ cần một email và mật" +" khẩu để đăng nhập trên tất cả các thiết bị và để truy cập Quản lý Tài " +"khoản. Vui lòng dành vài phút để thêm những thông tin này vào tài khoản của " +"bạn. " + +msgid "not_now" +msgstr "Không phải bây giờ " + +msgid "update_account" +msgstr "Cập nhật Tài khoản " + msgid "new" msgstr "Mới" @@ -41,6 +134,12 @@ msgstr "Sử dụng dữ liệu hàng ngày" msgid "is" msgstr "là" +msgid "connect" +msgstr "Kết nối" + +msgid "disconnect" +msgstr "Ngắt kết nối" + msgid "connected" msgstr "Đã kết nối" @@ -76,6 +175,8 @@ msgstr "Lên Pro!" msgid "Go Pro Description" msgstr "" +"Mở khóa tốc độ nhanh hơn, trải nghiệm không quảng cáo và dữ liệu không giới " +"hạn! " msgid "Server Location" msgstr "Địa chỉ Server" @@ -109,7 +210,7 @@ msgstr "Tôi xác nhận rằng tôi muốn xóa dữ liệu trò chuyện của msgid "Pro Account Management" msgstr "Quản Trị Tài Khoản Pro" -msgid "Link Device" +msgid "link_device" msgstr "Liên kết thiết bị" msgid "Upgrade to Lantern Pro" @@ -158,12 +259,6 @@ msgstr "Chung" msgid "about" msgstr "Thông tin" -msgid "privacy_policy" -msgstr "Chính sách Bảo mật" - -msgid "terms_of_service" -msgstr "Điều khoản Dịch vụ" - msgid "proxy_all" msgstr "Proxy tất cả" @@ -186,7 +281,7 @@ msgid "select_an_issue" msgstr "Hãy chọn 1 vấn đề" msgid "enter_description" -msgstr "" +msgstr "Vui lòng mô tả vấn đề của bạn " msgid "cannot_access_blocked_sites" msgstr "Không thể truy cập các trang bị chặn" @@ -194,17 +289,35 @@ msgstr "Không thể truy cập các trang bị chặn" msgid "cannot_complete_purchase" msgstr "Không thể hoàn tất việc mua" +msgid "discover_not_working" +msgstr "Tính năng khám phá không hoạt động " + msgid "cannot_sign_in" msgstr "Không thể đăng nhập" msgid "spinner_loads_endlessly" msgstr "Spinner tải vô tận" +msgid "slow" +msgstr "chậm" + +msgid "cannot_link_devices" +msgstr "Không thể liên kết các thiết bị" + +msgid "wrong_device_linking_code" +msgstr "Mã số kết nối máy sai hoặc không hợp lệ" + +msgid "cannot_remove_device" +msgstr "Không thể gỡ bỏ thiết bị " + +msgid "application_crashes" +msgstr "Ứng dụng bị treo" + msgid "other" msgstr "Khác" msgid "issue_description" -msgstr "Mô tả vấn đề" +msgstr "Mô tả vấn đề của bạn " msgid "check_for_updates" msgstr "Kiểm tra bản cập nhật" @@ -245,10 +358,10 @@ msgid "device_linking_pin" msgstr "Ghim liên kết thiết bị" msgid "link_device_step_one" -msgstr "" +msgstr "Mở mục menu 'Thêm thiết bị' trên thiết bị Pro của bạn. " msgid "link_device_step_two" -msgstr "" +msgstr "Nhập mã PIN liên kết thiết bị và nhấp vào gửi " msgid "ensure_most_recent_version_lantern" msgstr "* Đảm bảo cả hai thiết bị đang chạy phiên bản Lantern mới nhất" @@ -265,14 +378,14 @@ msgstr "Yêu cầu quyền truy cập vào email bạn đã sử dụng để mu msgid "Link via Email" msgstr "Liên kết qua Email" -msgid "lantern_pro_email" -msgstr "Lantern Pro Email" - msgid "lantern_desktop" msgstr "Lantern Để Bàn" +msgid "report_description" +msgstr "Mô tả vấn đề" + msgid "report_issue" -msgstr "" +msgstr "Báo cáo vấn đề " msgid "report_an_issue" msgstr "Báo cáo vấn đề" @@ -294,7 +407,7 @@ msgstr "" "và tất cả các nền tảng khác tại liên kết ở trên." msgid "share_link" -msgstr "" +msgstr "Chia sẻ liên kết " msgid "share_title" msgstr "Chia sẻ liên kết tải xuống Lantern" @@ -326,8 +439,8 @@ msgstr "tháng" msgid "months" msgstr "tháng" -msgid "enter_email" -msgstr "Nhập Email" +msgid "enter_email_to_complete_purchase" +msgstr "Nhập email để hoàn tất mua hàng " msgid "error_fetching_plans" msgstr "Lỗi khi tìm nạp gói, vui lòng khởi động lại Lantern và thử lại." @@ -357,10 +470,12 @@ msgid "referral_code" msgstr "Mã giới thiệu" msgid "share_referral_code" -msgstr "" +msgstr "Chia sẻ mã giới thiệu " msgid "share_message_referral_code" msgstr "" +"Để có một tháng miễn phí Lantern Pro, sử dụng mã giới thiệu này: %s; Bạn có " +"thể tải xuống từ đây: https://lantern.io/download " msgid "add_referral_code" msgstr "Thêm mã giới thiệu" @@ -431,7 +546,7 @@ msgstr "" " khoản của bạn." msgid "by_clicking_tos" -msgstr "Bằng cách nhấp vào %s, bạn đồng ý với" +msgstr "Bằng cách nhấp vào %s, bạn đồng ý với " msgid "tos" msgstr "Điều khoản Dịch vụ" @@ -481,6 +596,18 @@ msgstr "Có" msgid "No" msgstr "Không" +msgid "exit" +msgstr "Thoát" + +msgid "show" +msgstr "Hiển thị " + +msgid "status_on" +msgstr "Trạng thái: Bật " + +msgid "status_off" +msgstr "Trạng thái: Tắt " + msgid "Enter or paste linking code" msgstr "Nhập hoặc dán mã liên kết" @@ -518,6 +645,9 @@ msgstr "Đăng xuất" msgid "confirm_remove_device" msgstr "Bạn có chắc là muốn gỡ kết nối máy này ra khỏi tài khoản Pro?" +msgid "confirm_close_window" +msgstr "Bạn có chắc chắn muốn đóng Lantern không? " + msgid "unlimited_data" msgstr "Dung Lượng Không Giới Hạn" @@ -530,6 +660,9 @@ msgstr "Không có quảng cáo" msgid "choose_plan" msgstr "Chọn gói" +msgid "billed_one_time" +msgstr "tính phí một lần " + msgid "connect_up_to_3_devices" msgstr "Kết nối tối đa 3 thiết bị" @@ -728,9 +861,6 @@ msgstr "Tất cả tin nhắn sẽ không biến mất đối với bạn và %s msgid "message_disappearing_description" msgstr "Tất cả tin nhắn sẽ biến mất sau %s đối với bạn và %s." -msgid "delete" -msgstr "Xóa" - msgid "view_contact_info" msgstr "Xem thông tin liên hệ" @@ -1538,3 +1668,230 @@ msgstr "FAQ" msgid "add_device" msgstr "Thêm Máy" + +msgid "search_apps" +msgstr "Tìm kiếm ứng dụng " + +msgid "user_not_found" +msgstr "" +"Không nhận diện được người dùng. Vui lòng kiểm tra thông tin đăng nhập của " +"bạn và thử lại. Nếu bạn tiếp tục gặp vấn đề, vui lòng liên hệ đội hỗ trợ của" +" chúng tôi để được trợ giúp. " + +msgid "email_resend_message" +msgstr "Mã xác nhận mới của bạn đã được gửi " + +msgid "signup_error" +msgstr "" +"Có vẻ như có sự cố với việc đăng ký. Vui lòng thử lại, và nếu vấn đề tiếp " +"tục, liên hệ đội hỗ trợ của chúng tôi. " + +msgid "signup_error_user_exists" +msgstr "" +"Có vẻ như có sự cố với việc đăng ký. Vui lòng thử đặt lại mật khẩu của bạn, " +"và nếu vấn đề tiếp tục, liên hệ đội hỗ trợ của chúng tôi. " + +msgid "confirm_email_error" +msgstr "Vui lòng xác nhận email của bạn " + +msgid "password_cannot_be_empty" +msgstr "Mật khẩu không được để trống " + +msgid "password_must_be_at_least_8_characters" +msgstr "Mật khẩu phải có ít nhất 8 ký tự trở lên " + +msgid "sign_out" +msgstr "Đăng xuất " + +msgid "sign_out_message" +msgstr "" +"Đăng xuất sẽ chuyển Lantern sang phiên bản miễn phí. Để truy cập lại tài " +"khoản của bạn, bạn sẽ cần đăng nhập lại. " + +msgid "change_password" +msgstr "Đổi mật khẩu" + +msgid "password" +msgstr "Mật khẩu" + +msgid "new_email" +msgstr "Địa chỉ Email mới " + +msgid "new_email_same_as_old_email" +msgstr "Địa chỉ email mới không thể giống địa chỉ email cũ của bạn " + +msgid "danger_zone" +msgstr "Khu vực nguy hiểm " + +msgid "delete_account" +msgstr "Xóa Tài khoản " + +msgid "delete" +msgstr "Xóa" + +msgid "delete_account_message" +msgstr "" +"Việc xóa tài khoản của bạn không thể hoàn tác. Bạn sẽ mất tất cả quyền truy " +"cập thiết bị và từ bỏ bất kỳ thời gian tài khoản Pro nào mà không được hoàn " +"lại tiền. " + +msgid "delete_your_account" +msgstr "Xóa tài khoản của bạn? " + +msgid "confirm_deletion" +msgstr "Xác nhận Xóa " + +msgid "account_deleted" +msgstr "Tài khoản đã Xóa " + +msgid "account_deleted_message" +msgstr "Tài khoản của bạn đã bị xóa vĩnh viễn. " + +msgid "password_has_been_updated" +msgstr "Mật khẩu đã được cập nhật " + +msgid "password_has_been_updated_message" +msgstr "Mật khẩu của bạn đã được đặt lại thành công " + +msgid "invalid_code" +msgstr "Mã không đúng hoặc có thể đã hết hạn. Vui lòng thử gửi lại " + +msgid "get_lantern_pro" +msgstr "Lấy Lantern Pro" + +msgid "try_lantern_pro" +msgstr "Dùng thử Lantern miễn phí! " + +msgid "continue_for_free" +msgstr "Tiếp tục miễn phí " + +msgid "recovery_not_found" +msgstr "Máy không liên đới với người dùng này" + +msgid "device_added" +msgstr "Máy không liên đới với người dùng này" + +msgid "device_added_message" +msgstr "" +"Thiết bị của bạn đã được thêm thành công. Nếu bạn không thấy thiết bị được " +"kết nối, vui lòng khởi động lại ứng dụng trên cả hai thiết bị " + +msgid "wrong_link_code" +msgstr "Mã bạn nhập không đúng. Vui lòng kiểm tra và thử lại. " + +msgid "recovery_code_sent" +msgstr "Mã khôi phục đã được gửi " + +msgid "device_added_msg" +msgstr "Thiết bị này đã được liên kết với %s " + +msgid "new_to_lantern" +msgstr "Mới sử dụng Lantern? " + +msgid "email_has_been_updated" +msgstr "Email đã được cập nhật " + +msgid "email_has_been_updated_message" +msgstr "Email của bạn đã được thay đổi thành công " + +msgid "email_has_been_verified" +msgstr "Email đã được xác minh " + +msgid "email_has_been_verified_message" +msgstr "Email của bạn đã được xác minh thành công " + +msgid "device_limit_reached" +msgstr "Đã đạt Giới hạn Thiết bị " + +msgid "device_limit_reached_message" +msgstr "" +"Hiện tại bạn đã đăng nhập trên số lượng thiết bị tối đa (3). Để đăng nhập " +"trên thiết bị này, vui lòng chọn và xóa một trong các thiết bị khác của bạn " +"dưới đây. " + +msgid "enter_activation_code" +msgstr "Nhập Mã Kích hoạt " + +msgid "check_your_email" +msgstr "Kiểm tra Địa chỉ Email của Bạn " + +msgid "please_verify_email" +msgstr "Vui lòng xác nhận rằng địa chỉ email bạn đã nhập là chính xác. " + +msgid "verify" +msgstr "Xác minh" + +msgid "cannot_login" +msgstr "Không thể đăng nhập " + +msgid "privacy_policy" +msgstr "Chính sách Bảo mật" + +msgid "terms_of_service" +msgstr "Điều khoản Dịch vụ" + +msgid "invalid_verification_code" +msgstr "Mã số kiểm chứng không hợp lệ" + +msgid "wrong_seller_code" +msgstr "Mã người bán lại không chính xác. Vui lòng kiểm tra và thử lại. " + +msgid "follow_us" +msgstr "Theo dõi chúng tôi " + +msgid "follow_us_telegram" +msgstr "Theo dõi chúng tôi " + +msgid "follow_us_instagram" +msgstr "Theo dõi chúng tôi trên Instagram " + +msgid "follow_us_facebook" +msgstr "Theo dõi chúng tôi trên Facebook " + +msgid "follow_us_x" +msgstr "Theo dõi chúng tôi trên X " + +msgid "report_issue_error" +msgstr "Chúng tôi đang gặp sự cố kỹ thuật. Vui lòng thử lại sau. " + +msgid "we_are_experiencing_technical_difficulties" +msgstr "Chúng tôi đang gặp sự cố kỹ thuật. Vui lòng thử lại sau. " + +msgid "a_temporary_error_occurred" +msgstr "Đã xảy ra lỗi tạm thời " + +msgid "sorry_we_are_unable_to_load_that_page" +msgstr "Đã xảy ra lỗi tạm thời " + +msgid "refresh" +msgstr "Làm mới" + +msgid "check_your_internet_connection" +msgstr "Kiểm tra kết nối internet của bạn " + +msgid "please_try" +msgstr "Kiểm tra kết nối internet của bạn " + +msgid "turning_off_airplane_mode" +msgstr "Tắt chế độ máy bay " + +msgid "turning_on_mobile_data_or_wifi" +msgstr "Bật dữ liệu di động hoặc wifi " + +msgid "check_the_signal_in_your_area" +msgstr "Kiểm tra tín hiệu trong khu vực của bạn " + +msgid "got_it" +msgstr "Đã nhận được" + +msgid "fetching_configuration" +msgstr "Đang lấy cấu hình " + +msgid "establish_connection_to_server" +msgstr "Thiết lập kết nối với máy chủ " + +msgid "file_size_limit_title" +msgstr "Đã đạt giới hạn " + +msgid "file_size_limit_description" +msgstr "Kích thước tệp đính kèm phải dưới 100 MB" diff --git a/desktop/lib.go b/desktop/lib.go index 4dbb05a10..c96e96f35 100644 --- a/desktop/lib.go +++ b/desktop/lib.go @@ -31,6 +31,7 @@ import ( "github.com/getlantern/lantern-client/internalsdk/common" proclient "github.com/getlantern/lantern-client/internalsdk/pro" "github.com/getlantern/lantern-client/internalsdk/protos" + "github.com/getlantern/lantern-client/internalsdk/webclient" "github.com/getlantern/osversion" "github.com/joho/godotenv" @@ -79,7 +80,7 @@ func start() { cdir := configDir(&flags) settings := loadSettings(cdir) - proClient = proclient.NewClient(fmt.Sprintf("https://%s", common.ProAPIHost), &proclient.Opts{ + proClient = proclient.NewClient(fmt.Sprintf("https://%s", common.ProAPIHost), &webclient.Opts{ HttpClient: &http.Client{ Transport: proxied.ParallelForIdempotent(), Timeout: 30 * time.Second, diff --git a/go.mod b/go.mod index ac1fb3dff..aad5336fa 100644 --- a/go.mod +++ b/go.mod @@ -6,22 +6,15 @@ toolchain go1.22.4 // replace github.com/getlantern/flashlight/v7 => ../flashlight -// replace github.com/getlantern/fronted => ../fronted - -// replace github.com/getlantern/ipproxy => ../ipproxy - -// replace github.com/getlantern/dnsgrab => ../dnsgrab +replace github.com/getlantern/lantern-client => ../lantern-client -// v0.5.6 has a security issue and using require leaves a reference to it in go.sum -replace github.com/ulikunitz/xz => github.com/ulikunitz/xz v0.5.8 +// replace github.com/getlantern/fronted => ../fronted +// replace github.com/getlantern/pathdb => ../pathDb/pathDb replace github.com/elazarl/goproxy => github.com/getlantern/goproxy v0.0.0-20220805074304-4a43a9ed4ec6 replace github.com/lucas-clemente/quic-go => github.com/getlantern/quic-go v0.31.1-0.20230104154904-d810c964a217 -// git.apache.org isn't working at the moment, use mirror (should probably switch back once we can) -replace git.apache.org/thrift.git => github.com/apache/thrift v0.0.0-20180902110319-2566ecd5d999 - replace github.com/keighl/mandrill => github.com/getlantern/mandrill v0.0.0-20221004112352-e7c04248adcb replace github.com/google/netstack => github.com/getlantern/netstack v0.0.0-20220824143118-037ff0cd9c33 @@ -29,6 +22,7 @@ replace github.com/google/netstack => github.com/getlantern/netstack v0.0.0-2022 replace github.com/eycorsican/go-tun2socks => github.com/getlantern/go-tun2socks v1.16.12-0.20201218023150-b68f09e5ae93 require ( + github.com/1Password/srp v0.2.0 github.com/blang/semver v3.5.1+incompatible github.com/dustin/go-humanize v1.0.1 github.com/eycorsican/go-tun2socks v1.16.12-0.20201107203946-301549c435ff @@ -42,7 +36,7 @@ require ( github.com/getlantern/eventual v1.0.0 github.com/getlantern/eventual/v2 v2.0.2 github.com/getlantern/filepersist v0.0.0-20210901195658-ed29a1cb0b7c - github.com/getlantern/flashlight/v7 v7.6.86 + github.com/getlantern/flashlight/v7 v7.6.87 github.com/getlantern/fronted v0.0.0-20230601004823-7fec719639d8 github.com/getlantern/golog v0.0.0-20230503153817-8e72de7e0a65 github.com/getlantern/i18n v0.0.0-20181205222232-2afc4f49bb1c @@ -64,7 +58,7 @@ require ( github.com/getlantern/yaml v0.0.0-20190801163808-0c9bb1ebf426 github.com/getsentry/sentry-go v0.27.0 github.com/go-ping/ping v1.1.0 - github.com/go-resty/resty/v2 v2.12.0 + github.com/go-resty/resty/v2 v2.13.1 github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 @@ -73,8 +67,9 @@ require ( github.com/leekchan/accounting v1.0.0 github.com/shopspring/decimal v1.4.0 github.com/stretchr/testify v1.9.0 + golang.org/x/crypto v0.23.0 golang.org/x/mobile v0.0.0-20240404231514-09dbf07665ed - golang.org/x/net v0.24.0 + golang.org/x/net v0.25.0 golang.org/x/sys v0.20.0 google.golang.org/protobuf v1.33.0 nhooyr.io/websocket v1.8.10 @@ -215,7 +210,7 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/go-cmp v0.6.0 // indirect - github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 // indirect + github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/huandu/xstrings v1.4.0 // indirect @@ -229,21 +224,17 @@ require ( github.com/kr/binarydist v0.1.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect - github.com/lib/pq v1.10.6 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-sqlite3 v2.0.2+incompatible // indirect github.com/mdlayher/netlink v1.1.0 // indirect github.com/mholt/archiver/v3 v3.5.1 // indirect - github.com/miekg/dns v1.1.58 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mitchellh/go-server-timing v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/montanaflynn/stats v0.6.6 // indirect github.com/mschoch/smat v0.2.0 // indirect github.com/nwaples/rardecode v1.1.2 // indirect - github.com/onsi/ginkgo/v2 v2.15.0 // indirect + github.com/onsi/ginkgo/v2 v2.16.0 // indirect github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 // indirect github.com/oschwald/geoip2-golang v1.9.0 // indirect github.com/oschwald/maxminddb-golang v1.11.0 // indirect @@ -294,11 +285,10 @@ require ( github.com/ti-mo/netfilter v0.3.1 // indirect github.com/tidwall/btree v1.6.0 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect - github.com/tklauser/go-sysconf v0.3.12 // indirect - github.com/tklauser/numcpus v0.6.1 // indirect + github.com/tklauser/go-sysconf v0.3.10 // indirect + github.com/tklauser/numcpus v0.5.0 // indirect github.com/tkuchiki/go-timezone v0.2.2 // indirect github.com/ulikunitz/xz v0.5.11 // indirect - github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xjasonlyu/tun2socks/v2 v2.5.2 // indirect github.com/xtaci/smux v1.5.24 // indirect @@ -321,11 +311,10 @@ require ( go.uber.org/mock v0.4.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e // indirect + golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/sync v0.7.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/text v0.15.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.20.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect diff --git a/go.sum b/go.sum index ddfb022f1..20aba3740 100644 --- a/go.sum +++ b/go.sum @@ -9,6 +9,8 @@ filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5E git.torproject.org/pluggable-transports/goptlib.git v1.0.0/go.mod h1:YT4XMSkuEXbtqlydr9+OxqFAyspUv0Gr9qhM3B++o/Q= git.torproject.org/pluggable-transports/goptlib.git v1.2.0 h1:0qRF7Dw5qXd0FtZkjWUiAh5GTutRtDGL4GXUDJ4qMHs= git.torproject.org/pluggable-transports/goptlib.git v1.2.0/go.mod h1:4PBMl1dg7/3vMWSoWb46eGWlrxkUyn/CAJmxhDLAlDs= +github.com/1Password/srp v0.2.0 h1:PZKAafEyExnwevliL6d2+FDhJXZ0phxqiG2OeIaj9Xk= +github.com/1Password/srp v0.2.0/go.mod h1:LIGqQ7eEA0UJT98j7sXk60QWVpHJ3g00BX6LOm9kYTc= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Jigsaw-Code/outline-sdk v0.0.16 h1:WbHmv80FKDIpzEmR3GehTbq5CibYTLvcxIIpMMILiEs= github.com/Jigsaw-Code/outline-sdk v0.0.16/go.mod h1:e1oQZbSdLJBBuHgfeQsgEkvkuyIePPwstUeZRGq0KO8= @@ -282,8 +284,8 @@ github.com/getlantern/fdcount v0.0.0-20210503151800-5decd65b3731/go.mod h1:XZwE+ github.com/getlantern/filepersist v0.0.0-20160317154340-c5f0cd24e799/go.mod h1:8DGAx0LNUfXNnEH+fXI0s3OCBA/351kZCiz/8YSK3i8= github.com/getlantern/filepersist v0.0.0-20210901195658-ed29a1cb0b7c h1:mcz27xtAkb1OuOLBct/uFfL1p3XxAIcFct82GbT+UZM= github.com/getlantern/filepersist v0.0.0-20210901195658-ed29a1cb0b7c/go.mod h1:8DGAx0LNUfXNnEH+fXI0s3OCBA/351kZCiz/8YSK3i8= -github.com/getlantern/flashlight/v7 v7.6.86 h1:urjRCoxAUfI2p/tf3Y3eOZEDhmcPDjXnjy1qPAw+UkU= -github.com/getlantern/flashlight/v7 v7.6.86/go.mod h1:A+/KlrUCdU+nO7S4yOtsmfcNb7xec4nYk6uXRdwFnew= +github.com/getlantern/flashlight/v7 v7.6.87 h1:IRwthjTQQS2xcLHHKaIsloKKxg11DODPSiIa9bm88HU= +github.com/getlantern/flashlight/v7 v7.6.87/go.mod h1:A+/KlrUCdU+nO7S4yOtsmfcNb7xec4nYk6uXRdwFnew= github.com/getlantern/framed v0.0.0-20190601192238-ceb6431eeede h1:yrU6Px3ZkvCsDLPryPGi6FN+2iqFPq+JeCb7EFoDBhw= github.com/getlantern/framed v0.0.0-20190601192238-ceb6431eeede/go.mod h1:nhnoiS6DE6zfe+BaCMU4YI01UpsuiXnDqM5S8jxHuuI= github.com/getlantern/fronted v0.0.0-20230601004823-7fec719639d8 h1:r/Z/SPPIfLXDI3QA7/tE6nOfPncrqeUPDjiFjnNGP50= @@ -484,8 +486,8 @@ github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw= github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= -github.com/go-resty/resty/v2 v2.12.0 h1:rsVL8P90LFvkUYq/V5BTVe203WfRIU4gvcf+yfzJzGA= -github.com/go-resty/resty/v2 v2.12.0/go.mod h1:o0yGPrkS3lOe1+eFajk6kBW8ScXzwU3hD69/gt2yB/0= +github.com/go-resty/resty/v2 v2.13.1 h1:x+LHXBI2nMB1vqndymf26quycC4aggYJ7DECYbiz03g= +github.com/go-resty/resty/v2 v2.13.1/go.mod h1:GznXlLxkq6Nh4sU59rPmUw3VtgpO3aS96ORAI6Q7d+0= github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= @@ -551,8 +553,8 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= -github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 h1:E/LAvt58di64hlYjx7AsNS6C/ysHWYo+2qPCZKTQhRo= -github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 h1:y3N7Bm7Y9/CtpiVkw/ZWj6lSlDF3F74SfKwfTCer72Q= +github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -644,9 +646,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leekchan/accounting v1.0.0 h1:+Wd7dJ//dFPa28rc1hjyy+qzCbXPMR91Fb6F1VGTQHg= github.com/leekchan/accounting v1.0.0/go.mod h1:3timm6YPhY3YDaGxl0q3eaflX0eoSx3FXn7ckHe4tO0= +github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= -github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= @@ -656,8 +657,8 @@ github.com/mattn/go-colorable v0.0.10-0.20170816031813-ad5389df28cd/go.mod h1:9v github.com/mattn/go-isatty v0.0.2/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v2.0.2+incompatible h1:qzw9c2GNT8UFrgWNDhCTqRqYUSmu/Dav/9Z58LGpk7U= -github.com/mattn/go-sqlite3 v2.0.2+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= @@ -665,8 +666,8 @@ github.com/mdlayher/netlink v1.1.0 h1:mpdLgm+brq10nI9zM1BpX1kpDbh3NLl3RSnVq6ZSkf github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY= github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo= github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= -github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= -github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= +github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= +github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= @@ -681,9 +682,8 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/montanaflynn/stats v0.5.0 h1:2EkzeTSqBB4V4bJwWrt5gIIrZmpJBcoIRGS2kWLgzmk= github.com/montanaflynn/stats v0.5.0/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= -github.com/montanaflynn/stats v0.6.6 h1:Duep6KMIDpY4Yo11iFsvyqJDyfzLF9+sndUKT+v64GQ= -github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= @@ -701,8 +701,8 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108 github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= -github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= +github.com/onsi/ginkgo/v2 v2.16.0 h1:7q1w9frJDzninhXxjZd+Y/x54XNjG/UlRLIYPZafsPM= +github.com/onsi/ginkgo/v2 v2.16.0/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= @@ -926,20 +926,23 @@ github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDW github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= -github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= -github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= -github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= -github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw= +github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= +github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= +github.com/tklauser/numcpus v0.5.0 h1:ooe7gN0fg6myJ0EKoTAf5hebTZrH52px3New/D9iJ+A= +github.com/tklauser/numcpus v0.5.0/go.mod h1:OGzpTxpcIMNGYQdit2BYL1pvk/dSOaJWjKoflh+RQjo= github.com/tkuchiki/go-timezone v0.2.0/go.mod h1:b1Ean9v2UXtxSq4TZF0i/TU9NuoWa9hOzOKoGCV2zqY= github.com/tkuchiki/go-timezone v0.2.2 h1:MdHR65KwgVTwWFQrota4SKzc4L5EfuH5SdZZGtk/P2Q= github.com/tkuchiki/go-timezone v0.2.2/go.mod h1:oFweWxYl35C/s7HMVZXiA19Jr9Y0qJHMaG/J2TES4LY= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= +github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= -github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= -github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns= +github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vulcand/oxy v1.4.2 h1:KibUVdKrwy7eXR3uHS2pYoZ9dCzKVcgDNHD2jkPZmxU= github.com/vulcand/oxy v1.4.2/go.mod h1:Yq8OBb0XWU/7nPSglwUH5LS2Pcp4yvad8SVayobZbSo= github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= @@ -1027,12 +1030,11 @@ golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIi golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e h1:723BNChdd0c2Wk6WOE320qGBiPtYx0F0Bbm1kriShfE= -golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1088,9 +1090,8 @@ golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.0.0-20170912212905-13449ad91cb2/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1132,7 +1133,6 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1145,6 +1145,8 @@ golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1159,7 +1161,6 @@ golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -1173,10 +1174,11 @@ golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= @@ -1184,8 +1186,9 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20170424234030-8be79e1e0910/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= diff --git a/installer-resources-lantern/darwin/Lantern.app_template/Contents/Info.plist b/installer-resources-lantern/darwin/Lantern.app_template/Contents/Info.plist deleted file mode 100644 index aefb1adc0..000000000 --- a/installer-resources-lantern/darwin/Lantern.app_template/Contents/Info.plist +++ /dev/null @@ -1,27 +0,0 @@ - - - - - CFBundleExecutable - Lantern - CFBundleIconFile - app.icns - CFBundleIdentifier - com.getlantern.lantern - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - Lantern - CFBundlePackageType - APPL - CFBundleSignature - WRUN - - LSUIElement - 1 - NSHighResolutionCapable - True - - diff --git a/installer-resources-lantern/darwin/Lantern.app_template/Contents/PkgInfo b/installer-resources-lantern/darwin/Lantern.app_template/Contents/PkgInfo deleted file mode 100644 index 3e3578b93..000000000 --- a/installer-resources-lantern/darwin/Lantern.app_template/Contents/PkgInfo +++ /dev/null @@ -1 +0,0 @@ -APPLLANTERN \ No newline at end of file diff --git a/installer-resources-lantern/darwin/Lantern.app_template/Contents/Resources/app.icns b/installer-resources-lantern/darwin/Lantern.app_template/Contents/Resources/app.icns deleted file mode 100644 index 3934730dc..000000000 Binary files a/installer-resources-lantern/darwin/Lantern.app_template/Contents/Resources/app.icns and /dev/null differ diff --git a/internalsdk/android.go b/internalsdk/android.go index 26e8dc4f9..9b3eec2e1 100644 --- a/internalsdk/android.go +++ b/internalsdk/android.go @@ -640,6 +640,7 @@ func run(configDir, locale string, settings Settings, session PanickingSession) checkFeatures() } }() + replicaServer.CheckEnabled() go runner.Run( diff --git a/internalsdk/auth/auth.go b/internalsdk/auth/auth.go new file mode 100644 index 000000000..2c92111d2 --- /dev/null +++ b/internalsdk/auth/auth.go @@ -0,0 +1,224 @@ +package auth + +import ( + "context" + "net/http" + "strings" + + "github.com/getlantern/golog" + "github.com/getlantern/lantern-client/internalsdk/common" + "github.com/getlantern/lantern-client/internalsdk/protos" + "github.com/getlantern/lantern-client/internalsdk/webclient" + + "github.com/getlantern/lantern-client/internalsdk/webclient/defaultwebclient" + "github.com/go-resty/resty/v2" +) + +var ( + log = golog.LoggerFor("authclient") +) + +type authClient struct { + webclient webclient.RESTClient + userConfig func() common.UserConfig +} + +type AuthClient interface { + //Sign up methods + SignUp(ctx context.Context, signupData *protos.SignupRequest) (bool, error) + SignupEmailResendCode(ctx context.Context, data *protos.SignupEmailResendRequest) (bool, error) + SignupEmailConfirmation(ctx context.Context, data *protos.ConfirmSignupRequest) (bool, error) + + //Login methods + GetSalt(ctx context.Context, email string) (*protos.GetSaltResponse, error) + LoginPrepare(ctx context.Context, loginData *protos.PrepareRequest) (*protos.PrepareResponse, error) + Login(ctx context.Context, loginData *protos.LoginRequest) (*protos.LoginResponse, error) + // Recovery methods + StartRecoveryByEmail(ctx context.Context, loginData *protos.StartRecoveryByEmailRequest) (bool, error) + CompleteRecoveryByEmail(ctx context.Context, loginData *protos.CompleteRecoveryByEmailRequest) (bool, error) + ValidateEmailRecoveryCode(ctx context.Context, loginData *protos.ValidateRecoveryCodeRequest) (*protos.ValidateRecoveryCodeResponse, error) + // Change email methods + ChangeEmail(ctx context.Context, loginData *protos.ChangeEmailRequest) (bool, error) + // Complete change email methods + CompleteChangeEmail(ctx context.Context, loginData *protos.CompleteChangeEmailRequest) (bool, error) + DeleteAccount(ctc context.Context, loginData *protos.DeleteUserRequest) (bool, error) + + //Logout + SignOut(ctx context.Context, logoutData *protos.LogoutRequest) (bool, error) +} + +// NewClient creates a new instance of AuthClient +func NewClient(baseURL string, opts *webclient.Opts) AuthClient { + httpClient := opts.HttpClient + if httpClient == nil { + httpClient = &http.Client{} + } + authClient := &authClient{ + userConfig: opts.UserConfig, + } + authClient.webclient = webclient.NewRESTClient(defaultwebclient.SendToURL(httpClient, baseURL, prepareUserRequest(opts.UserConfig), nil)) + return authClient +} + +func prepareUserRequest(userConfig func() common.UserConfig) func(client *resty.Client, req *http.Request) error { + return func(client *resty.Client, req *http.Request) error { + req.Header.Set(common.ContentType, "application/x-protobuf") + req.Header.Set("Access-Control-Allow-Headers", strings.Join([]string{ + common.DeviceIdHeader, + common.ProTokenHeader, + common.UserIdHeader, + }, ", ")) + uc := userConfig() + if req.URL != nil && strings.HasSuffix(req.URL.Path, "/users/signup") { + // for the /users/signup endpoint, we do need to pass all default headers + common.AddCommonHeadersWithOptions(uc, req, false) + } else { + common.AddCommonNonUserHeaders(uc, req) + } + return nil + } +} + +// Auth APIS +// GetSalt is used to get the salt for a given email address +func (c *authClient) GetSalt(ctx context.Context, email string) (*protos.GetSaltResponse, error) { + var resp protos.GetSaltResponse + err := c.webclient.GetPROTOC(ctx, "/users/salt", map[string]interface{}{ + "email": email, + }, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} + +// Sign up API +// SignUp is used to sign up a new user with the SignupRequest +func (c *authClient) SignUp(ctx context.Context, signupData *protos.SignupRequest) (bool, error) { + var resp protos.EmptyResponse + err := c.webclient.PostPROTOC(ctx, "/users/signup", nil, signupData, &resp) + if err != nil { + return false, log.Errorf("error while sign up %v", err) + } + return true, nil +} + +// SignupEmailResendCode is used to resend the email confirmation code +// Params: ctx context.Context, data *protos.SignupEmailResendRequest +func (c *authClient) SignupEmailResendCode(ctx context.Context, data *protos.SignupEmailResendRequest) (bool, error) { + var resp protos.EmptyResponse + err := c.webclient.PostPROTOC(ctx, "/users/signup/resend/email", nil, data, &resp) + if err != nil { + return false, err + } + return true, nil +} + +// SignupEmailConfirmation is used to confirm the email address once user enter code +// Params: ctx context.Context, data *protos.ConfirmSignupRequest +func (c *authClient) SignupEmailConfirmation(ctx context.Context, data *protos.ConfirmSignupRequest) (bool, error) { + var resp protos.EmptyResponse + err := c.webclient.PostPROTOC(ctx, "/users/signup/complete/email", nil, data, &resp) + if err != nil { + return false, err + } + return true, nil +} + +// LoginPrepare does the initial login preparation with come make sure the user exists and match user salt +func (c *authClient) LoginPrepare(ctx context.Context, loginData *protos.PrepareRequest) (*protos.PrepareResponse, error) { + var model protos.PrepareResponse + err := c.webclient.PostPROTOC(ctx, "/users/prepare", nil, loginData, &model) + if err != nil { + // Send custom error to show error on client side + return nil, log.Errorf("user_not_found %v", err) + } + return &model, nil +} + +// Login is used to login a user with the LoginRequest +func (c *authClient) Login(ctx context.Context, loginData *protos.LoginRequest) (*protos.LoginResponse, error) { + var resp protos.LoginResponse + err := c.webclient.PostPROTOC(ctx, "/users/login", nil, loginData, &resp) + if err != nil { + return nil, err + } + + return &resp, nil +} + +// StartRecoveryByEmail is used to start the recovery process by sending a recovery code to the user's email +func (c *authClient) StartRecoveryByEmail(ctx context.Context, loginData *protos.StartRecoveryByEmailRequest) (bool, error) { + var resp protos.EmptyResponse + err := c.webclient.PostPROTOC(ctx, "/users/recovery/start/email", nil, loginData, &resp) + if err != nil { + return false, err + } + return true, nil +} + +// CompleteRecoveryByEmail is used to complete the recovery process by validating the recovery code +func (c *authClient) CompleteRecoveryByEmail(ctx context.Context, loginData *protos.CompleteRecoveryByEmailRequest) (bool, error) { + var resp protos.EmptyResponse + err := c.webclient.PostPROTOC(ctx, "/users/recovery/complete/email", nil, loginData, &resp) + if err != nil { + return false, err + } + return true, nil +} + +// // ValidateEmailRecoveryCode is used to validate the recovery code +func (c *authClient) ValidateEmailRecoveryCode(ctx context.Context, recoveryData *protos.ValidateRecoveryCodeRequest) (*protos.ValidateRecoveryCodeResponse, error) { + var resp protos.ValidateRecoveryCodeResponse + log.Debugf("ValidateEmailRecoveryCode request is %v", recoveryData) + err := c.webclient.PostPROTOC(ctx, "/users/recovery/validate/email", nil, recoveryData, &resp) + if err != nil { + return nil, err + } + if !resp.Valid { + return nil, log.Errorf("invalid_code Error decoding response body: %v", err) + } + return &resp, nil +} + +// ChangeEmail is used to change the email address of a user +func (c *authClient) ChangeEmail(ctx context.Context, loginData *protos.ChangeEmailRequest) (bool, error) { + var resp protos.EmptyResponse + err := c.webclient.PostPROTOC(ctx, "/users/change_email", nil, loginData, &resp) + if err != nil { + return false, err + } + return true, nil +} + +// CompleteChangeEmail is used to complete the email change process +func (c *authClient) CompleteChangeEmail(ctx context.Context, loginData *protos.CompleteChangeEmailRequest) (bool, error) { + var resp protos.EmptyResponse + err := c.webclient.PostPROTOC(ctx, "/users/change_email/complete/email", nil, loginData, &resp) + if err != nil { + return false, err + } + return true, nil +} + +// DeleteAccount is used to delete the account of a user +// Once account is delete make sure to create new account +func (c *authClient) DeleteAccount(ctx context.Context, accountData *protos.DeleteUserRequest) (bool, error) { + var resp protos.EmptyResponse + err := c.webclient.PostPROTOC(ctx, "/users/delete", nil, accountData, &resp) + if err != nil { + return false, err + } + return true, nil +} + +// DeleteAccount is used to delete the account of a user +// Once account is delete make sure to create new account +func (c *authClient) SignOut(ctx context.Context, logoutData *protos.LogoutRequest) (bool, error) { + var resp protos.EmptyResponse + err := c.webclient.PostPROTOC(ctx, "/users/logout", nil, logoutData, &resp) + if err != nil { + return false, err + } + return true, nil +} diff --git a/internalsdk/common/const.go b/internalsdk/common/const.go index 65798315b..90fd2eec3 100644 --- a/internalsdk/common/const.go +++ b/internalsdk/common/const.go @@ -42,6 +42,8 @@ var ( ProAPIHost = "api.getiantem.org" + V1BaseUrl = "iantem.io/api/v1" + log = golog.LoggerFor("flashlight.common") forceAds bool diff --git a/internalsdk/common/headers.go b/internalsdk/common/headers.go index a050050d3..934888423 100644 --- a/internalsdk/common/headers.go +++ b/internalsdk/common/headers.go @@ -34,8 +34,10 @@ const ( RandomNoiseHeader = "X-Lantern-Rand" SleepHeader = "X-Lantern-Sleep" LocaleHeader = "X-Lantern-Locale" + Referer = "Referer" XBQHeader = "XBQ" XBQHeaderv2 = "XBQv2" + ContentType = "Content-Type" ) var ( diff --git a/internalsdk/common/user_config.go b/internalsdk/common/user_config.go index 9f5ded861..276f2110b 100644 --- a/internalsdk/common/user_config.go +++ b/internalsdk/common/user_config.go @@ -1,35 +1,35 @@ package common import ( - "time" + "time" - "github.com/getlantern/timezone" + "github.com/getlantern/timezone" ) // AuthConfig retrieves any custom info for interacting with internal services. type AuthConfig interface { - GetAppName() string - GetDeviceID() string - GetUserID() int64 - GetToken() string + GetAppName() string + GetDeviceID() string + GetUserID() int64 + GetToken() string } type UserConfig interface { - AuthConfig - GetLanguage() string - GetTimeZone() (string, error) - GetInternalHeaders() map[string]string - GetEnabledExperiments() []string + AuthConfig + GetLanguage() string + GetTimeZone() (string, error) + GetInternalHeaders() map[string]string + GetEnabledExperiments() []string } // an implementation of UserConfig type UserConfigData struct { - AppName string - DeviceID string - UserID int64 - Token string - Language string - Headers map[string]string + AppName string + DeviceID string + UserID int64 + Token string + Language string + Headers map[string]string } func (uc *UserConfigData) GetAppName() string { return uc.AppName } @@ -40,28 +40,28 @@ func (uc *UserConfigData) GetLanguage() string { return uc.Language func (uc *UserConfigData) GetTimeZone() (string, error) { return timezone.IANANameForTime(time.Now()) } func (uc *UserConfigData) GetEnabledExperiments() []string { return nil } func (uc *UserConfigData) GetInternalHeaders() map[string]string { - h := make(map[string]string) - for k, v := range uc.Headers { - h[k] = v - } - return h + h := make(map[string]string) + for k, v := range uc.Headers { + h[k] = v + } + return h } var _ UserConfig = (*UserConfigData)(nil) // NewUserConfig constucts a new UserConfigData with the given options. func NewUserConfig(appName string, deviceID string, userID int64, token string, headers map[string]string, - lang string) *UserConfigData { - uc := &UserConfigData{ - AppName: appName, - DeviceID: deviceID, - UserID: userID, - Token: token, - Language: lang, - Headers: make(map[string]string), - } - for k, v := range headers { - uc.Headers[k] = v - } - return uc + lang string) *UserConfigData { + uc := &UserConfigData{ + AppName: appName, + DeviceID: deviceID, + UserID: userID, + Token: token, + Language: lang, + Headers: make(map[string]string), + } + for k, v := range headers { + uc.Headers[k] = v + } + return uc } diff --git a/internalsdk/pro/pro.go b/internalsdk/pro/pro.go index 09d6ac206..2230cf87f 100644 --- a/internalsdk/pro/pro.go +++ b/internalsdk/pro/pro.go @@ -3,24 +3,25 @@ package pro import ( "context" "encoding/json" + "fmt" "net/http" "strings" "github.com/getlantern/errors" "github.com/getlantern/golog" "github.com/getlantern/lantern-client/internalsdk/common" - "github.com/getlantern/lantern-client/internalsdk/pro/webclient" - "github.com/getlantern/lantern-client/internalsdk/pro/webclient/defaultwebclient" "github.com/getlantern/lantern-client/internalsdk/protos" - + "github.com/getlantern/lantern-client/internalsdk/webclient" + "github.com/getlantern/lantern-client/internalsdk/webclient/defaultwebclient" "github.com/go-resty/resty/v2" + "github.com/leekchan/accounting" "github.com/shopspring/decimal" "google.golang.org/protobuf/encoding/protojson" ) var ( - log = golog.LoggerFor("webclient") + log = golog.LoggerFor("proclient") errMissingDeviceName = errors.New("Missing device name") ) @@ -29,18 +30,8 @@ type proClient struct { webclient webclient.RESTClient } -type Opts struct { - // HttpClient represents an http.Client that should be used by the resty client - HttpClient *http.Client - // UserConfig is a function that returns the user config associated with the Lantern user - UserConfig func() common.UserConfig -} - type ProClient interface { - DeviceRemove(ctx context.Context, deviceId string) (*LinkResponse, error) EmailExists(ctx context.Context, email string) (*protos.BaseResponse, error) - LinkCodeApprove(ctx context.Context, code string) (*protos.BaseResponse, error) - LinkCodeRequest(ctx context.Context, deviceName string) (*LinkCodeResponse, error) PaymentMethods(ctx context.Context) (*PaymentMethodsResponse, error) PaymentMethodsV4(ctx context.Context) (*PaymentMethodsResponse, error) PaymentRedirect(ctx context.Context, req *protos.PaymentRedirectRequest) (*PaymentRedirectResponse, error) @@ -48,10 +39,19 @@ type ProClient interface { RedeemResellerCode(ctx context.Context, req *protos.RedeemResellerCodeRequest) (*protos.BaseResponse, error) UserCreate(ctx context.Context) (*UserDataResponse, error) UserData(ctx context.Context) (*UserDataResponse, error) + PurchaseRequest(ctx context.Context, data map[string]interface{}) (*PurchaseResponse, error) + //Device Linking + LinkCodeApprove(ctx context.Context, code string) (*protos.BaseResponse, error) + LinkCodeRequest(ctx context.Context, deviceName string) (*LinkCodeResponse, error) + LinkCodeRedeem(ctx context.Context, deviceName string, deviceCode string) (*LinkCodeRedeemResponse, error) + UserLinkCodeRequest(ctx context.Context, deviceId string) (bool, error) + UserLinkValidate(ctx context.Context, code string) (*UserRecovery, error) + DeviceRemove(ctx context.Context, deviceId string) (*LinkResponse, error) + DeviceAdd(ctx context.Context, deviceName string) (bool, error) } // NewClient creates a new instance of ProClient -func NewClient(baseURL string, opts *Opts) ProClient { +func NewClient(baseURL string, opts *webclient.Opts) ProClient { httpClient := opts.HttpClient if httpClient == nil { httpClient = &http.Client{} @@ -121,8 +121,6 @@ func (c *proClient) PaymentMethods(ctx context.Context) (*PaymentMethodsResponse if err != nil { return nil, err } - b, _ := json.Marshal(resp) - log.Debugf("PaymentMethods response is %v", string(b)) return &resp, nil } @@ -136,20 +134,25 @@ func (c *proClient) PaymentMethodsV4(ctx context.Context) (*PaymentMethodsRespon if resp.BaseResponse != nil && resp.BaseResponse.Error != "" { return nil, errors.New("error received from server: %v", resp.BaseResponse.Error) } - log.Debugf("PaymentMethods-V4 plans is %v", resp.Plans) + + // process plans for currency for i, plan := range resp.Plans { parts := strings.Split(plan.Id, "-") if len(parts) != 3 { continue } cur := parts[1] - if currency, ok := accounting.LocaleInfo[strings.ToUpper(cur)]; ok { - if oneMonthCost, ok2 := plan.ExpectedMonthlyPrice[strings.ToLower(cur)]; ok2 { - ac := accounting.Accounting{Symbol: currency.ComSymbol, Precision: 2} - amount := decimal.NewFromInt(oneMonthCost).Div(decimal.NewFromInt(100)) - resp.Plans[i].OneMonthCost = ac.FormatMoneyDecimal(amount) - } - } + + currency1 := accounting.LocaleInfo[strings.ToUpper(cur)] + ac := accounting.Accounting{Symbol: currency1.ComSymbol, Precision: 2} + monthlyPrice := plan.ExpectedMonthlyPrice[strings.ToLower(cur)] + yearlyPrice := plan.Price[strings.ToLower(cur)] + + amount := decimal.NewFromInt(monthlyPrice).Div(decimal.NewFromInt(100)) + yearAmount := decimal.NewFromInt(yearlyPrice).Div(decimal.NewFromInt(100)) + resp.Plans[i].OneMonthCost = ac.FormatMoneyDecimal(amount) + resp.Plans[i].TotalCost = ac.FormatMoneyDecimal(yearAmount) + resp.Plans[i].TotalCostBilledOneTime = fmt.Sprintf("%v billed one time", ac.FormatMoneyDecimal(yearAmount)) } return &resp, nil } @@ -185,6 +188,9 @@ func (c *proClient) UserCreate(ctx context.Context) (*UserDataResponse, error) { if err != nil { return nil, errors.New("error fetching user data: %v", err) } + if resp.BaseResponse != nil && resp.BaseResponse.Error != "" { + return nil, errors.New("error received: %v", resp.BaseResponse.Error) + } log.Debugf("UserCreate response is %v", resp) return &resp, nil } @@ -206,7 +212,9 @@ func (c *proClient) RedeemResellerCode(ctx context.Context, req *protos.RedeemRe log.Errorf("Failed to redeem reseller code: %v", err) return nil, err } - + if resp.Error != "" { + return nil, errors.New("error redeeming reseller code: %v", resp.Error) + } return &resp, nil } @@ -222,6 +230,22 @@ func (c *proClient) DeviceRemove(ctx context.Context, deviceId string) (*LinkRes return &resp, nil } +// DeviceAdd adds a device with the given name to a user's Pro account +// This get calles when user login to attech device +func (c *proClient) DeviceAdd(ctx context.Context, deviceName string) (bool, error) { + var resp protos.BaseResponse + params := c.defaultParams() + params["deviceName"] = deviceName + err := c.webclient.PostJSONReadingJSON(ctx, "/device-add", params, nil, &resp) + if err != nil { + return false, err + } + if resp.Error != "" && resp.Status != "ok" { + return false, errors.New("%v adding device: %v", resp.ErrorId, resp.Error) + } + return true, nil +} + // LinkCodeApprove is used to approve a code to link a device to an existing Pro account func (c *proClient) LinkCodeApprove(ctx context.Context, code string) (*protos.BaseResponse, error) { var resp protos.BaseResponse @@ -231,6 +255,10 @@ func (c *proClient) LinkCodeApprove(ctx context.Context, code string) (*protos.B if err != nil { return nil, err } + if resp.Error != "" && resp.Status != "ok" { + return nil, errors.New("%v approving link code: %v", resp.ErrorId, resp.Error) + } + return &resp, nil } @@ -248,7 +276,64 @@ func (c *proClient) LinkCodeRequest(ctx context.Context, deviceName string) (*Li if err != nil { return nil, err } - b, _ := json.Marshal(resp) - log.Debugf("LinkCodeResponse is %s", string(b)) + return &resp, nil +} + +// LinkCodeRequest returns a code that can be used to link a device to an existing Pro account +func (c *proClient) LinkCodeRedeem(ctx context.Context, deviceName string, deviceCode string) (*LinkCodeRedeemResponse, error) { + var resp LinkCodeRedeemResponse + err := c.webclient.PostJSONReadingJSON(ctx, "/link-code-redeem", map[string]interface{}{ + "deviceName": deviceName, + "code": deviceCode, + }, nil, &resp) + if err != nil { + return nil, err + } + if resp.BaseResponse != nil && resp.Status != "ok" { + return nil, errors.New("%v redeeming link code: %v", resp.ErrorId, resp.Error) + } + return &resp, nil +} + +// UserLinkCodeRequest returns a code to email register pro account email that can be used to link device to an existing Pro account +func (c *proClient) UserLinkCodeRequest(ctx context.Context, deviceId string) (bool, error) { + if deviceId == "" { + return false, errMissingDeviceName + } + var resp LinkCodeResponse + uc := c.userConfig() + err := c.webclient.PostJSONReadingJSON(ctx, "/user-link-request", map[string]interface{}{ + "deviceName": deviceId, + "locale": uc.GetLanguage(), + }, nil, &resp) + if err != nil { + return false, err + } + + return true, nil +} + +// UserLinkCodeRequest returns a code to email register pro account email that can be used to link device to an existing Pro account +func (c *proClient) UserLinkValidate(ctx context.Context, code string) (*UserRecovery, error) { + var resp UserRecovery + uc := c.userConfig() + err := c.webclient.PostJSONReadingJSON(ctx, "/user-link-validate", map[string]interface{}{ + "code": code, + "locale": uc.GetLanguage(), + }, nil, &resp) + if err != nil { + return nil, err + } + + return &resp, nil +} + +// PurchaseRequest is used to request a purchase of a Pro plan is will be used for all most all the payment providers +func (c *proClient) PurchaseRequest(ctx context.Context, data map[string]interface{}) (*PurchaseResponse, error) { + var resp PurchaseResponse + err := c.webclient.PostJSONReadingJSON(ctx, "/purchase", data, nil, &resp) + if err != nil { + return nil, err + } return &resp, nil } diff --git a/internalsdk/pro/pro_test.go b/internalsdk/pro/pro_test.go deleted file mode 100644 index c9eb12244..000000000 --- a/internalsdk/pro/pro_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package pro - -import ( - "context" - "net/http" - "testing" - - "github.com/getlantern/golog" - "github.com/getlantern/lantern-client/internalsdk/common" - "github.com/stretchr/testify/assert" -) - -func TestClient(t *testing.T) { - log := golog.LoggerFor("pro-http-test") - client := NewClient("https://api.getiantem.org", &Opts{ - // Just use the default transport since otherwise test setup is difficult. - // This means it does not actually touch the proxying code, but that should - // be tested separately. - HttpClient: &http.Client{}, - UserConfig: func() common.UserConfig { - return common.NewUserConfig( - "Lantern", - "device123", // deviceID - 123, // userID - "token", // token - nil, - "en", // language - ) - }, - }) - res, e := client.Plans(context.Background()) - if !assert.NoError(t, e) { - return - } - log.Debugf("Got response: %v", res) - assert.NotNil(t, res) -} diff --git a/internalsdk/pro/responses.go b/internalsdk/pro/responses.go index 38c671e02..b5c75b015 100644 --- a/internalsdk/pro/responses.go +++ b/internalsdk/pro/responses.go @@ -37,3 +37,21 @@ type LinkCodeResponse struct { Code string ExpireAt int64 } +type LinkCodeRedeemResponse struct { + *protos.BaseResponse `json:",inline"` + Status string `json:"status"` + UserID int64 `json:"userId"` + Token string `json:"token"` +} + +type PurchaseResponse struct { + PaymentStatus string `json:"paymentStatus"` + Plan protos.Plan `json:"plan"` + Status string `json:"status"` +} + +type UserRecovery struct { + Status string `json:"status"` + UserID int64 `json:"userID"` + Token string `json:"token"` +} diff --git a/internalsdk/protos/auth.pb.go b/internalsdk/protos/auth.pb.go new file mode 100644 index 000000000..ce391939f --- /dev/null +++ b/internalsdk/protos/auth.pb.go @@ -0,0 +1,1780 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.33.0-devel +// protoc v4.23.4 +// source: protos_shared/auth.proto + +package protos + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// POST /users/signup +type SignupRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` + Salt []byte `protobuf:"bytes,2,opt,name=salt,proto3" json:"salt,omitempty"` + Verifier []byte `protobuf:"bytes,3,opt,name=verifier,proto3" json:"verifier,omitempty"` + SkipEmailConfirmation bool `protobuf:"varint,4,opt,name=skip_email_confirmation,json=skipEmailConfirmation,proto3" json:"skip_email_confirmation,omitempty"` +} + +func (x *SignupRequest) Reset() { + *x = SignupRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_shared_auth_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SignupRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SignupRequest) ProtoMessage() {} + +func (x *SignupRequest) ProtoReflect() protoreflect.Message { + mi := &file_protos_shared_auth_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SignupRequest.ProtoReflect.Descriptor instead. +func (*SignupRequest) Descriptor() ([]byte, []int) { + return file_protos_shared_auth_proto_rawDescGZIP(), []int{0} +} + +func (x *SignupRequest) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *SignupRequest) GetSalt() []byte { + if x != nil { + return x.Salt + } + return nil +} + +func (x *SignupRequest) GetVerifier() []byte { + if x != nil { + return x.Verifier + } + return nil +} + +func (x *SignupRequest) GetSkipEmailConfirmation() bool { + if x != nil { + return x.SkipEmailConfirmation + } + return false +} + +type EmptyResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *EmptyResponse) Reset() { + *x = EmptyResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_shared_auth_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EmptyResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EmptyResponse) ProtoMessage() {} + +func (x *EmptyResponse) ProtoReflect() protoreflect.Message { + mi := &file_protos_shared_auth_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EmptyResponse.ProtoReflect.Descriptor instead. +func (*EmptyResponse) Descriptor() ([]byte, []int) { + return file_protos_shared_auth_proto_rawDescGZIP(), []int{1} +} + +// POST /users/signup/resend/email +type SignupEmailResendRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` + Salt []byte `protobuf:"bytes,2,opt,name=salt,proto3" json:"salt,omitempty"` +} + +func (x *SignupEmailResendRequest) Reset() { + *x = SignupEmailResendRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_shared_auth_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SignupEmailResendRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SignupEmailResendRequest) ProtoMessage() {} + +func (x *SignupEmailResendRequest) ProtoReflect() protoreflect.Message { + mi := &file_protos_shared_auth_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SignupEmailResendRequest.ProtoReflect.Descriptor instead. +func (*SignupEmailResendRequest) Descriptor() ([]byte, []int) { + return file_protos_shared_auth_proto_rawDescGZIP(), []int{2} +} + +func (x *SignupEmailResendRequest) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *SignupEmailResendRequest) GetSalt() []byte { + if x != nil { + return x.Salt + } + return nil +} + +// POST /users/signup/complete/email +type ConfirmSignupRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` + Code string `protobuf:"bytes,2,opt,name=code,proto3" json:"code,omitempty"` +} + +func (x *ConfirmSignupRequest) Reset() { + *x = ConfirmSignupRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_shared_auth_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ConfirmSignupRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConfirmSignupRequest) ProtoMessage() {} + +func (x *ConfirmSignupRequest) ProtoReflect() protoreflect.Message { + mi := &file_protos_shared_auth_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConfirmSignupRequest.ProtoReflect.Descriptor instead. +func (*ConfirmSignupRequest) Descriptor() ([]byte, []int) { + return file_protos_shared_auth_proto_rawDescGZIP(), []int{3} +} + +func (x *ConfirmSignupRequest) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *ConfirmSignupRequest) GetCode() string { + if x != nil { + return x.Code + } + return "" +} + +// GET /users/salt +type GetSaltResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Salt []byte `protobuf:"bytes,1,opt,name=salt,proto3" json:"salt,omitempty"` +} + +func (x *GetSaltResponse) Reset() { + *x = GetSaltResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_shared_auth_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetSaltResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetSaltResponse) ProtoMessage() {} + +func (x *GetSaltResponse) ProtoReflect() protoreflect.Message { + mi := &file_protos_shared_auth_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetSaltResponse.ProtoReflect.Descriptor instead. +func (*GetSaltResponse) Descriptor() ([]byte, []int) { + return file_protos_shared_auth_proto_rawDescGZIP(), []int{4} +} + +func (x *GetSaltResponse) GetSalt() []byte { + if x != nil { + return x.Salt + } + return nil +} + +// POST /users/prepare +type PrepareRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` + A []byte `protobuf:"bytes,2,opt,name=a,proto3" json:"a,omitempty"` +} + +func (x *PrepareRequest) Reset() { + *x = PrepareRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_shared_auth_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PrepareRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PrepareRequest) ProtoMessage() {} + +func (x *PrepareRequest) ProtoReflect() protoreflect.Message { + mi := &file_protos_shared_auth_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PrepareRequest.ProtoReflect.Descriptor instead. +func (*PrepareRequest) Descriptor() ([]byte, []int) { + return file_protos_shared_auth_proto_rawDescGZIP(), []int{5} +} + +func (x *PrepareRequest) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *PrepareRequest) GetA() []byte { + if x != nil { + return x.A + } + return nil +} + +type PrepareResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + B []byte `protobuf:"bytes,1,opt,name=b,proto3" json:"b,omitempty"` + Proof []byte `protobuf:"bytes,2,opt,name=proof,proto3" json:"proof,omitempty"` +} + +func (x *PrepareResponse) Reset() { + *x = PrepareResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_shared_auth_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PrepareResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PrepareResponse) ProtoMessage() {} + +func (x *PrepareResponse) ProtoReflect() protoreflect.Message { + mi := &file_protos_shared_auth_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PrepareResponse.ProtoReflect.Descriptor instead. +func (*PrepareResponse) Descriptor() ([]byte, []int) { + return file_protos_shared_auth_proto_rawDescGZIP(), []int{6} +} + +func (x *PrepareResponse) GetB() []byte { + if x != nil { + return x.B + } + return nil +} + +func (x *PrepareResponse) GetProof() []byte { + if x != nil { + return x.Proof + } + return nil +} + +// POST /users/login +type LoginRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` + Proof []byte `protobuf:"bytes,2,opt,name=proof,proto3" json:"proof,omitempty"` + DeviceId string `protobuf:"bytes,3,opt,name=deviceId,proto3" json:"deviceId,omitempty"` +} + +func (x *LoginRequest) Reset() { + *x = LoginRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_shared_auth_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LoginRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LoginRequest) ProtoMessage() {} + +func (x *LoginRequest) ProtoReflect() protoreflect.Message { + mi := &file_protos_shared_auth_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LoginRequest.ProtoReflect.Descriptor instead. +func (*LoginRequest) Descriptor() ([]byte, []int) { + return file_protos_shared_auth_proto_rawDescGZIP(), []int{7} +} + +func (x *LoginRequest) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *LoginRequest) GetProof() []byte { + if x != nil { + return x.Proof + } + return nil +} + +func (x *LoginRequest) GetDeviceId() string { + if x != nil { + return x.DeviceId + } + return "" +} + +type LoginResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + LegacyID int64 `protobuf:"varint,1,opt,name=legacyID,proto3" json:"legacyID,omitempty"` + LegacyToken string `protobuf:"bytes,2,opt,name=legacyToken,proto3" json:"legacyToken,omitempty"` + Id string `protobuf:"bytes,3,opt,name=id,proto3" json:"id,omitempty"` + EmailConfirmed bool `protobuf:"varint,4,opt,name=emailConfirmed,proto3" json:"emailConfirmed,omitempty"` + Success bool `protobuf:"varint,5,opt,name=Success,proto3" json:"Success,omitempty"` + // this maps to /user-data call in pro-server and is returned only on successful login + LegacyUserData *LoginResponse_UserData `protobuf:"bytes,6,opt,name=legacyUserData,proto3" json:"legacyUserData,omitempty"` + // list of current user devices. returned only on successful login that is blocked by 'too many devices' + Devices []*LoginResponse_Device `protobuf:"bytes,7,rep,name=devices,proto3" json:"devices,omitempty"` +} + +func (x *LoginResponse) Reset() { + *x = LoginResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_shared_auth_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LoginResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LoginResponse) ProtoMessage() {} + +func (x *LoginResponse) ProtoReflect() protoreflect.Message { + mi := &file_protos_shared_auth_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LoginResponse.ProtoReflect.Descriptor instead. +func (*LoginResponse) Descriptor() ([]byte, []int) { + return file_protos_shared_auth_proto_rawDescGZIP(), []int{8} +} + +func (x *LoginResponse) GetLegacyID() int64 { + if x != nil { + return x.LegacyID + } + return 0 +} + +func (x *LoginResponse) GetLegacyToken() string { + if x != nil { + return x.LegacyToken + } + return "" +} + +func (x *LoginResponse) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *LoginResponse) GetEmailConfirmed() bool { + if x != nil { + return x.EmailConfirmed + } + return false +} + +func (x *LoginResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +func (x *LoginResponse) GetLegacyUserData() *LoginResponse_UserData { + if x != nil { + return x.LegacyUserData + } + return nil +} + +func (x *LoginResponse) GetDevices() []*LoginResponse_Device { + if x != nil { + return x.Devices + } + return nil +} + +// POST /users/recovery/start/email +type StartRecoveryByEmailRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` +} + +func (x *StartRecoveryByEmailRequest) Reset() { + *x = StartRecoveryByEmailRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_shared_auth_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StartRecoveryByEmailRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StartRecoveryByEmailRequest) ProtoMessage() {} + +func (x *StartRecoveryByEmailRequest) ProtoReflect() protoreflect.Message { + mi := &file_protos_shared_auth_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StartRecoveryByEmailRequest.ProtoReflect.Descriptor instead. +func (*StartRecoveryByEmailRequest) Descriptor() ([]byte, []int) { + return file_protos_shared_auth_proto_rawDescGZIP(), []int{9} +} + +func (x *StartRecoveryByEmailRequest) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +// POST /users/recovery/complete/email +type CompleteRecoveryByEmailRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` + Code string `protobuf:"bytes,2,opt,name=code,proto3" json:"code,omitempty"` + NewSalt []byte `protobuf:"bytes,3,opt,name=new_salt,json=newSalt,proto3" json:"new_salt,omitempty"` + NewVerifier []byte `protobuf:"bytes,4,opt,name=new_verifier,json=newVerifier,proto3" json:"new_verifier,omitempty"` +} + +func (x *CompleteRecoveryByEmailRequest) Reset() { + *x = CompleteRecoveryByEmailRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_shared_auth_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CompleteRecoveryByEmailRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CompleteRecoveryByEmailRequest) ProtoMessage() {} + +func (x *CompleteRecoveryByEmailRequest) ProtoReflect() protoreflect.Message { + mi := &file_protos_shared_auth_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CompleteRecoveryByEmailRequest.ProtoReflect.Descriptor instead. +func (*CompleteRecoveryByEmailRequest) Descriptor() ([]byte, []int) { + return file_protos_shared_auth_proto_rawDescGZIP(), []int{10} +} + +func (x *CompleteRecoveryByEmailRequest) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *CompleteRecoveryByEmailRequest) GetCode() string { + if x != nil { + return x.Code + } + return "" +} + +func (x *CompleteRecoveryByEmailRequest) GetNewSalt() []byte { + if x != nil { + return x.NewSalt + } + return nil +} + +func (x *CompleteRecoveryByEmailRequest) GetNewVerifier() []byte { + if x != nil { + return x.NewVerifier + } + return nil +} + +// POST /users/change_email +type ChangeEmailRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + OldEmail string `protobuf:"bytes,1,opt,name=old_email,json=oldEmail,proto3" json:"old_email,omitempty"` + NewEmail string `protobuf:"bytes,2,opt,name=new_email,json=newEmail,proto3" json:"new_email,omitempty"` + Proof []byte `protobuf:"bytes,3,opt,name=proof,proto3" json:"proof,omitempty"` +} + +func (x *ChangeEmailRequest) Reset() { + *x = ChangeEmailRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_shared_auth_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ChangeEmailRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ChangeEmailRequest) ProtoMessage() {} + +func (x *ChangeEmailRequest) ProtoReflect() protoreflect.Message { + mi := &file_protos_shared_auth_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ChangeEmailRequest.ProtoReflect.Descriptor instead. +func (*ChangeEmailRequest) Descriptor() ([]byte, []int) { + return file_protos_shared_auth_proto_rawDescGZIP(), []int{11} +} + +func (x *ChangeEmailRequest) GetOldEmail() string { + if x != nil { + return x.OldEmail + } + return "" +} + +func (x *ChangeEmailRequest) GetNewEmail() string { + if x != nil { + return x.NewEmail + } + return "" +} + +func (x *ChangeEmailRequest) GetProof() []byte { + if x != nil { + return x.Proof + } + return nil +} + +// POST /users/change_email/complete/email +type CompleteChangeEmailRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + OldEmail string `protobuf:"bytes,1,opt,name=old_email,json=oldEmail,proto3" json:"old_email,omitempty"` + NewEmail string `protobuf:"bytes,2,opt,name=new_email,json=newEmail,proto3" json:"new_email,omitempty"` + Code string `protobuf:"bytes,3,opt,name=code,proto3" json:"code,omitempty"` + NewSalt []byte `protobuf:"bytes,4,opt,name=new_salt,json=newSalt,proto3" json:"new_salt,omitempty"` + NewVerifier []byte `protobuf:"bytes,5,opt,name=new_verifier,json=newVerifier,proto3" json:"new_verifier,omitempty"` +} + +func (x *CompleteChangeEmailRequest) Reset() { + *x = CompleteChangeEmailRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_shared_auth_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CompleteChangeEmailRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CompleteChangeEmailRequest) ProtoMessage() {} + +func (x *CompleteChangeEmailRequest) ProtoReflect() protoreflect.Message { + mi := &file_protos_shared_auth_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CompleteChangeEmailRequest.ProtoReflect.Descriptor instead. +func (*CompleteChangeEmailRequest) Descriptor() ([]byte, []int) { + return file_protos_shared_auth_proto_rawDescGZIP(), []int{12} +} + +func (x *CompleteChangeEmailRequest) GetOldEmail() string { + if x != nil { + return x.OldEmail + } + return "" +} + +func (x *CompleteChangeEmailRequest) GetNewEmail() string { + if x != nil { + return x.NewEmail + } + return "" +} + +func (x *CompleteChangeEmailRequest) GetCode() string { + if x != nil { + return x.Code + } + return "" +} + +func (x *CompleteChangeEmailRequest) GetNewSalt() []byte { + if x != nil { + return x.NewSalt + } + return nil +} + +func (x *CompleteChangeEmailRequest) GetNewVerifier() []byte { + if x != nil { + return x.NewVerifier + } + return nil +} + +// POST /users/delete +type DeleteUserRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` + Permanent bool `protobuf:"varint,2,opt,name=permanent,proto3" json:"permanent,omitempty"` + Proof []byte `protobuf:"bytes,3,opt,name=proof,proto3" json:"proof,omitempty"` + DeviceId string `protobuf:"bytes,4,opt,name=deviceId,proto3" json:"deviceId,omitempty"` +} + +func (x *DeleteUserRequest) Reset() { + *x = DeleteUserRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_shared_auth_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteUserRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteUserRequest) ProtoMessage() {} + +func (x *DeleteUserRequest) ProtoReflect() protoreflect.Message { + mi := &file_protos_shared_auth_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteUserRequest.ProtoReflect.Descriptor instead. +func (*DeleteUserRequest) Descriptor() ([]byte, []int) { + return file_protos_shared_auth_proto_rawDescGZIP(), []int{13} +} + +func (x *DeleteUserRequest) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *DeleteUserRequest) GetPermanent() bool { + if x != nil { + return x.Permanent + } + return false +} + +func (x *DeleteUserRequest) GetProof() []byte { + if x != nil { + return x.Proof + } + return nil +} + +func (x *DeleteUserRequest) GetDeviceId() string { + if x != nil { + return x.DeviceId + } + return "" +} + +// POST /users/recovery/validate/email +type ValidateRecoveryCodeRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` + Code string `protobuf:"bytes,2,opt,name=code,proto3" json:"code,omitempty"` +} + +func (x *ValidateRecoveryCodeRequest) Reset() { + *x = ValidateRecoveryCodeRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_shared_auth_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ValidateRecoveryCodeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ValidateRecoveryCodeRequest) ProtoMessage() {} + +func (x *ValidateRecoveryCodeRequest) ProtoReflect() protoreflect.Message { + mi := &file_protos_shared_auth_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ValidateRecoveryCodeRequest.ProtoReflect.Descriptor instead. +func (*ValidateRecoveryCodeRequest) Descriptor() ([]byte, []int) { + return file_protos_shared_auth_proto_rawDescGZIP(), []int{14} +} + +func (x *ValidateRecoveryCodeRequest) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *ValidateRecoveryCodeRequest) GetCode() string { + if x != nil { + return x.Code + } + return "" +} + +type ValidateRecoveryCodeResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Valid bool `protobuf:"varint,1,opt,name=valid,proto3" json:"valid,omitempty"` +} + +func (x *ValidateRecoveryCodeResponse) Reset() { + *x = ValidateRecoveryCodeResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_shared_auth_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ValidateRecoveryCodeResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ValidateRecoveryCodeResponse) ProtoMessage() {} + +func (x *ValidateRecoveryCodeResponse) ProtoReflect() protoreflect.Message { + mi := &file_protos_shared_auth_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ValidateRecoveryCodeResponse.ProtoReflect.Descriptor instead. +func (*ValidateRecoveryCodeResponse) Descriptor() ([]byte, []int) { + return file_protos_shared_auth_proto_rawDescGZIP(), []int{15} +} + +func (x *ValidateRecoveryCodeResponse) GetValid() bool { + if x != nil { + return x.Valid + } + return false +} + +// POST /users/logout +type LogoutRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` + DeviceId string `protobuf:"bytes,2,opt,name=deviceId,proto3" json:"deviceId,omitempty"` + LegacyUserID int64 `protobuf:"varint,3,opt,name=legacyUserID,proto3" json:"legacyUserID,omitempty"` + LegacyToken string `protobuf:"bytes,4,opt,name=legacyToken,proto3" json:"legacyToken,omitempty"` +} + +func (x *LogoutRequest) Reset() { + *x = LogoutRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_shared_auth_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LogoutRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LogoutRequest) ProtoMessage() {} + +func (x *LogoutRequest) ProtoReflect() protoreflect.Message { + mi := &file_protos_shared_auth_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LogoutRequest.ProtoReflect.Descriptor instead. +func (*LogoutRequest) Descriptor() ([]byte, []int) { + return file_protos_shared_auth_proto_rawDescGZIP(), []int{16} +} + +func (x *LogoutRequest) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *LogoutRequest) GetDeviceId() string { + if x != nil { + return x.DeviceId + } + return "" +} + +func (x *LogoutRequest) GetLegacyUserID() int64 { + if x != nil { + return x.LegacyUserID + } + return 0 +} + +func (x *LogoutRequest) GetLegacyToken() string { + if x != nil { + return x.LegacyToken + } + return "" +} + +type LoginResponse_Device struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Created int64 `protobuf:"varint,3,opt,name=created,proto3" json:"created,omitempty"` +} + +func (x *LoginResponse_Device) Reset() { + *x = LoginResponse_Device{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_shared_auth_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LoginResponse_Device) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LoginResponse_Device) ProtoMessage() {} + +func (x *LoginResponse_Device) ProtoReflect() protoreflect.Message { + mi := &file_protos_shared_auth_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LoginResponse_Device.ProtoReflect.Descriptor instead. +func (*LoginResponse_Device) Descriptor() ([]byte, []int) { + return file_protos_shared_auth_proto_rawDescGZIP(), []int{8, 0} +} + +func (x *LoginResponse_Device) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *LoginResponse_Device) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *LoginResponse_Device) GetCreated() int64 { + if x != nil { + return x.Created + } + return 0 +} + +type LoginResponse_UserData struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId int64 `protobuf:"varint,1,opt,name=userId,proto3" json:"userId,omitempty"` + Code string `protobuf:"bytes,2,opt,name=code,proto3" json:"code,omitempty"` + Token string `protobuf:"bytes,3,opt,name=token,proto3" json:"token,omitempty"` + Referral string `protobuf:"bytes,4,opt,name=referral,proto3" json:"referral,omitempty"` + Phone string `protobuf:"bytes,5,opt,name=phone,proto3" json:"phone,omitempty"` + Email string `protobuf:"bytes,6,opt,name=email,proto3" json:"email,omitempty"` + UserStatus string `protobuf:"bytes,7,opt,name=userStatus,proto3" json:"userStatus,omitempty"` + UserLevel string `protobuf:"bytes,8,opt,name=userLevel,proto3" json:"userLevel,omitempty"` + Locale string `protobuf:"bytes,9,opt,name=locale,proto3" json:"locale,omitempty"` + Expiration int64 `protobuf:"varint,10,opt,name=expiration,proto3" json:"expiration,omitempty"` + Servers []string `protobuf:"bytes,11,rep,name=servers,proto3" json:"servers,omitempty"` + Subscription string `protobuf:"bytes,12,opt,name=subscription,proto3" json:"subscription,omitempty"` + Purchases []string `protobuf:"bytes,13,rep,name=purchases,proto3" json:"purchases,omitempty"` + BonusDays string `protobuf:"bytes,14,opt,name=bonusDays,proto3" json:"bonusDays,omitempty"` + BonusMonths string `protobuf:"bytes,15,opt,name=bonusMonths,proto3" json:"bonusMonths,omitempty"` + Inviters []string `protobuf:"bytes,16,rep,name=inviters,proto3" json:"inviters,omitempty"` + Invitees []string `protobuf:"bytes,17,rep,name=invitees,proto3" json:"invitees,omitempty"` + Devices []*LoginResponse_Device `protobuf:"bytes,18,rep,name=devices,proto3" json:"devices,omitempty"` + YinbiEnabled bool `protobuf:"varint,19,opt,name=yinbiEnabled,proto3" json:"yinbiEnabled,omitempty"` +} + +func (x *LoginResponse_UserData) Reset() { + *x = LoginResponse_UserData{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_shared_auth_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LoginResponse_UserData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LoginResponse_UserData) ProtoMessage() {} + +func (x *LoginResponse_UserData) ProtoReflect() protoreflect.Message { + mi := &file_protos_shared_auth_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LoginResponse_UserData.ProtoReflect.Descriptor instead. +func (*LoginResponse_UserData) Descriptor() ([]byte, []int) { + return file_protos_shared_auth_proto_rawDescGZIP(), []int{8, 1} +} + +func (x *LoginResponse_UserData) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *LoginResponse_UserData) GetCode() string { + if x != nil { + return x.Code + } + return "" +} + +func (x *LoginResponse_UserData) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +func (x *LoginResponse_UserData) GetReferral() string { + if x != nil { + return x.Referral + } + return "" +} + +func (x *LoginResponse_UserData) GetPhone() string { + if x != nil { + return x.Phone + } + return "" +} + +func (x *LoginResponse_UserData) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *LoginResponse_UserData) GetUserStatus() string { + if x != nil { + return x.UserStatus + } + return "" +} + +func (x *LoginResponse_UserData) GetUserLevel() string { + if x != nil { + return x.UserLevel + } + return "" +} + +func (x *LoginResponse_UserData) GetLocale() string { + if x != nil { + return x.Locale + } + return "" +} + +func (x *LoginResponse_UserData) GetExpiration() int64 { + if x != nil { + return x.Expiration + } + return 0 +} + +func (x *LoginResponse_UserData) GetServers() []string { + if x != nil { + return x.Servers + } + return nil +} + +func (x *LoginResponse_UserData) GetSubscription() string { + if x != nil { + return x.Subscription + } + return "" +} + +func (x *LoginResponse_UserData) GetPurchases() []string { + if x != nil { + return x.Purchases + } + return nil +} + +func (x *LoginResponse_UserData) GetBonusDays() string { + if x != nil { + return x.BonusDays + } + return "" +} + +func (x *LoginResponse_UserData) GetBonusMonths() string { + if x != nil { + return x.BonusMonths + } + return "" +} + +func (x *LoginResponse_UserData) GetInviters() []string { + if x != nil { + return x.Inviters + } + return nil +} + +func (x *LoginResponse_UserData) GetInvitees() []string { + if x != nil { + return x.Invitees + } + return nil +} + +func (x *LoginResponse_UserData) GetDevices() []*LoginResponse_Device { + if x != nil { + return x.Devices + } + return nil +} + +func (x *LoginResponse_UserData) GetYinbiEnabled() bool { + if x != nil { + return x.YinbiEnabled + } + return false +} + +var File_protos_shared_auth_proto protoreflect.FileDescriptor + +var file_protos_shared_auth_proto_rawDesc = []byte{ + 0x0a, 0x18, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x5f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2f, + 0x61, 0x75, 0x74, 0x68, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x8d, 0x01, 0x0a, 0x0d, 0x53, + 0x69, 0x67, 0x6e, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, + 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, + 0x69, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x61, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x04, 0x73, 0x61, 0x6c, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, + 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, + 0x65, 0x72, 0x12, 0x36, 0x0a, 0x17, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, + 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x15, 0x73, 0x6b, 0x69, 0x70, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x0f, 0x0a, 0x0d, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x44, 0x0a, 0x18, 0x53, + 0x69, 0x67, 0x6e, 0x75, 0x70, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x73, 0x65, 0x6e, 0x64, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x12, 0x0a, + 0x04, 0x73, 0x61, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x73, 0x61, 0x6c, + 0x74, 0x22, 0x40, 0x0a, 0x14, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x53, 0x69, 0x67, 0x6e, + 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, + 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, + 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, + 0x6f, 0x64, 0x65, 0x22, 0x25, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x53, 0x61, 0x6c, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x61, 0x6c, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x73, 0x61, 0x6c, 0x74, 0x22, 0x34, 0x0a, 0x0e, 0x50, 0x72, + 0x65, 0x70, 0x61, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, + 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, + 0x69, 0x6c, 0x12, 0x0c, 0x0a, 0x01, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x01, 0x61, + 0x22, 0x35, 0x0a, 0x0f, 0x50, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x0c, 0x0a, 0x01, 0x62, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x01, + 0x62, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x05, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x22, 0x56, 0x0a, 0x0c, 0x4c, 0x6f, 0x67, 0x69, 0x6e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x14, 0x0a, + 0x05, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x70, 0x72, + 0x6f, 0x6f, 0x66, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x22, + 0x8f, 0x07, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x49, 0x44, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x08, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x49, 0x44, 0x12, 0x20, 0x0a, + 0x0b, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, + 0x26, 0x0a, 0x0e, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, + 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x53, 0x75, 0x63, 0x63, 0x65, + 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, + 0x73, 0x12, 0x3f, 0x0a, 0x0e, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x55, 0x73, 0x65, 0x72, 0x44, + 0x61, 0x74, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x4c, 0x6f, 0x67, 0x69, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x44, 0x61, + 0x74, 0x61, 0x52, 0x0e, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x55, 0x73, 0x65, 0x72, 0x44, 0x61, + 0x74, 0x61, 0x12, 0x2f, 0x0a, 0x07, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x07, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x07, 0x64, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x73, 0x1a, 0x46, 0x0a, 0x06, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x1a, 0xb3, 0x04, 0x0a, 0x08, + 0x55, 0x73, 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, + 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x63, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, + 0x66, 0x65, 0x72, 0x72, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, + 0x66, 0x65, 0x72, 0x72, 0x61, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x12, 0x14, 0x0a, 0x05, + 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, + 0x69, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, + 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x65, 0x78, + 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x75, 0x72, 0x63, 0x68, 0x61, + 0x73, 0x65, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x70, 0x75, 0x72, 0x63, 0x68, + 0x61, 0x73, 0x65, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x62, 0x6f, 0x6e, 0x75, 0x73, 0x44, 0x61, 0x79, + 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x6f, 0x6e, 0x75, 0x73, 0x44, 0x61, + 0x79, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x62, 0x6f, 0x6e, 0x75, 0x73, 0x4d, 0x6f, 0x6e, 0x74, 0x68, + 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x62, 0x6f, 0x6e, 0x75, 0x73, 0x4d, 0x6f, + 0x6e, 0x74, 0x68, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x72, 0x73, + 0x18, 0x10, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x72, 0x73, + 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x65, 0x73, 0x18, 0x11, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x08, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x65, 0x73, 0x12, 0x2f, 0x0a, 0x07, + 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x12, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, + 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x44, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x52, 0x07, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x22, 0x0a, + 0x0c, 0x79, 0x69, 0x6e, 0x62, 0x69, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x13, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x0c, 0x79, 0x69, 0x6e, 0x62, 0x69, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x64, 0x22, 0x33, 0x0a, 0x1b, 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, + 0x72, 0x79, 0x42, 0x79, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0x88, 0x01, 0x0a, 0x1e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, + 0x65, 0x74, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x42, 0x79, 0x45, 0x6d, 0x61, + 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, + 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, + 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, + 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x65, 0x77, 0x5f, 0x73, 0x61, 0x6c, 0x74, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6e, 0x65, 0x77, 0x53, 0x61, 0x6c, 0x74, 0x12, 0x21, + 0x0a, 0x0c, 0x6e, 0x65, 0x77, 0x5f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6e, 0x65, 0x77, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, + 0x72, 0x22, 0x64, 0x0a, 0x12, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x45, 0x6d, 0x61, 0x69, 0x6c, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x6c, 0x64, 0x5f, 0x65, + 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x6c, 0x64, 0x45, + 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x65, 0x77, 0x5f, 0x65, 0x6d, 0x61, 0x69, + 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x65, 0x77, 0x45, 0x6d, 0x61, 0x69, + 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x05, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x22, 0xa8, 0x01, 0x0a, 0x1a, 0x43, 0x6f, 0x6d, 0x70, + 0x6c, 0x65, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x6c, 0x64, 0x5f, 0x65, 0x6d, + 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x6c, 0x64, 0x45, 0x6d, + 0x61, 0x69, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x65, 0x77, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x65, 0x77, 0x45, 0x6d, 0x61, 0x69, 0x6c, + 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x63, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x65, 0x77, 0x5f, 0x73, 0x61, 0x6c, 0x74, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6e, 0x65, 0x77, 0x53, 0x61, 0x6c, 0x74, 0x12, + 0x21, 0x0a, 0x0c, 0x6e, 0x65, 0x77, 0x5f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6e, 0x65, 0x77, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, + 0x65, 0x72, 0x22, 0x79, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1c, 0x0a, + 0x09, 0x70, 0x65, 0x72, 0x6d, 0x61, 0x6e, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x09, 0x70, 0x65, 0x72, 0x6d, 0x61, 0x6e, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x70, + 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x70, 0x72, 0x6f, 0x6f, + 0x66, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x22, 0x47, 0x0a, + 0x1b, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, + 0x79, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, + 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, + 0x69, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x34, 0x0a, 0x1c, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, + 0x74, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x22, 0x87, 0x01, 0x0a, + 0x0d, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, + 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, + 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, + 0x12, 0x22, 0x0a, 0x0c, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x55, 0x73, + 0x65, 0x72, 0x49, 0x44, 0x12, 0x20, 0x0a, 0x0b, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6c, 0x65, 0x67, 0x61, 0x63, + 0x79, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x42, 0x09, 0x5a, 0x07, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_protos_shared_auth_proto_rawDescOnce sync.Once + file_protos_shared_auth_proto_rawDescData = file_protos_shared_auth_proto_rawDesc +) + +func file_protos_shared_auth_proto_rawDescGZIP() []byte { + file_protos_shared_auth_proto_rawDescOnce.Do(func() { + file_protos_shared_auth_proto_rawDescData = protoimpl.X.CompressGZIP(file_protos_shared_auth_proto_rawDescData) + }) + return file_protos_shared_auth_proto_rawDescData +} + +var file_protos_shared_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 19) +var file_protos_shared_auth_proto_goTypes = []interface{}{ + (*SignupRequest)(nil), // 0: SignupRequest + (*EmptyResponse)(nil), // 1: EmptyResponse + (*SignupEmailResendRequest)(nil), // 2: SignupEmailResendRequest + (*ConfirmSignupRequest)(nil), // 3: ConfirmSignupRequest + (*GetSaltResponse)(nil), // 4: GetSaltResponse + (*PrepareRequest)(nil), // 5: PrepareRequest + (*PrepareResponse)(nil), // 6: PrepareResponse + (*LoginRequest)(nil), // 7: LoginRequest + (*LoginResponse)(nil), // 8: LoginResponse + (*StartRecoveryByEmailRequest)(nil), // 9: StartRecoveryByEmailRequest + (*CompleteRecoveryByEmailRequest)(nil), // 10: CompleteRecoveryByEmailRequest + (*ChangeEmailRequest)(nil), // 11: ChangeEmailRequest + (*CompleteChangeEmailRequest)(nil), // 12: CompleteChangeEmailRequest + (*DeleteUserRequest)(nil), // 13: DeleteUserRequest + (*ValidateRecoveryCodeRequest)(nil), // 14: ValidateRecoveryCodeRequest + (*ValidateRecoveryCodeResponse)(nil), // 15: ValidateRecoveryCodeResponse + (*LogoutRequest)(nil), // 16: LogoutRequest + (*LoginResponse_Device)(nil), // 17: LoginResponse.Device + (*LoginResponse_UserData)(nil), // 18: LoginResponse.UserData +} +var file_protos_shared_auth_proto_depIdxs = []int32{ + 18, // 0: LoginResponse.legacyUserData:type_name -> LoginResponse.UserData + 17, // 1: LoginResponse.devices:type_name -> LoginResponse.Device + 17, // 2: LoginResponse.UserData.devices:type_name -> LoginResponse.Device + 3, // [3:3] is the sub-list for method output_type + 3, // [3:3] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_protos_shared_auth_proto_init() } +func file_protos_shared_auth_proto_init() { + if File_protos_shared_auth_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_protos_shared_auth_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SignupRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_shared_auth_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*EmptyResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_shared_auth_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SignupEmailResendRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_shared_auth_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConfirmSignupRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_shared_auth_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetSaltResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_shared_auth_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PrepareRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_shared_auth_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PrepareResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_shared_auth_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LoginRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_shared_auth_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LoginResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_shared_auth_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StartRecoveryByEmailRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_shared_auth_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CompleteRecoveryByEmailRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_shared_auth_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChangeEmailRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_shared_auth_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CompleteChangeEmailRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_shared_auth_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteUserRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_shared_auth_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ValidateRecoveryCodeRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_shared_auth_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ValidateRecoveryCodeResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_shared_auth_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LogoutRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_shared_auth_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LoginResponse_Device); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_shared_auth_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LoginResponse_UserData); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_protos_shared_auth_proto_rawDesc, + NumEnums: 0, + NumMessages: 19, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_protos_shared_auth_proto_goTypes, + DependencyIndexes: file_protos_shared_auth_proto_depIdxs, + MessageInfos: file_protos_shared_auth_proto_msgTypes, + }.Build() + File_protos_shared_auth_proto = out.File + file_protos_shared_auth_proto_rawDesc = nil + file_protos_shared_auth_proto_goTypes = nil + file_protos_shared_auth_proto_depIdxs = nil +} diff --git a/internalsdk/protos/vpn.pb.go b/internalsdk/protos/vpn.pb.go index 39be32da6..692d74158 100644 --- a/internalsdk/protos/vpn.pb.go +++ b/internalsdk/protos/vpn.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.33.0 -// protoc v4.24.3 +// protoc-gen-go v1.33.0-devel +// protoc v4.23.4 // source: protos_shared/vpn.proto package protos @@ -335,6 +335,53 @@ func (x *Devices) GetDevices() []*Device { return nil } +type Plans struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Plan []*Plan `protobuf:"bytes,1,rep,name=plan,proto3" json:"plan,omitempty"` +} + +func (x *Plans) Reset() { + *x = Plans{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_shared_vpn_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Plans) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Plans) ProtoMessage() {} + +func (x *Plans) ProtoReflect() protoreflect.Message { + mi := &file_protos_shared_vpn_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Plans.ProtoReflect.Descriptor instead. +func (*Plans) Descriptor() ([]byte, []int) { + return file_protos_shared_vpn_proto_rawDescGZIP(), []int{5} +} + +func (x *Plans) GetPlan() []*Plan { + if x != nil { + return x.Plan + } + return nil +} + type Plan struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -351,12 +398,13 @@ type Plan struct { TotalCost string `protobuf:"bytes,9,opt,name=totalCost,proto3" json:"totalCost,omitempty"` FormattedBonus string `protobuf:"bytes,10,opt,name=formattedBonus,proto3" json:"formattedBonus,omitempty"` RenewalText string `protobuf:"bytes,11,opt,name=renewalText,proto3" json:"renewalText,omitempty"` + RenewalBonusExpected map[string]int64 `protobuf:"bytes,13,rep,name=renewalBonusExpected,proto3" json:"renewalBonusExpected,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` } func (x *Plan) Reset() { *x = Plan{} if protoimpl.UnsafeEnabled { - mi := &file_protos_shared_vpn_proto_msgTypes[5] + mi := &file_protos_shared_vpn_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -369,7 +417,7 @@ func (x *Plan) String() string { func (*Plan) ProtoMessage() {} func (x *Plan) ProtoReflect() protoreflect.Message { - mi := &file_protos_shared_vpn_proto_msgTypes[5] + mi := &file_protos_shared_vpn_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -382,7 +430,7 @@ func (x *Plan) ProtoReflect() protoreflect.Message { // Deprecated: Use Plan.ProtoReflect.Descriptor instead. func (*Plan) Descriptor() ([]byte, []int) { - return file_protos_shared_vpn_proto_rawDescGZIP(), []int{5} + return file_protos_shared_vpn_proto_rawDescGZIP(), []int{6} } func (x *Plan) GetId() string { @@ -462,6 +510,13 @@ func (x *Plan) GetRenewalText() string { return "" } +func (x *Plan) GetRenewalBonusExpected() map[string]int64 { + if x != nil { + return x.RenewalBonusExpected + } + return nil +} + type PaymentProviders struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -474,7 +529,7 @@ type PaymentProviders struct { func (x *PaymentProviders) Reset() { *x = PaymentProviders{} if protoimpl.UnsafeEnabled { - mi := &file_protos_shared_vpn_proto_msgTypes[6] + mi := &file_protos_shared_vpn_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -487,7 +542,7 @@ func (x *PaymentProviders) String() string { func (*PaymentProviders) ProtoMessage() {} func (x *PaymentProviders) ProtoReflect() protoreflect.Message { - mi := &file_protos_shared_vpn_proto_msgTypes[6] + mi := &file_protos_shared_vpn_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -500,7 +555,7 @@ func (x *PaymentProviders) ProtoReflect() protoreflect.Message { // Deprecated: Use PaymentProviders.ProtoReflect.Descriptor instead. func (*PaymentProviders) Descriptor() ([]byte, []int) { - return file_protos_shared_vpn_proto_rawDescGZIP(), []int{6} + return file_protos_shared_vpn_proto_rawDescGZIP(), []int{7} } func (x *PaymentProviders) GetName() string { @@ -529,7 +584,7 @@ type PaymentMethod struct { func (x *PaymentMethod) Reset() { *x = PaymentMethod{} if protoimpl.UnsafeEnabled { - mi := &file_protos_shared_vpn_proto_msgTypes[7] + mi := &file_protos_shared_vpn_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -542,7 +597,7 @@ func (x *PaymentMethod) String() string { func (*PaymentMethod) ProtoMessage() {} func (x *PaymentMethod) ProtoReflect() protoreflect.Message { - mi := &file_protos_shared_vpn_proto_msgTypes[7] + mi := &file_protos_shared_vpn_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -555,7 +610,7 @@ func (x *PaymentMethod) ProtoReflect() protoreflect.Message { // Deprecated: Use PaymentMethod.ProtoReflect.Descriptor instead. func (*PaymentMethod) Descriptor() ([]byte, []int) { - return file_protos_shared_vpn_proto_rawDescGZIP(), []int{7} + return file_protos_shared_vpn_proto_rawDescGZIP(), []int{8} } func (x *PaymentMethod) GetMethod() string { @@ -577,25 +632,28 @@ type User struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - UserId int64 `protobuf:"varint,1,opt,name=userId,proto3" json:"userId,omitempty"` - Email string `protobuf:"bytes,2,opt,name=email,proto3" json:"email,omitempty"` - Telephone string `protobuf:"bytes,3,opt,name=telephone,proto3" json:"telephone,omitempty"` - UserStatus string `protobuf:"bytes,4,opt,name=userStatus,proto3" json:"userStatus,omitempty"` - UserLevel string `protobuf:"bytes,5,opt,name=userLevel,proto3" json:"userLevel,omitempty"` - Locale string `protobuf:"bytes,6,opt,name=locale,proto3" json:"locale,omitempty"` - Expiration int64 `protobuf:"varint,7,opt,name=expiration,proto3" json:"expiration,omitempty"` - Devices []*Device `protobuf:"bytes,8,rep,name=devices,proto3" json:"devices,omitempty"` - Code string `protobuf:"bytes,9,opt,name=code,proto3" json:"code,omitempty"` - ExpireAt int64 `protobuf:"varint,10,opt,name=expireAt,proto3" json:"expireAt,omitempty"` - Referral string `protobuf:"bytes,11,opt,name=referral,proto3" json:"referral,omitempty"` - Token string `protobuf:"bytes,12,opt,name=token,proto3" json:"token,omitempty"` - YinbiEnabled bool `protobuf:"varint,13,opt,name=yinbiEnabled,proto3" json:"yinbiEnabled,omitempty"` + UserId int64 `protobuf:"varint,1,opt,name=userId,proto3" json:"userId,omitempty"` + Email string `protobuf:"bytes,2,opt,name=email,proto3" json:"email,omitempty"` + Telephone string `protobuf:"bytes,3,opt,name=telephone,proto3" json:"telephone,omitempty"` + UserStatus string `protobuf:"bytes,4,opt,name=userStatus,proto3" json:"userStatus,omitempty"` + UserLevel string `protobuf:"bytes,5,opt,name=userLevel,proto3" json:"userLevel,omitempty"` + Locale string `protobuf:"bytes,6,opt,name=locale,proto3" json:"locale,omitempty"` + Expiration int64 `protobuf:"varint,7,opt,name=expiration,proto3" json:"expiration,omitempty"` + Devices []*Device `protobuf:"bytes,8,rep,name=devices,proto3" json:"devices,omitempty"` + Code string `protobuf:"bytes,9,opt,name=code,proto3" json:"code,omitempty"` + ExpireAt int64 `protobuf:"varint,10,opt,name=expireAt,proto3" json:"expireAt,omitempty"` + Referral string `protobuf:"bytes,11,opt,name=referral,proto3" json:"referral,omitempty"` + Token string `protobuf:"bytes,12,opt,name=token,proto3" json:"token,omitempty"` + YinbiEnabled bool `protobuf:"varint,13,opt,name=yinbiEnabled,proto3" json:"yinbiEnabled,omitempty"` + Inviters []string `protobuf:"bytes,14,rep,name=inviters,proto3" json:"inviters,omitempty"` + Invitees []string `protobuf:"bytes,15,rep,name=invitees,proto3" json:"invitees,omitempty"` + Purchases []*Purchase `protobuf:"bytes,16,rep,name=purchases,proto3" json:"purchases,omitempty"` } func (x *User) Reset() { *x = User{} if protoimpl.UnsafeEnabled { - mi := &file_protos_shared_vpn_proto_msgTypes[8] + mi := &file_protos_shared_vpn_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -608,7 +666,7 @@ func (x *User) String() string { func (*User) ProtoMessage() {} func (x *User) ProtoReflect() protoreflect.Message { - mi := &file_protos_shared_vpn_proto_msgTypes[8] + mi := &file_protos_shared_vpn_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -621,7 +679,7 @@ func (x *User) ProtoReflect() protoreflect.Message { // Deprecated: Use User.ProtoReflect.Descriptor instead. func (*User) Descriptor() ([]byte, []int) { - return file_protos_shared_vpn_proto_rawDescGZIP(), []int{8} + return file_protos_shared_vpn_proto_rawDescGZIP(), []int{9} } func (x *User) GetUserId() int64 { @@ -715,6 +773,74 @@ func (x *User) GetYinbiEnabled() bool { return false } +func (x *User) GetInviters() []string { + if x != nil { + return x.Inviters + } + return nil +} + +func (x *User) GetInvitees() []string { + if x != nil { + return x.Invitees + } + return nil +} + +func (x *User) GetPurchases() []*Purchase { + if x != nil { + return x.Purchases + } + return nil +} + +type Purchase struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Plan string `protobuf:"bytes,1,opt,name=plan,proto3" json:"plan,omitempty"` +} + +func (x *Purchase) Reset() { + *x = Purchase{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_shared_vpn_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Purchase) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Purchase) ProtoMessage() {} + +func (x *Purchase) ProtoReflect() protoreflect.Message { + mi := &file_protos_shared_vpn_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Purchase.ProtoReflect.Descriptor instead. +func (*Purchase) Descriptor() ([]byte, []int) { + return file_protos_shared_vpn_proto_rawDescGZIP(), []int{10} +} + +func (x *Purchase) GetPlan() string { + if x != nil { + return x.Plan + } + return "" +} + // API type BaseResponse struct { state protoimpl.MessageState @@ -729,7 +855,7 @@ type BaseResponse struct { func (x *BaseResponse) Reset() { *x = BaseResponse{} if protoimpl.UnsafeEnabled { - mi := &file_protos_shared_vpn_proto_msgTypes[9] + mi := &file_protos_shared_vpn_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -742,7 +868,7 @@ func (x *BaseResponse) String() string { func (*BaseResponse) ProtoMessage() {} func (x *BaseResponse) ProtoReflect() protoreflect.Message { - mi := &file_protos_shared_vpn_proto_msgTypes[9] + mi := &file_protos_shared_vpn_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -755,7 +881,7 @@ func (x *BaseResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BaseResponse.ProtoReflect.Descriptor instead. func (*BaseResponse) Descriptor() ([]byte, []int) { - return file_protos_shared_vpn_proto_rawDescGZIP(), []int{9} + return file_protos_shared_vpn_proto_rawDescGZIP(), []int{11} } func (x *BaseResponse) GetStatus() string { @@ -796,7 +922,7 @@ type PaymentRedirectRequest struct { func (x *PaymentRedirectRequest) Reset() { *x = PaymentRedirectRequest{} if protoimpl.UnsafeEnabled { - mi := &file_protos_shared_vpn_proto_msgTypes[10] + mi := &file_protos_shared_vpn_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -809,7 +935,7 @@ func (x *PaymentRedirectRequest) String() string { func (*PaymentRedirectRequest) ProtoMessage() {} func (x *PaymentRedirectRequest) ProtoReflect() protoreflect.Message { - mi := &file_protos_shared_vpn_proto_msgTypes[10] + mi := &file_protos_shared_vpn_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -822,7 +948,7 @@ func (x *PaymentRedirectRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PaymentRedirectRequest.ProtoReflect.Descriptor instead. func (*PaymentRedirectRequest) Descriptor() ([]byte, []int) { - return file_protos_shared_vpn_proto_rawDescGZIP(), []int{10} + return file_protos_shared_vpn_proto_rawDescGZIP(), []int{12} } func (x *PaymentRedirectRequest) GetPlan() string { @@ -890,7 +1016,7 @@ type RedeemResellerCodeRequest struct { func (x *RedeemResellerCodeRequest) Reset() { *x = RedeemResellerCodeRequest{} if protoimpl.UnsafeEnabled { - mi := &file_protos_shared_vpn_proto_msgTypes[11] + mi := &file_protos_shared_vpn_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -903,7 +1029,7 @@ func (x *RedeemResellerCodeRequest) String() string { func (*RedeemResellerCodeRequest) ProtoMessage() {} func (x *RedeemResellerCodeRequest) ProtoReflect() protoreflect.Message { - mi := &file_protos_shared_vpn_proto_msgTypes[11] + mi := &file_protos_shared_vpn_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -916,7 +1042,7 @@ func (x *RedeemResellerCodeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RedeemResellerCodeRequest.ProtoReflect.Descriptor instead. func (*RedeemResellerCodeRequest) Descriptor() ([]byte, []int) { - return file_protos_shared_vpn_proto_rawDescGZIP(), []int{11} + return file_protos_shared_vpn_proto_rawDescGZIP(), []int{13} } func (x *RedeemResellerCodeRequest) GetEmail() string { @@ -975,7 +1101,7 @@ type PaymentRedirectResponse struct { func (x *PaymentRedirectResponse) Reset() { *x = PaymentRedirectResponse{} if protoimpl.UnsafeEnabled { - mi := &file_protos_shared_vpn_proto_msgTypes[12] + mi := &file_protos_shared_vpn_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -988,7 +1114,7 @@ func (x *PaymentRedirectResponse) String() string { func (*PaymentRedirectResponse) ProtoMessage() {} func (x *PaymentRedirectResponse) ProtoReflect() protoreflect.Message { - mi := &file_protos_shared_vpn_proto_msgTypes[12] + mi := &file_protos_shared_vpn_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1001,7 +1127,7 @@ func (x *PaymentRedirectResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PaymentRedirectResponse.ProtoReflect.Descriptor instead. func (*PaymentRedirectResponse) Descriptor() ([]byte, []int) { - return file_protos_shared_vpn_proto_rawDescGZIP(), []int{12} + return file_protos_shared_vpn_proto_rawDescGZIP(), []int{14} } func (x *PaymentRedirectResponse) GetStatus() string { @@ -1047,7 +1173,7 @@ type LinkResponse struct { func (x *LinkResponse) Reset() { *x = LinkResponse{} if protoimpl.UnsafeEnabled { - mi := &file_protos_shared_vpn_proto_msgTypes[13] + mi := &file_protos_shared_vpn_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1060,7 +1186,7 @@ func (x *LinkResponse) String() string { func (*LinkResponse) ProtoMessage() {} func (x *LinkResponse) ProtoReflect() protoreflect.Message { - mi := &file_protos_shared_vpn_proto_msgTypes[13] + mi := &file_protos_shared_vpn_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1073,7 +1199,7 @@ func (x *LinkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LinkResponse.ProtoReflect.Descriptor instead. func (*LinkResponse) Descriptor() ([]byte, []int) { - return file_protos_shared_vpn_proto_rawDescGZIP(), []int{13} + return file_protos_shared_vpn_proto_rawDescGZIP(), []int{15} } func (x *LinkResponse) GetUserID() int64 { @@ -1144,127 +1270,147 @@ var file_protos_shared_vpn_proto_rawDesc = []byte{ 0x52, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x22, 0x2c, 0x0a, 0x07, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x07, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x07, - 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x22, 0xb6, 0x04, 0x0a, 0x04, 0x50, 0x6c, 0x61, 0x6e, - 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, - 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x62, 0x65, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x62, 0x65, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x64, 0x50, 0x72, 0x69, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x08, 0x75, 0x73, 0x64, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x26, 0x0a, 0x05, - 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x50, 0x6c, - 0x61, 0x6e, 0x2e, 0x50, 0x72, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x70, - 0x72, 0x69, 0x63, 0x65, 0x12, 0x53, 0x0a, 0x14, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, - 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x6c, 0x79, 0x50, 0x72, 0x69, 0x63, 0x65, 0x18, 0x06, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x2e, 0x45, 0x78, 0x70, 0x65, 0x63, 0x74, - 0x65, 0x64, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x6c, 0x79, 0x50, 0x72, 0x69, 0x63, 0x65, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x52, 0x14, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4d, 0x6f, 0x6e, - 0x74, 0x68, 0x6c, 0x79, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x36, 0x0a, 0x16, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x43, 0x6f, 0x73, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x65, 0x64, 0x4f, 0x6e, 0x65, 0x54, - 0x69, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x74, 0x6f, 0x74, 0x61, 0x6c, - 0x43, 0x6f, 0x73, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x65, 0x64, 0x4f, 0x6e, 0x65, 0x54, 0x69, 0x6d, - 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x6f, 0x6e, 0x65, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x43, 0x6f, 0x73, - 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x6e, 0x65, 0x4d, 0x6f, 0x6e, 0x74, - 0x68, 0x43, 0x6f, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, - 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, - 0x6f, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x74, 0x65, 0x64, - 0x42, 0x6f, 0x6e, 0x75, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x66, 0x6f, 0x72, - 0x6d, 0x61, 0x74, 0x74, 0x65, 0x64, 0x42, 0x6f, 0x6e, 0x75, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x72, - 0x65, 0x6e, 0x65, 0x77, 0x61, 0x6c, 0x54, 0x65, 0x78, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0b, 0x72, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x6c, 0x54, 0x65, 0x78, 0x74, 0x1a, 0x38, 0x0a, - 0x0a, 0x50, 0x72, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, - 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x47, 0x0a, 0x19, 0x45, 0x78, 0x70, 0x65, 0x63, - 0x74, 0x65, 0x64, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x6c, 0x79, 0x50, 0x72, 0x69, 0x63, 0x65, 0x45, + 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x22, 0x22, 0x0a, 0x05, 0x50, 0x6c, 0x61, 0x6e, 0x73, + 0x12, 0x19, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x05, + 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x22, 0xd4, 0x05, 0x0a, 0x04, + 0x50, 0x6c, 0x61, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x02, 0x69, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x62, 0x65, 0x73, 0x74, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x62, 0x65, 0x73, 0x74, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x64, 0x50, 0x72, 0x69, 0x63, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x75, 0x73, 0x64, 0x50, 0x72, 0x69, 0x63, 0x65, + 0x12, 0x26, 0x0a, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x10, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x2e, 0x50, 0x72, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x52, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x12, 0x53, 0x0a, 0x14, 0x65, 0x78, 0x70, 0x65, + 0x63, 0x74, 0x65, 0x64, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x6c, 0x79, 0x50, 0x72, 0x69, 0x63, 0x65, + 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x2e, 0x45, 0x78, + 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x6c, 0x79, 0x50, 0x72, 0x69, + 0x63, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x14, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, + 0x64, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x6c, 0x79, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x36, 0x0a, + 0x16, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x73, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x65, 0x64, + 0x4f, 0x6e, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x74, + 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x73, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x65, 0x64, 0x4f, 0x6e, + 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x6f, 0x6e, 0x65, 0x4d, 0x6f, 0x6e, 0x74, + 0x68, 0x43, 0x6f, 0x73, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x6e, 0x65, + 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x43, 0x6f, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x6f, 0x74, + 0x61, 0x6c, 0x43, 0x6f, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x6f, + 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x66, 0x6f, 0x72, 0x6d, 0x61, + 0x74, 0x74, 0x65, 0x64, 0x42, 0x6f, 0x6e, 0x75, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x74, 0x65, 0x64, 0x42, 0x6f, 0x6e, 0x75, 0x73, 0x12, + 0x20, 0x0a, 0x0b, 0x72, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x6c, 0x54, 0x65, 0x78, 0x74, 0x18, 0x0b, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x6c, 0x54, 0x65, 0x78, + 0x74, 0x12, 0x53, 0x0a, 0x14, 0x72, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x6c, 0x42, 0x6f, 0x6e, 0x75, + 0x73, 0x45, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x1f, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x2e, 0x52, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x6c, 0x42, 0x6f, + 0x6e, 0x75, 0x73, 0x45, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x52, 0x14, 0x72, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x6c, 0x42, 0x6f, 0x6e, 0x75, 0x73, 0x45, 0x78, + 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x1a, 0x38, 0x0a, 0x0a, 0x50, 0x72, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, - 0x22, 0x42, 0x0a, 0x10, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x76, 0x69, - 0x64, 0x65, 0x72, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x6f, 0x67, 0x6f, - 0x55, 0x72, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f, 0x67, 0x6f, - 0x55, 0x72, 0x6c, 0x73, 0x22, 0x58, 0x0a, 0x0d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, - 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x2f, 0x0a, - 0x09, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x11, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, - 0x65, 0x72, 0x73, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x22, 0xf1, - 0x02, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, - 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x68, 0x6f, - 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x68, - 0x6f, 0x6e, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, - 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, 0x70, - 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x65, - 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x07, 0x64, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x44, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x52, 0x07, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, - 0x63, 0x6f, 0x64, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, - 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x74, 0x18, 0x0a, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x08, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x74, 0x12, 0x1a, 0x0a, 0x08, - 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x61, 0x6c, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x61, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, - 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x22, - 0x0a, 0x0c, 0x79, 0x69, 0x6e, 0x62, 0x69, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x0d, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x79, 0x69, 0x6e, 0x62, 0x69, 0x45, 0x6e, 0x61, 0x62, 0x6c, - 0x65, 0x64, 0x22, 0x56, 0x0a, 0x0c, 0x42, 0x61, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, - 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, - 0x12, 0x18, 0x0a, 0x07, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x49, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x49, 0x64, 0x22, 0xd4, 0x01, 0x0a, 0x16, 0x50, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, - 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, - 0x79, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x6f, 0x63, - 0x61, 0x6c, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6c, 0x6f, 0x63, 0x61, 0x6c, - 0x65, 0x22, 0xd5, 0x01, 0x0a, 0x19, 0x52, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x52, 0x65, 0x73, 0x65, - 0x6c, 0x6c, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x22, 0x0a, 0x0c, 0x72, 0x65, 0x73, 0x65, 0x6c, 0x6c, 0x65, - 0x72, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, - 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x75, 0x72, - 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x75, 0x72, - 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x26, 0x0a, 0x0e, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, - 0x65, 0x6e, 0x63, 0x79, 0x4b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x69, - 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, - 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x22, 0x7d, 0x0a, 0x17, 0x50, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x49, 0x64, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, - 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x22, 0x84, 0x01, 0x0a, 0x0c, 0x4c, 0x69, 0x6e, - 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, - 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, - 0x44, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x1a, 0x47, 0x0a, 0x19, 0x45, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4d, 0x6f, 0x6e, 0x74, + 0x68, 0x6c, 0x79, 0x50, 0x72, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x47, 0x0a, 0x19, 0x52, 0x65, 0x6e, + 0x65, 0x77, 0x61, 0x6c, 0x42, 0x6f, 0x6e, 0x75, 0x73, 0x45, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, + 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, + 0x38, 0x01, 0x22, 0x42, 0x0a, 0x10, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x6f, + 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x6f, + 0x67, 0x6f, 0x55, 0x72, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f, + 0x67, 0x6f, 0x55, 0x72, 0x6c, 0x73, 0x22, 0x58, 0x0a, 0x0d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, + 0x2f, 0x0a, 0x09, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x76, + 0x69, 0x64, 0x65, 0x72, 0x73, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, + 0x22, 0xd2, 0x03, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x65, 0x6c, 0x65, 0x70, + 0x68, 0x6f, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x65, 0x6c, 0x65, + 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, + 0x65, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x4c, 0x65, + 0x76, 0x65, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x65, + 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x07, 0x64, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x44, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x07, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x12, + 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, + 0x64, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x74, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x74, 0x12, 0x1a, + 0x0a, 0x08, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x61, 0x6c, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x61, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, + 0x12, 0x22, 0x0a, 0x0c, 0x79, 0x69, 0x6e, 0x62, 0x69, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, + 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x79, 0x69, 0x6e, 0x62, 0x69, 0x45, 0x6e, 0x61, + 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x72, 0x73, + 0x18, 0x0e, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x72, 0x73, + 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x08, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x65, 0x73, 0x12, 0x27, 0x0a, 0x09, + 0x70, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x18, 0x10, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x09, 0x2e, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x52, 0x09, 0x70, 0x75, 0x72, 0x63, + 0x68, 0x61, 0x73, 0x65, 0x73, 0x22, 0x1e, 0x0a, 0x08, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x22, 0x56, 0x0a, 0x0c, 0x42, 0x61, 0x73, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, + 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x49, 0x64, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x49, 0x64, 0x22, 0xd4, 0x01, + 0x0a, 0x16, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x1a, 0x0a, 0x08, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x75, 0x72, 0x72, + 0x65, 0x6e, 0x63, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x75, 0x72, 0x72, + 0x65, 0x6e, 0x63, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, + 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6c, 0x6f, + 0x63, 0x61, 0x6c, 0x65, 0x22, 0xd5, 0x01, 0x0a, 0x19, 0x52, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x52, + 0x65, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x22, 0x0a, 0x0c, 0x72, 0x65, 0x73, 0x65, + 0x6c, 0x6c, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x72, 0x65, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1e, 0x0a, 0x0a, + 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, + 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x26, 0x0a, 0x0e, 0x69, 0x64, 0x65, 0x6d, + 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0e, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4b, 0x65, 0x79, + 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x22, 0x7d, 0x0a, 0x17, + 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, + 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x49, 0x64, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x49, 0x64, 0x42, - 0x1b, 0x0a, 0x10, 0x69, 0x6f, 0x2e, 0x6c, 0x61, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x2e, 0x6d, 0x6f, - 0x64, 0x65, 0x6c, 0x5a, 0x07, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x49, 0x64, 0x12, + 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x22, 0x84, 0x01, 0x0a, 0x0c, + 0x4c, 0x69, 0x6e, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, + 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, + 0x65, 0x72, 0x49, 0x44, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x72, 0x72, 0x6f, + 0x72, 0x49, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x49, 0x64, 0x42, 0x1b, 0x0a, 0x10, 0x69, 0x6f, 0x2e, 0x6c, 0x61, 0x6e, 0x74, 0x65, 0x72, 0x6e, + 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x5a, 0x07, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1279,36 +1425,42 @@ func file_protos_shared_vpn_proto_rawDescGZIP() []byte { return file_protos_shared_vpn_proto_rawDescData } -var file_protos_shared_vpn_proto_msgTypes = make([]protoimpl.MessageInfo, 16) +var file_protos_shared_vpn_proto_msgTypes = make([]protoimpl.MessageInfo, 19) var file_protos_shared_vpn_proto_goTypes = []interface{}{ (*ServerInfo)(nil), // 0: ServerInfo (*Bandwidth)(nil), // 1: Bandwidth (*AppData)(nil), // 2: AppData (*Device)(nil), // 3: Device (*Devices)(nil), // 4: Devices - (*Plan)(nil), // 5: Plan - (*PaymentProviders)(nil), // 6: PaymentProviders - (*PaymentMethod)(nil), // 7: PaymentMethod - (*User)(nil), // 8: User - (*BaseResponse)(nil), // 9: BaseResponse - (*PaymentRedirectRequest)(nil), // 10: PaymentRedirectRequest - (*RedeemResellerCodeRequest)(nil), // 11: RedeemResellerCodeRequest - (*PaymentRedirectResponse)(nil), // 12: PaymentRedirectResponse - (*LinkResponse)(nil), // 13: LinkResponse - nil, // 14: Plan.PriceEntry - nil, // 15: Plan.ExpectedMonthlyPriceEntry + (*Plans)(nil), // 5: Plans + (*Plan)(nil), // 6: Plan + (*PaymentProviders)(nil), // 7: PaymentProviders + (*PaymentMethod)(nil), // 8: PaymentMethod + (*User)(nil), // 9: User + (*Purchase)(nil), // 10: Purchase + (*BaseResponse)(nil), // 11: BaseResponse + (*PaymentRedirectRequest)(nil), // 12: PaymentRedirectRequest + (*RedeemResellerCodeRequest)(nil), // 13: RedeemResellerCodeRequest + (*PaymentRedirectResponse)(nil), // 14: PaymentRedirectResponse + (*LinkResponse)(nil), // 15: LinkResponse + nil, // 16: Plan.PriceEntry + nil, // 17: Plan.ExpectedMonthlyPriceEntry + nil, // 18: Plan.RenewalBonusExpectedEntry } var file_protos_shared_vpn_proto_depIdxs = []int32{ 3, // 0: Devices.devices:type_name -> Device - 14, // 1: Plan.price:type_name -> Plan.PriceEntry - 15, // 2: Plan.expectedMonthlyPrice:type_name -> Plan.ExpectedMonthlyPriceEntry - 6, // 3: PaymentMethod.providers:type_name -> PaymentProviders - 3, // 4: User.devices:type_name -> Device - 5, // [5:5] is the sub-list for method output_type - 5, // [5:5] is the sub-list for method input_type - 5, // [5:5] is the sub-list for extension type_name - 5, // [5:5] is the sub-list for extension extendee - 0, // [0:5] is the sub-list for field type_name + 6, // 1: Plans.plan:type_name -> Plan + 16, // 2: Plan.price:type_name -> Plan.PriceEntry + 17, // 3: Plan.expectedMonthlyPrice:type_name -> Plan.ExpectedMonthlyPriceEntry + 18, // 4: Plan.renewalBonusExpected:type_name -> Plan.RenewalBonusExpectedEntry + 7, // 5: PaymentMethod.providers:type_name -> PaymentProviders + 3, // 6: User.devices:type_name -> Device + 10, // 7: User.purchases:type_name -> Purchase + 8, // [8:8] is the sub-list for method output_type + 8, // [8:8] is the sub-list for method input_type + 8, // [8:8] is the sub-list for extension type_name + 8, // [8:8] is the sub-list for extension extendee + 0, // [0:8] is the sub-list for field type_name } func init() { file_protos_shared_vpn_proto_init() } @@ -1378,7 +1530,7 @@ func file_protos_shared_vpn_proto_init() { } } file_protos_shared_vpn_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Plan); i { + switch v := v.(*Plans); i { case 0: return &v.state case 1: @@ -1390,7 +1542,7 @@ func file_protos_shared_vpn_proto_init() { } } file_protos_shared_vpn_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PaymentProviders); i { + switch v := v.(*Plan); i { case 0: return &v.state case 1: @@ -1402,7 +1554,7 @@ func file_protos_shared_vpn_proto_init() { } } file_protos_shared_vpn_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PaymentMethod); i { + switch v := v.(*PaymentProviders); i { case 0: return &v.state case 1: @@ -1414,7 +1566,7 @@ func file_protos_shared_vpn_proto_init() { } } file_protos_shared_vpn_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*User); i { + switch v := v.(*PaymentMethod); i { case 0: return &v.state case 1: @@ -1426,7 +1578,7 @@ func file_protos_shared_vpn_proto_init() { } } file_protos_shared_vpn_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BaseResponse); i { + switch v := v.(*User); i { case 0: return &v.state case 1: @@ -1438,7 +1590,7 @@ func file_protos_shared_vpn_proto_init() { } } file_protos_shared_vpn_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PaymentRedirectRequest); i { + switch v := v.(*Purchase); i { case 0: return &v.state case 1: @@ -1450,7 +1602,7 @@ func file_protos_shared_vpn_proto_init() { } } file_protos_shared_vpn_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RedeemResellerCodeRequest); i { + switch v := v.(*BaseResponse); i { case 0: return &v.state case 1: @@ -1462,7 +1614,7 @@ func file_protos_shared_vpn_proto_init() { } } file_protos_shared_vpn_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PaymentRedirectResponse); i { + switch v := v.(*PaymentRedirectRequest); i { case 0: return &v.state case 1: @@ -1474,6 +1626,30 @@ func file_protos_shared_vpn_proto_init() { } } file_protos_shared_vpn_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RedeemResellerCodeRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_shared_vpn_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PaymentRedirectResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_shared_vpn_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*LinkResponse); i { case 0: return &v.state @@ -1492,7 +1668,7 @@ func file_protos_shared_vpn_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_protos_shared_vpn_proto_rawDesc, NumEnums: 0, - NumMessages: 16, + NumMessages: 19, NumExtensions: 0, NumServices: 0, }, diff --git a/internalsdk/session_model.go b/internalsdk/session_model.go index c1c2e3e9a..74c8d5b39 100644 --- a/internalsdk/session_model.go +++ b/internalsdk/session_model.go @@ -3,16 +3,21 @@ package internalsdk import ( "context" "fmt" + "math/big" + "net/http" "strconv" + "strings" "time" + "github.com/1Password/srp" + "github.com/getlantern/errors" "github.com/getlantern/flashlight/v7/proxied" - - //"github.com/getlantern/flashlight/v7/proxied" + "github.com/getlantern/lantern-client/internalsdk/auth" "github.com/getlantern/lantern-client/internalsdk/common" "github.com/getlantern/lantern-client/internalsdk/pro" "github.com/getlantern/lantern-client/internalsdk/protos" + "github.com/getlantern/lantern-client/internalsdk/webclient" "github.com/getlantern/pathdb" "github.com/getlantern/pathdb/minisql" ) @@ -21,9 +26,20 @@ import ( // SessionModel is a custom model derived from the baseModel. type SessionModel struct { *baseModel - proClient pro.ProClient + authClient auth.AuthClient + proClient pro.ProClient } +// Expose payment providers +const ( + paymentProviderStripe = "stripe" + paymentProviderFreekassa = "freekassa" + paymentProviderGooglePlay = "googleplay" + paymentProviderApplePay = "applepay" + paymentProviderBTCPay = "btcpay" + paymentProviderResellerCode = "reseller-code" +) + // List of we are using for Session Model const ( pathDeviceID = "deviceid" @@ -63,14 +79,27 @@ const ( pathHasAllNetworkPermssion = "/hasAllNetworkPermssion" pathShouldShowGoogleAds = "shouldShowGoogleAds" currentTermsVersion = 1 - pathHasConfig = "hasConfigFetched" - pathHasProxy = "hasProxyFetched" - pathHasonSuccess = "hasOnSuccess" + pathUserSalt = "user_salt" + + pathPlans = "/plans/" + pathResellerCode = "resellercode" + pathExpirydate = "expirydate" + pathExpirystr = "expirydatestr" + + pathIsUserLoggedIn = "IsUserLoggedIn" + pathIsFirstTime = "isFirstTime" + pathDeviceLinkingCode = "devicelinkingcode" + pathDeviceCodeExp = "devicecodeexp" + + group = srp.RFC5054Group3072 + pathHasConfig = "hasConfigFetched" + pathHasProxy = "hasProxyFetched" + pathHasonSuccess = "hasOnSuccess" ) type SessionModelOpts struct { DevelopmentMode bool - ProUser bool + // ProUser bool DeviceID string Device string Model string @@ -93,25 +122,42 @@ func NewSessionModel(mdb minisql.DB, opts *SessionModelOpts) (*SessionModel, err dialTimeout = 20 * time.Second base.db.RegisterType(1000, &protos.ServerInfo{}) base.db.RegisterType(2000, &protos.Devices{}) + base.db.RegisterType(5000, &protos.Device{}) + base.db.RegisterType(3000, &protos.Plan{}) + base.db.RegisterType(4000, &protos.Plans{}) } + m := &SessionModel{baseModel: base} - m.proClient = pro.NewClient(fmt.Sprintf("https://%s", common.ProAPIHost), &pro.Opts{ - HttpClient: proxied.DirectThenFrontedClient(dialTimeout), + + webclientOpts := &webclient.Opts{ + // Use proxied.Fronted for IOS client since ChainedThenFronted it does not work with ios due to (chained proxy unavailable) + // because we are not using the flashlight on ios + // We need to figure out where to put proxied SetProxyAddr + HttpClient: &http.Client{ + Transport: proxied.Fronted(dialTimeout), + Timeout: dialTimeout, + }, UserConfig: func() common.UserConfig { deviceID, _ := m.GetDeviceID() userID, _ := m.GetUserID() token, _ := m.GetToken() lang, _ := m.Locale() + internalHeaders := map[string]string{ + common.PlatformHeader: opts.Platform, + common.AppVersionHeader: common.ApplicationVersion, + } return common.NewUserConfig( common.DefaultAppName, deviceID, userID, token, - nil, + internalHeaders, lang, ) }, - }) + } + m.proClient = pro.NewClient(fmt.Sprintf("https://%s", common.ProAPIHost), webclientOpts) + m.authClient = auth.NewClient(fmt.Sprintf("https://%s", common.V1BaseUrl), webclientOpts) m.baseModel.doInvokeMethod = m.doInvokeMethod go m.initSessionModel(context.Background(), opts) @@ -150,16 +196,18 @@ func (m *SessionModel) doInvokeMethod(method string, arguments Arguments) (inter return true, nil case "setProUser": // Todo Implement setCurrency server - err := setCurrency(m.baseModel, "Test") + err := setProUser(m.baseModel, arguments.Scalar().Bool()) if err != nil { return nil, err } return true, nil case "setLanguage": - err := setLanguage(m.baseModel, arguments.Get("lang").String()) + err := setLanguage(m, arguments.Get("lang").String()) if err != nil { return nil, err } + //Todo find way to call PLans api everytime user chnage lang + //So plans will apper in there local lang return true, nil case "acceptTerms": err := acceptTerms(m.baseModel) @@ -182,8 +230,25 @@ func (m *SessionModel) doInvokeMethod(method string, arguments Arguments) (inter return nil, err } return true, nil + case "redeemResellerCode": + email := arguments.Get("email").String() + resellerCode := arguments.Get("resellerCode").String() + err := redeemResellerCode(m, email, resellerCode) + if err != nil { + return nil, err + } + return true, nil + + case "signup": + email := arguments.Get("email").String() + password := arguments.Get("password").String() + err := signup(m, email, password) + if err != nil { + return nil, err + } + return true, nil case "createUser": - err := m.userCreate(context.Background(), arguments.Scalar().String()) + err := m.userCreate(context.Background()) if err != nil { log.Error(err) } @@ -195,7 +260,140 @@ func (m *SessionModel) doInvokeMethod(method string, arguments Arguments) (inter if err != nil { return nil, err } - checkAdsEnabled(m) + return true, nil // Add return statement here + case "signupEmailResendCode": + email := arguments.Get("email").String() + err := signupEmailResend(m, email) + if err != nil { + return nil, err + } + return true, nil + case "signupEmailConfirmation": + email := arguments.Get("email").String() + code := arguments.Get("code").String() + err := signupEmailConfirmation(m, email, code) + if err != nil { + return nil, err + } + return true, nil + + case "login": + email := arguments.Get("email").String() + password := arguments.Get("password").String() + err := login(m, email, password) + if err != nil { + return nil, err + } + return true, nil + + case "submitApplePayPayment": + plandId := arguments.Get("planID").String() + purchaseId := arguments.Get("purchaseId").String() + email := arguments.Get("email").String() + err := submitApplePayPayment(m, email, plandId, purchaseId) + if err != nil { + return nil, err + } + return true, nil + + //Recovery + case "startRecoveryByEmail": + email := arguments.Get("email").String() + err := startRecoveryByEmail(m, email) + if err != nil { + return nil, err + } + return true, nil + case "completeRecoveryByEmail": + email := arguments.Get("email").String() + code := arguments.Get("code").String() + password := arguments.Get("password").String() + err := completeRecoveryByEmail(m, email, code, password) + if err != nil { + return nil, err + } + return true, nil + case "validateRecoveryCode": + email := arguments.Get("email").String() + code := arguments.Get("code").String() + err := validateRecoveryByEmail(m, email, code) + if err != nil { + return nil, err + } + return true, nil + case "startChangeEmail": + email := arguments.Get("email").String() + newEmail := arguments.Get("newEmail").String() + password := arguments.Get("password").String() + err := startChangeEmail(*m, email, newEmail, password) + if err != nil { + return nil, err + } + return true, nil + case "completeChangeEmail": + email := arguments.Get("email").String() + newEmail := arguments.Get("newEmail").String() + password := arguments.Get("password").String() + code := arguments.Get("code").String() + err := completeChangeEmail(*m, email, newEmail, password, code) + if err != nil { + return nil, err + } + return true, nil + case "signOut": + err := signOut(*m) + if err != nil { + return nil, err + } + return true, nil + case "deleteAccount": + password := arguments.Get("password").String() + err := deleteAccount(*m, password) + if err != nil { + return nil, err + } + return true, nil + + // Device Linking + case "requestLinkCode": + err := linkCodeRequest(m) + if err != nil { + return nil, err + } + return true, nil + case "redeemLinkCode": + err := linkCodeRedeem(m) + if err != nil { + return nil, err + } + return true, nil + case "authorizeViaEmail": + email := arguments.Get("emailAddress").String() + err := requestRecoveryEmail(m, email) + if err != nil { + return nil, err + } + return true, nil + case "approveDevice": + code := arguments.Get("code").String() + err := linkCodeApprove(m, code) + if err != nil { + return nil, err + } + return true, nil + case "removeDevice": + deviceId := arguments.Get("deviceId").String() + err := userLinkRemove(m, deviceId) + if err != nil { + return nil, err + } + return true, nil + case "validateDeviceRecoveryCode": + code := arguments.Get("code").String() + err := validateDeviceRecoveryCode(m, code) + if err != nil { + return nil, err + } return true, nil case "updateStats": city := arguments.Get("city").String() @@ -209,6 +407,21 @@ func (m *SessionModel) doInvokeMethod(method string, arguments Arguments) (inter return nil, err } return true, nil + case "isUserFirstTimeVisit": + return checkFirstTimeVisit(m.baseModel) + case "setFirstTimeVisit": + err := isShowFirstTimeUserVisit(m.baseModel) + if err != nil { + return nil, err + } + return true, nil + + case "updatePaymentPlans": + err := m.paymentMethods() + if err != nil { + return nil, err + } + return true, nil default: return m.methodNotImplemented(method) } @@ -230,9 +443,9 @@ func (m *SessionModel) initSessionModel(ctx context.Context, opts *SessionModelO if err != nil { return err } + log.Debugf("my device id %v", opts.DeviceID) err = pathdb.PutAll(tx, map[string]interface{}{ pathDevelopmentMode: opts.DevelopmentMode, - pathProUser: opts.ProUser, pathDeviceID: opts.DeviceID, pathStoreVersion: opts.PlayVersion, pathTimezoneID: opts.TimeZone, @@ -265,6 +478,7 @@ func (m *SessionModel) initSessionModel(ctx context.Context, opts *SessionModelO err = tx.Commit() if err != nil { + log.Debugf("Error while commiting transaction %v", err) return err } // Check if user is already registered or not @@ -274,15 +488,13 @@ func (m *SessionModel) initSessionModel(ctx context.Context, opts *SessionModelO } log.Debugf("UserId is %v", userId) if userId == 0 { - local, err := m.Locale() + // Create user + pathdb.Mutate(m.db, func(tx pathdb.TX) error { + return pathdb.Put(tx, pathIsFirstTime, true, "") + }) + err = m.userCreate(ctx) if err != nil { log.Error(err) - } else { - // Create user - err = m.userCreate(ctx, local) - if err != nil { - log.Error(err) - } } } @@ -291,9 +503,34 @@ func (m *SessionModel) initSessionModel(ctx context.Context, opts *SessionModelO if err != nil { log.Error(err) } + + go func() { + err = m.paymentMethods() + if err != nil { + log.Debugf("Plans V3 error: %v", err) + // return err + } + }() + return checkAdsEnabled(m) } +func (session *SessionModel) paymentMethods() error { + plans, err := session.proClient.PaymentMethodsV4(context.Background()) + if err != nil { + log.Debugf("Plans V3 error: %v", err) + return err + } + log.Debugf("Plans V4 response: %+v", plans) + + /// Process Plans and providers + err = storePlanDetail(session.baseModel, plans) + if err != nil { + return err + } + return nil +} + func (m *SessionModel) GetAppName() string { return "Lantern-IOS" } @@ -359,7 +596,6 @@ func (m *SessionModel) UpdateAdSettings(adsetting AdSettings) error { // Note - the names of these parameters have to match what's defined on the `Session` interface func (m *SessionModel) UpdateStats(serverCity string, serverCountry string, serverCountryCode string, p3 int, p4 int, hasSucceedingProxy bool) error { - if serverCity != "" && serverCountry != "" && serverCountryCode != "" { serverInfo := &protos.ServerInfo{ @@ -403,6 +639,22 @@ func setUserLevel(m *baseModel, userLevel string) error { return pathdb.Put(tx, pathUserLevel, userLevel, "") }) } +func setExpiration(m *baseModel, expiration int64) error { + if expiration == 0 { + return nil + } + expiry := time.Unix(0, expiration*int64(time.Second)) + dateFormat := "01/02/2006" + dateStr := expiry.Format(dateFormat) + + return pathdb.Mutate(m.db, func(tx pathdb.TX) error { + err := pathdb.Put[string](tx, pathExpirystr, dateStr, "") + if err != nil { + return err + } + return pathdb.Put[int64](tx, pathExpirydate, expiration, "") + }) +} func getUserLevel(m *baseModel) (string, error) { return pathdb.Get[string](m.db, pathUserLevel) @@ -416,20 +668,73 @@ func (m *SessionModel) Locale() (string, error) { return pathdb.Get[string](m.baseModel.db, pathLang) } -func setLanguage(m *baseModel, lang string) error { +func (m *SessionModel) isUserLoggedIn() (bool, error) { + return pathdb.Get[bool](m.baseModel.db, pathIsUserLoggedIn) +} + +func setLanguage(m *SessionModel, lang string) error { + go func() { + err := m.paymentMethods() + if err != nil { + log.Errorf("Plans V4 error: %v", err) + // return + } + }() return pathdb.Mutate(m.db, func(tx pathdb.TX) error { return pathdb.Put(tx, pathLang, lang, "") }) } func setDevices(m *baseModel, devices []*protos.Device) error { + if len(devices) == 0 { + log.Debugf("No devices to found") + return nil + } + log.Debugf("Device list %v", devices) + var protoDevices []*protos.Device + for _, device := range devices { + protoDevice := &protos.Device{ + Id: device.Id, + Name: device.Name, + Created: device.Created, + } + protoDevices = append(protoDevices, protoDevice) + } + + userDevice := &protos.Devices{Devices: protoDevices} pathdb.Mutate(m.db, func(tx pathdb.TX) error { - pathdb.Put(tx, pathDevices, devices, "") + pathdb.Put(tx, pathDevices, userDevice, "") return nil }) + log.Debugf("Device stored successfully") + return nil +} + +func storePlanDetail(m *baseModel, plan *pro.PaymentMethodsResponse) error { + log.Debugf("Storing Plan details ") + err := setPlans(m, plan.Plans) + if err != nil { + return err + } + log.Debugf("Plan details stored successful") return nil } +func setPlans(m *baseModel, plans []protos.Plan) error { + return pathdb.Mutate(m.db, func(tx pathdb.TX) error { + for _, plans := range plans { + log.Debugf("Plans Values %+v", &plans) + pathPlanId := pathPlans + strings.Split(plans.Id, "-")[0] + err := pathdb.Put(tx, pathPlanId, &plans, "") + if err != nil { + log.Debugf("Error while addding price") + return err + } + } + return nil + }) +} + func (m *SessionModel) GetTimeZone() (string, error) { return pathdb.Get[string](m.baseModel.db, pathTimezoneID) } @@ -562,6 +867,11 @@ func (m *SessionModel) SerializedInternalHeaders() (string, error) { return "", nil } +func saveUserSalt(m *baseModel, salt []byte) error { + return pathdb.Mutate(m.db, func(tx pathdb.TX) error { + return pathdb.Put[[]byte](tx, pathUserSalt, salt, "") + }) +} func (m *SessionModel) SetHasConfigFetched(fetached bool) { panicIfNecessary(pathdb.Mutate(m.db, func(tx pathdb.TX) error { return pathdb.Put(tx, pathHasConfig, fetached, "") @@ -591,25 +901,77 @@ func setStoreVersion(m *baseModel, isStoreVersion bool) error { }) } -func setUserIdAndToken(m *baseModel, userId int, token string) error { +func checkFirstTimeVisit(m *baseModel) (bool, error) { + firsttime, err := pathdb.Get[bool](m.db, pathIsFirstTime) + if err != nil { + return false, err + } + log.Debugf("First time visit %v", firsttime) + return firsttime, nil +} + +func isShowFirstTimeUserVisit(m *baseModel) error { + log.Debugf("Setting first time visit to false") + return pathdb.Mutate(m.db, func(tx pathdb.TX) error { + return pathdb.Put(tx, pathIsFirstTime, false, "") + }) +} + +func setUserIdAndToken(m *baseModel, userId int64, token string) error { + log.Debugf("Setting user id %v token %v", userId, token) return pathdb.Mutate(m.db, func(tx pathdb.TX) error { - if err := pathdb.Put(tx, pathUserID, userId, ""); err != nil { + if err := pathdb.Put[int64](tx, pathUserID, userId, ""); err != nil { + log.Errorf("Error while setting user id %v", err) + return err + } + userid, err := pathdb.Get[int64](tx, pathUserID) + if err != nil { return err } + log.Debugf("User id %v", userid) return pathdb.Put(tx, pathToken, token, "") }) } +func setResellerCode(m *baseModel, resellerCode string) error { + return pathdb.Mutate(m.db, func(tx pathdb.TX) error { + return pathdb.Put(tx, pathResellerCode, resellerCode, "") + }) +} + +func getUserSalt(m *SessionModel, email string) ([]byte, error) { + lowerCaseEmail := strings.ToLower(email) + userSalt, err := pathdb.Get[[]byte](m.db, pathUserSalt) + if err != nil { + return nil, err + } + if len(userSalt) == 16 { + log.Debugf("salt return from cache %v", userSalt) + return userSalt, nil + } + log.Debugf("Salt not found calling api for %s", email) + salt, err := m.authClient.GetSalt(context.Background(), lowerCaseEmail) + if err != nil { + return nil, err + } + log.Debugf("Salt Response-> %v", salt.Salt) + return salt.Salt, nil +} // userCreate creates a new user and stores it in pathdb -func (session *SessionModel) userCreate(ctx context.Context, local string) error { +func (session *SessionModel) userCreate(ctx context.Context) error { resp, err := session.proClient.UserCreate(ctx) if err != nil { log.Errorf("Error sending request: %v", err) return err } user := resp.User + if user == nil || user.UserId == 0 { + log.Errorf("User not found in response") + return errors.New("User not found in response") + } + //Save user id and token - err = setUserIdAndToken(session.baseModel, int(user.UserId), user.Token) + err = setUserIdAndToken(session.baseModel, int64(user.UserId), user.Token) if err != nil { return err } @@ -622,50 +984,112 @@ func (session *SessionModel) userDetail(ctx context.Context) error { if err != nil { return nil } - log.Debugf("User detail: %+v", resp.User) - err = cacheUserDetail(session.baseModel, resp.User) + if resp.User == nil { + return errors.New("User data not found") + } + userDetail := resp.User + log.Debugf("User detail: %+v", userDetail) + + logged, err := session.isUserLoggedIn() + if err != nil { + log.Errorf("Error while checking user login status %v", err) + } + // This because we do not want to store email in cache when user is logged in + // Legacy user can overide there new email + if logged { + userDetail.Email = "" + } + log.Debugf("User detail: %+v", userDetail) + err = cacheUserDetail(session, userDetail) if err != nil { return err } return nil } -func cacheUserDetail(m *baseModel, user *protos.User) error { +func cacheUserDetail(session *SessionModel, userDetail *protos.User) error { + if userDetail.Email != "" { + setEmail(session.baseModel, userDetail.Email) + } //Save user refferal code - if user.Referral != "" { - err := setReferalCode(m, user.Referral) + if userDetail.Referral != "" { + err := setReferalCode(session.baseModel, userDetail.Referral) if err != nil { return err } } - if user.UserStatus != "" && user.UserStatus == "active" && user.UserLevel == "pro" { - setProUser(m, true) - } else { - setProUser(m, false) + + err := setUserLevel(session.baseModel, userDetail.UserLevel) + if err != nil { + return err } - err := setUserLevel(m, user.UserLevel) + + err = setExpiration(session.baseModel, userDetail.Expiration) if err != nil { return err } + currentDevice, err := session.GetDeviceID() + if err != nil { + log.Debugf("Error while getting device id %v", err) + } + // Check if devuce id is connect to same device if not create new user + // this is for the case when user removed device from other device + deviceFound := false + if userDetail.Devices != nil { + for _, device := range userDetail.Devices { + if device.Id == currentDevice { + deviceFound = true + break + } + } + } + log.Debugf("Device found %v", deviceFound) + // if !deviceFound { + // // Device has not found in the list + // // Switch to free user + // signOut(*session) + // log.Debugf("Device has not found in the list creating new user") + // err = session.userCreate(context.Background()) + // if err != nil { + // return err + // } + // return nil + // } + + /// Check if user has installed app first time + firstTime, err := checkFirstTimeVisit(session.baseModel) + if err != nil { + log.Debugf("Error while checking first time visit %v", err) + } + log.Debugf("First time visit %v", firstTime) + + if userDetail.UserLevel == "pro" && firstTime { + log.Debugf("User is pro and first time") + setProUser(session.baseModel, true) + } else if userDetail.UserLevel == "pro" && !firstTime && deviceFound { + log.Debugf("User is pro and not first time") + setProUser(session.baseModel, true) + } else { + log.Debugf("User is not pro") + setProUser(session.baseModel, false) + } + //Store all device - err = setDevices(m, user.Devices) + err = setDevices(session.baseModel, userDetail.Devices) if err != nil { return err } - log.Debugf("User caching successful: %+v", user) - return setUserIdAndToken(m, int(user.UserId), user.Token) + log.Debugf("User caching successful: %+v", userDetail) + return setUserIdAndToken(session.baseModel, int64(userDetail.UserId), userDetail.Token) } func reportIssue(session *SessionModel, email string, issue string, description string) error { // Check if email is there is yes then store it if email != "" { - err := pathdb.Mutate(session.db, func(tx pathdb.TX) error { + pathdb.Mutate(session.db, func(tx pathdb.TX) error { return pathdb.Put(tx, pathEmailAddress, email, "") }) - if err != nil { - return err - } } level, err := getUserLevel(session.baseModel) @@ -725,3 +1149,724 @@ func checkAdsEnabled(session *SessionModel) error { }) } +func redeemResellerCode(m *SessionModel, email string, resellerCode string) error { + lowerCaseEmail := strings.ToLower(email) + err := setEmail(m.baseModel, lowerCaseEmail) + if err != nil { + log.Errorf("Error while setting email %v", err) + return err + } + setResellerCode(m.baseModel, resellerCode) + if err != nil { + log.Errorf("Error while setting resellerCode %v", err) + return err + } + + err, purchaseData := createPurchaseData(m, lowerCaseEmail, paymentProviderResellerCode, resellerCode, "", "") + if err != nil { + log.Errorf("Error while creating purchase data %v", err) + return err + } + + purchase, err := m.proClient.PurchaseRequest(context.Background(), purchaseData) + if err != nil { + return err + } + log.Debugf("Purchase Request response %v", purchase) + + // Set user to pro + return setProUser(m.baseModel, true) +} + +func submitApplePayPayment(m *SessionModel, email string, planId string, purchaseToken string) error { + log.Debugf("Submit Apple Pay Payment planId %v purchaseToken %v email %v", planId, purchaseToken, email) + err, purchaseData := createPurchaseData(m, email, paymentProviderApplePay, "", purchaseToken, planId) + if err != nil { + log.Errorf("Error while creating purchase data %v", err) + return err + } + log.Debugf("Purchase data %+v", purchaseData) + purchase, err := m.proClient.PurchaseRequest(context.Background(), purchaseData) + if err != nil { + return err + } + log.Debugf("Purchase response %+v", purchase) + + if purchase.Status != "ok" { + return errors.New("Purchase Request failed") + } + // Set user to pro + return setProUser(m.baseModel, true) +} + +/// Auth APIS + +// Authenticates the user with the given email and password. +// +// Note-: On Sign up Client needed to generate 16 byte slat +// Then use that salt, password and email generate encryptedKey once you created encryptedKey pass it to srp.NewSRPClient +// Then use srpClient.Verifier() to generate verifierKey +func signup(session *SessionModel, email string, password string) error { + lowerCaseEmail := strings.ToLower(email) + err := setEmail(session.baseModel, lowerCaseEmail) + if err != nil { + return err + } + salt, err := GenerateSalt() + if err != nil { + return err + } + + srpClient := srp.NewSRPClient(srp.KnownGroups[group], GenerateEncryptedKey(password, lowerCaseEmail, salt), nil) + verifierKey, err := srpClient.Verifier() + if err != nil { + return err + } + signUpRequestBody := &protos.SignupRequest{ + Email: lowerCaseEmail, + Salt: salt, + Verifier: verifierKey.Bytes(), + SkipEmailConfirmation: true, + } + log.Debugf("Sign up request email %v, salt %v verifier %v verifiter in bytes %v", lowerCaseEmail, salt, verifierKey, verifierKey.Bytes()) + signupResponse, err := session.authClient.SignUp(context.Background(), signUpRequestBody) + if err != nil { + return err + } + log.Debugf("sign up response %v", signupResponse) + //Request successfull then save salt + err = pathdb.Mutate(session.db, func(tx pathdb.TX) error { + return pathdb.PutAll(tx, map[string]interface{}{ + pathUserSalt: salt, + pathEmailAddress: lowerCaseEmail, + }) + }) + if err != nil { + return err + } + return pathdb.Mutate(session.db, func(tx pathdb.TX) error { + return pathdb.Put[bool](tx, pathIsUserLoggedIn, true, "") + }) +} + +func signupEmailResend(session *SessionModel, email string) error { + salt, err := getUserSalt(session, email) + if err != nil { + return err + } + + signUpEmailResendRequestBody := &protos.SignupEmailResendRequest{ + Email: email, + Salt: salt, + } + + signupEmailResendResponse, err := session.authClient.SignupEmailResendCode(context.Background(), signUpEmailResendRequestBody) + if err != nil { + return err + } + log.Debugf("Signup email resend %v", signupEmailResendResponse) + return nil +} + +func signupEmailConfirmation(session *SessionModel, email string, code string) error { + signUpEmailResendRequestBody := &protos.ConfirmSignupRequest{ + Email: email, + Code: code, + } + + log.Debugf("Signup verfication request body %v", signUpEmailResendRequestBody) + signupEmailResendResponse, err := session.authClient.SignupEmailConfirmation(context.Background(), signUpEmailResendRequestBody) + if err != nil { + return err + } + log.Debugf("Signup verfication response %v", signupEmailResendResponse) + //Chaneg account status + return nil +} + +// Todo find way to optimize this method +func login(session *SessionModel, email string, password string) error { + lowerCaseEmail := strings.ToLower(email) + start := time.Now() + // Get the salt + salt, err := getUserSalt(session, lowerCaseEmail) + if err != nil { + return err + } + + encryptedKey := GenerateEncryptedKey(password, lowerCaseEmail, salt) + log.Debugf("Encrypted key %v Login", encryptedKey) + // Prepare login request body + client := srp.NewSRPClient(srp.KnownGroups[group], encryptedKey, nil) + //Send this key to client + A := client.EphemeralPublic() + //Create body + prepareRequestBody := &protos.PrepareRequest{ + Email: lowerCaseEmail, + A: A.Bytes(), + } + srpB, err := session.authClient.LoginPrepare(context.Background(), prepareRequestBody) + if err != nil { + return err + } + log.Debugf("Login prepare response %v", srpB) + + // // Once the client receives B from the server Client should check error status here as defense against + // // a malicious B sent from server + B := big.NewInt(0).SetBytes(srpB.B) + + if err = client.SetOthersPublic(B); err != nil { + log.Errorf("Error while setting srpB %v", err) + return err + } + + // client can now make the session key + clientKey, err := client.Key() + if err != nil || clientKey == nil { + return log.Errorf("user_not_found error while generating Client key %v", err) + } + + // // Step 3 + + // // check if the server proof is valid + if !client.GoodServerProof(salt, lowerCaseEmail, srpB.Proof) { + return log.Errorf("user_not_found error while checking server proof%v", err) + } + + clientProof, err := client.ClientProof() + if err != nil { + return log.Errorf("user_not_found error while generating client proof %v", err) + } + deviceId, err := session.GetDeviceID() + if err != nil { + return err + } + loginRequestBody := &protos.LoginRequest{ + Email: lowerCaseEmail, + Proof: clientProof, + DeviceId: deviceId, + } + log.Debugf("Login request body %v", loginRequestBody) + + login, err := session.authClient.Login(context.Background(), loginRequestBody) + if err != nil { + return err + } + if !login.Success { + err := deviceLimitFlow(session, login) + if err != nil { + return log.Errorf("error while starting device limit flow %v", err) + } + return log.Errorf("too-many-devices %v", err) + } + log.Debugf("Login response %+v", login) + + err = pathdb.Mutate(session.db, func(tx pathdb.TX) error { + return pathdb.Put[bool](tx, pathIsUserLoggedIn, true, "") + }) + if err != nil { + log.Errorf("Error while saving user status %v", err) + } + err = saveUserSalt(session.baseModel, salt) + if err != nil { + log.Errorf("Error while saving user salt %v", err) + } + + //Store all the user details + userData := ConvertToUserDetailsResponse(login) + // once login is successfull save user details + // but overide there email with login email + userData.Email = email + err = cacheUserDetail(session, userData) + if err != nil { + log.Errorf("Error while caching user details %v", err) + return err + } + end := time.Now() + + log.Debugf("Login took %v", end.Sub(start)) + return nil +} + +// Add device to user currect device list +// this gets called when user to login +func deviceAdd(session *SessionModel, deviceName string) error { + device, err := pathdb.Get[string](session.db, pathDevice) + if err != nil { + log.Errorf("Error while getting device %v", err) + return err + } + addDevice, err := session.proClient.DeviceAdd(context.Background(), device) + if err != nil { + log.Errorf("Error while adding device %v", err) + } + log.Debugf("Add device response %v", addDevice) + return nil +} + +func deviceLimitFlow(session *SessionModel, login *protos.LoginResponse) error { + // User has reached device limit + // Save latest device + var protoDevices []*protos.Device + for _, device := range login.Devices { + protoDevice := &protos.Device{ + Id: device.Id, + Name: device.Name, + Created: device.Created, + } + protoDevices = append(protoDevices, protoDevice) + } + + userDevice := &protos.Devices{Devices: protoDevices} + err := pathdb.Mutate(session.db, func(tx pathdb.TX) error { + pathdb.Put(tx, pathDevices, userDevice, "") + return nil + }) + if err != nil { + return err + } + return setUserIdAndToken(session.baseModel, login.LegacyID, login.LegacyToken) +} + +func startRecoveryByEmail(session *SessionModel, email string) error { + //Create body + lowerCaseEmail := strings.ToLower(email) + prepareRequestBody := &protos.StartRecoveryByEmailRequest{ + Email: lowerCaseEmail, + } + recovery, err := session.authClient.StartRecoveryByEmail(context.Background(), prepareRequestBody) + if err != nil { + return err + } + log.Debugf("StartRecoveryByEmail response %v", recovery) + return nil +} + +func completeRecoveryByEmail(session *SessionModel, email string, code string, password string) error { + //Create body + lowerCaseEmail := strings.ToLower(email) + newsalt, err := GenerateSalt() + if err != nil { + return err + } + log.Debugf("Slat %v and length %v", newsalt, len(newsalt)) + + encryptedKey := GenerateEncryptedKey(password, lowerCaseEmail, newsalt) + log.Debugf("Encrypted key %v completeRecoveryByEmail", encryptedKey) + srpClient := srp.NewSRPClient(srp.KnownGroups[group], encryptedKey, nil) + verifierKey, err := srpClient.Verifier() + if err != nil { + return err + } + + prepareRequestBody := &protos.CompleteRecoveryByEmailRequest{ + Email: lowerCaseEmail, + Code: code, + NewSalt: newsalt, + NewVerifier: verifierKey.Bytes(), + } + + log.Debugf("new Verifier %v and salt %v", verifierKey.Bytes(), newsalt) + recovery, err := session.authClient.CompleteRecoveryByEmail(context.Background(), prepareRequestBody) + if err != nil { + return err + } + //User has been recovered successfully + //Save new salt + saveUserSalt(session.baseModel, newsalt) + log.Debugf("CompleteRecoveryByEmail response %v", recovery) + //refresh the user details + go func() { + session.userDetail(context.Background()) + }() + return nil +} + +// This will validate code send by server +func validateRecoveryByEmail(session *SessionModel, email string, code string) error { + lowerCaseEmail := strings.ToLower(email) + prepareRequestBody := &protos.ValidateRecoveryCodeRequest{ + Email: lowerCaseEmail, + Code: code, + } + recovery, err := session.authClient.ValidateEmailRecoveryCode(context.Background(), prepareRequestBody) + if err != nil { + return err + } + if !recovery.Valid { + return log.Errorf("invalid_code Error: %v", err) + } + log.Debugf("Validate code response %v", recovery.Valid) + return nil +} + +// Change Email flow + +func startChangeEmail(session SessionModel, email string, newEmail string, password string) error { + lowerCaseEmail := strings.ToLower(email) + lowerCaseNewEmail := strings.ToLower(newEmail) + salt, err := getUserSalt(&session, lowerCaseEmail) + if err != nil { + return err + } + + // Prepare login request body + client := srp.NewSRPClient(srp.KnownGroups[group], GenerateEncryptedKey(password, lowerCaseEmail, salt), nil) + + //Send this key to client + A := client.EphemeralPublic() + + //Create body + prepareRequestBody := &protos.PrepareRequest{ + Email: lowerCaseEmail, + A: A.Bytes(), + } + srpB, err := session.authClient.LoginPrepare(context.Background(), prepareRequestBody) + if err != nil { + return err + } + log.Debugf("Login prepare response %v", srpB) + // // Once the client receives B from the server Client should check error status here as defense against + // // a malicious B sent from server + B := big.NewInt(0).SetBytes(srpB.B) + + if err = client.SetOthersPublic(B); err != nil { + log.Errorf("Error while setting srpB %v", err) + return err + } + + // client can now make the session key + clientKey, err := client.Key() + if err != nil || clientKey == nil { + return log.Errorf("user_not_found error while generating Client key %v", err) + } + + // // check if the server proof is valid + if !client.GoodServerProof(salt, lowerCaseEmail, srpB.Proof) { + return log.Errorf("user_not_found error while checking server proof%v", err) + } + + clientProof, err := client.ClientProof() + if err != nil { + return log.Errorf("user_not_found error while generating client proof %v", err) + } + + changeEmailRequestBody := &protos.ChangeEmailRequest{ + OldEmail: lowerCaseEmail, + NewEmail: lowerCaseNewEmail, + Proof: clientProof, + } + + isEmailChanged, err := session.authClient.ChangeEmail(context.Background(), changeEmailRequestBody) + if err != nil { + return err + } + log.Debugf("Change Email response %v", isEmailChanged) + return nil +} + +func completeChangeEmail(session SessionModel, email string, newEmail string, password string, code string) error { + // Create new salt and verifier with new email and new slat + newsalt, err := GenerateSalt() + if err != nil { + return err + } + log.Debugf("Slat %v and length %v", newsalt, len(newsalt)) + + srpClient := srp.NewSRPClient(srp.KnownGroups[group], GenerateEncryptedKey(password, newEmail, newsalt), nil) + verifierKey, err := srpClient.Verifier() + if err != nil { + return err + } + + completeChangeEmail := &protos.CompleteChangeEmailRequest{ + OldEmail: email, + NewEmail: newEmail, + NewSalt: newsalt, + NewVerifier: verifierKey.Bytes(), + Code: code, + } + + isEmailChanged, err := session.authClient.CompleteChangeEmail(context.Background(), completeChangeEmail) + if err != nil { + return err + } + log.Debugf("Compelte Change Email response %v", isEmailChanged) + return setEmail(session.baseModel, newEmail) +} + +// Clear slat and change accoutn state +func signOut(session SessionModel) error { + email, err := session.Email() + if err != nil { + return log.Errorf("Email not found %v", err) + } + + deviceId, err := session.GetDeviceID() + if err != nil { + return log.Errorf("deviceId not found %v", err) + } + + token, err := session.GetToken() + if err != nil { + return log.Errorf("token not found %v", err) + } + + userId, err := session.GetUserID() + if err != nil { + return log.Errorf("userid not found %v", err) + } + + signoutData := &protos.LogoutRequest{ + Email: email, + DeviceId: deviceId, + LegacyToken: token, + LegacyUserID: userId, + } + + log.Debugf("Sign out request %+v", signoutData) + + loggedOut, logoutErr := session.authClient.SignOut(context.Background(), signoutData) + if logoutErr != nil { + return log.Errorf("Error while signing out %v", logoutErr) + } + + if !loggedOut { + return log.Errorf("Error while signing out %v", logoutErr) + } + + err = clearLocalUserData(session) + if err != nil { + return log.Errorf("Error while clearing local data %v", err) + } + return session.userCreate(context.Background()) +} + +func clearLocalUserData(session SessionModel) error { + err1 := pathdb.Mutate(session.db, func(tx pathdb.TX) error { + return pathdb.PutAll(tx, map[string]interface{}{ + pathUserSalt: nil, + pathEmailAddress: "", + }) + }) + if err1 != nil { + return err1 + } + _ = pathdb.Mutate(session.db, func(tx pathdb.TX) error { + return pathdb.Put[bool](tx, pathIsUserLoggedIn, false, "") + }) + + return pathdb.Mutate(session.db, func(tx pathdb.TX) error { + return pathdb.Put[bool](tx, pathProUser, false, "") + + }) +} + +func deleteAccount(session SessionModel, password string) error { + email, err := session.Email() + if err != nil { + return err + } + if email == "" { + return errors.New("Email not found") + } + + lowerCaseEmail := strings.ToLower(email) + salt, err := getUserSalt(&session, lowerCaseEmail) + if err != nil { + return err + } + + // Prepare login request body + client := srp.NewSRPClient(srp.KnownGroups[group], GenerateEncryptedKey(password, lowerCaseEmail, salt), nil) + + //Send this key to client + A := client.EphemeralPublic() + + //Create body + prepareRequestBody := &protos.PrepareRequest{ + Email: lowerCaseEmail, + A: A.Bytes(), + } + log.Debugf("Login prepare request email %v, a bytes %v", lowerCaseEmail, A.Bytes()) + srpB, err := session.authClient.LoginPrepare(context.Background(), prepareRequestBody) + if err != nil { + return err + } + log.Debugf("Login prepare response %v", srpB) + + B := big.NewInt(0).SetBytes(srpB.B) + + if err = client.SetOthersPublic(B); err != nil { + log.Errorf("Error while setting srpB %v", err) + return err + } + + clientKey, err := client.Key() + if err != nil || clientKey == nil { + return log.Errorf("user_not_found error while generating Client key %v", err) + } + + // // check if the server proof is valid + if !client.GoodServerProof(salt, lowerCaseEmail, srpB.Proof) { + return log.Errorf("user_not_found error while checking server proof%v", err) + } + + clientProof, err := client.ClientProof() + if err != nil { + return log.Errorf("user_not_found error while generating client proof %v", err) + } + + deviceId, err := session.GetDeviceID() + if err != nil { + return err + } + changeEmailRequestBody := &protos.DeleteUserRequest{ + Email: lowerCaseEmail, + Proof: clientProof, + Permanent: true, + DeviceId: deviceId, + } + + log.Debugf("Delete Account request email %v prooof %v deviceId %v", lowerCaseEmail, clientProof, deviceId) + isAccountDeleted, err := session.authClient.DeleteAccount(context.Background(), changeEmailRequestBody) + if err != nil { + return err + } + log.Debugf("Account Delted response %v", isAccountDeleted) + + // Clear Local DB + err = pathdb.Mutate(session.db, func(tx pathdb.TX) error { + return pathdb.PutAll(tx, map[string]interface{}{ + pathEmailAddress: "", + pathUserID: 0, + pathProUser: false, + }) + }) + if err != nil { + return err + } + err = clearLocalUserData(session) + if err != nil { + return err + } + return session.userCreate(context.Background()) +} + +// Device Linking methods + +// Request code for linking device for LINK WITH PIN method +func linkCodeRequest(session *SessionModel) error { + log.Debug("LinkCodeRequest") + device, err := pathdb.Get[string](session.db, pathDevice) + if err != nil { + log.Errorf("Error while getting device %v", err) + return err + } + log.Debugf("Device %v", device) + + linkResponse, err := session.proClient.LinkCodeRequest(context.Background(), device) + if err != nil { + return err + } + log.Debugf("LinkCodeRequest response %v", linkResponse) + return pathdb.Mutate(session.db, func(tx pathdb.TX) error { + err := pathdb.Put[int64](tx, pathDeviceCodeExp, (linkResponse.ExpireAt * 1000), "") + if err != nil { + return err + } + return pathdb.Put[string](tx, pathDeviceLinkingCode, linkResponse.Code, "") + }) +} + +// Approve code for linking device for LINK WITH PIN method +func linkCodeRedeem(session *SessionModel) error { + device, err := pathdb.Get[string](session.db, pathDevice) + if err != nil { + log.Errorf("Error while getting device %v", err) + return err + } + deviceCode, err := pathdb.Get[string](session.db, pathDeviceLinkingCode) + if err != nil { + log.Errorf("Error while getting device %v", err) + return err + } + if deviceCode == "" || device == "" { + return errors.New("Device code or device not found") + } + log.Debugf("Device %v deviceCode %v", device, deviceCode) + linkRedeemResponse, err := session.proClient.LinkCodeRedeem(context.Background(), device, deviceCode) + if err != nil { + return err + } + log.Debugf("linkCodeRedeem response %+v", linkRedeemResponse) + err = setUserIdAndToken(session.baseModel, linkRedeemResponse.UserID, linkRedeemResponse.Token) + if err != nil { + return log.Errorf("Error while setting user id and token %v", err) + } + return session.userDetail(context.Background()) +} + +// Approve code for linking device for LINK WITH PIN method +func linkCodeApprove(session *SessionModel, code string) error { + linkResponse, err := session.proClient.LinkCodeApprove(context.Background(), code) + if err != nil { + return err + } + log.Debugf("LinkCodeApprove response %v", linkResponse) + // refresh user detail in background + go func() { + session.userDetail(context.Background()) + }() + return nil +} + +// Remove device for LINK WITH PIN method +func userLinkRemove(session *SessionModel, deviceId string) error { + linkResponse, err := session.proClient.DeviceRemove(context.Background(), deviceId) + if err != nil { + return err + } + log.Debugf("UserLink Remove response %v", linkResponse) + return session.userDetail(context.Background()) +} + +// Add device for LINK WITH EMAIL method +func requestRecoveryEmail(session *SessionModel, email string) error { + deviceId, err := pathdb.Get[string](session.db, pathDeviceID) + if err != nil { + log.Errorf("Error while getting deviceId %v", err) + return err + } + + linkResponse, err := session.proClient.UserLinkCodeRequest(context.Background(), deviceId) + if err != nil { + return err + } + log.Debugf("requestRecoveryEmail response %v", linkResponse) + return nil +} + +// Validate code for LINK WITH EMAIL method +func validateDeviceRecoveryCode(session *SessionModel, code string) error { + deviceId, err := pathdb.Get[string](session.db, pathDeviceID) + if err != nil { + log.Errorf("Error while getting deviceId %v", err) + return err + } + + linkResponse, err := session.proClient.UserLinkValidate(context.Background(), deviceId) + if err != nil { + return err + } + log.Debugf("ValidateRecovery code response %v", linkResponse) + err = setUserIdAndToken(session.baseModel, linkResponse.UserID, linkResponse.Token) + if err != nil { + return err + } + pathdb.Mutate(session.db, func(tx pathdb.TX) error { + return pathdb.Put[bool](tx, pathIsUserLoggedIn, true, "") + }) + // Update user detail to reflact on UI + return session.userDetail(context.Background()) +} diff --git a/internalsdk/tun.go b/internalsdk/tun.go index 3b914f4ef..c9c92d320 100644 --- a/internalsdk/tun.go +++ b/internalsdk/tun.go @@ -20,8 +20,8 @@ import ( var ( currentDeviceMx sync.Mutex - currentDevice io.ReadWriteCloser - currentIPP ipproxy.Proxy + // currentDevice io.ReadWriteCloser + currentIPP ipproxy.Proxy ) // Tun2Socks wraps the TUN device identified by fd with an ipproxy server that @@ -30,7 +30,6 @@ var ( // 1. dns packets (any UDP packets to port 53) are routed to dnsGrabAddr // 2. All other udp packets are routed directly to their destination // 3. All TCP traffic is routed through the Lantern proxy at the given socksAddr. -// func Tun2Socks(fd int, socksAddr, dnsGrabAddr string, mtu int, wrappedSession Session) error { runtime.LockOSThread() @@ -47,7 +46,7 @@ func Tun2Socks(fd int, socksAddr, dnsGrabAddr string, mtu int, wrappedSession Se DeviceName: fmt.Sprintf("fd://%d", fd), IdleTimeout: 70 * time.Second, StatsInterval: 15 * time.Second, - DisableIPv6: true, + DisableIPv6: true, MTU: mtu, OutboundBufferDepth: 10000, TCPConnectBacklog: 100, @@ -101,6 +100,7 @@ func StopTun2Socks() { currentDeviceMx.Lock() ipp := currentIPP + currentIPP = nil currentDeviceMx.Unlock() if ipp != nil { diff --git a/internalsdk/utils.go b/internalsdk/utils.go index 152e0af02..c0f9fef60 100644 --- a/internalsdk/utils.go +++ b/internalsdk/utils.go @@ -1,10 +1,22 @@ package internalsdk import ( + "bytes" + "crypto/rand" + cryptoRand "crypto/rand" + "crypto/sha256" "encoding/binary" + "fmt" "math" + "math/big" + "strconv" + "strings" + "time" "github.com/getlantern/errors" + "github.com/getlantern/lantern-client/internalsdk/protos" + "github.com/getlantern/pathdb" + "golang.org/x/crypto/pbkdf2" ) func BytesToFloat64LittleEndian(b []byte) (float64, error) { @@ -14,3 +26,151 @@ func BytesToFloat64LittleEndian(b []byte) (float64, error) { bits := binary.LittleEndian.Uint64(b) return math.Float64frombits(bits), nil } + +// Create Purchase Request +func createPurchaseData(session *SessionModel, email string, paymentProvider string, resellerCode string, purchaseToken string, planId string) (error, map[string]interface{}) { + if email == "" { + return errors.New("Email is empty"), nil + } + + device, err := pathdb.Get[string](session.db, pathModel) + if err != nil { + return err, nil + } + data := map[string]interface{}{ + "idempotencyKey": strconv.FormatInt(time.Now().UnixNano(), 10), + "provider": paymentProvider, + "email": email, + "deviceName": device, + } + + switch paymentProvider { + case paymentProviderResellerCode: + data["provider"] = paymentProviderResellerCode + data["resellerCode"] = resellerCode + data["plan"] = planId + case paymentProviderApplePay: + // get currency from plan id + parts := strings.Split(planId, "-") + if len(parts) != 3 { + return errors.New("Invalid plan id"), nil + } + cur := parts[1] + + data["token"] = purchaseToken + data["plan"] = planId + data["currency"] = cur + } + return nil, data +} + +func BytesToInt64Slice(b []byte) []int { + var int64Slice []int + buf := bytes.NewBuffer(b) + + for buf.Len() >= 8 { + var n int + if err := binary.Read(buf, binary.BigEndian, &n); err != nil { + fmt.Println("binary.Read failed:", err) + return nil + } + int64Slice = append(int64Slice, n) + } + + return int64Slice +} + +func Int64SliceToBytes(int64Slice []int) []byte { + var buf bytes.Buffer + for _, n := range int64Slice { + if err := binary.Write(&buf, binary.BigEndian, n); err != nil { + fmt.Println("binary.Write failed:", err) + return nil + } + } + return buf.Bytes() +} + +var maxDigit = big.NewInt(9) + +func GenerateRandomString(length int) string { + random := "" + + for i := 0; i < length; i++ { + bb, _ := cryptoRand.Int(cryptoRand.Reader, maxDigit) + random += bb.String() + } + return random +} +func GenerateSalt() ([]byte, error) { + salt := make([]byte, 16) + if n, err := rand.Read(salt); err != nil { + return nil, err + } else if n != 16 { + return nil, errors.New("failed to generate 16 byte salt") + } + return salt, nil +} + +func ToString(value int64) string { + return fmt.Sprintf("%d", value) +} + +func StringToIntSlice(str string) ([]int, error) { + var slice []int + + for _, char := range str { + digit, err := strconv.Atoi(string(char)) + if err != nil { + return nil, err + } + slice = append(slice, digit) + } + + return slice, nil +} + +func ConvertToUserDetailsResponse(userResponse *protos.LoginResponse) *protos.User { + // Convert protobuf to usre details struct + log.Debugf("ConvertToUserDetailsResponse %+v", userResponse) + + user := userResponse.LegacyUserData + + userData := protos.User{ + UserId: userResponse.LegacyUserData.UserId, + Code: user.Code, + Token: userResponse.LegacyToken, + Referral: user.Code, + UserLevel: user.UserLevel, + Expiration: user.Expiration, + Email: user.Email, + UserStatus: user.UserStatus, + Locale: user.Locale, + YinbiEnabled: user.YinbiEnabled, + Inviters: user.Inviters, + Invitees: user.Invitees, + // Purchases: user.Purchases, + } + log.Debugf("ConvertToUserDetailsResponse %+v", &userData) + + for _, d := range user.Devices { + // Map the fields from LoginResponse_Device to UserDevice + userDevice := &protos.Device{ + Id: d.Id, + Name: d.GetName(), + Created: d.GetCreated(), + } + userData.Devices = append(userData.Devices, userDevice) + } + + return &userData +} + +// Takes password and email, salt and returns encrypted key +func GenerateEncryptedKey(password string, email string, salt []byte) *big.Int { + lowerCaseEmail := strings.ToLower(email) + combinedInput := password + lowerCaseEmail + encryptedKey := pbkdf2.Key([]byte(combinedInput), salt, 4096, 32, sha256.New) + encryptedKeyBigInt := big.NewInt(0).SetBytes(encryptedKey) + return encryptedKeyBigInt +} diff --git a/internalsdk/webclient/common.go b/internalsdk/webclient/common.go new file mode 100644 index 000000000..61f21b9a2 --- /dev/null +++ b/internalsdk/webclient/common.go @@ -0,0 +1,47 @@ +package webclient + +import ( + "net/http" + "strconv" + + "github.com/getlantern/lantern-client/internalsdk/common" + "github.com/go-resty/resty/v2" +) + +// Opts are common Opts that instances of RESTClient may be configured with +type Opts struct { + // HttpClient represents an http.Client that should be used by the resty client + HttpClient *http.Client + // UserConfig is a function that returns the user config associated with a Lantern user + UserConfig func() common.UserConfig +} + +// AddCommonUserHeaders adds all common headers that are user or device specific. +func AddCommonUserHeaders(uc common.UserConfig, req *resty.Request) { + params := map[string]string{} + if deviceID := uc.GetDeviceID(); deviceID != "" { + params[common.DeviceIdHeader] = deviceID + } + if userID := strconv.FormatInt(uc.GetUserID(), 10); userID != "" && userID != "0" { + params[common.UserIdHeader] = userID + } + if token := uc.GetToken(); token != "" { + params[common.ProTokenHeader] = token + } + // Include all the internal headers + for k, v := range uc.GetInternalHeaders() { + if v != "" { + params[k] = v + } + } + req.SetHeaders(params) +} + +// AddInternalHeaders adds the common.UserConfig internal headers to the given request +func AddInternalHeaders(uc common.UserConfig, req *resty.Request) { + for k, v := range uc.GetInternalHeaders() { + if v != "" { + req.SetHeader(k, v) + } + } +} diff --git a/internalsdk/pro/webclient/defaultwebclient/rest.go b/internalsdk/webclient/defaultwebclient/rest.go similarity index 87% rename from internalsdk/pro/webclient/defaultwebclient/rest.go rename to internalsdk/webclient/defaultwebclient/rest.go index e12ef4c51..24c21a4ac 100644 --- a/internalsdk/pro/webclient/defaultwebclient/rest.go +++ b/internalsdk/webclient/defaultwebclient/rest.go @@ -7,7 +7,7 @@ import ( "github.com/getlantern/errors" "github.com/getlantern/golog" - "github.com/getlantern/lantern-client/internalsdk/pro/webclient" + "github.com/getlantern/lantern-client/internalsdk/webclient" "github.com/go-resty/resty/v2" ) @@ -54,7 +54,12 @@ func SendToURL(httpClient *http.Client, baseURL string, beforeRequest resty.PreR if err != nil { return nil, err } + + // command, _ := http2curl.GetCurlCommand(req.RawRequest) + // log.Debugf("curl command: %v", command) responseBody := resp.Body() + log.Debugf("response body: %v status code %v", string(responseBody), resp.StatusCode()) + if resp.StatusCode() < 200 || resp.StatusCode() >= 300 { log.Errorf("Unexpected status code %d\n\n%v", resp.StatusCode(), string(responseBody)) return nil, errors.New("Unexpected status code %d", resp.StatusCode()) diff --git a/internalsdk/pro/webclient/webclient.go b/internalsdk/webclient/webclient.go similarity index 65% rename from internalsdk/pro/webclient/webclient.go rename to internalsdk/webclient/webclient.go index d4dab4aeb..5b310f7e6 100644 --- a/internalsdk/pro/webclient/webclient.go +++ b/internalsdk/webclient/webclient.go @@ -6,6 +6,8 @@ import ( "net/http" "github.com/getlantern/golog" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" ) var ( @@ -21,6 +23,12 @@ type RESTClient interface { // Post the given body as JSON with the given querystring parameters and reads the result JSON into target. PostJSONReadingJSON(ctx context.Context, path string, params, body, target any) error + + // Get data from server and parse to protoc file + GetPROTOC(ctx context.Context, path string, params any, target protoreflect.ProtoMessage) error + + // PostPROTOC sends a POST request with protoc file and parse the response to protoc file + PostPROTOC(ctx context.Context, path string, params, body protoreflect.ProtoMessage, target protoreflect.ProtoMessage) error } // A function that can send RESTful requests and receive response bodies. @@ -35,9 +43,7 @@ type restClient struct { // Construct a REST client using the given SendRequest function func NewRESTClient(send SendRequest) RESTClient { - return &restClient{ - send: send, - } + return &restClient{send} } func (c *restClient) GetJSON(ctx context.Context, path string, params, target any) error { @@ -48,6 +54,19 @@ func (c *restClient) GetJSON(ctx context.Context, path string, params, target an return unmarshalJSON(path, b, target) } +func (c *restClient) GetPROTOC(ctx context.Context, path string, params any, target protoreflect.ProtoMessage) error { + body, err := c.send(ctx, http.MethodGet, path, params, nil) + if err != nil { + return err + } + err1 := proto.Unmarshal(body, target) + if err1 != nil { + return err1 + } + return nil + +} + func (c *restClient) PostFormReadingJSON(ctx context.Context, path string, params, target any) error { b, err := c.send(ctx, http.MethodPost, path, params, nil) if err != nil { @@ -68,6 +87,23 @@ func (c *restClient) PostJSONReadingJSON(ctx context.Context, path string, param return unmarshalJSON(path, b, target) } +func (c *restClient) PostPROTOC(ctx context.Context, path string, params, body protoreflect.ProtoMessage, target protoreflect.ProtoMessage) error { + bodyBytes, err := proto.Marshal(body) + if err != nil { + return err + } + bo, err := c.send(ctx, http.MethodPost, path, params, bodyBytes) + if err != nil { + log.Debugf("Error in sending request: %v", err) + return err + } + err1 := proto.Unmarshal(bo, target) + if err1 != nil { + return err1 + } + return nil +} + func unmarshalJSON(path string, b []byte, target any) error { err := json.Unmarshal(b, target) if err != nil { diff --git a/ios/Podfile b/ios/Podfile index f357745ef..0f4447593 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,12 +1,11 @@ source 'https://github.com/CocoaPods/Specs.git' -source 'https://github.com/cleveradssolutions/CAS-Specs.git' # Uncomment this line to define a global platform for your project platform :ios, '13.0' use_frameworks! :linkage => :static $casVersion = '~> 3.5.1' inhibit_all_warnings! # CocoaPods analytics sends network stats synchronously affecting flutter build latency. -ENV['COCOAPODS_DISABLE_STATS'] = 'false' +ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, @@ -37,8 +36,6 @@ target 'Runner' do flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) # pod 'SQLite.swift', '~> 0.12.2' pod 'Toast-Swift', '~> 5.0.1' - pod 'CleverAdsSolutions-Base', $casVersion - pod 'Sentry/HybridSDK', '~> 8.29.0' end # target 'LanternTests' do @@ -59,16 +56,15 @@ post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) target.build_configurations.each do |config| - config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '11.0' config.build_settings['ENABLE_BITCODE'] = 'NO' config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ '$(inherited)', ## dart: PermissionGroup.calendar - 'PERMISSION_EVENTS=0', + #'PERMISSION_EVENTS=0', ## dart: PermissionGroup.reminders - 'PERMISSION_REMINDERS=0', + #'PERMISSION_REMINDERS=0', ## dart: PermissionGroup.contacts # 'PERMISSION_CONTACTS=0', @@ -89,7 +85,7 @@ post_install do |installer| # 'PERMISSION_LOCATION=0', ## dart: PermissionGroup.notification - # 'PERMISSION_NOTIFICATIONS=0', + 'PERMISSION_NOTIFICATIONS=0', ## dart: PermissionGroup.mediaLibrary 'PERMISSION_MEDIA_LIBRARY=0', diff --git a/ios/Podfile.lock b/ios/Podfile.lock index e0dbb2ac4..2fb8b429e 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -4,7 +4,6 @@ PODS: - Flutter - audioplayers_darwin (0.0.1): - Flutter - - CleverAdsSolutions-Base (3.5.6) - connectivity_plus (0.0.1): - Flutter - FlutterMacOS @@ -80,6 +79,9 @@ PODS: - Google-Mobile-Ads-SDK (~> 11.2.0) - webview_flutter_wkwebview - GoogleUserMessagingPlatform (2.4.0) + - in_app_purchase_storekit (0.0.1): + - Flutter + - FlutterMacOS - integration_test (0.0.1): - Flutter - libwebp (1.3.2): @@ -139,15 +141,12 @@ PODS: - video_thumbnail (0.0.1): - Flutter - libwebp - - wakelock (0.0.1): - - Flutter - webview_flutter_wkwebview (0.0.1): - Flutter DEPENDENCIES: - app_links (from `.symlinks/plugins/app_links/ios`) - audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/ios`) - - CleverAdsSolutions-Base (~> 3.5.1) - connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - emoji_picker_flutter (from `.symlinks/plugins/emoji_picker_flutter/ios`) @@ -162,12 +161,12 @@ DEPENDENCIES: - flutter_uploader (from `.symlinks/plugins/flutter_uploader/ios`) - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - google_mobile_ads (from `.symlinks/plugins/google_mobile_ads/ios`) + - in_app_purchase_storekit (from `.symlinks/plugins/in_app_purchase_storekit/darwin`) - integration_test (from `.symlinks/plugins/integration_test/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - qr_code_scanner (from `.symlinks/plugins/qr_code_scanner/ios`) - - Sentry/HybridSDK (~> 8.29.0) - sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) @@ -176,12 +175,9 @@ DEPENDENCIES: - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`) - video_thumbnail (from `.symlinks/plugins/video_thumbnail/ios`) - - wakelock (from `.symlinks/plugins/wakelock/ios`) - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`) SPEC REPOS: - https://github.com/cleveradssolutions/CAS-Specs.git: - - CleverAdsSolutions-Base https://github.com/CocoaPods/Specs.git: - Alamofire - DKImagePickerController @@ -232,6 +228,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/fluttertoast/ios" google_mobile_ads: :path: ".symlinks/plugins/google_mobile_ads/ios" + in_app_purchase_storekit: + :path: ".symlinks/plugins/in_app_purchase_storekit/darwin" integration_test: :path: ".symlinks/plugins/integration_test/ios" package_info_plus: @@ -256,8 +254,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/video_player_avfoundation/darwin" video_thumbnail: :path: ".symlinks/plugins/video_thumbnail/ios" - wakelock: - :path: ".symlinks/plugins/wakelock/ios" webview_flutter_wkwebview: :path: ".symlinks/plugins/webview_flutter_wkwebview/ios" @@ -265,7 +261,6 @@ SPEC CHECKSUMS: Alamofire: 814429acc853c6c54ff123fc3d2ef66803823ce0 app_links: e70ca16b4b0f88253b3b3660200d4a10b4ea9795 audioplayers_darwin: 877d9a4d06331c5c374595e46e16453ac7eafa40 - CleverAdsSolutions-Base: b2b98815a732a72d75dadfde430c5f24eafafdd5 connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c @@ -284,6 +279,7 @@ SPEC CHECKSUMS: Google-Mobile-Ads-SDK: 5a6d005a6cb5b5e8f4c7b69ca05cdea79c181139 google_mobile_ads: 9379c80fdfa9988fb0e105a407890ff8deb3cf86 GoogleUserMessagingPlatform: f131fa7978d2ba88d7426702b057c2cc318e6595 + in_app_purchase_storekit: 8c3b0b3eb1b0f04efbff401c3de6266d4258d433 integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4 libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009 Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d @@ -306,9 +302,8 @@ SPEC CHECKSUMS: url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3 video_thumbnail: c4e2a3c539e247d4de13cd545344fd2d26ffafd1 - wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f webview_flutter_wkwebview: be0f0d33777f1bfd0c9fdcb594786704dbf65f36 -PODFILE CHECKSUM: 523488a8cf8e6f08da1cff051423582c25acb4eb +PODFILE CHECKSUM: edbeaea3b499feb7b2b276309b09c237de1a6cff -COCOAPODS: 1.15.2 +COCOAPODS: 1.14.3 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 00c8b3179..1cc81c2b0 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -31,12 +31,15 @@ 035BE7062A7122BC0084059A /* SessionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035BE7042A7122BC0084059A /* SessionModel.swift */; }; 036663D92A9E0C0E00595971 /* JsonUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036663D82A9E0C0E00595971 /* JsonUtils.swift */; }; 03802FA32A98C9B000DACDFC /* SubscriberRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03802FA22A98C9B000DACDFC /* SubscriberRequest.swift */; }; + 038562282C131388008958B6 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 038562272C131388008958B6 /* StoreKit.framework */; }; + 039D7C442B43E047007667CD /* KeychainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039D7C432B43E047007667CD /* KeychainService.swift */; }; 03A1AB142AA88BDA00FB41B2 /* VPNManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03A1AB132AA88BDA00FB41B2 /* VPNManager.swift */; }; 03A1AB322AA890AB00FB41B2 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A1AB312AA890AB00FB41B2 /* NetworkExtension.framework */; }; 03A1AB352AA890AB00FB41B2 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03A1AB342AA890AB00FB41B2 /* PacketTunnelProvider.swift */; }; 03A1AB3A2AA890AB00FB41B2 /* Tunnel.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 03A1AB302AA890AB00FB41B2 /* Tunnel.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 03A1AB422AA8930A00FB41B2 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03A1AB412AA8930A00FB41B2 /* Result.swift */; }; 03A1AB442AA8946400FB41B2 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03A1AB432AA8946400FB41B2 /* Constants.swift */; }; + 03D0F0F02B2C7FC300C32918 /* ModelExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03D0F0EF2B2C7FC300C32918 /* ModelExtension.swift */; }; 03F00B5F2AB07C8000E991E2 /* LoadingIndicatorManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03F00B5E2AB07C8000E991E2 /* LoadingIndicatorManager.swift */; }; 03F2FE342A6949EF0082B34C /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03F2FE332A6949EF0082B34C /* Logger.swift */; }; 03F4CD822A77E43C00F7BDD8 /* BaseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03F4CD812A77E43C00F7BDD8 /* BaseModel.swift */; }; @@ -109,7 +112,9 @@ 035FDDD42A6A547F007B15C8 /* Internalsdk.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Internalsdk.xcframework; sourceTree = ""; }; 036663D82A9E0C0E00595971 /* JsonUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JsonUtils.swift; sourceTree = ""; }; 03802FA22A98C9B000DACDFC /* SubscriberRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriberRequest.swift; sourceTree = ""; }; + 038562272C131388008958B6 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; 038EBA8F187F39DA9C6ED912 /* Pods-LanternTests.release-appium-test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LanternTests.release-appium-test.xcconfig"; path = "Target Support Files/Pods-LanternTests/Pods-LanternTests.release-appium-test.xcconfig"; sourceTree = ""; }; + 039D7C432B43E047007667CD /* KeychainService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainService.swift; sourceTree = ""; }; 03A1AB102AA8893900FB41B2 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; 03A1AB132AA88BDA00FB41B2 /* VPNManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VPNManager.swift; sourceTree = ""; }; 03A1AB152AA88BF200FB41B2 /* VPNBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VPNBase.swift; sourceTree = ""; }; @@ -121,6 +126,7 @@ 03A1AB412AA8930A00FB41B2 /* Result.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = ""; }; 03A1AB432AA8946400FB41B2 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 03C58B082AC18BE7003C9788 /* DatabaseFramework.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DatabaseFramework.framework; path = ../../../../DatabaseFramework.framework; sourceTree = ""; }; + 03D0F0EF2B2C7FC300C32918 /* ModelExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelExtension.swift; sourceTree = ""; }; 03D97B1B2AC1900E0092BFC2 /* Pods_DatabaseFramework.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Pods_DatabaseFramework.framework; path = "../../../../../Library/Developer/Xcode/DerivedData/DatabaseFramework-csnjouuwjwleiddbptbupimqigld/Build/Products/Debug-iphoneos/Pods_DatabaseFramework.framework"; sourceTree = ""; }; 03F00B5E2AB07C8000E991E2 /* LoadingIndicatorManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingIndicatorManager.swift; sourceTree = ""; }; 03F2FE332A6949EF0082B34C /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; @@ -188,6 +194,7 @@ files = ( ECA871BD2AC59104007C400D /* Pods_Runner.framework in Frameworks */, ECA871BF2AC59109007C400D /* NetworkExtension.framework in Frameworks */, + 038562282C131388008958B6 /* StoreKit.framework in Frameworks */, EC27DE902AC44FAD00B840F2 /* DBModule in Frameworks */, EC22F6242AC44C8E0017D36C /* Internalsdk.xcframework in Frameworks */, ); @@ -309,6 +316,8 @@ 03567DBF2AA9F47200A233EA /* FileUtils.swift */, 03F00B5E2AB07C8000E991E2 /* LoadingIndicatorManager.swift */, 0324CC2F2AE279CF00490B46 /* SentryUtils.swift */, + 03D0F0EF2B2C7FC300C32918 /* ModelExtension.swift */, + 039D7C432B43E047007667CD /* KeychainService.swift */, ); path = Utils; sourceTree = ""; @@ -416,6 +425,7 @@ FF47084EFEAB686FD7974BE4 /* Frameworks */ = { isa = PBXGroup; children = ( + 038562272C131388008958B6 /* StoreKit.framework */, 0315F55C2AC1C1A500D49F7B /* DatabaseFramework.framework */, 03D97B1B2AC1900E0092BFC2 /* Pods_DatabaseFramework.framework */, 03C58B082AC18BE7003C9788 /* DatabaseFramework.framework */, @@ -691,6 +701,7 @@ 0308A1342AAEF5130086157A /* VPNBase.swift in Sources */, 03F2FE342A6949EF0082B34C /* Logger.swift in Sources */, 0308A12E2AAEEF410086157A /* Events.swift in Sources */, + 03D0F0F02B2C7FC300C32918 /* ModelExtension.swift in Sources */, 0333203B2AC698FA00333DA9 /* MockVPNManager.swift in Sources */, 0308A12C2AAEEE2C0086157A /* UserNotificationsManager.swift in Sources */, 03FAF1B42AA1E9F40063580C /* VpnModel.swift in Sources */, @@ -706,6 +717,7 @@ 03567DC02AA9F47200A233EA /* FileUtils.swift in Sources */, 03802FA32A98C9B000DACDFC /* SubscriberRequest.swift in Sources */, 03A1AB422AA8930A00FB41B2 /* Result.swift in Sources */, + 039D7C442B43E047007667CD /* KeychainService.swift in Sources */, 0324CC302AE279CF00490B46 /* SentryUtils.swift in Sources */, 5FA8BFF12BFB34F80097D1B9 /* FlashlightManagerExtension.swift in Sources */, 0308A1282AAEED7E0086157A /* VpnHelper.swift in Sources */, @@ -808,7 +820,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -827,7 +839,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 12; + CURRENT_PROJECT_VERSION = 35; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 4FYC28AXA2; ENABLE_BITCODE = NO; @@ -837,13 +849,12 @@ ); INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Lantern; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - LINK_WITH_STANDARD_LIBRARIES = YES; - MARKETING_VERSION = 7.5.0; + MARKETING_VERSION = 7.5.18; PRODUCT_BUNDLE_IDENTIFIER = org.getlantern.lantern; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -974,7 +985,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -995,7 +1006,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 12; + CURRENT_PROJECT_VERSION = 35; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 4FYC28AXA2; ENABLE_BITCODE = NO; @@ -1005,13 +1016,12 @@ ); INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Lantern; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - LINK_WITH_STANDARD_LIBRARIES = YES; - MARKETING_VERSION = 7.5.0; + MARKETING_VERSION = 7.5.18; PRODUCT_BUNDLE_IDENTIFIER = org.getlantern.lantern; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1139,7 +1149,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -1159,7 +1169,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 12; + CURRENT_PROJECT_VERSION = 35; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 4FYC28AXA2; ENABLE_BITCODE = NO; @@ -1169,13 +1179,12 @@ ); INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Lantern; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - LINK_WITH_STANDARD_LIBRARIES = YES; - MARKETING_VERSION = 7.5.0; + MARKETING_VERSION = 7.5.18; PRODUCT_BUNDLE_IDENTIFIER = org.getlantern.lantern; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1309,7 +1318,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -1328,7 +1337,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 12; + CURRENT_PROJECT_VERSION = 35; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 4FYC28AXA2; ENABLE_BITCODE = NO; @@ -1338,13 +1347,12 @@ ); INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Lantern; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - LINK_WITH_STANDARD_LIBRARIES = YES; - MARKETING_VERSION = 7.5.0; + MARKETING_VERSION = 7.5.18; PRODUCT_BUNDLE_IDENTIFIER = org.getlantern.lantern; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1602,7 +1610,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -1622,7 +1630,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 12; + CURRENT_PROJECT_VERSION = 35; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 4FYC28AXA2; ENABLE_BITCODE = NO; @@ -1632,13 +1640,12 @@ ); INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Lantern; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - LINK_WITH_STANDARD_LIBRARIES = YES; - MARKETING_VERSION = 7.5.0; + MARKETING_VERSION = 7.5.18; PRODUCT_BUNDLE_IDENTIFIER = org.getlantern.lantern; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1698,7 +1705,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -1749,7 +1756,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -1770,7 +1777,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 12; + CURRENT_PROJECT_VERSION = 35; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 4FYC28AXA2; ENABLE_BITCODE = NO; @@ -1780,13 +1787,12 @@ ); INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Lantern; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - LINK_WITH_STANDARD_LIBRARIES = YES; - MARKETING_VERSION = 7.5.0; + MARKETING_VERSION = 7.5.18; PRODUCT_BUNDLE_IDENTIFIER = org.getlantern.lantern; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1809,7 +1815,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 12; + CURRENT_PROJECT_VERSION = 35; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 4FYC28AXA2; ENABLE_BITCODE = NO; @@ -1819,13 +1825,12 @@ ); INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Lantern; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - LINK_WITH_STANDARD_LIBRARIES = YES; - MARKETING_VERSION = 7.5.0; + MARKETING_VERSION = 7.5.18; PRODUCT_BUNDLE_IDENTIFIER = org.getlantern.lantern; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1885,7 +1890,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -1904,7 +1909,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 12; + CURRENT_PROJECT_VERSION = 35; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 4FYC28AXA2; ENABLE_BITCODE = NO; @@ -1914,13 +1919,12 @@ ); INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Lantern; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - LINK_WITH_STANDARD_LIBRARIES = YES; - MARKETING_VERSION = 7.5.0; + MARKETING_VERSION = 7.5.18; PRODUCT_BUNDLE_IDENTIFIER = org.getlantern.lantern; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/prod.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/prod.xcscheme index 4e44cd149..90233b0c6 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/prod.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/prod.xcscheme @@ -23,7 +23,7 @@ + buildConfiguration = "Release-prod"> (Domain: group.getlantern.lantern, User: kCFPreferencesAnyUser, ByHost: Yes, Container: (null), Contents Need Refresh: Yes): Using kCFPreferencesAnyUser with a container is only allowed for System Containers, + +// For IOS App Lunch time issue +//https://developer.apple.com/videos/play/wwdc2019/423/?time=305 @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { // Flutter Properties @@ -14,46 +19,45 @@ import UIKit private var lanternModel: LanternModel! private var vpnModel: VpnModel! private var messagingModel: MessagingModel! - // IOS - var loadingManager: LoadingIndicatorManager? override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { initializeFlutterComponents() - do { - try setupAppComponents() - } catch { - logger.error("Unexpected error setting up app components: \(error)") - exit(1) - } + try! setupModels() + try! setupAppComponents() GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } // Flutter related stuff private func initializeFlutterComponents() { - flutterViewController = window?.rootViewController as! FlutterViewController - flutterbinaryMessenger = flutterViewController.binaryMessenger + if flutterViewController == nil || flutterbinaryMessenger == nil { + flutterViewController = window?.rootViewController as! FlutterViewController + flutterbinaryMessenger = flutterViewController.binaryMessenger + } + } // Intlize this GO model and callback private func setupAppComponents() throws { - try self.setupModels() - self.startUpSequency() - self.setupLoadingBar() + startUpSequency() } // Init all the models private func setupModels() throws { logger.log("setupModels method called") - sessionModel = try SessionModel(flutterBinary: flutterbinaryMessenger) - lanternModel = LanternModel(flutterBinary: flutterbinaryMessenger) + + // If flutterbinaryMessenger nil somehow then assign it again + if flutterbinaryMessenger == nil || flutterViewController == nil { + initializeFlutterComponents() + } + lanternModel = LanternModel(flutterBinary: self.flutterbinaryMessenger) + sessionModel = try SessionModel(flutterBinary: self.flutterbinaryMessenger) vpnModel = try VpnModel( - flutterBinary: flutterbinaryMessenger, vpnBase: VPNManager.appDefault, + flutterBinary: self.flutterbinaryMessenger, vpnBase: VPNManager.appDefault, sessionModel: sessionModel) - // navigationModel = NavigationModel(flutterBinary: flutterbinaryMessenger) messagingModel = try MessagingModel(flutterBinary: flutterbinaryMessenger) } @@ -76,8 +80,4 @@ import UIKit } } - func setupLoadingBar() { - loadingManager = LoadingIndicatorManager(parentView: flutterViewController.view) - } - } diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index bb4b7ab00..505a369e0 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -19,13 +19,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - NSUserTrackingUsageDescription - This identifier will be used to deliver personalized ads to you. + 7.5.21 CFBundleSignature ???? CFBundleVersion - 14 + 38 CURRENT_SCHEME_NAME $(CURRENT_SCHEME_NAME) GADApplicationIdentifier @@ -41,6 +39,8 @@ Lantern require access to your photo library for essential functionality. NSPhotoLibraryUsageDescription Lantern require access to your photo library for essential functionality. + NSUserTrackingUsageDescription + This identifier will be used to deliver personalized ads to you. SKAdNetworkItems @@ -260,7 +260,7 @@ UIInterfaceOrientationPortraitUpsideDown UIViewControllerBasedStatusBarAppearance - + io.flutter.embedded_views_preview diff --git a/ios/Runner/Lantern/Core/Vpn/FlashlightManagerExtension.swift b/ios/Runner/Lantern/Core/Vpn/FlashlightManagerExtension.swift index c86ad34ca..b3239e9ba 100644 --- a/ios/Runner/Lantern/Core/Vpn/FlashlightManagerExtension.swift +++ b/ios/Runner/Lantern/Core/Vpn/FlashlightManagerExtension.swift @@ -15,7 +15,7 @@ extension FlashlightManager { ) { var configError: NSError? let configDirectory = constants.configDirectoryURL.path - let deviceID = UIDevice.current.identifierForVendor!.uuidString + let deviceID = DeviceIdentifier.getUDID() let workItem = DispatchWorkItem { logger.debug("Calling IosConfigure") diff --git a/ios/Runner/Lantern/Models/BaseModel.swift b/ios/Runner/Lantern/Models/BaseModel.swift index 410acc48f..fd0af57b2 100644 --- a/ios/Runner/Lantern/Models/BaseModel.swift +++ b/ios/Runner/Lantern/Models/BaseModel.swift @@ -45,11 +45,15 @@ open class BaseModel: NSObject, FlutterStreamHandle internal static func getDatabasePath() -> String { let fileManager = FileManager.default - let dbDir = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! - .appendingPathComponent("masterDBv2") + + var dbDir = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! + .appendingPathComponent("masterDBv3") + var values = URLResourceValues() + values.isExcludedFromBackup = true do { try fileManager.createDirectory(at: dbDir, withIntermediateDirectories: true, attributes: nil) let dbLocation = dbDir.appendingPathComponent("db").path + try dbDir.setResourceValues(values) logger.log("DB location \(dbLocation)") return dbLocation } catch { @@ -148,9 +152,9 @@ open class BaseModel: NSObject, FlutterStreamHandle internal func doOnMethodCall(call: FlutterMethodCall, result: @escaping FlutterResult) { do { + let arguments = call.arguments != nil ? try Arguments(call.arguments!) : nil let invocationResult = try model.invokeMethod( - call.method, arguments: Arguments(call.arguments)) - + call.method, arguments: arguments) if let originalValue = ValueUtil.convertFromMinisqlValue( from: invocationResult as! MinisqlValue) { @@ -175,8 +179,11 @@ open class BaseModel: NSObject, FlutterStreamHandle // Handle all other errors else { result( - FlutterError(code: "UNKNOWN_ERROR", message: error.localizedDescription, details: nil)) + FlutterError(code: String(error.code), message: error.localizedDescription, details: nil)) } + } catch let error as NSException { + result( + FlutterError(code: error.name.rawValue, message: error.reason, details: nil)) } } diff --git a/ios/Runner/Lantern/Models/LanternModel.swift b/ios/Runner/Lantern/Models/LanternModel.swift index 07fe74f69..b6bdf2a9e 100644 --- a/ios/Runner/Lantern/Models/LanternModel.swift +++ b/ios/Runner/Lantern/Models/LanternModel.swift @@ -20,6 +20,8 @@ class LanternModel: NSObject, FlutterStreamHandler { var flutterbinaryMessenger: FlutterBinaryMessenger init(flutterBinary: FlutterBinaryMessenger) { + logger.log("Initializing LanternModel") + self.flutterbinaryMessenger = flutterBinary super.init() diff --git a/ios/Runner/Lantern/Models/SessionModel.swift b/ios/Runner/Lantern/Models/SessionModel.swift index 6bbff6b8c..e75b07fae 100644 --- a/ios/Runner/Lantern/Models/SessionModel.swift +++ b/ios/Runner/Lantern/Models/SessionModel.swift @@ -19,20 +19,21 @@ class SessionModel: BaseModel { private let sessionAsyncHandler = DispatchQueue.global(qos: .background) init(flutterBinary: FlutterBinaryMessenger) throws { + logger.log("Initializing SessionModel") let opts = InternalsdkSessionModelOpts() let device = UIDevice.current - let deviceId = device.identifierForVendor!.uuidString - let deviceModel = device.model - let systemName = device.systemName + let deviceId = DeviceIdentifier.getUDID() + let modelName = UIDevice.modelName let systemVersion = device.systemVersion + let systemName = device.systemName opts.deviceID = deviceId opts.lang = Locale.current.identifier - opts.developmentMode = false - opts.proUser = false + opts.developmentMode = !isRunningFromAppStore() && !isRunningInTestFlightEnvironment() opts.playVersion = (isRunningFromAppStore() || isRunningInTestFlightEnvironment()) + opts.timeZone = TimeZone.current.identifier - opts.device = systemName // IOS does not provide Device name directly - opts.model = deviceModel + opts.device = modelName + opts.model = modelName opts.osVersion = systemVersion opts.paymentTestMode = AppEnvironment.current == AppEnvironment.appiumTest opts.platform = "ios" @@ -89,7 +90,7 @@ class SessionModel: BaseModel { Constants.appGroupDefaults.set(proToken, forKey: Constants.proToken) logger.log("Sucessfully got protoken \(proToken)") } else { - logger.log("Failed to get user id") + logger.log("Failed to get protoken") } } } catch { diff --git a/ios/Runner/Lantern/Utils/Constants.swift b/ios/Runner/Lantern/Utils/Constants.swift index f524a5c95..508cf6bea 100644 --- a/ios/Runner/Lantern/Utils/Constants.swift +++ b/ios/Runner/Lantern/Utils/Constants.swift @@ -64,6 +64,7 @@ struct Constants { process: Process, sharedContainerURL: URL = Constants.appGroupContainerURL ) { + self.sharedContainerURL = sharedContainerURL self.configDirectoryURL = sharedContainerURL.appendingPathComponent("config", isDirectory: true) diff --git a/ios/Runner/Lantern/Utils/FileUtils.swift b/ios/Runner/Lantern/Utils/FileUtils.swift index 7a85e9bab..a32e07352 100644 --- a/ios/Runner/Lantern/Utils/FileUtils.swift +++ b/ios/Runner/Lantern/Utils/FileUtils.swift @@ -23,7 +23,12 @@ extension FileManager { @discardableResult func ensureFilesExist(at urls: [URL]) -> Bool { var overallSuccess = true + // // Make sure ICloud does not backdup all file + // var values = URLResourceValues() + // values.isExcludedFromBackup = true + urls.forEach { url in + // url.setResourceValues(values) let path = url.path if !fileExists(atPath: path) { // posix permission 666 is `rw-rw-rw` aka read/write for all diff --git a/ios/Runner/Lantern/Utils/KeychainService.swift b/ios/Runner/Lantern/Utils/KeychainService.swift new file mode 100644 index 000000000..7974b2a7d --- /dev/null +++ b/ios/Runner/Lantern/Utils/KeychainService.swift @@ -0,0 +1,60 @@ +// +// KeychainService.swift +// Runner +// +// Created by jigar fumakiya on 02/01/24. +// + +import Foundation +import Security + +// Service that store deviced user id to keychanin +// so when user comes back fetch there IDs from key chain +class KeychainService { + static func save(_ data: Data, for key: String) { + let query = + [ + kSecClass as String: kSecClassGenericPassword as String, + kSecAttrAccount as String: key, + kSecValueData as String: data, + ] as [String: Any] + + SecItemDelete(query as CFDictionary) + SecItemAdd(query as CFDictionary, nil) + } + + static func load(key: String) -> Data? { + let query = + [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: key, + kSecReturnData as String: kCFBooleanTrue!, + kSecMatchLimit as String: kSecMatchLimitOne, + ] as [String: Any] + + var dataTypeRef: AnyObject? = nil + let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef) + + if status == noErr { + return dataTypeRef as? Data + } else { + return nil + } + } +} + +class DeviceIdentifier { + static let key = "lantern.udid" + + static func getUDID() -> String { + if let retrievedData = KeychainService.load(key: key), + let udid = String(data: retrievedData, encoding: .utf8) + { + return udid + } else { + let newUDID = UUID().uuidString + KeychainService.save(newUDID.data(using: .utf8)!, for: key) + return newUDID + } + } +} diff --git a/ios/Runner/Lantern/Utils/ModelExtension.swift b/ios/Runner/Lantern/Utils/ModelExtension.swift new file mode 100644 index 000000000..b6f8560d3 --- /dev/null +++ b/ios/Runner/Lantern/Utils/ModelExtension.swift @@ -0,0 +1,126 @@ +// +// ModelExtension.swift +// Runner +// +// Created by jigar fumakiya on 15/12/23. +// + +import UIKit + +extension UIDevice { + + public static let modelName: String = { + var systemInfo = utsname() + uname(&systemInfo) + let machineMirror = Mirror(reflecting: systemInfo.machine) + let identifier = machineMirror.children.reduce("") { identifier, element in + guard let value = element.value as? Int8, value != 0 else { return identifier } + return identifier + String(UnicodeScalar(UInt8(value))) + } + + func mapToDevice(identifier: String) -> String { // swiftlint:disable:this cyclomatic_complexity + #if os(iOS) + switch identifier { + case "iPod5,1": return "iPod touch (5th generation)" + case "iPod7,1": return "iPod touch (6th generation)" + case "iPod9,1": return "iPod touch (7th generation)" + case "iPhone3,1", "iPhone3,2", "iPhone3,3": return "iPhone 4" + case "iPhone4,1": return "iPhone 4s" + case "iPhone5,1", "iPhone5,2": return "iPhone 5" + case "iPhone5,3", "iPhone5,4": return "iPhone 5c" + case "iPhone6,1", "iPhone6,2": return "iPhone 5s" + case "iPhone7,2": return "iPhone 6" + case "iPhone7,1": return "iPhone 6 Plus" + case "iPhone8,1": return "iPhone 6s" + case "iPhone8,2": return "iPhone 6s Plus" + case "iPhone9,1", "iPhone9,3": return "iPhone 7" + case "iPhone9,2", "iPhone9,4": return "iPhone 7 Plus" + case "iPhone10,1", "iPhone10,4": return "iPhone 8" + case "iPhone10,2", "iPhone10,5": return "iPhone 8 Plus" + case "iPhone10,3", "iPhone10,6": return "iPhone X" + case "iPhone11,2": return "iPhone XS" + case "iPhone11,4", "iPhone11,6": return "iPhone XS Max" + case "iPhone11,8": return "iPhone XR" + case "iPhone12,1": return "iPhone 11" + case "iPhone12,3": return "iPhone 11 Pro" + case "iPhone12,5": return "iPhone 11 Pro Max" + case "iPhone13,1": return "iPhone 12 mini" + case "iPhone13,2": return "iPhone 12" + case "iPhone13,3": return "iPhone 12 Pro" + case "iPhone13,4": return "iPhone 12 Pro Max" + case "iPhone14,4": return "iPhone 13 mini" + case "iPhone14,5": return "iPhone 13" + case "iPhone14,2": return "iPhone 13 Pro" + case "iPhone14,3": return "iPhone 13 Pro Max" + case "iPhone14,7": return "iPhone 14" + case "iPhone14,8": return "iPhone 14 Plus" + case "iPhone15,2": return "iPhone 14 Pro" + case "iPhone15,3": return "iPhone 14 Pro Max" + case "iPhone15,4": return "iPhone 15" + case "iPhone15,5": return "iPhone 15 Plus" + case "iPhone16,1": return "iPhone 15 Pro" + case "iPhone16,2": return "iPhone 15 Pro Max" + case "iPhone8,4": return "iPhone SE" + case "iPhone12,8": return "iPhone SE (2nd generation)" + case "iPhone14,6": return "iPhone SE (3rd generation)" + case "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4": return "iPad 2" + case "iPad3,1", "iPad3,2", "iPad3,3": return "iPad (3rd generation)" + case "iPad3,4", "iPad3,5", "iPad3,6": return "iPad (4th generation)" + case "iPad6,11", "iPad6,12": return "iPad (5th generation)" + case "iPad7,5", "iPad7,6": return "iPad (6th generation)" + case "iPad7,11", "iPad7,12": return "iPad (7th generation)" + case "iPad11,6", "iPad11,7": return "iPad (8th generation)" + case "iPad12,1", "iPad12,2": return "iPad (9th generation)" + case "iPad13,18", "iPad13,19": return "iPad (10th generation)" + case "iPad4,1", "iPad4,2", "iPad4,3": return "iPad Air" + case "iPad5,3", "iPad5,4": return "iPad Air 2" + case "iPad11,3", "iPad11,4": return "iPad Air (3rd generation)" + case "iPad13,1", "iPad13,2": return "iPad Air (4th generation)" + case "iPad13,16", "iPad13,17": return "iPad Air (5th generation)" + case "iPad2,5", "iPad2,6", "iPad2,7": return "iPad mini" + case "iPad4,4", "iPad4,5", "iPad4,6": return "iPad mini 2" + case "iPad4,7", "iPad4,8", "iPad4,9": return "iPad mini 3" + case "iPad5,1", "iPad5,2": return "iPad mini 4" + case "iPad11,1", "iPad11,2": return "iPad mini (5th generation)" + case "iPad14,1", "iPad14,2": return "iPad mini (6th generation)" + case "iPad6,3", "iPad6,4": return "iPad Pro (9.7-inch)" + case "iPad7,3", "iPad7,4": return "iPad Pro (10.5-inch)" + case "iPad8,1", "iPad8,2", "iPad8,3", "iPad8,4": + return "iPad Pro (11-inch) (1st generation)" + case "iPad8,9", "iPad8,10": return "iPad Pro (11-inch) (2nd generation)" + case "iPad13,4", "iPad13,5", "iPad13,6", "iPad13,7": + return "iPad Pro (11-inch) (3rd generation)" + case "iPad14,3", "iPad14,4": return "iPad Pro (11-inch) (4th generation)" + case "iPad6,7", "iPad6,8": return "iPad Pro (12.9-inch) (1st generation)" + case "iPad7,1", "iPad7,2": return "iPad Pro (12.9-inch) (2nd generation)" + case "iPad8,5", "iPad8,6", "iPad8,7", "iPad8,8": + return "iPad Pro (12.9-inch) (3rd generation)" + case "iPad8,11", "iPad8,12": return "iPad Pro (12.9-inch) (4th generation)" + case "iPad13,8", "iPad13,9", "iPad13,10", "iPad13,11": + return "iPad Pro (12.9-inch) (5th generation)" + case "iPad14,5", "iPad14,6": return "iPad Pro (12.9-inch) (6th generation)" + case "AppleTV5,3": return "Apple TV" + case "AppleTV6,2": return "Apple TV 4K" + case "AudioAccessory1,1": return "HomePod" + case "AudioAccessory5,1": return "HomePod mini" + case "i386", "x86_64", "arm64": + return + "Simulator \(mapToDevice(identifier: ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "iOS"))" + default: return identifier + } + #elseif os(tvOS) + switch identifier { + case "AppleTV5,3": return "Apple TV 4" + case "AppleTV6,2": return "Apple TV 4K" + case "i386", "x86_64": + return + "Simulator \(mapToDevice(identifier: ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "tvOS"))" + default: return identifier + } + #endif + } + + return mapToDevice(identifier: identifier) + }() + +} diff --git a/lib/account/account.dart b/lib/account/account.dart index 6bdcfd77e..4b8226fa0 100644 --- a/lib/account/account.dart +++ b/lib/account/account.dart @@ -3,9 +3,14 @@ import 'package:lantern/common/common.dart'; import 'package:lantern/messaging/messaging_model.dart'; @RoutePage(name: 'Account') -class AccountMenu extends StatelessWidget { +class AccountMenu extends StatefulWidget { const AccountMenu({Key? key}) : super(key: key); + @override + State createState() => _AccountMenuState(); +} + +class _AccountMenuState extends State { Future authorizeDeviceForPro(BuildContext context) async => await context.pushRoute(AuthorizePro()); @@ -21,6 +26,53 @@ class AccountMenu extends StatelessWidget { context.pushRoute(const Support()); } + Future onSignOut() async { + try { + context.loaderOverlay.show(); + await sessionModel.signOut(); + context.loaderOverlay.hide(); + } catch (e) { + mainLogger.e('Error signing out', error: e); + context.loaderOverlay.hide(); + } + } + + void showSingOutDialog(BuildContext context) { + CDialog( + title: 'sign_out'.i18n, + description: "sign_out_message".i18n, + icon: const CAssetImage( + path: ImagePaths.signOut, + height: 40, + ), + agreeText: "sign_out".i18n, + dismissText: "not_now".i18n, + includeCancel: true, + agreeAction: () async { + onSignOut(); + return true; + }, + dismissAction: () async {}, + ).show(context); + } + + void onAccountManagementTap( + BuildContext context, bool isProUser, bool hasUserLoggedIn) { + if (Platform.isIOS) { + if (hasUserLoggedIn) { + // User has gone through onboarding + context.pushRoute(AccountManagement(isPro: isProUser)); + } else { + // Ask user to update their email and password + showProUserDialog(context); + } + } else { + context.pushRoute(AccountManagement(isPro: isProUser)); + } + } + + void openSignIn(BuildContext context) => context.pushRoute(SignIn()); + void upgradeToLanternPro(BuildContext context) async => await context.pushRoute(const PlansPage()); @@ -36,8 +88,15 @@ class AccountMenu extends StatelessWidget { ); } - List freeItems(BuildContext context, SessionModel sessionModel) { + List freeItems(BuildContext context, bool hasUserLoggedIn) { return [ + if(Platform.isIOS) + if (!hasUserLoggedIn) + ListItemFactory.settingsItem( + icon: ImagePaths.signIn, + content: 'sign_in'.i18n, + onTap: () => openSignIn(context), + ), if (Platform.isAndroid) messagingModel.getOnBoardingStatus( (context, hasBeenOnboarded, child) => hasBeenOnboarded == true @@ -80,15 +139,13 @@ class AccountMenu extends StatelessWidget { ListItemFactory.settingsItem( icon: ImagePaths.devices, content: 'Authorize Device for Pro'.i18n, - onTap: () { - authorizeDeviceForPro(context); - }, + onTap: () => authorizeDeviceForPro(context), ), - ...commonItems(context) + ...commonItems(context, hasUserLoggedIn) ]; } - List proItems(BuildContext context) { + List proItems(BuildContext context, bool hasUserLoggedIn) { return [ messagingModel.getOnBoardingStatus( (context, hasBeenOnboarded, child) => @@ -98,8 +155,8 @@ class AccountMenu extends StatelessWidget { key: AppKeys.account_management, icon: ImagePaths.account, content: 'account_management'.i18n, - onTap: () async => - await context.pushRoute(AccountManagement(isPro: true)), + onTap: () => + onAccountManagementTap(context, true, hasUserLoggedIn), trailingArray: [ if (!hasCopiedRecoveryKey && hasBeenOnboarded == true) const CAssetImage( @@ -115,17 +172,16 @@ class AccountMenu extends StatelessWidget { inviteFriends(context); }, ), - ListItemFactory.settingsItem( - icon: ImagePaths.devices, - content: 'add_device'.i18n, - onTap: () async => await context.pushRoute(ApproveDevice()), - ), - ...commonItems(context) + icon: ImagePaths.devices, + content: 'add_device'.i18n, + onTap: () async => await context.pushRoute(ApproveDevice()), + ), + ...commonItems(context, hasUserLoggedIn) ]; } - List commonItems(BuildContext context) { + List commonItems(BuildContext context, bool hasUserLoggedIn) { return [ if (isMobile()) ListItemFactory.settingsItem( @@ -157,6 +213,13 @@ class AccountMenu extends StatelessWidget { openSettings(context); }, ), + if (Platform.isIOS) + if (hasUserLoggedIn) + ListItemFactory.settingsItem( + icon: ImagePaths.signOut, + content: 'sign_out'.i18n, + onTap: () => showSingOutDialog(context), + ) ]; } @@ -167,10 +230,20 @@ class AccountMenu extends StatelessWidget { automaticallyImplyLeading: false, body: sessionModel .proUser((BuildContext sessionContext, bool proUser, Widget? child) { + if (Platform.isIOS) { + return sessionModel.isUserSignedIn((context, hasUserLoggedIn, child) { + return ListView( + children: proUser + ? proItems(sessionContext, hasUserLoggedIn) + : freeItems(sessionContext, hasUserLoggedIn), + ); + }); + } + return ListView( children: proUser - ? proItems(sessionContext) - : freeItems(sessionContext, sessionModel), + ? proItems(sessionContext, false) + : freeItems(sessionContext, false), ); }), ); diff --git a/lib/account/account_management.dart b/lib/account/account_management.dart index 09fd28001..6ce3bcd86 100644 --- a/lib/account/account_management.dart +++ b/lib/account/account_management.dart @@ -14,6 +14,7 @@ class _AccountManagementState extends State with SingleTickerProviderStateMixin { late final TabController tabController; bool textCopied = false; + @override void initState() { tabController = TabController(length: 2, vsync: this); @@ -26,6 +27,102 @@ class _AccountManagementState extends State tabController.dispose(); } + void openChangeEmail(String emailAddress) { + context.router.push(ChangeEmail(email: emailAddress)); + } + + void openEmailVerification(String emailAddress) { + sessionModel.signUpEmailResendCode(emailAddress); + context + .pushRoute( + Verification(email: emailAddress, authFlow: AuthFlow.verifyEmail)) + .then((value) => setState(() {})); + } + + void showDeleteAccountDialog() { + final passwordFormKey = GlobalKey(); + late final passwordController = CustomTextEditingController( + formKey: passwordFormKey, + validator: (value) { + if (value!.isEmpty) { + return 'password_cannot_be_empty'.i18n; + } + if (value.length < 8) { + return 'password_must_be_at_least_8_characters'.i18n; + } + return null; + }, + ); + CDialog( + icon: CAssetImage( + path: ImagePaths.alert, + size: 24, + color: red, + ), + title: 'delete_your_account'.i18n, + description: Column( + children: [ + CText( + "delete_account_message".i18n, + style: tsBody1.copiedWith( + color: grey5, + ), + ), + const SizedBox(height: 16.0), + CPasswordTextFiled( + label: "enter_password".i18n, + passwordFormKey: passwordFormKey, + passwordCustomTextEditingController: passwordController, + ), + const SizedBox(height: 16.0), + ], + ), + agreeText: 'cancel'.i18n, + dismissText: "confirm_deletion".i18n, + barrierDismissible: false, + autoCloseOnDismiss: false, + agreeAction: () async { + return true; + }, + dismissAction: () async { + if (passwordController.text.length < 8) { + return; + } + Future.delayed(const Duration(milliseconds: 200), + () => deleteAccount(passwordController.text)); + }, + ).show(context); + } + + Future deleteAccount(String password) async { + try { + context.loaderOverlay.show(); + await sessionModel.deleteAccount(password); + context.loaderOverlay.hide(); + showAccountDeletedDialog(); + } catch (e) { + context.loaderOverlay.hide(); + mainLogger.e("Error while deleting account", error: e); + CDialog.showError(context, description: e.localizedDescription); + } + } + + void showAccountDeletedDialog() { + CDialog( + iconPath: ImagePaths.check_green_large, + title: "account_deleted".i18n, + description: "account_deleted_message".i18n, + barrierDismissible: false, + includeCancel: false, + agreeAction: () async { + Future.delayed(const Duration(milliseconds: 300), () { + context.router.popUntilRoot(); + }); + return true; + }, + ).show(context); + } + @override Widget build(BuildContext context) { var title = widget.isPro @@ -38,15 +135,15 @@ class _AccountManagementState extends State body: !widget.isPro ? freeItemsWidget() : sessionModel.chatEnabled( - (context, chatEnabled, child) { - return messagingModel - .getOnBoardingStatus((context, hasBeenOnboarded, child) { - return (hasBeenOnboarded == true) && chatEnabled - ? _buildTapBar() - : _buildProItem(); - }); - }, - )); + (context, chatEnabled, child) { + return messagingModel + .getOnBoardingStatus((context, hasBeenOnboarded, child) { + return (hasBeenOnboarded == true) && chatEnabled + ? _buildTapBar() + : _buildProItem(); + }); + }, + )); } Widget _buildTapBar() { @@ -101,7 +198,7 @@ class _AccountManagementState extends State ), children: [ messagingModel.me( - (BuildContext context, Contact me, Widget? child) => StatefulBuilder( + (BuildContext context, Contact me, Widget? child) => StatefulBuilder( builder: (context, setState) => ListItemFactory.settingsItem( header: 'your_chat_number'.i18n, icon: ImagePaths.chatNumber, @@ -121,7 +218,7 @@ class _AccountManagementState extends State setState(() => textCopied = true); await Future.delayed( defaultAnimationDuration, - () => setState(() => textCopied = false), + () => setState(() => textCopied = false), ); }, child: CAssetImage( @@ -142,26 +239,26 @@ class _AccountManagementState extends State ), // * RECOVERY KEY messagingModel.getCopiedRecoveryStatus( - ( - BuildContext context, - bool hasCopiedRecoveryKey, - Widget? child, - ) => + ( + BuildContext context, + bool hasCopiedRecoveryKey, + Widget? child, + ) => ListItemFactory.settingsItem( - icon: ImagePaths.lock_outline, - content: 'backup_recovery_key'.i18n, - trailingArray: [ - if (!hasCopiedRecoveryKey) - const Padding( - padding: EdgeInsetsDirectional.only(start: 16.0, end: 16.0), - child: CAssetImage( - path: ImagePaths.badge, - ), - ), - mirrorLTR(context: context, child: const ContinueArrow()) - ], - onTap: () => context.router.push(RecoveryKey()), - ), + icon: ImagePaths.lock_outline, + content: 'backup_recovery_key'.i18n, + trailingArray: [ + if (!hasCopiedRecoveryKey) + const Padding( + padding: EdgeInsetsDirectional.only(start: 16.0, end: 16.0), + child: CAssetImage( + path: ImagePaths.badge, + ), + ), + mirrorLTR(context: context, child: const ContinueArrow()) + ], + onTap: () => context.router.push(RecoveryKey()), + ), ), // * Delete all Chat data ListItemFactory.settingsItem( @@ -195,7 +292,6 @@ class _AccountManagementState extends State ); } - Widget _buildProItem() { return ListView( padding: const EdgeInsetsDirectional.only( @@ -205,23 +301,29 @@ class _AccountManagementState extends State ), children: [ sessionModel.emailAddress(( - BuildContext context, - String emailAddress, - Widget? child, - ) { + BuildContext context, + String emailAddress, + Widget? child, + ) { return ListItemFactory.settingsItem( header: 'lantern_pro_email'.i18n, icon: ImagePaths.email, content: emailAddress, - trailingArray: [], + trailingArray: [ + ], ); }), - + if(Platform.isIOS) + ListItemFactory.settingsItem( + header: 'password'.i18n, + icon: ImagePaths.lockFiled, + content: "********", + ), sessionModel.expiryDate(( - BuildContext context, - String expirationDate, - Widget? child, - ) { + BuildContext context, + String expirationDate, + Widget? child, + ) { return ListItemFactory.settingsItem( key: AppKeys.account_renew, header: 'Pro Account Expiration'.i18n, @@ -237,14 +339,41 @@ class _AccountManagementState extends State }), //Disable device linking in IOS const UserDevices(), - + if(Platform.isIOS) + ListItemFactory.settingsItem( + header: 'danger_zone'.i18n, + icon: ImagePaths.alert, + content: "delete_account".i18n, + trailingArray: [ + Padding( + padding: const EdgeInsetsDirectional.only(start: 16.0), + child: TextButton( + onPressed: showDeleteAccountDialog, + child: CText( + 'delete'.i18n.toUpperCase(), + style: tsButtonPink, + ), + ), + ), + ], + ) ], ); } + Future onUnlink(BuildContext context, String deviceId) async { + try { + context.loaderOverlay.show(); + await sessionModel.removeDevice(deviceId); + context.loaderOverlay.hide(); + context.popRoute(); + } catch (e) { + context.loaderOverlay.hide(); + showError(context, error: e); + } + } } - class UserDevices extends StatelessWidget { const UserDevices({super.key}); @@ -288,53 +417,51 @@ class UserDevices extends StatelessWidget { context.popRoute(); } catch (e) { context.loaderOverlay.hide(); - showError(context,error: e); - + CDialog.showError(context, description: e.localizedDescription); } } @override Widget build(BuildContext context) { return sessionModel.deviceId((context, myDeviceId, child) => sessionModel - .devices((BuildContext context, Devices devices, Widget? child) { - return Column(children: [ - ...devices.devices.map((device) { - var isMyDevice = device.id == myDeviceId; - var allowRemoval = devices.devices.length > 1 || !isMyDevice; - var index = devices.devices.indexWhere((d) => d == device); - return Padding( - padding: const EdgeInsetsDirectional.only(start: 4), - child: ListItemFactory.settingsItem( - header: index == 0 ? 'pro_devices_header'.i18n : null, - content: device.name, - onTap: - !allowRemoval ? null : () => onRemoveTap(context, device), - trailingArray: !allowRemoval - ? [] - : [ - CText( - (isMyDevice ? 'Log Out' : 'Remove') - .i18n - .toUpperCase(), - style: tsButtonPink, - ) - ], - ), - ); - }).toList(), - // IOS does not support Link devices at the moment - if (devices.devices.length < 3) - ListItemFactory.settingsItem( - content: '', - onTap: () async => await context.pushRoute(ApproveDevice()), - trailingArray: [ - CText( - 'Link Device'.i18n.toUpperCase(), - style: tsButtonPink, + .devices((BuildContext context, Devices devices, Widget? child) { + return Column(children: [ + ...devices.devices.map((device) { + var isMyDevice = device.id == myDeviceId; + var allowRemoval = devices.devices.length > 1 || !isMyDevice; + var index = devices.devices.indexWhere((d) => d == device); + return Padding( + padding: const EdgeInsetsDirectional.only(start: 4), + child: ListItemFactory.settingsItem( + header: index == 0 ? 'pro_devices_header'.i18n : null, + content: device.name, + onTap: + !allowRemoval ? null : () => onRemoveTap(context, device), + trailingArray: !allowRemoval + ? [] + : [ + CText( + (isMyDevice ? '' : 'Remove').i18n.toUpperCase(), + style: tsButtonPink, + ) + ], + ), + ); + }).toList(), + // IOS does not need device linking + // User should use Username and Password flow + if (devices.devices.length < 3) + ListItemFactory.settingsItem( + content: '', + onTap: () async => await context.pushRoute(ApproveDevice()), + trailingArray: [ + CText( + 'link_device'.i18n.toUpperCase(), + style: tsButtonPink, + ) + ], ) - ], - ) - ]); - })); + ]); + })); } } diff --git a/lib/account/auth/auth_landing.dart b/lib/account/auth/auth_landing.dart new file mode 100644 index 000000000..51432a74b --- /dev/null +++ b/lib/account/auth/auth_landing.dart @@ -0,0 +1,88 @@ +import 'package:flutter/gestures.dart'; + +import '../../common/common.dart'; + +@RoutePage(name: 'AuthLanding') +class AuthLanding extends StatelessWidget { + const AuthLanding({super.key}); + + @override + Widget build(BuildContext context) { + return BaseScreen( + showAppBar: false, + body: sessionModel.proUser((context, proUser, child) { + return _buildBody(context, proUser); + }), + ); + } + + Widget _buildBody(BuildContext context, bool proUser) { + return SafeArea( + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset(ImagePaths.lantern_logo), + const SizedBox(height: 16), + SvgPicture.asset(ImagePaths.lantern_logotype), + const SizedBox(height: 32.0), + SizedBox( + width: double.infinity, + child: Button( + text: proUser ? 'update_pro_account'.i18n : 'sign_in'.i18n, + onPressed: () => openSignIn(context, proUser), + ), + ), + const SizedBox(height: 24.0), + SizedBox( + width: double.infinity, + child: Button( + text: proUser ? 'sign_in'.i18n : 'get_lantern_pro'.i18n, + secondary: true, + onPressed: () => openPlans(context, proUser), + ), + ), + const SizedBox(height: 32.0), + if (!proUser) + RichText( + text: TextSpan( + text: 'try_lantern_pro'.i18n, + style: tsBody1.copyWith( + fontWeight: FontWeight.w400, color: grey5), + children: [ + TextSpan( + text: "continue_for_free".i18n.toUpperCase(), + style: tsBody1.copyWith( + fontWeight: FontWeight.w500, color: pink5), + recognizer: TapGestureRecognizer() + ..onTap = () => openHomePage(context), + ), + ], + ), + ), + ], + ), + ), + ); + } + + void openHomePage(BuildContext context) { + context.router.maybePop(); + } + + void openSignIn(BuildContext context, bool proUser) { + if (proUser) { + context.router.popAndPush(SignIn(authFlow: AuthFlow.updateAccount)); + } else { + context.router.popAndPush(SignIn(authFlow: AuthFlow.signIn)); + } + } + + void openPlans(BuildContext context, bool proUser) { + if (proUser) { + context.router.popAndPush(SignIn(authFlow: AuthFlow.signIn)); + } else { + context.router.popAndPush(const PlansPage()); + } + } +} diff --git a/lib/account/auth/change_email.dart b/lib/account/auth/change_email.dart new file mode 100644 index 000000000..13afedeb4 --- /dev/null +++ b/lib/account/auth/change_email.dart @@ -0,0 +1,145 @@ +// ignore_for_file: use_build_context_synchronously + +import 'package:email_validator/email_validator.dart'; + +import '../../common/common.dart'; + +@RoutePage(name: 'ChangeEmail') +class ChangeEmail extends StatefulWidget { + final String email; + + const ChangeEmail({ + super.key, + required this.email, + }); + + @override + State createState() => _ChangeEmailState(); +} + +class _ChangeEmailState extends State { + final _newEmailFormKey = GlobalKey(); + late final _newEmailController = CustomTextEditingController( + formKey: _newEmailFormKey, + validator: (value) => EmailValidator.validate(value ?? '') + ? null + : 'please_enter_a_valid_email_address'.i18n, + ); + + bool obscureText = false; + final _passwordFormKey = GlobalKey(); + late final _passwordController = CustomTextEditingController( + formKey: _passwordFormKey, + ); + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return BaseScreen( + title: const AppBarProHeader(), + body: _buildBody(), + ); + } + + Widget _buildBody() { + return Column( + children: [ + const SizedBox(height: 24), + HeadingText( + title: 'change_email'.i18n, + ), + const SizedBox(height: 24), + Form( + key: _newEmailFormKey, + child: CTextField( + controller: _newEmailController, + label: "new_email".i18n, + inputFormatters: [EmojiFilteringTextInputFormatter()], + autovalidateMode: AutovalidateMode.onUserInteraction, + textInputAction: TextInputAction.done, + keyboardType: TextInputType.emailAddress, + prefixIcon: SvgPicture.asset(ImagePaths.email), + onChanged: (value) { + setState(() {}); + }, + ), + ), + const SizedBox(height: 20), + CPasswordTextFiled( + label: "password".i18n, + passwordFormKey: _passwordFormKey, + passwordCustomTextEditingController: _passwordController, + onChanged: (vaule) { + setState(() {}); + }, + ), + const SizedBox(height: 24), + SizedBox( + width: double.infinity, + child: Button( + disabled: isButtonDisabled(), + text: 'change_email'.i18n, + onPressed: onChangeEmail, + ), + ), + ], + ); + } + + ///Widget methods + bool isButtonDisabled() { + if (EmailValidator.validate(_newEmailController.text) && + _passwordController.text.length > 8) { + return false; + } + return true; + } + + Future onChangeEmail() async { + //Close keyboard + FocusManager.instance.primaryFocus?.unfocus(); + if (widget.email.validateEmail.toLowerCase() == + _newEmailController.text.validateEmail.toLowerCase()) { + CDialog.showError(context, + description: 'new_email_same_as_old_email'.i18n); + return; + } + + try { + context.loaderOverlay.show(); + await sessionModel.startChangeEmail(widget.email, _newEmailController.text, _passwordController.text); + context.loaderOverlay.hide(); + context.pushRoute( + Verification( + email: _newEmailController.text.validateEmail, + authFlow: AuthFlow.changeEmail, + changeEmailArgs: ChangeEmailPageArgs( + widget.email, + _newEmailController.text.validateEmail, + _passwordController.text, + ), + ), + ); + } catch (e) { + context.loaderOverlay.hide(); + CDialog.showError(context, description: e.localizedDescription); + } + } +} + +class ChangeEmailPageArgs { + final String email; + final String newEmail; + final String password; + + ChangeEmailPageArgs(this.email, this.newEmail, this.password); +} diff --git a/lib/account/auth/confirm_email.dart b/lib/account/auth/confirm_email.dart new file mode 100644 index 000000000..e3d4287ee --- /dev/null +++ b/lib/account/auth/confirm_email.dart @@ -0,0 +1,15 @@ +import '../../common/common.dart'; +@RoutePage(name: 'ConfirmEmail') +class ConfirmEmail extends StatefulWidget { + const ConfirmEmail({super.key}); + + @override + State createState() => _ConfirmEmailState(); +} + +class _ConfirmEmailState extends State { + @override + Widget build(BuildContext context) { + return const Placeholder(); + } +} diff --git a/lib/account/auth/create_account_email.dart b/lib/account/auth/create_account_email.dart new file mode 100644 index 000000000..5a531a3e0 --- /dev/null +++ b/lib/account/auth/create_account_email.dart @@ -0,0 +1,179 @@ +// ignore_for_file: use_build_context_synchronously + +import 'package:email_validator/email_validator.dart'; +import 'package:flutter/gestures.dart'; +import 'package:lantern/common/ui/custom/email_tag.dart'; + +import '../../common/common.dart'; + +@RoutePage(name: 'CreateAccountEmail') +class CreateAccountEmail extends StatefulWidget { + final Plan? plan; + final AuthFlow authFlow; + + const CreateAccountEmail({ + super.key, + this.plan, + this.authFlow = AuthFlow.createAccount, + }); + + @override + State createState() => _CreateAccountEmailState(); +} + +class _CreateAccountEmailState extends State { + final _emailFormKey = GlobalKey(); + late final _emailController = CustomTextEditingController( + formKey: _emailFormKey, + validator: (value) => EmailValidator.validate(value ?? '') + ? null + : 'please_enter_a_valid_email_address'.i18n, + ); + + @override + Widget build(BuildContext context) { + return BaseScreen( + title: const AppBarProHeader(), + body: _buildBody(context), + ); + } + + Widget _buildBody(BuildContext context) { + return SizedBox( + width: double.infinity, + child: SafeArea( + child: Column( + children: [ + const SizedBox(height: 24), + HeadingText(title: 'create_account'.i18n), + const SizedBox(height: 24), + Form( + key: _emailFormKey, + child: CTextField( + inputFormatters: [ + EmojiFilteringTextInputFormatter(), + ], + controller: _emailController, + label: "enter_email".i18n, + autovalidateMode: AutovalidateMode.onUserInteraction, + textInputAction: TextInputAction.done, + keyboardType: TextInputType.emailAddress, + prefixIcon: SvgPicture.asset(ImagePaths.email), + onChanged: (value) { + setState(() {}); + }, + ), + ), + const SizedBox(height: 24), + SizedBox( + width: double.infinity, + child: Button( + disabled: _emailController.text.isEmpty || + _emailFormKey?.currentState?.validate() == false, + text: 'continue'.i18n, + onPressed: onContinue, + ), + ), + const SizedBox(height: 24), + RichText( + text: TextSpan( + text: 'already_have_an_account'.i18n, + style: + tsBody1.copyWith(fontWeight: FontWeight.w400, color: grey5), + children: [ + TextSpan( + text: "sign_in".i18n.toUpperCase(), + style: tsBody1.copyWith( + fontWeight: FontWeight.w500, color: pink5), + recognizer: TapGestureRecognizer()..onTap = openSignInFlow, + ), + ], + ), + ), + ], + ), + ), + ); + } + + ///Widget methods + + void openSignInFlow() { + context.pushRoute(SignIn()); + } + + void onContinue() { + FocusManager.instance.primaryFocus?.unfocus(); + createAccount(); + } + + void _showEmailVerificationDialog({required VoidCallback onVerified}) { + CDialog( + title: "check_your_email".i18n, + description: Column( + mainAxisSize: MainAxisSize.min, + children: [ + CText( + "please_verify_email".i18n, + style: + tsBody1.copiedWith(fontWeight: FontWeight.w400, color: grey5), + ), + const SizedBox(height: 24.0), + EmailTag(email: _emailController.text.validateEmail), + const SizedBox(height: 24.0), + ], + ), + barrierDismissible: false, + dismissText: 'change_email'.i18n.toUpperCase(), + agreeText: "verify".i18n.toUpperCase(), + agreeAction: () async { + context.popRoute(); + Future.delayed( + const Duration(milliseconds: 300), + () { + onVerified.call(); + }, + ); + return false; + }, + ).show(context); + } + + /// Process for creating account + /// Create new temp account with random password + /// Once account is created by pass email verification start forgot password flow + Future createAccount() async { + try { + context.loaderOverlay.show(); + final userTempPass = AppMethods().generatePassword(); + mainLogger.d('Generated password is $userTempPass'); + await sessionModel.signUp( + _emailController.text.validateEmail, userTempPass); + //start forgot password flow + forgotPasswordFlow(userTempPass); + } catch (e, s) { + mainLogger.e('Error while creating account', error: e, stackTrace: s); + context.loaderOverlay.hide(); + CDialog.showError(context, description: e.localizedDescription); + } + } + + //forgot password flow + Future forgotPasswordFlow(String userTempPass) async { + try { + final email = _emailController.text.validateEmail; + //Send verification code to email + await sessionModel.startRecoveryByEmail(email); + context.loaderOverlay.hide(); + context.pushRoute(Verification( + email: email, + authFlow: widget.authFlow, + plan: widget.plan, + tempPassword: userTempPass)); + } catch (e, s) { + mainLogger.w('Error starting recovery', error: e, stackTrace: s); + context.loaderOverlay.hide(); + CDialog.showError(context, description: e.localizedDescription); + } + } +} diff --git a/lib/account/auth/create_account_password.dart b/lib/account/auth/create_account_password.dart new file mode 100644 index 000000000..8ebbd23c7 --- /dev/null +++ b/lib/account/auth/create_account_password.dart @@ -0,0 +1,174 @@ +// ignore_for_file: use_build_context_synchronously + +import 'package:flutter/gestures.dart'; +import 'package:lantern/common/ui/password_criteria.dart'; + +import '../../common/common.dart'; +import '../../plans/utils.dart'; + +@RoutePage(name: 'CreateAccountPassword') +class CreateAccountPassword extends StatefulWidget { + final String email; + final String code; + + const CreateAccountPassword({ + super.key, + required this.email, + required this.code + }); + + @override + State createState() => _CreateAccountPasswordState(); +} + +class _CreateAccountPasswordState extends State { + bool obscureText = false; + final _passwordFormKey = GlobalKey(); + late final _passwordController = CustomTextEditingController( + formKey: _passwordFormKey, + ); + + @override + Widget build(BuildContext context) { + return BaseScreen( + title: const AppBarProHeader(), + body: _buildBody(context), + ); + } + + Widget _buildBody(BuildContext context) { + return SizedBox( + width: double.infinity, + child: SafeArea( + child: SingleChildScrollView( + child: Column( + children: [ + const SizedBox(height: 24), + HeadingText( + title: 'create_password'.i18n, + ), + const SizedBox(height: 24), + _buildEmail(), + const SizedBox(height: 24), + CPasswordTextFiled( + label: "create_password".i18n, + passwordFormKey: _passwordFormKey, + passwordCustomTextEditingController: _passwordController, + onChanged: (vaule) { + setState(() {}); + }, + ), + const SizedBox(height: 14), + PasswordCriteriaWidget( + textEditingController: _passwordController, + ), + const SizedBox(height: 24), + SizedBox( + width: double.infinity, + child: Button( + disabled: (!_passwordController.text.isPasswordValid()), + text: 'continue'.i18n, + onPressed: onContinueTap, + ), + ), + const SizedBox(height: 24), + Text.rich( + TextSpan( + children: [ + TextSpan( + text: 'by_creating_an_account'.i18n, + style: tsFloatingLabel, + ), + const TextSpan( + text: ' ', + ), + TextSpan( + text: 'terms_of_service'.i18n, + style: tsFloatingLabel!.copiedWith( + decoration: TextDecoration.underline, + ), + recognizer: TapGestureRecognizer() + ..onTap = openTermsOfService, + ), + ], + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 20), + ], + ), + ), + ), + ); + } + + Widget _buildEmail() { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(100), + color: grey1, + border: Border.all( + width: 1, + color: grey3, + ), + ), + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SvgPicture.asset( + ImagePaths.email, + ), + const SizedBox(width: 8), + CText(widget.email, + textAlign: TextAlign.center, + style: tsBody1!.copiedWith( + leadingDistribution: TextLeadingDistribution.even, + )) + ], + ), + ); + } + + ///Widget methods + + Future onContinueTap() async { + //Close keyboard + FocusManager.instance.primaryFocus?.unfocus(); + try { + context.loaderOverlay.show(); + await sessionModel.completeRecoveryByEmail( + widget.email, _passwordController.text, widget.code); + context.loaderOverlay.hide(); + postSignUp(); + } catch (e, s) { + context.loaderOverlay.hide(); + CDialog.showError(context, description: e.localizedDescription); + } + } + + void postSignUp() { + // Show success dialog and send user to root page + showSuccessDialog( + context, + false, + barrierDismissible: false, + onAgree: () { + Future.delayed( + const Duration(milliseconds: 300), + () { + //If all successful then send user to back to root + context.router.popUntilRoot(); + }, + ); + }, + ); + } + + void openTermsOfService() { + FocusScope.of(context).unfocus(); + context.pushRoute(AppWebview(url: termsOfService)); + } +} diff --git a/lib/account/auth/reset_password.dart b/lib/account/auth/reset_password.dart new file mode 100644 index 000000000..10f6cf988 --- /dev/null +++ b/lib/account/auth/reset_password.dart @@ -0,0 +1,196 @@ +import 'package:flutter/cupertino.dart'; + +import '../../common/common.dart'; +import '../../common/ui/password_criteria.dart'; + +@RoutePage(name: 'ResetPassword') +class ResetPassword extends StatefulWidget { + final String? email; + final String? code; + final AuthFlow authFlow; + + const ResetPassword({ + super.key, + this.email, + this.code, + this.authFlow = AuthFlow.reset, + }); + + @override + State createState() => _ResetPasswordState(); +} + +class _ResetPasswordState extends State { + bool obscureText = false; + final _passwordFormKey = GlobalKey(); + late final _passwordController = CustomTextEditingController( + formKey: _passwordFormKey, + ); + final _confirmPasswordFormKey = GlobalKey(); + late final _confirmPasswordController = CustomTextEditingController( + formKey: _confirmPasswordFormKey, + validator: (value) { + if (value!.isEmpty) { + return "Confirm Password is required"; + } + if (value != _passwordController.text) { + return "Confirm Password is not match"; + } + return null; + }, + ); + + @override + Widget build(BuildContext context) { + return BaseScreen( + title: const AppBarProHeader(), + body: _buildBody(context), + ); + } + + Widget _buildBody(BuildContext context) { + return SizedBox( + width: double.infinity, + child: SafeArea( + child: SingleChildScrollView( + child: Column( + children: [ + const SizedBox(height: 24), + HeadingText(title: 'reset_password'.i18n), + const SizedBox(height: 24), + Form( + key: _passwordFormKey, + child: CTextField( + controller: _passwordController, + label: "new_password".i18n, + autovalidateMode: AutovalidateMode.onUserInteraction, + textInputAction: TextInputAction.done, + keyboardType: TextInputType.visiblePassword, + obscureText: obscureText, + maxLines: 1, + prefixIcon: SvgPicture.asset(ImagePaths.lock), + suffixIcon: GestureDetector( + onTap: () { + setState(() { + obscureText = !obscureText; + }); + }, + child: obscureText + ? const Icon(CupertinoIcons.eye_slash_fill) + : SvgPicture.asset(ImagePaths.eye)), + onChanged: (value) { + setState(() {}); + }, + ), + ), + const SizedBox(height: 14), + Form( + key: _confirmPasswordFormKey, + child: CTextField( + controller: _confirmPasswordController, + label: "confirm_new_password".i18n, + autovalidateMode: AutovalidateMode.onUserInteraction, + textInputAction: TextInputAction.done, + keyboardType: TextInputType.visiblePassword, + obscureText: obscureText, + maxLines: 1, + prefixIcon: SvgPicture.asset(ImagePaths.lock), + suffixIcon: GestureDetector( + onTap: () { + setState(() { + obscureText = !obscureText; + }); + }, + child: obscureText + ? const Icon(CupertinoIcons.eye_slash_fill) + : SvgPicture.asset(ImagePaths.eye), + ), + // suffix: SvgPicture.asset(ImagePaths.eye), + onChanged: (value) { + setState(() {}); + }, + ), + ), + const SizedBox(height: 24), + PasswordCriteriaWidget( + textEditingController: _passwordController), + const SizedBox(height: 24), + SizedBox( + width: double.infinity, + child: Button( + disabled: + (!_confirmPasswordController.text.isPasswordValid()), + text: 'reset_password'.i18n, + onPressed: onResetPasswordTap, + ), + ), + const SizedBox(height: 24), + ], + ), + ), + ), + ); + } + + Future onResetPasswordTap() async { + try { + context.loaderOverlay.show(); + await sessionModel.completeRecoveryByEmail( + widget.email!, _passwordController.text, widget.code!); + context.loaderOverlay.hide(); + if (widget.authFlow == AuthFlow.updateAccount) { + showContinueDialog(); + } else { + showPasswordSuccessDialog(); + } + } catch (e) { + mainLogger.e(e); + context.loaderOverlay.hide(); + CDialog.showError(context, description: e.localizedDescription); + } + } + + void showPasswordSuccessDialog() { + CDialog( + icon: const CAssetImage(path: ImagePaths.check_green_large), + title: "password_has_been_updated".i18n, + description: "password_has_been_updated_message".i18n, + barrierDismissible: false, + dismissText: "continue".i18n, + agreeText: "sign_in".i18n, + includeCancel: true, + dismissAction: () async { + Future.delayed(const Duration(milliseconds: 300), () { + context.router.popUntilRoot(); + }); + }, + agreeAction: () async { + print("agree"); + Future.delayed(const Duration(milliseconds: 300), () { + context.router.pushAndPopUntil( + SignIn(), + predicate: (route) => route.isFirst, + ); + }); + return true; + }, + ).show(context); + } + + void showContinueDialog() { + CDialog( + icon: const CAssetImage(path: ImagePaths.check_green_large), + title: "password_has_been_updated".i18n, + description: "password_has_been_updated_message".i18n, + barrierDismissible: false, + agreeText: "continue".i18n, + includeCancel: false, + agreeAction: () async { + Future.delayed(const Duration(milliseconds: 300), () { + context.router.popUntilRoot(); + }); + return true; + }, + ).show(context); + } +} diff --git a/lib/account/auth/sign_in.dart b/lib/account/auth/sign_in.dart new file mode 100644 index 000000000..dfe3a7eca --- /dev/null +++ b/lib/account/auth/sign_in.dart @@ -0,0 +1,195 @@ +import 'package:email_validator/email_validator.dart'; +import 'package:flutter/gestures.dart'; + +import '../../common/common.dart'; + +/// Test users +// jigar+iosa@getlantern.org +// Jigar@123 +@RoutePage(name: 'SignIn') +class SignIn extends StatefulWidget { + final AuthFlow authFlow; + + const SignIn({ + super.key, + this.authFlow = AuthFlow.signIn, + }); + + @override + State createState() => _SignInState(); +} + +class _SignInState extends State { + final _emailFormKey = GlobalKey(); + late final _emailController = CustomTextEditingController( + formKey: _emailFormKey, + validator: (value) => EmailValidator.validate(value ?? '') + ? null + : 'please_enter_a_valid_email_address'.i18n, + ); + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + setState(() { + _emailController.text = sessionModel.userEmail.value ?? ""; + }); + }); + } + + @override + Widget build(BuildContext context) { + return BaseScreen( + title: const AppBarProHeader(), + body: _buildBody(context), + ); + } + + Widget _buildBody(BuildContext context) { + return SizedBox( + width: double.infinity, + child: SafeArea( + child: Column( + children: [ + const SizedBox(height: 24), + HeadingText( + title: widget.authFlow.isReset || widget.authFlow.isUpdateAccount + ? 'reset_password'.i18n + : 'sign_in'.i18n, + ), + const SizedBox(height: 24), + Form( + key: _emailFormKey, + child: CTextField( + controller: _emailController, + label: widget.authFlow.isReset || widget.authFlow.isReset + ? "lantern_pro_email".i18n + : "enter_email".i18n, + textInputAction: TextInputAction.done, + keyboardType: TextInputType.emailAddress, + prefixIcon: SvgPicture.asset(ImagePaths.email), + onChanged: (value) { + setState(() {}); + }, + ), + ), + const SizedBox(height: 24), + SizedBox( + width: double.infinity, + child: Button( + disabled: _emailController.text.isEmpty || + _emailFormKey?.currentState?.validate() == false, + text: widget.authFlow.isReset ? "next".i18n : 'continue'.i18n, + onPressed: onTapResolved), + ), + const SizedBox(height: 24), + if (widget.authFlow.isSignIn && + sessionModel.hasUserSignedInNotifier.value == false) + RichText( + text: TextSpan( + text: 'new_to_lantern'.i18n, + style: tsBody1.copyWith( + fontWeight: FontWeight.w400, color: grey5), + children: [ + TextSpan( + text: "create_account".i18n.toUpperCase(), + style: tsBody1.copyWith( + fontWeight: FontWeight.w500, color: pink5), + recognizer: TapGestureRecognizer()..onTap = openPlans, + ), + ], + ), + ), + ], + ), + ), + ); + } + + void onTapResolved() { + switch (widget.authFlow) { + case AuthFlow.reset: + resetPasswordFlow(); + break; + case AuthFlow.updateAccount: + createAccount(); + break; + default: + openCreatePassword(); + break; + } + } + + ///Widget methods + void openCreatePassword() { + context.pushRoute(SignInPassword(email: _emailController.text)); + } + + Future resetPasswordFlow() async { + try { + FocusManager.instance.primaryFocus?.unfocus(); + context.loaderOverlay.show(); + await sessionModel + .startRecoveryByEmail(_emailController.text.validateEmail); + context.loaderOverlay.hide(); + openVerification(); + } catch (e) { + print(e.localizedDescription); + context.loaderOverlay.hide(); + CDialog.showError(context, description: 'Error while seeding email'); + } + } + + void openVerification() { + context.pushRoute(Verification( + email: _emailController.text, + authFlow: AuthFlow.reset, + )); + } + + void returnToSignIn() { + context.maybePop(); + } + + Future openPlans() async { + await context.pushRoute(const PlansPage()); + } + + /// Process for creating account + /// Create new temp account with random password + /// Once account is created by pass email verification start forgot password flow + /// THis needs to be done to make sure user has legit email + Future createAccount() async { + try { + context.loaderOverlay.show(); + final userTempPass = AppMethods().generatePassword(); + await sessionModel.signUp( + _emailController.text.validateEmail, userTempPass); + //start forgot password flow + forgotPasswordFlow(userTempPass); + } catch (e, s) { + mainLogger.e('Error while creating account', error: e, stackTrace: s); + context.loaderOverlay.hide(); + CDialog.showError(context, description: e.localizedDescription); + } + } + + //forgot password flow + Future forgotPasswordFlow(String userTempPass) async { + try { + final email = _emailController.text.validateEmail; + //Send verification code to email + await sessionModel.startRecoveryByEmail(email); + context.loaderOverlay.hide(); + context.pushRoute(Verification( + email: _emailController.text, + authFlow: widget.authFlow, + tempPassword: userTempPass)); + } catch (e, s) { + mainLogger.w('Error starting recovery', error: e, stackTrace: s); + context.loaderOverlay.hide(); + CDialog.showError(context, description: e.localizedDescription); + } + } +} diff --git a/lib/account/auth/sign_in_password.dart b/lib/account/auth/sign_in_password.dart new file mode 100644 index 000000000..a40eb860d --- /dev/null +++ b/lib/account/auth/sign_in_password.dart @@ -0,0 +1,129 @@ +import 'package:flutter/gestures.dart'; + +import '../../common/common.dart'; + +@RoutePage(name: 'SignInPassword') +class SignInPassword extends StatefulWidget { + final String email; + + const SignInPassword({ + super.key, + required this.email, + }); + + @override + State createState() => _SignInPasswordState(); +} + +class _SignInPasswordState extends State { + final _passwordFormKey = GlobalKey(); + late final _passwordController = CustomTextEditingController( + formKey: _passwordFormKey, + validator: (value) { + if (value!.isEmpty) { + return 'password_cannot_be_empty'.i18n; + } + if (value.length < 8) { + return 'password_must_be_at_least_8_characters'.i18n; + } + return null; + }, + ); + + bool obscureText = false; + + @override + Widget build(BuildContext context) { + return BaseScreen( + title: const AppBarProHeader(), + body: _buildBody(context), + ); + } + + Widget _buildBody(BuildContext context) { + return SizedBox( + width: double.infinity, + child: SafeArea( + child: Column( + children: [ + const SizedBox(height: 24), + HeadingText(title: 'enter_password'.i18n), + const SizedBox(height: 24), + CPasswordTextFiled( + label: "enter_password".i18n, + passwordFormKey: _passwordFormKey, + passwordCustomTextEditingController: _passwordController, + onChanged: (vaule) { + setState(() {}); + }, + ), + const SizedBox(height: 24), + SizedBox( + width: double.infinity, + child: Button( + disabled: _passwordController.text.length < 8, + text: 'continue'.i18n, + onPressed: onContinueTap, + ), + ), + const SizedBox(height: 24), + RichText( + text: TextSpan( + text: 'forgot_your_password'.i18n, + style: + tsBody1.copyWith(fontWeight: FontWeight.w400, color: grey5), + children: [ + TextSpan( + text: "click_here".i18n.toUpperCase(), + style: tsBody1.copyWith( + fontWeight: FontWeight.w500, color: pink5), + recognizer: TapGestureRecognizer() + ..onTap = openResetPasswordFlow, + ), + ], + ), + ), + ], + ), + ), + ); + } + + @override + void dispose() { + super.dispose(); + } + + /// Widget methods + void openResetPasswordFlow() { + //Pop current route and push SignIn so back stack will handle + context.router.popAndPush(SignIn(authFlow: AuthFlow.reset)); + } + + void onContinueTap() async { + //Close keyboard + FocusManager.instance.primaryFocus?.unfocus(); + try { + context.loaderOverlay.show(); + await sessionModel.login(widget.email, _passwordController.text); + context.loaderOverlay.hide(); + context.router.popUntilRoot(); + } catch (error) { + mainLogger.e("Error while sign in ", error: error); + context.loaderOverlay.hide(); + + /// User has connected more then 3 device + /// Show screen to user to remove device + if ((error as PlatformException).message!.contains("too-many-devices")) { + context.pushRoute(const DeviceLimit()).then((value) { + if (value != null && value as bool) { + mainLogger.i("Device has been removed"); + onContinueTap(); + } + }); + return; + } + CDialog.showError(context, description: error.localizedDescription); + } + } +} diff --git a/lib/account/auth/verification.dart b/lib/account/auth/verification.dart new file mode 100644 index 000000000..0d18ef881 --- /dev/null +++ b/lib/account/auth/verification.dart @@ -0,0 +1,317 @@ +// ignore_for_file: use_build_context_synchronously + +import 'package:lantern/core/purchase/app_purchase.dart'; +import 'package:lantern/vpn/vpn.dart'; + +import '../../common/common.dart'; +import 'change_email.dart'; + +@RoutePage(name: 'Verification') +class Verification extends StatefulWidget { + final String email; + final AuthFlow authFlow; + final Plan? plan; + final ChangeEmailPageArgs? changeEmailArgs; + final String? tempPassword; + + const Verification({ + super.key, + required this.email, + this.authFlow = AuthFlow.reset, + this.changeEmailArgs, + this.plan, + this.tempPassword, + }); + + @override + State createState() => _VerificationState(); +} + +class _VerificationState extends State { + final pinCodeController = TextEditingController(); + + @override + Widget build(BuildContext context) { + return BaseScreen( + title: const AppBarProHeader(), + onBackButtonPressed: onBackPressed, + body: _buildBody(context), + ); + } + + Widget _buildBody(BuildContext context) { + return SizedBox( + width: double.infinity, + child: SafeArea( + child: SingleChildScrollView( + child: Column( + children: [ + const SizedBox(height: 24), + HeadingText( + title: widget.authFlow.isCreateAccount || + widget.authFlow.isVerifyEmail + ? 'confirm_email'.i18n + : 'reset_password'.i18n, + ), + const SizedBox(height: 24), + CText( + "enter_confirmation_code".i18n.toUpperCase(), + style: tsOverline, + ), + const SizedBox(height: 8), + PinField( + length: 6, + controller: pinCodeController, + onDone: onDone, + ), + LabeledDivider( + padding: const EdgeInsetsDirectional.only(top: 24, bottom: 10), + ), + Align( + alignment: Alignment.centerLeft, + child: CText( + 'confirmation_code_msg'.i18n.replaceAll('XX', widget.email), + style: tsBody1, + ), + ), + const SizedBox(height: 24), + Button( + text: "resend_confirmation_code".i18n, + onPressed: resendConfirmationCode, + ), + const SizedBox(height: 14), + if (!widget.authFlow.isVerifyEmail && + !widget.authFlow.isCreateAccount && + !widget.authFlow.isProCodeActivation) + AppTextButton( + text: 'change_email'.i18n.toUpperCase(), + onPressed: () { + context.maybePop(); + }, + ), + ], + ), + ), + ), + ); + } + + /// widget methods + Future resendConfirmationCode() async { + switch (widget.authFlow) { + case AuthFlow.createAccount: + resendResetEmailVerificationCode(); + break; + case AuthFlow.reset: + resendResetEmailVerificationCode(); + break; + case AuthFlow.verifyEmail: + + /// This should be handled when account created + break; + case AuthFlow.proCodeActivation: + resendResetEmailVerificationCode(); + break; + case AuthFlow.changeEmail: + resendChangeEmailVerificationCode(); + case AuthFlow.signIn: + /// there is no verification flow for sign in + case AuthFlow.updateAccount: + resendResetEmailVerificationCode(); + } + } + + Future resendChangeEmailVerificationCode() async { + assert(widget.changeEmailArgs != null, 'ChangeEmailArgs is null'); + try { + final emailArgs = widget.changeEmailArgs!; + context.loaderOverlay.show(); + await sessionModel.startChangeEmail( + emailArgs.email, emailArgs.newEmail, emailArgs.password); + context.loaderOverlay.hide(); + AppMethods.showToast('email_resend_message'.i18n); + } catch (e, s) { + mainLogger.e('Error while resending code', error: e, stackTrace: s); + context.loaderOverlay.hide(); + CDialog.showError(context, description: e.localizedDescription); + } + } + + Future resendResetEmailVerificationCode() async { + try { + context.loaderOverlay.show(); + await sessionModel.startRecoveryByEmail(widget.email); + context.loaderOverlay.hide(); + showSnackbar(context: context, content: 'email_resend_message'.i18n); + } catch (e, s) { + mainLogger.e('Error while resending code', error: e, stackTrace: s); + context.loaderOverlay.hide(); + CDialog.showError(context, description: e.localizedDescription); + } + } + + void onDone(String code) { + switch (widget.authFlow) { + case AuthFlow.createAccount: + _verifyEmail(code); + break; + case AuthFlow.reset: + openResetPassword(code); + break; + case AuthFlow.signIn: + + ///While sign in there will be no verification flow + throw Exception('Invalid AuthFlow'); + case AuthFlow.verifyEmail: + _verifyEmail(code); + break; + case AuthFlow.proCodeActivation: + _verifyEmail(code); + case AuthFlow.changeEmail: + _changeEmail(code); + case AuthFlow.updateAccount: + _verifyEmail(code); + } + } + + void openResetPassword(String code) { + context.pushRoute(ResetPassword(email: widget.email, code: code,authFlow: widget.authFlow)); + } + + void _verifyEmail(String code) async { + try { + context.loaderOverlay.show(); + await sessionModel.validateRecoveryCode(widget.email, code); + context.loaderOverlay.hide(); + resolveRoute(code); + } catch (e) { + mainLogger.e(e); + context.loaderOverlay.hide(); + pinCodeController.clear(); + CDialog.showError( + context, + description: e.localizedDescription, + ); + } + } + + Future _changeEmail(String code) async { + assert(widget.changeEmailArgs != null, 'ChangeEmailArgs is null'); + final emailArgs = widget.changeEmailArgs!; + mainLogger.d( + "email ${emailArgs.email} newEmail ${emailArgs.newEmail} password ${emailArgs.password} code ${pinCodeController.text}"); + try { + context.loaderOverlay.show(); + await sessionModel.completeChangeEmail(emailArgs.email, + emailArgs.newEmail, emailArgs.password, pinCodeController.text); + context.loaderOverlay.hide(); + CDialog.successDialog( + context: context, + title: 'email_has_been_updated'.i18n, + description: 'email_has_been_updated_message'.i18n, + successCallback: () { + //Once email changed, pop to account management + context.router.popUntil( + (route) => route.settings.name == AccountManagement.name); + }, + ); + } catch (e) { + mainLogger.e(e); + context.loaderOverlay.hide(); + pinCodeController.clear(); + CDialog.showError( + context, + description: e.localizedDescription, + ); + } + } + + // Purchase flow + void startPurchase() { + assert(widget.plan != null, 'Plan object is null'); + final appPurchase = sl(); + try { + context.loaderOverlay.show(); + appPurchase.startPurchase( + email: widget.email.validateEmail, + planId: widget.plan!.id, + onSuccess: () { + context.loaderOverlay.hide(); + Future.delayed(const Duration(milliseconds: 400), openPassword); + }, + onFailure: (error) { + pinCodeController.clear(); + context.loaderOverlay.hide(); + CDialog.showError( + context, + error: error, + description: error.toString(), + ); + }, + ); + } catch (e) { + mainLogger.e("Error while purchase flow", error: e); + context.loaderOverlay.hide(); + CDialog.showError( + context, + error: e, + description: e.toString(), + ); + } + } + + void openPassword() { + context.pushRoute(CreateAccountPassword( + email: widget.email.validateEmail, + code: pinCodeController.text, + )); + } + + void resolveRoute(String code) { + switch (widget.authFlow) { + case AuthFlow.signIn: + // TODO: Handle this case. + case AuthFlow.reset: + context.router.maybePop(); + case AuthFlow.createAccount: + startPurchase(); + case AuthFlow.verifyEmail: + context.router.maybePop(); + case AuthFlow.proCodeActivation: + context.pushRoute(ResellerCodeCheckout( + isPro: false, + email: widget.email, + otp: pinCodeController.text, + )); + case AuthFlow.changeEmail: + // TODO: Handle this case. + case AuthFlow.updateAccount: + openResetPassword(code); + } + } + + Future onBackPressed() async { + if (widget.authFlow == AuthFlow.createAccount || + widget.authFlow == AuthFlow.updateAccount||widget.authFlow == AuthFlow.proCodeActivation) { + assert(widget.tempPassword != null, 'Temp password is null'); + // if user press back button while creating account + // we need to delete that temp account + await _deleteAccount(widget.tempPassword!); + context.maybePop(); + return; + } + context.maybePop(); + } + + Future _deleteAccount(String password) async { + try { + context.loaderOverlay.show(); + await sessionModel.deleteAccount(password); + context.loaderOverlay.hide(); + } catch (e) { + context.loaderOverlay.hide(); + mainLogger.e("Error while deleting account", error: e); + CDialog.showError(context, description: e.localizedDescription); + } + } +} diff --git a/lib/account/device_linking/approve_device.dart b/lib/account/device_linking/add_device.dart similarity index 93% rename from lib/account/device_linking/approve_device.dart rename to lib/account/device_linking/add_device.dart index 9411c03da..0ca28a0d2 100644 --- a/lib/account/device_linking/approve_device.dart +++ b/lib/account/device_linking/add_device.dart @@ -4,8 +4,8 @@ import 'package:lantern/plans/utils.dart'; import 'explanation_step.dart'; @RoutePage(name: 'ApproveDevice') -class ApproveDevice extends StatelessWidget { - ApproveDevice({Key? key}) : super(key: key); +class AddDevice extends StatelessWidget { + AddDevice({Key? key}) : super(key: key); final pinCodeController = TextEditingController(); final formKey = GlobalKey(); @@ -78,7 +78,11 @@ class ApproveDevice extends StatelessWidget { appLogger.e("Error while approving device", error: e); context.loaderOverlay.hide(); pinCodeController.clear(); - showError(context, error: e); + CDialog.showError( + context, + description: e.localizedDescription, + ); + } } } diff --git a/lib/account/device_linking/authorize_device_for_pro.dart b/lib/account/device_linking/authorize_device_for_pro.dart index 87767e92f..e070913d3 100644 --- a/lib/account/device_linking/authorize_device_for_pro.dart +++ b/lib/account/device_linking/authorize_device_for_pro.dart @@ -1,9 +1,8 @@ import 'package:lantern/common/common.dart'; - @RoutePage(name: 'AuthorizePro') class AuthorizeDeviceForPro extends StatelessWidget { - AuthorizeDeviceForPro({Key? key}) : super(key: key); + const AuthorizeDeviceForPro({Key? key}) : super(key: key); @override Widget build(BuildContext context) { @@ -30,7 +29,7 @@ class AuthorizeDeviceForPro extends StatelessWidget { Button( width: 200, text: 'Link with PIN'.i18n, - onPressed: () async => await context.pushRoute(LinkDevice()), + onPressed: () => openLinkDevice(context), ), const Spacer(), Flexible( @@ -57,12 +56,19 @@ class AuthorizeDeviceForPro extends StatelessWidget { Button( text: 'Link via Email'.i18n, secondary: true, - onPressed: () async => - await context.pushRoute(AuthorizeDeviceEmail()), + onPressed: () => openLinkDeviceViaEmail(context), ), const Spacer(), ], ), ); } + + void openLinkDevice(BuildContext context) { + context.pushRoute(LinkDevice()); + } + + void openLinkDeviceViaEmail(BuildContext context) { + context.pushRoute(AuthorizeDeviceEmail()); + } } diff --git a/lib/account/device_linking/authorize_device_via_email.dart b/lib/account/device_linking/authorize_device_via_email.dart index b65d494e5..e9f0d6012 100644 --- a/lib/account/device_linking/authorize_device_via_email.dart +++ b/lib/account/device_linking/authorize_device_via_email.dart @@ -29,8 +29,12 @@ class AuthorizeDeviceViaEmail extends StatelessWidget { margin: const EdgeInsetsDirectional.only(top: 32), child: CTextField( controller: emailController, - autovalidateMode: AutovalidateMode.disabled, //TODO: this throws an error when we set it to AutovalidateMode.onUserInteraction + contentPadding: const EdgeInsetsDirectional.only( + top: 8.0, + bottom: 8.0, + ), + autovalidateMode: AutovalidateMode.onUserInteraction, label: 'Email'.i18n, helperText: 'auth_email_helper_text'.i18n, keyboardType: TextInputType.emailAddress, @@ -52,16 +56,22 @@ class AuthorizeDeviceViaEmail extends StatelessWidget { } Future onSubmit(BuildContext context) async { + FocusManager.instance.primaryFocus?.unfocus(); + if (!formKey.currentState!.validate()) { + return; + } try { - if (!formKey.currentState!.validate()) { - return; - } - context.loaderOverlay.show(widget: spinner); - await sessionModel.authorizeViaEmail(emailController.value.text); + context.loaderOverlay.show(); + await sessionModel + .authorizeViaEmail(emailController.value.text.validateEmail); context.loaderOverlay.hide(); - context.pushRoute(AuthorizeDeviceEmailPin(email: emailController.value.text)); + context.pushRoute( + AuthorizeDeviceEmailPin( + email: emailController.value.text.validateEmail), + ); } catch (e) { context.loaderOverlay.hide(); + CDialog.showError(context, description: e.localizedDescription); } } } diff --git a/lib/account/device_linking/authorize_device_via_email_pin.dart b/lib/account/device_linking/authorize_device_via_email_pin.dart index 4774b3bcc..91748a0db 100644 --- a/lib/account/device_linking/authorize_device_via_email_pin.dart +++ b/lib/account/device_linking/authorize_device_via_email_pin.dart @@ -72,44 +72,37 @@ class AuthorizeDeviceViaEmailPin extends StatelessWidget { Future onDone(String code, BuildContext context) async { try { - FocusManager.instance.primaryFocus?.unfocus(); context.loaderOverlay.show(); - await sessionModel.validateRecoveryCode(code); + await sessionModel.validateDeviceRecoveryCode(code); pinCodeController.clear(); context.loaderOverlay.hide(); - CDialog.showInfo( - context, + CDialog.successDialog( + context: context, title: "device_added".i18n, - description: "device_added_message".i18n, - agreeAction: () async { - Future.delayed( - const Duration(milliseconds: 400), - () { - context.router.popUntilRoot(); - }, - ); - - return true; + description: "device_added_msg".i18n.replaceAll('%s', email), + successCallback: () { + context.router.popUntilRoot(); }, ); } catch (e) { pinCodeController.clear(); context.loaderOverlay.hide(); - // CDialog.showError(context, description: e.toString()); + CDialog.showError(context, description: e.localizedDescription); } } Future onResendCode(BuildContext context) async { try { context.loaderOverlay.show(); - await sessionModel.authorizeViaEmail(email); + await sessionModel.authorizeViaEmail(email.validateEmail); context.loaderOverlay.hide(); - CDialog.showInfo(context, + CDialog.successDialog( + context: context, title: "recovery_code_sent".i18n, description: "recovery_email_sent".i18n.replaceAll('%s', email)); } catch (e) { context.loaderOverlay.hide(); - CDialog.showError(context, description: e.toString()); + CDialog.showError(context, description: e.localizedDescription); } } } diff --git a/lib/account/device_linking/device_limit.dart b/lib/account/device_linking/device_limit.dart new file mode 100644 index 000000000..12efc8303 --- /dev/null +++ b/lib/account/device_linking/device_limit.dart @@ -0,0 +1,88 @@ +// ignore_for_file: use_build_context_synchronously + +import '../../common/common.dart'; + +@RoutePage(name: 'DeviceLimit') +class DeviceLimit extends StatefulWidget { + const DeviceLimit({super.key}); + + @override + State createState() => _DeviceLimitState(); +} + +class _DeviceLimitState extends State { + Device? selectedDevice; + + @override + Widget build(BuildContext context) { + return BaseScreen( + title: 'device_limit_reached'.i18n, + body: _buildBody(), + ); + } + + Widget _buildBody() { + return Column( + children: [ + const SizedBox(height: 24), + const LogoWithText(), + const SizedBox(height: 24), + CText("device_limit_reached_message".i18n, style: tsBody1), + userDeviceSelection(), + const SizedBox(height: 24), + SizedBox( + width: double.infinity, + child: Button( + text: 'remove', + disabled: selectedDevice == null, + onPressed: onRemove, + ), + ) + ], + ); + } + + Widget userDeviceSelection() { + return sessionModel.devices((context, devices, child) { + return ListView.builder( + shrinkWrap: true, + itemCount: devices.devices.length, + itemBuilder: (context, index) { + final device = devices.devices[index]; + return Padding( + padding: const EdgeInsetsDirectional.only(start: 4), + child: ListItemFactory.settingsItem( + header: index == 0 ? 'pro_devices_header'.i18n : null, + content: device.name, + onTap: () { + setState(() { + selectedDevice = device; + }); + }, + trailingArray: [ + CAssetImage( + path: selectedDevice?.id == device.id + ? ImagePaths.check_green + : ImagePaths.emptyCheck, + ), + ], + ), + ); + }); + }); + } + + Future onRemove() async { + try { + context.loaderOverlay.show(); + await sessionModel.removeDevice(selectedDevice!.id); + context.loaderOverlay.hide(); + // Once device has been removed + // Pop routes and continue with sign in + context.popRoute(true); + } catch (e) { + context.loaderOverlay.hide(); + CDialog.showError(context, description: e.localizedDescription); + } + } +} diff --git a/lib/account/device_linking/link_device.dart b/lib/account/device_linking/link_device.dart index 32c78f2fc..0c333a665 100644 --- a/lib/account/device_linking/link_device.dart +++ b/lib/account/device_linking/link_device.dart @@ -17,7 +17,7 @@ class _LinkDeviceState extends State { @override void initState() { super.initState(); - if (Platform.isAndroid) { + if (isMobile()) { requestLinkCode(); } } @@ -28,7 +28,15 @@ class _LinkDeviceState extends State { // We need to call redeemLinkCode multiple times when user enter code we redeem it // then it will show on the device list retry( - () => {sessionModel.redeemLinkCode()}, + () async { + return sessionModel.redeemLinkCode().then((value) { + if (context.mounted) { + context.router.popUntilRoot(); + } + }); + }, + delayFactor: const Duration(seconds: 1), + retryIf: (e) => e is PlatformException, ); } catch (e) { appLogger.e("error while requesting link code: $e", error: e); diff --git a/lib/account/report_issue.dart b/lib/account/report_issue.dart index 0f485e3e4..c6a1602bc 100644 --- a/lib/account/report_issue.dart +++ b/lib/account/report_issue.dart @@ -16,6 +16,7 @@ class ReportIssue extends StatefulWidget { class _ReportIssueState extends State { final emailFieldKey = GlobalKey(); late final emailController = CustomTextEditingController( + text: sessionModel.userEmail.value, formKey: emailFieldKey, validator: (value) { if (value == null || value.isEmpty) { @@ -26,6 +27,7 @@ class _ReportIssueState extends State { : 'please_enter_a_valid_email_address'.i18n; }, ); + final issueFieldKey = GlobalKey(); late final issueController = CustomTextEditingController( formKey: issueFieldKey, @@ -48,144 +50,136 @@ class _ReportIssueState extends State { return null; }); - - @override Widget build(BuildContext context) { - return sessionModel.emailAddress(( - BuildContext context, - String emailAddress, - Widget? child, - ) { - return BaseScreen( - title: 'report_an_issue'.i18n, - resizeToAvoidBottomInset: false, - body: Padding( - padding: const EdgeInsetsDirectional.only( - start: 20, - end: 20, - ), - child: Column( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - // * Email field - Container( - margin: const EdgeInsetsDirectional.only( - top: 24, - bottom: 8, - ), - child: Form( - key: emailFieldKey, - child: CTextField( - initialValue: emailAddress, - controller: emailController, - autovalidateMode: AutovalidateMode.disabled, - label: 'email'.i18n, - onChanged: (value) { - setState(() {}); - }, - keyboardType: TextInputType.emailAddress, - prefixIcon: const CAssetImage(path: ImagePaths.email), - ), - ), + return BaseScreen( + title: 'report_an_issue'.i18n, + resizeToAvoidBottomInset: false, + body: Padding( + padding: const EdgeInsetsDirectional.only( + start: 20, + end: 20, + ), + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // * Email field + Container( + margin: const EdgeInsetsDirectional.only( + top: 24, + bottom: 8, ), - Container( - margin: const EdgeInsetsDirectional.only( - top: 8, - bottom: 8, - ), - child: Form( - key: issueFieldKey, - child: DropdownButtonFormField( - decoration: InputDecoration( - border: OutlineInputBorder( - borderSide: BorderSide( - width: 1, - color: grey3, - ), - ), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - width: 2, - color: blue4, - ), - ), - prefixIcon: Transform.scale( - scale: 0.4, - child: - const CAssetImage(path: ImagePaths.alert))), - hint: CText('select_an_issue'.i18n, style: tsBody1), - value: issueController.text != '' - ? issueController.text - : null, - icon: const CAssetImage(path: ImagePaths.arrow_down), - //iconSize: iconSize, - elevation: 16, - onChanged: (String? newValue) { - setState(() { - issueController.text = newValue!; - }); - }, - padding: isDesktop() - ? const EdgeInsetsDirectional.only( - top: 8, - bottom: 8, - ) - : const EdgeInsetsDirectional.all(0), - items: [ - 'cannot_access_blocked_sites'.i18n, - 'cannot_complete_purchase'.i18n, - 'cannot_sign_in'.i18n, - 'discover_not_working'.i18n, - 'spinner_loads_endlessly'.i18n, - 'slow'.i18n, - 'cannot_link_devices'.i18n, - 'application_crashes'.i18n, - 'other'.i18n - ].map>((String value) { - return DropdownMenuItem( - value: value, - child: CText(value, style: tsBody1), - ); - }).toList(), - ))), - const SizedBox(height: 8), - Form( - key: descFieldKey, + child: Form( + key: emailFieldKey, child: CTextField( - tooltipMessage: 'report_description'.i18n, - controller: descController, - contentPadding: isDesktop() - ? const EdgeInsetsDirectional.all(16.0) - : const EdgeInsetsDirectional.all(8.0), - hintText: 'issue_description'.i18n, - floatingLabelBehavior: FloatingLabelBehavior.always, + controller: emailController, autovalidateMode: AutovalidateMode.disabled, - maxLines: 8, - keyboardType: TextInputType.multiline, + label: 'email'.i18n, onChanged: (value) { setState(() {}); }, + keyboardType: TextInputType.emailAddress, + prefixIcon: const CAssetImage(path: ImagePaths.email), ), ), - const Spacer(), - Tooltip( - message: isDesktop() ? '' : AppKeys.sendReport, - child: Button( - width: 200, - disabled: isButtonDisabled(), - text: 'send_report'.i18n, - onPressed: onSendReportTap, - )), - const SizedBox( - height: 56.0, + ), + Container( + margin: const EdgeInsetsDirectional.only( + top: 8, + bottom: 8, + ), + child: Form( + key: issueFieldKey, + child: DropdownButtonFormField( + decoration: InputDecoration( + border: OutlineInputBorder( + borderSide: BorderSide( + width: 1, + color: grey3, + ), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + width: 2, + color: blue4, + ), + ), + prefixIcon: Transform.scale( + scale: 0.4, + child: + const CAssetImage(path: ImagePaths.alert))), + hint: CText('select_an_issue'.i18n, style: tsBody1), + value: issueController.text != '' + ? issueController.text + : null, + icon: const CAssetImage(path: ImagePaths.arrow_down), + //iconSize: iconSize, + elevation: 16, + onChanged: (String? newValue) { + setState(() { + issueController.text = newValue!; + }); + }, + padding: isDesktop() + ? const EdgeInsetsDirectional.only( + top: 8, + bottom: 8, + ) + : const EdgeInsetsDirectional.all(0), + items: [ + 'cannot_access_blocked_sites'.i18n, + 'cannot_complete_purchase'.i18n, + 'cannot_sign_in'.i18n, + 'discover_not_working'.i18n, + 'spinner_loads_endlessly'.i18n, + 'slow'.i18n, + 'cannot_link_devices'.i18n, + 'application_crashes'.i18n, + 'other'.i18n + ].map>((String value) { + return DropdownMenuItem( + value: value, + child: CText(value, style: tsBody1), + ); + }).toList(), + ))), + const SizedBox(height: 8), + Form( + key: descFieldKey, + child: CTextField( + tooltipMessage: 'report_description'.i18n, + controller: descController, + contentPadding: isDesktop() + ? const EdgeInsetsDirectional.all(16.0) + : const EdgeInsetsDirectional.all(8.0), + hintText: 'issue_description'.i18n, + floatingLabelBehavior: FloatingLabelBehavior.always, + autovalidateMode: AutovalidateMode.disabled, + maxLines: 8, + keyboardType: TextInputType.multiline, + textInputAction: TextInputAction.done, + onChanged: (value) { + setState(() {}); + }, ), - ], - ), + ), + const Spacer(), + Tooltip( + message: isDesktop() ? '' : AppKeys.sendReport, + child: Button( + width: 200, + disabled: isButtonDisabled(), + text: 'send_report'.i18n, + onPressed: onSendReportTap, + )), + const SizedBox( + height: 56.0, + ), + ], ), - ); - }); + ), + ); } bool isButtonDisabled() { diff --git a/lib/ad_helper.dart b/lib/ad_helper.dart index 0edaf10c3..3fef4cb0c 100644 --- a/lib/ad_helper.dart +++ b/lib/ad_helper.dart @@ -11,9 +11,6 @@ const googleAttributes = { 'provider': "Google", }; -const casAttributes = { - 'provider': "CAS", -}; var logger = Logger(printer: PrettyPrinter(), level: Level.debug); @@ -26,14 +23,14 @@ class AdHelper { return _instance; } - AdType? _currentAdType; + InterstitialAd? _interstitialAd; int _failedLoadAttempts = 0; //If ads are getting failed to load we want to make lot of calls // Just try 5 times final int _maxFailAttempts = 5; - + bool isAdsLoading = false; //Google Test ID if needed to test // return 'ca-app-pub-3940256099942544/1033173712'; String get interstitialAdUnitId { @@ -49,7 +46,6 @@ class AdHelper { checkForConsent(); logger.d('[Ads Manager] Google Ads enable $shouldShowGoogleAds:'); if (shouldShowGoogleAds) { - _currentAdType = AdType.Google; logger.i('[Ads Manager] Decision: Loading Google Ads.'); await _loadInterstitialAd(); } @@ -67,6 +63,7 @@ class AdHelper { Future _loadInterstitialAd() async { //To avoid calling multiple ads request repeatedly + isAdsLoading= true; assert(interstitialAdUnitId != "", "interstitialAdUnitId should not be null or empty"); if (_interstitialAd == null && _failedLoadAttempts < _maxFailAttempts) { @@ -77,6 +74,7 @@ class AdHelper { adLoadCallback: InterstitialAdLoadCallback( onAdLoaded: (ad) { _failedLoadAttempts = 0; + isAdsLoading= false; ad.fullScreenContentCallback = FullScreenContentCallback( onAdClicked: (ad) { logger.i('[Ads Manager] onAdClicked callback'); @@ -126,6 +124,11 @@ class AdHelper { Future loadAds({ required bool shouldShowGoogleAds, }) async { + if(isAdsLoading) { + logger.i('[Ads Manager] Request: Ads already loading. Ignoring request.'); + return; + } + await _decideAndLoadAds( shouldShowGoogleAds: shouldShowGoogleAds, ); diff --git a/lib/app.dart b/lib/app.dart index 4caf81384..90f2608ae 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -1,6 +1,5 @@ import 'package:animated_loading_border/animated_loading_border.dart'; import 'package:app_links/app_links.dart'; -import 'package:flutter/scheduler.dart'; import 'package:internet_connection_checker_plus/internet_connection_checker_plus.dart'; import 'package:lantern/core/router/router.dart'; import 'package:lantern/custom_bottom_bar.dart'; @@ -25,27 +24,15 @@ enum AppFontFamily { final String fontFamily; } -class _TickerProviderImpl extends TickerProvider { - @override - Ticker createTicker(TickerCallback onTick) { - return Ticker(onTick); - } -} - class LanternApp extends StatefulWidget { - LanternApp({Key? key}) : super(key: key) { - // Animate the visibility of the network warning notification bar. here in - // Since this notification is visible on all screens and we want the - // animation state to remain consistent across screens, we put the animation - // controller here at the app level since the app contains all screens. - } + const LanternApp({Key? key}) : super(key: key); @override State createState() => _LanternAppState(); } -class _LanternAppState extends State { - final translations = Localization.ensureInitialized(); +class _LanternAppState extends State + with SingleTickerProviderStateMixin { late final AnimationController networkWarningAnimationController; late final Animation networkWarningAnimation; @@ -64,7 +51,7 @@ class _LanternAppState extends State { .addListener(toggleConnectivityWarningIfNecessary); networkWarningAnimationController = AnimationController( duration: shortAnimationDuration, - vsync: _TickerProviderImpl(), + vsync: this, ); networkWarningAnimation = Tween(begin: 0.0, end: 1.0) .animate(networkWarningAnimationController) @@ -103,14 +90,22 @@ class _LanternAppState extends State { if (!hasConnection) { return; } + final vpnConnected = await vpnModel.isVpnConnected(); + + /// If vpn is not connected then we should not show the connectivity warning + if(!vpnConnected){ + return; + } final shouldShowConnectivityWarning = (sessionModel.proxyAvailable.value != null && sessionModel.proxyAvailable.value == false); + if (shouldShowConnectivityWarning != showConnectivityWarning) { showConnectivityWarning = shouldShowConnectivityWarning; if (showConnectivityWarning) { networkWarningAnimationController.forward(); } else { + print("networkWarningAnimationController reverse"); networkWarningAnimationController.reverse(); } // Update the state after running the animations. @@ -129,74 +124,66 @@ class _LanternAppState extends State { ], child: ChangeNotifierProvider( create: (context) => BottomBarChangeNotifier(), - child: FutureBuilder( - future: translations, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (!snapshot.hasData) { - return Container(); - } - return sessionModel.language( - (context, lang, child) { - Localization.locale = lang; - return GlobalLoaderOverlay( - useDefaultLoading: false, - overlayColor: Colors.black.withOpacity(0.5), - overlayWidget: Center( - child: AnimatedLoadingBorder( - borderWidth: 5, - borderColor: yellow3, - cornerRadius: 100, - child: SvgPicture.asset( - ImagePaths.lantern_logo, - ), - ), + child: sessionModel.language( + (context, lang, child) { + Localization.locale = lang.startsWith('en') ? "en_us" : lang; + return GlobalLoaderOverlay( + useDefaultLoading: false, + overlayColor: Colors.black.withOpacity(0.5), + overlayWidget: Center( + child: AnimatedLoadingBorder( + borderWidth: 5, + borderColor: yellow3, + cornerRadius: 100, + child: SvgPicture.asset( + ImagePaths.lantern_logo, ), - child: I18n( - initialLocale: currentLocale(lang), - child: MaterialApp.router( - locale: currentLocale(lang), - debugShowCheckedModeBanner: false, - theme: ThemeData( - useMaterial3: false, - fontFamily: _getLocaleBasedFont(currentLocal), - brightness: Brightness.light, - primarySwatch: Colors.grey, - appBarTheme: const AppBarTheme( - systemOverlayStyle: SystemUiOverlayStyle.dark, - ), - colorScheme: ColorScheme.fromSwatch() - .copyWith(secondary: Colors.black), - ), - title: 'app_name'.i18n, - localizationsDelegates: const [ - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], - routerConfig: globalRouter.config( - deepLinkBuilder: navigateToDeepLink, - ), - supportedLocales: const [ - Locale('ar', 'EG'), - Locale('fr', 'FR'), - Locale('en', 'US'), - Locale('fa', 'IR'), - Locale('th', 'TH'), - Locale('ms', 'MY'), - Locale('ru', 'RU'), - Locale('ur', 'IN'), - Locale('zh', 'CN'), - Locale('zh', 'HK'), - Locale('es', 'ES'), - Locale('es', 'CU'), - Locale('tr', 'TR'), - Locale('vi', 'VN'), - Locale('my', 'MM'), - ], + ), + ), + child: I18n( + initialLocale: currentLocale(lang), + child: MaterialApp.router( + locale: currentLocale(lang), + debugShowCheckedModeBanner: false, + theme: ThemeData( + useMaterial3: false, + fontFamily: _getLocaleBasedFont(currentLocal), + brightness: Brightness.light, + primarySwatch: Colors.grey, + appBarTheme: const AppBarTheme( + systemOverlayStyle: SystemUiOverlayStyle.dark, ), + colorScheme: ColorScheme.fromSwatch() + .copyWith(secondary: Colors.black), + ), + title: 'app_name'.i18n, + localizationsDelegates: const [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + routerConfig: globalRouter.config( + deepLinkBuilder: navigateToDeepLink, ), - ); - }, + supportedLocales: const [ + Locale('ar', 'EG'), + Locale('fr', 'FR'), + Locale('en', 'US'), + Locale('fa', 'IR'), + Locale('th', 'TH'), + Locale('ms', 'MY'), + Locale('ru', 'RU'), + Locale('ur', 'IN'), + Locale('zh', 'CN'), + Locale('zh', 'HK'), + Locale('es', 'ES'), + Locale('es', 'CU'), + Locale('tr', 'TR'), + Locale('vi', 'VN'), + Locale('my', 'MM'), + ], + ), + ), ); }, ), diff --git a/lib/common/app_const.dart b/lib/common/app_const.dart new file mode 100644 index 000000000..22dcea70d --- /dev/null +++ b/lib/common/app_const.dart @@ -0,0 +1,3 @@ +const termsOfService = 'https://s3.amazonaws.com/lantern/Lantern-TOS.pdf'; +const privacyPolicy = + 'https://s3.amazonaws.com/lantern/LanternPrivacyPolicy.pdf'; diff --git a/lib/common/app_enums.dart b/lib/common/app_enums.dart new file mode 100644 index 000000000..6f7af9d33 --- /dev/null +++ b/lib/common/app_enums.dart @@ -0,0 +1,23 @@ +//Enum this is mange current flow of auth +enum AuthFlow { + signIn, + reset, + createAccount, + verifyEmail, + proCodeActivation, + changeEmail, + updateAccount +} + +extension AuthFlowExtension on AuthFlow { + bool get isSignIn => this == AuthFlow.signIn; + + bool get isReset => this == AuthFlow.reset; + + bool get isCreateAccount => this == AuthFlow.createAccount; + + bool get isVerifyEmail => this == AuthFlow.verifyEmail; + + bool get isProCodeActivation => this == AuthFlow.proCodeActivation; + bool get isUpdateAccount => this == AuthFlow.updateAccount; +} diff --git a/lib/common/app_extension.dart b/lib/common/app_extension.dart new file mode 100644 index 000000000..6e8fadce9 --- /dev/null +++ b/lib/common/app_extension.dart @@ -0,0 +1,65 @@ +import 'package:lantern/common/common.dart'; + +extension ErrorX on Object { + String get localizedDescription { + if (this is Exception || this is Error) { + // Check if the error is a PlatformException + if (this is PlatformException) { + // Extract the message from the PlatformException + String description = (this as PlatformException).message ?? ''; + if (description.contains("user_not_found")) { + return "user_not_found".i18n; + } + if (description.contains("invalid_code")) { + return "invalid_code".i18n; + } + if (description.contains("recovery_not_found")) { + return "recovery_not_found".i18n; + } + if (description.contains("wrong-link-code")) { + return "wrong_link_code".i18n; + } + if (description + .contains("we_are_experiencing_technical_difficulties")) { + return "we_are_experiencing_technical_difficulties".i18n; + } + if (description.contains("user already exists")) { + return "signup_error_user_exists".i18n; + } + if (description.contains("error while sign up")) { + return "signup_error".i18n; + } else { + return description.i18n; + } + } else { + return toString().i18n; + } + } else { + return toString().i18n; + } + } +} + +extension Validations on String? { + String get validateEmail { + if (this == null) return ""; + + return this!.trim(); + } +} + +extension PasswordValidations on String { + bool isPasswordValid() { + trim(); // Remove spaces at the start and end + bool has6Characters = length >= 8; + bool hasUppercase = contains(RegExp(r'[A-Z]')); + bool hasLowercase = contains(RegExp(r'[a-z]')); + bool hasNumber = contains(RegExp(r'[0-9]')); + bool hasSpecialCharacter = contains(RegExp(r'[!@#$%^&*(),.?":{}|<>]')); + return has6Characters && + hasUppercase && + hasLowercase && + hasNumber && + hasSpecialCharacter; + } +} diff --git a/lib/common/app_methods.dart b/lib/common/app_methods.dart index f028d8c4c..bd73609ff 100644 --- a/lib/common/app_methods.dart +++ b/lib/common/app_methods.dart @@ -1,8 +1,9 @@ +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:lantern/common/common.dart'; import 'package:url_launcher/url_launcher.dart'; -class AppMethods{ - - static openAppstore(){ +class AppMethods { + static openAppstore() { //Launch App store const appId = 'id1457872372'; final url = Uri.parse( @@ -13,4 +14,24 @@ class AppMethods{ mode: LaunchMode.externalApplication, ); } -} \ No newline at end of file + + static showToast(String message) { + //Show Toast + Fluttertoast.showToast( + msg: message, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.BOTTOM, + backgroundColor: indicatorGreen, + textColor: white, + fontSize: 16.0, + ); + } + + String generatePassword() { + const allChars = + 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789!@#\$%^&*()-=+{};:,<.>/?'; + final random = Random.secure(); + return List.generate(8, (i) => allChars[random.nextInt(allChars.length)]) + .join(); + } +} diff --git a/lib/common/common.dart b/lib/common/common.dart index f1864c086..9d41a4397 100644 --- a/lib/common/common.dart +++ b/lib/common/common.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:lantern/replica/common.dart'; +import 'package:logger/logger.dart'; export 'dart:async'; export 'dart:convert'; @@ -23,6 +24,9 @@ export 'package:flutter_advanced_switch/flutter_advanced_switch.dart'; // export 'package:flutter_switch/flutter_switch.dart'; export 'package:i18n_extension/src/i18n_widget.dart'; export 'package:lantern/core/router/router.gr.dart'; + +// Services +export 'package:lantern/core/services.dart'; export 'package:lantern/event_extension.dart'; export 'package:lantern/event_manager.dart'; export 'package:lantern/i18n/i18n.dart'; @@ -35,6 +39,11 @@ export 'package:stop_watch_timer/stop_watch_timer.dart'; export 'add_nonbreaking_spaces.dart'; export 'app_keys.dart'; +export 'app_const.dart'; +export 'app_enums.dart'; +export 'app_extension.dart'; +export 'app_keys.dart'; +export 'app_methods.dart'; export 'app_secret.dart'; export 'disable_back_button.dart'; export 'iterable_extension.dart'; @@ -45,10 +54,10 @@ export 'model_event_channel.dart'; export 'once.dart'; export 'session_model.dart'; export 'single_value_subscriber.dart'; +export 'ui/app_buttons.dart'; export 'ui/audio.dart'; export 'ui/base_screen.dart'; export 'ui/basic_memory_image.dart'; -export 'ui/button.dart'; export 'ui/colors.dart'; export 'ui/continue_arrow.dart'; export 'ui/copy_text.dart'; @@ -64,6 +73,7 @@ export 'ui/custom/fullscreen_video_viewer.dart'; export 'ui/custom/fullscreen_viewer.dart'; export 'ui/custom/ink_well.dart'; export 'ui/custom/list_item_factory.dart'; +export 'ui/custom/logo_with_text.dart'; export 'ui/custom/retry_widget.dart'; export 'ui/custom/rounded_rectangle_border.dart'; export 'ui/custom/text.dart'; @@ -92,6 +102,23 @@ export 'ui/show_snackbar.dart'; export 'ui/text_highlighter.dart'; export 'ui/text_styles.dart'; export 'ui/transitions.dart'; +export 'ui/custom/logo_with_text.dart'; + +// custom components +export 'ui/custom/asset_image.dart'; +export 'ui/custom/badge.dart'; +export 'ui/custom/dialog.dart'; +export 'ui/custom/divider.dart'; +export 'ui/custom/ink_well.dart'; +export 'ui/custom/list_item_factory.dart'; +export 'ui/custom/rounded_rectangle_border.dart'; +export 'ui/custom/text.dart'; +export 'ui/custom/text_field.dart'; +export 'ui/custom/fullscreen_video_viewer.dart'; +export 'ui/custom/fullscreen_image_viewer.dart'; +export 'ui/custom/fullscreen_viewer.dart'; + +export 'ui/custom/heading_text.dart'; final appLogger = Logger( printer: PrettyPrinter( @@ -112,3 +139,6 @@ bool isMobile() { bool isDesktop() { return Platform.isMacOS || Platform.isLinux || Platform.isWindows; } + +final mainLogger = Logger( + printer: PrettyPrinter(), filter: DevelopmentFilter(), level: Level.debug); diff --git a/lib/common/model.dart b/lib/common/model.dart index 0472643b6..f753cb5b0 100644 --- a/lib/common/model.dart +++ b/lib/common/model.dart @@ -286,7 +286,7 @@ abstract class SubscribedNotifier extends ValueNotifier { super.removeListener(listener); if (refCount == 0) { removeFromCache(); - cancel(); + // cancel(); } } } diff --git a/lib/common/session_model.dart b/lib/common/session_model.dart index 21f8456b3..791c58383 100644 --- a/lib/common/session_model.dart +++ b/lib/common/session_model.dart @@ -19,6 +19,15 @@ const TAB_DEVELOPER = 'developer'; class SessionModel extends Model { late final EventManager eventManager; + ValueNotifier networkAvailable = ValueNotifier(true); + late ValueNotifier isPlayVersion; + late ValueNotifier isStoreVersion; + late ValueNotifier proxyAvailable; + late ValueNotifier proUserNotifier; + late ValueNotifier country; + late ValueNotifier userEmail; + late ValueNotifier hasUserSignedInNotifier; + SessionModel() : super('session') { if (isMobile()) { eventManager = EventManager('lantern_event_channel'); @@ -37,6 +46,17 @@ class SessionModel extends Model { */ proxyAvailable = singleValueNotifier('hasSucceedingProxy', true); country = singleValueNotifier('geo_country_code', 'US'); + + /// This warning is not needed for the Non pro user + /// This flow is not needed anymore + /// We don't user create account if email address is not verified + hasUserSignedInNotifier = singleValueNotifier('IsUserLoggedIn', false); + proUserNotifier = singleValueNotifier('prouser', false); + + userEmail = singleValueNotifier( + 'emailAddress', + "", + ); } else { country = ffiValueNotifier(ffiLang, 'lang', 'US'); isPlayVersion = ffiValueNotifier( @@ -54,6 +74,8 @@ class SessionModel extends Model { 'hasSucceedingProxy', false, ); + userEmail = ffiValueNotifier(ffiEmailAddress, 'emailAddress',""); + proUserNotifier = ffiValueNotifier(ffiProUser,'prouser', false); } if (Platform.isAndroid) { @@ -63,12 +85,6 @@ class SessionModel extends Model { } } - // ValueNotifier networkAvailable = ValueNotifier(true); - late ValueNotifier isPlayVersion; - late ValueNotifier isStoreVersion; - late ValueNotifier proxyAvailable; - late ValueNotifier country; - ValueNotifier pathValueNotifier(String path, T defaultValue) { return singleValueNotifier(path, defaultValue); } @@ -293,6 +309,108 @@ class SessionModel extends Model { ); } + ///Auth Widgets + + Widget isUserSignedIn(ValueWidgetBuilder builder) { + return subscribedSingleValueBuilder('IsUserLoggedIn', + builder: builder, defaultValue: false); + } + + /// Auth Method channel + + Future signUp(String email, String password) { + return methodChannel.invokeMethod('signup', { + 'email': email, + 'password': password, + }); + } + + Future signUpEmailResendCode(String email) { + return methodChannel + .invokeMethod('signupEmailResendCode', { + 'email': email, + }); + } + + Future signupEmailConfirmation(String email, String code) { + return methodChannel + .invokeMethod('signupEmailConfirmation', { + 'email': email, + 'code': code, + }); + } + + Future login(String email, String password) { + return methodChannel.invokeMethod('login', { + 'email': email, + 'password': password, + }); + } + + Future startRecoveryByEmail(String email) { + return methodChannel.invokeMethod('startRecoveryByEmail', { + 'email': email, + }); + } + + Future completeRecoveryByEmail( + String email, String password, String code) { + return methodChannel + .invokeMethod('completeRecoveryByEmail', { + 'email': email, + 'password': password, + 'code': code, + }); + } + + Future validateRecoveryCode(String email, String code) { + return methodChannel.invokeMethod('validateRecoveryCode', { + 'email': email, + 'code': code, + }); + } + + Future startChangeEmail( + String email, String newEmail, String password) { + return methodChannel.invokeMethod('startChangeEmail', { + 'email': email, + 'newEmail': newEmail, + 'password': password, + }); + } + + Future completeChangeEmail( + String email, String newEmail, String password, String code) { + return methodChannel.invokeMethod('completeChangeEmail', { + 'email': email, + 'newEmail': newEmail, + 'password': password, + 'code': code, + }); + } + + Future signOut() { + return methodChannel.invokeMethod('signOut', {}); + } + + Future deleteAccount(String password) { + return methodChannel.invokeMethod('deleteAccount', { + 'password': password, + }); + } + + Future isUserFirstTimeVisit() async { + final firsTime = await methodChannel + .invokeMethod('isUserFirstTimeVisit', {}); + print("firsTime $firsTime"); + return firsTime ?? false; + } + + Future setFirstTimeVisit() async { + return methodChannel + .invokeMethod('setFirstTimeVisit', {}); + } + Future setProxyAll(bool isOn) async { if (isDesktop()) { return await compute(ffiSetProxyAll, isOn ? 'true' : 'false'); @@ -300,6 +418,7 @@ class SessionModel extends Model { throw Exception("Not supported on mobile"); } + /// Auth API end Future getCountryCode() async { return await methodChannel .invokeMethod('getCountryCode', {}); @@ -318,23 +437,24 @@ class SessionModel extends Model { return Future(() => null); } - Future authorizeViaEmail(String emailAddress) { + Future authorizeViaEmail(String emailAddress) { return methodChannel.invokeMethod('authorizeViaEmail', { 'emailAddress': emailAddress, - }).then((value) => value as String); + }).then((value) => value.toString()); } - Future validateRecoveryCode(String code) { - return methodChannel.invokeMethod('validateRecoveryCode', { + Future validateDeviceRecoveryCode(String code) async { + return await methodChannel + .invokeMethod('validateDeviceRecoveryCode', { 'code': code, - }).then((value) => value as String); + }).then((value) => value.toString()); } - Future approveDevice(String code) async { + Future approveDevice(String code) async { if (isMobile()) { return methodChannel.invokeMethod('approveDevice', { 'code': code, - }).then((value) => value as String); + }); } return await compute(ffiApproveDevice, code); } @@ -622,7 +742,7 @@ class SessionModel extends Model { String url, [ String title = '', ]) async { - if (isMobile()) { + if (Platform.isAndroid) { return methodChannel.invokeMethod('trackUserAction', { 'name': name, 'url': url, @@ -719,6 +839,16 @@ class SessionModel extends Model { ffiPaymentRedirect, [planID, currencyName, provider.name, email, os]); } + Future submitApplePlay( + String email, String planID, String purchaseToken) async { + return methodChannel + .invokeMethod('submitApplePayPayment', { + 'planID': planID, + 'purchaseId': purchaseToken, + 'email': email, + }); + } + Future submitStripePayment( String planID, String email, diff --git a/lib/common/ui/button.dart b/lib/common/ui/app_buttons.dart similarity index 66% rename from lib/common/ui/button.dart rename to lib/common/ui/app_buttons.dart index 1d3785b2a..e1aa0bb88 100644 --- a/lib/common/ui/button.dart +++ b/lib/common/ui/app_buttons.dart @@ -2,16 +2,16 @@ import 'package:lantern/common/common.dart'; //// A TextButton with our standard styling class Button extends StatelessWidget { - late final String text; - late final String? iconPath; - late final void Function()? onPressed; - late final double? width; - late final bool primary; - late final bool secondary; - late final bool disabled; - late final bool tertiary; + final String text; + final String? iconPath; + final void Function()? onPressed; + final double? width; + final bool primary; + final bool secondary; + final bool disabled; + final bool tertiary; - Button({ + const Button({super.key, required this.text, this.iconPath, this.onPressed, @@ -23,7 +23,6 @@ class Button extends StatelessWidget { }); void _handleOnPress() { - if (disabled) return null; return onPressed!(); } @@ -33,7 +32,7 @@ class Button extends StatelessWidget { height: 56, constraints: BoxConstraints(minWidth: width ?? 200.0), child: OutlinedButton( - onPressed: _handleOnPress, + onPressed: disabled ? null : _handleOnPress, style: OutlinedButton.styleFrom( splashFactory: disabled ? NoSplash.splashFactory : InkSplash.splashFactory, @@ -52,7 +51,11 @@ class Button extends StatelessWidget { padding: const EdgeInsetsDirectional.only(end: 8.0), child: CAssetImage( path: iconPath!, - color: !secondary ? white : !disabled ? pink4 : grey5, + color: !secondary + ? white + : !disabled + ? pink4 + : grey5, ), ), Expanded( @@ -71,3 +74,27 @@ class Button extends StatelessWidget { ); } } + +class AppTextButton extends StatelessWidget { + final String text; + void Function()? onPressed; + Color? color; + + AppTextButton({ + super.key, + required this.text, + this.onPressed, + this.color, + }); + + @override + Widget build(BuildContext context) { + return TextButton( + onPressed: onPressed, + style: TextButton.styleFrom( + foregroundColor: color ?? pink5, + ), + child: Text(text), + ); + } +} diff --git a/lib/common/ui/app_webview.dart b/lib/common/ui/app_webview.dart index df88cf6f2..596ff0ee2 100644 --- a/lib/common/ui/app_webview.dart +++ b/lib/common/ui/app_webview.dart @@ -10,7 +10,7 @@ class AppWebView extends StatefulWidget { const AppWebView({ super.key, required this.url, - required this.title, + this.title= "", }); @override diff --git a/lib/common/ui/base_screen.dart b/lib/common/ui/base_screen.dart index 00b564a90..ce7ef8303 100644 --- a/lib/common/ui/base_screen.dart +++ b/lib/common/ui/base_screen.dart @@ -57,6 +57,7 @@ class BaseScreen extends StatelessWidget { } Widget doBuild(BuildContext context, double networkWarningBarHeightRatio) { + bool canPop = Navigator.of(context).canPop(); final screenInfo = MediaQuery.of(context); var verticalCorrection = (screenInfo.viewInsets.top + screenInfo.padding.top) * @@ -80,7 +81,7 @@ class BaseScreen extends StatelessWidget { ), AppBar( automaticallyImplyLeading: automaticallyImplyLeading, - leading: automaticallyImplyLeading + leading: automaticallyImplyLeading && canPop ? IconButton( icon: const Icon(Icons.arrow_back, color: Colors.black), @@ -137,7 +138,6 @@ class BaseScreen extends StatelessWidget { ); } } - class ConnectivityWarning extends StatelessWidget { const ConnectivityWarning({ Key? key, @@ -151,15 +151,15 @@ class ConnectivityWarning extends StatelessWidget { return GestureDetector( onTap: sessionModel.proxyAvailable.value != true ? () => CDialog( - title: 'connection_error'.i18n, - description: 'connection_error_des'.i18n, - agreeText: 'connection_error_button'.i18n, - agreeAction: () async { - context.popRoute(); - await context.pushRoute(ReportIssue()); - return true; - }, - ).show(context) + title: 'connection_error'.i18n, + description: 'connection_error_des'.i18n, + agreeText: 'connection_error_button'.i18n, + agreeAction: () async { + context.popRoute(); + await context.pushRoute(ReportIssue()); + return true; + }, + ).show(context) : null, child: AnimatedContainer( duration: const Duration(milliseconds: 600), @@ -172,8 +172,8 @@ class ConnectivityWarning extends StatelessWidget { children: [ CText( (sessionModel.proxyAvailable.value != true - ? 'connection_error' - : 'no_network_connection') + ? 'connection_error' + : 'no_network_connection') .i18n .toUpperCase(), style: tsBody2.copiedWith( @@ -195,4 +195,4 @@ class ConnectivityWarning extends StatelessWidget { ), ); } -} +} \ No newline at end of file diff --git a/lib/common/ui/colors.dart b/lib/common/ui/colors.dart index c582f7bc1..b9a205cad 100644 --- a/lib/common/ui/colors.dart +++ b/lib/common/ui/colors.dart @@ -17,6 +17,7 @@ Color yellow6 = HexColor('#957000'); Color pink1 = HexColor('#FFF4F8'); Color pink3 = HexColor('#FF4081'); Color pink4 = HexColor('#DB0A5B'); +Color pink5 = HexColor('#C20850'); // Grey scale Color white = HexColor('#FFFFFF'); @@ -27,6 +28,10 @@ Color grey4 = HexColor('#BFBFBF'); Color grey5 = HexColor('#707070'); Color scrimGrey = HexColor('#C4C4C4'); Color black = HexColor('#000000'); + +Color red = HexColor('#D5001F'); + + Color videoControlsGrey = black.withOpacity(0.1); // Avatars diff --git a/lib/common/ui/custom/dialog.dart b/lib/common/ui/custom/dialog.dart index 164d8ddbe..65f497621 100644 --- a/lib/common/ui/custom/dialog.dart +++ b/lib/common/ui/custom/dialog.dart @@ -1,5 +1,6 @@ import 'dart:developer' as developer; +import 'package:flutter/gestures.dart'; import 'package:lantern/common/common.dart'; /// CDialog incorporates the standard dialog styling and behavior as defined @@ -20,6 +21,32 @@ class CDialog extends StatefulWidget { description: description, iconPath: ImagePaths.alert, barrierDismissible: false, + agreeText: 'OK'.i18n, + ).show(context); + } + + static void successDialog( + {required BuildContext context, + required String title, + required String description, + String? agreeText, + VoidCallback? successCallback}) { + CDialog( + iconPath: ImagePaths.check_green_large, + title: title, + description: description, + barrierDismissible: false, + agreeText: agreeText ?? "continue".i18n, + includeCancel: false, + agreeAction: () async { + Future.delayed(const Duration(milliseconds: 300), () { + if (successCallback != null) { + successCallback.call(); + return true; + } + }); + return true; + }, ).show(context); } @@ -31,6 +58,8 @@ class CDialog extends StatefulWidget { double? size, required String title, required String description, + bool barrierDismissible = true, + bool autoCloseOnDismiss = true, String? actionLabel, Future Function()? agreeAction, Future Function()? dismissAction, @@ -44,6 +73,7 @@ class CDialog extends StatefulWidget { description: description, agreeAction: agreeAction, dismissAction: dismissAction, + barrierDismissible: barrierDismissible, ).show(context); } @@ -167,6 +197,7 @@ class CDialog extends StatefulWidget { this.maybeAgreeAction, this.dismissAction, this.includeCancel = true, + this.autoCloseOnDismiss = true, }) : super(); final String? iconPath; @@ -185,6 +216,7 @@ class CDialog extends StatefulWidget { final Future Function()? dismissAction; final closeOnce = once(); final bool includeCancel; + final bool autoCloseOnDismiss; void Function() show(BuildContext context) { showDialog( @@ -239,8 +271,7 @@ class CDialogState extends State { ), content: Column( mainAxisSize: MainAxisSize.min, - crossAxisAlignment: - hasIcon ? CrossAxisAlignment.center : CrossAxisAlignment.start, + // crossAxisAlignment: hasIcon ? CrossAxisAlignment.center : CrossAxisAlignment.start, children: [ if (hasIcon) Padding( @@ -326,6 +357,9 @@ class CDialogState extends State { // See https://dart.dev/tools/linter-rules/use_build_context_synchronously if (!context.mounted) return; widget.close(context); + if (widget.autoCloseOnDismiss) { + widget.close(context); + } }, child: CText( (widget.dismissText ?? 'cancel'.i18n).toUpperCase(), @@ -370,3 +404,95 @@ class CDialogState extends State { ); } } + +void showEmailExistsDialog( + {required BuildContext context, required VoidCallback recoverTap}) { + CDialog( + title: 'email_already_exists'.i18n, + icon: const CAssetImage( + path: ImagePaths.warning, + ), + agreeText: "recover_account".i18n, + dismissText: "back".i18n, + includeCancel: true, + agreeAction: () async { + recoverTap.call(); + return true; + }, + dismissAction: () async { + print("Go back"); + }, + description: "email_already_exists_msg".i18n, + ).show(context); +} + +void showProUserDialog(BuildContext context, {VoidCallback? onSuccess}) { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + title: const CAssetImage( + path: ImagePaths.addAccountIllustration, + height: 110, + ), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + CText( + 'update_pro_account'.i18n, + textAlign: TextAlign.center, + style: tsSubtitle1Short, + ), + const SizedBox(height: 16), + CText( + 'update_pro_account_message'.i18n, + style: tsBody1.copiedWith( + color: grey5, + ), + ), + const SizedBox( + height: 16, + ), + SizedBox( + width: double.infinity, + child: Button( + text: "update_account".i18n, + onPressed: () { + if (onSuccess != null) { + onSuccess.call(); + return; + } + context.maybePop(); + context.pushRoute(SignIn(authFlow: AuthFlow.updateAccount)); + }, + ), + ), + const SizedBox(height: 16), + RichText( + text: TextSpan( + text: 'already_have_an_account'.i18n, + style: + tsBody1.copyWith(fontWeight: FontWeight.w400, color: grey5), + children: [ + TextSpan( + text: "sign_in".i18n.toUpperCase(), + style: tsBody1.copyWith( + fontWeight: FontWeight.w500, color: pink5), + recognizer: TapGestureRecognizer() + ..onTap = () { + context.maybePop(); + context.pushRoute(SignIn(authFlow: AuthFlow.signIn)); + }, + ), + ], + ), + ), + ], + ), + ); + }, + ); +} diff --git a/lib/common/ui/custom/email_tag.dart b/lib/common/ui/custom/email_tag.dart new file mode 100644 index 000000000..490f870c3 --- /dev/null +++ b/lib/common/ui/custom/email_tag.dart @@ -0,0 +1,41 @@ +import '../../common.dart'; + +class EmailTag extends StatelessWidget { + final String email; + + const EmailTag({ + super.key, + required this.email, + }); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(100), + color: grey1, + border: Border.all( + width: 1, + color: grey3, + ), + ), + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 18), + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SvgPicture.asset( + ImagePaths.email, + ), + const SizedBox(width: 8), + CText(email, + textAlign: TextAlign.center, + style: tsBody1!.copiedWith( + leadingDistribution: TextLeadingDistribution.even, + )) + ], + ), + ); + } +} diff --git a/lib/common/ui/custom/heading_text.dart b/lib/common/ui/custom/heading_text.dart new file mode 100644 index 000000000..db0e659e4 --- /dev/null +++ b/lib/common/ui/custom/heading_text.dart @@ -0,0 +1,28 @@ +import '../../common.dart'; + +class HeadingText extends StatelessWidget { + final String title; + + const HeadingText({ + super.key, + required this.title, + }); + + @override + Widget build(BuildContext context) { + return CText(title, style: tsHeading1); + } +} + +class AppBarProHeader extends StatelessWidget { + const AppBarProHeader({super.key}); + + @override + Widget build(BuildContext context) { + return SvgPicture.asset( + ImagePaths.pro_logo, + height: 16, + fit: BoxFit.contain, + ); + } +} diff --git a/lib/common/ui/custom/internet_checker.dart b/lib/common/ui/custom/internet_checker.dart index dbc37afe9..d8156c138 100644 --- a/lib/common/ui/custom/internet_checker.dart +++ b/lib/common/ui/custom/internet_checker.dart @@ -12,7 +12,7 @@ class InternetChecker extends StatelessWidget { CDialog.showInternetUnavailableDialog(context); }, child: Container( - padding: const EdgeInsets.only(top: 5,bottom: 8), + padding: const EdgeInsets.only(top: 5, bottom: 8), alignment: Alignment.center, decoration: ShapeDecoration( color: const Color(0xFFFFF9DB), @@ -25,7 +25,7 @@ class InternetChecker extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ - SvgPicture.asset(ImagePaths.cloudOff,height: 25), + SvgPicture.asset(ImagePaths.cloudOff, height: 25), const SizedBox(width: 10), CText( "No internet connection detected", @@ -45,13 +45,15 @@ class InternetStatusProvider extends ChangeNotifier { bool _isDisconnected = false; /// Using debounce to avoid flickering when the connection is unstable - final _debounceDuration = const Duration(seconds: 2); + final _debounceDuration = Duration(seconds: Platform.isIOS ? 4 : 2); Timer? _debounceTimer; InternetStatusProvider() { // Listen for connection status changes - _connectionSubscription = - InternetConnection().onStatusChange.listen((status) { + _connectionSubscription = InternetConnection.createInstance( + checkInterval: const Duration(seconds: 10)) + .onStatusChange + .listen((status) { if (status == InternetStatus.connected) { _handleConnected(); } else { diff --git a/lib/common/ui/custom/logo_with_text.dart b/lib/common/ui/custom/logo_with_text.dart new file mode 100644 index 000000000..18f87e9a1 --- /dev/null +++ b/lib/common/ui/custom/logo_with_text.dart @@ -0,0 +1,25 @@ +import '../../common.dart'; + +class LogoWithText extends StatelessWidget { + const LogoWithText({super.key}); + + @override + Widget build(BuildContext context) { + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset( + ImagePaths.lantern_logo, + height: 42, + ), + const SizedBox(width: 15), + SvgPicture.asset( + ImagePaths.free_logo, + height: 25, + ), + ], + ); + } +} diff --git a/lib/common/ui/custom/text.dart b/lib/common/ui/custom/text.dart index 23107fbea..72ab28c5d 100644 --- a/lib/common/ui/custom/text.dart +++ b/lib/common/ui/custom/text.dart @@ -102,6 +102,7 @@ class CTextStyle extends TextStyle { final double? minFontSize; final double lineHeight; + CTextStyle({ bool inherit = true, Color? color, diff --git a/lib/common/ui/custom/text_field.dart b/lib/common/ui/custom/text_field.dart index 200837011..86e4cfb32 100644 --- a/lib/common/ui/custom/text_field.dart +++ b/lib/common/ui/custom/text_field.dart @@ -33,6 +33,7 @@ class CTextField extends StatefulWidget { late final bool? autofocus; late final void Function(String value)? onChanged; String? tooltipMessage; + final bool obscureText; CTextField({ required this.controller, @@ -63,6 +64,7 @@ class CTextField extends StatefulWidget { this.autofocus = false, this.onChanged, this.tooltipMessage, + this.obscureText = false, }) { if (initialValue != null && initialValue != '') { controller.text = initialValue!; @@ -113,102 +115,87 @@ class _CTextFieldState extends State { children: [ Container( padding: const EdgeInsetsDirectional.only(top: 7), - child: Scrollbar( - controller: scrollController, - interactive: true, - // TODO: this generates an annoying error https://github.com/flutter/flutter/issues/97873 - // thumbVisibility: true, - trackVisibility: true, - child: Tooltip( - message: isDesktop() ? '' : (widget.tooltipMessage ?? ''), - child: TextFormField( - key: fieldKey, - autofocus: widget.autofocus!, - enabled: widget.enabled, - controller: widget.controller, - scrollPhysics: defaultScrollPhysics, - autovalidateMode: widget.autovalidateMode, - focusNode: widget.controller.focusNode, - keyboardType: widget.keyboardType, - maxLength: widget.maxLength, - validator: (value) { - // this was raising a stubborn error, fixed by this https://stackoverflow.com/a/59478165 - var result = widget.controller.validate(value); - WidgetsBinding.instance.addPostFrameCallback((_) { - setState(() {}); - }); - return result; - }, - onTap: widget.onTap, - onChanged: (value) { - if (widget.onChanged != null) { - widget.onChanged!(value); - } - fieldKey.currentState!.validate(); - }, - onFieldSubmitted: widget.onFieldSubmitted, - textInputAction: widget.textInputAction, - minLines: widget.minLines, - maxLines: widget.maxLines, - style: widget.style, - inputFormatters: widget.inputFormatters, - textCapitalization: - widget.textCapitalization ?? TextCapitalization.none, - decoration: InputDecoration( - contentPadding: widget.contentPadding ?? - (isDesktop() - ? const EdgeInsetsDirectional.only( - top: 24, - bottom: 24, - ) - : const EdgeInsetsDirectional.only( - top: 8, - bottom: 8, - )), - isDense: true, - floatingLabelBehavior: widget.floatingLabelBehavior ?? FloatingLabelBehavior.never, - // we handle floating labels using our custom method below - labelText: (widget.label is String) ? widget.label : '', - helperText: widget.helperText, - hintText: widget.hintText, - helperMaxLines: 2, - focusedBorder: !widget.removeBorder - ? OutlineInputBorder( - borderSide: BorderSide( - width: 2, - color: blue4, - ), - ) - : noBorder, - errorBorder: !widget.removeBorder - ? OutlineInputBorder( - borderSide: BorderSide( - width: 2, - color: indicatorRed, - ), - ) - : noBorder, - border: !widget.removeBorder - ? OutlineInputBorder( - borderSide: BorderSide( - width: 1, - color: grey3, - ), - ) - : noBorder, - prefixIcon: - // There seems to be a problem with TextField and custom SVGs sizing so I had to size down manually - widget.prefixIcon != null - ? Transform.scale( - scale: 0.5, child: widget.prefixIcon) - : null, - suffixIcon: renderSuffixRow(), - // forcibly remove if removeBorder == true - // otherwise, it will show up if we have a maxLength set - counterText: (widget.removeCounterText || widget.removeBorder) - ? '' - : null, - ), + child: Tooltip( + message: widget.tooltipMessage ?? '', + child: TextFormField( + key: fieldKey, + autofocus: widget.autofocus!, + enabled: widget.enabled, + controller: widget.controller, + scrollPhysics: defaultScrollPhysics, + obscureText: widget.obscureText, + autovalidateMode: widget.autovalidateMode, + focusNode: widget.controller.focusNode, + keyboardType: widget.keyboardType, + maxLength: widget.maxLength, + validator: (value) { + // this was raising a stubborn error, fixed by this https://stackoverflow.com/a/59478165 + var result = widget.controller.validate(value); + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() {}); + }); + return result; + }, + onTap: widget.onTap, + onChanged: (value) { + if (widget.onChanged != null) { + widget.onChanged!(value); + } + fieldKey.currentState!.validate(); + }, + onFieldSubmitted: widget.onFieldSubmitted, + textInputAction: widget.textInputAction, + minLines: widget.minLines, + maxLines: widget.maxLines, + style: widget.style, + inputFormatters: widget.inputFormatters, + textCapitalization: + widget.textCapitalization ?? TextCapitalization.none, + decoration: InputDecoration( + contentPadding: + widget.contentPadding ?? const EdgeInsetsDirectional.all(10), + isDense: true, + floatingLabelBehavior: FloatingLabelBehavior.never, + // we handle floating labels using our custom method below + labelText: (widget.label is String) ? widget.label : '', + helperText: widget.helperText, + hintText: widget.hintText, + helperMaxLines: 2, + focusedBorder: !widget.removeBorder + ? OutlineInputBorder( + borderSide: BorderSide( + width: 2, + color: blue4, + ), + ) + : noBorder, + errorBorder: !widget.removeBorder + ? OutlineInputBorder( + borderSide: BorderSide( + width: 2, + color: indicatorRed, + ), + ) + : noBorder, + border: !widget.removeBorder + ? OutlineInputBorder( + borderSide: BorderSide( + width: 1, + color: grey3, + ), + ) + : noBorder, + prefixIcon: + // There seems to be a problem with TextField and custom SVGs sizing so I had to size down manually + widget.prefixIcon != null + ? Transform.scale(scale: 0.4, child: widget.prefixIcon) + : null, + suffixIcon: renderSuffixRow(), + // forcibly remove if removeBorder == true + // otherwise, it will show up if we have a maxLength set + counterText: (widget.removeCounterText || widget.removeBorder) + ? '' + : null, ), ), ), @@ -262,9 +249,7 @@ class _CTextFieldState extends State { Widget? renderSuffix() { final hasError = fieldKey.currentState?.mounted == true && fieldKey.currentState?.hasError == true; - final isEmpty = fieldKey.currentState?.mounted == true && - fieldKey.currentState?.value == ''; - if (isEmpty) return null; + return hasError ? Transform.scale( scale: 0.4, @@ -274,7 +259,7 @@ class _CTextFieldState extends State { ), ) : widget.suffixIcon != null - ? Transform.scale(scale: 0.4, child: widget.suffixIcon) + ? Transform.scale(scale: 0.5, child: widget.suffixIcon) : null; } @@ -351,3 +336,83 @@ class CustomTextEditingController extends TextEditingController { formKey.currentState?.validate(); } } + +class CPasswordTextFiled extends StatefulWidget { + final GlobalKey passwordFormKey; + final CustomTextEditingController passwordCustomTextEditingController; + final String label; + final Function(String vaule)? onChanged; + + const CPasswordTextFiled({ + super.key, + required this.label, + required this.passwordFormKey, + required this.passwordCustomTextEditingController, + this.onChanged, + }); + + @override + State createState() => _CPasswordTextFiledState(); +} + +class _CPasswordTextFiledState extends State { + bool obscureText = true; + + @override + Widget build(BuildContext context) { + return Form( + key: widget.passwordFormKey, + child: CTextField( + controller: widget.passwordCustomTextEditingController, + label: widget.label, + autovalidateMode: AutovalidateMode.onUserInteraction, + textInputAction: TextInputAction.done, + keyboardType: TextInputType.visiblePassword, + obscureText: obscureText, + maxLines: 1, + prefixIcon: SvgPicture.asset(ImagePaths.lockFiled), + suffixIcon: GestureDetector( + onTap: () { + setState(() { + obscureText = !obscureText; + }); + }, + child: !obscureText + ? SvgPicture.asset(ImagePaths.eyeCross) + : SvgPicture.asset(ImagePaths.eye)), + // suffix: SvgPicture.asset(ImagePaths.eye), + onChanged: widget.onChanged ?? + (value) { + setState(() {}); + }, + ), + ); + } +} + + +/// TextInputFormatter formatter + +class EmojiFilteringTextInputFormatter extends TextInputFormatter { + final RegExp _emojiRegex = RegExp(r'(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])'); + + @override + TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) { + final newText = newValue.text; + final replacements = _emojiRegex.allMatches(newText).toList().reversed; + if (replacements.isNotEmpty) { + int offsetDelta = 0; // Keep track of offset change due to emoji removal + final String withoutEmoji = replacements.fold(newText, (text, match) { + final int removedLength = match.end - match.start; + offsetDelta += removedLength; // Update offsetDelta + return text.replaceRange(match.start, match.end, ""); + }); + // Adjust selection based on offset delta + final newSelection = TextSelection( + baseOffset: newValue.selection.baseOffset - offsetDelta, + extentOffset: newValue.selection.extentOffset - offsetDelta); + return TextEditingValue(text: withoutEmoji, selection: newSelection); + } + return newValue; + } +} \ No newline at end of file diff --git a/lib/common/ui/image_paths.dart b/lib/common/ui/image_paths.dart index 1d3724f7d..618c4896f 100644 --- a/lib/common/ui/image_paths.dart +++ b/lib/common/ui/image_paths.dart @@ -14,6 +14,8 @@ class ImagePaths { static const devices = 'assets/images/devices.svg'; static const star = 'assets/images/star.svg'; static const desktop = 'assets/images/desktop.svg'; + static const signIn = 'assets/images/sign_in.svg'; + static const signOut = 'assets/images/sign_out.svg'; static const settings = 'assets/images/settings.svg'; static const support = 'assets/images/support.svg'; static const qr_code = 'assets/images/qr_code.svg'; @@ -33,6 +35,9 @@ class ImagePaths { static const credit_card = 'assets/images/credit_card.svg'; static const calendar = 'assets/images/calendar.svg'; static const lock = 'assets/images/lock.svg'; + static const lockFiled = 'assets/images/lock_filled.svg'; + static const eye = 'assets/images/eye.svg'; + static const eyeCross = 'assets/images/eye_cross.svg'; static const split_tunneling = 'assets/images/split_tunneling.svg'; static const forum = 'assets/images/forum.svg'; static const webMoney = 'assets/images/webmoney.svg'; @@ -131,6 +136,8 @@ class ImagePaths { static const verified_user = 'assets/images/verified_user.svg'; static const user = 'assets/images/user.svg'; static const image_inactive = 'assets/images/image_inactive.svg'; + static const warning = 'assets/images/warning.svg'; + static const emptyCheck = 'assets/images/empty_check.svg'; // illustrations @@ -148,6 +155,7 @@ class ImagePaths { static const empty_search = 'assets/images/empty_search.svg'; static const lantern_logotype = 'assets/images/lantern_logotype.svg'; static const lantern_pro_logotype = 'assets/images/lantern_pro_logotype.svg'; + static const addAccountIllustration = 'assets/images/add_account_ illustration.svg'; static const cloudOff = 'assets/images/cloud_off.svg'; static String countdownPath(int index) => diff --git a/lib/common/ui/password_criteria.dart b/lib/common/ui/password_criteria.dart new file mode 100644 index 000000000..380652590 --- /dev/null +++ b/lib/common/ui/password_criteria.dart @@ -0,0 +1,88 @@ +import 'package:lantern/common/common.dart'; + +class PasswordCriteriaWidget extends StatefulWidget { + final TextEditingController textEditingController; + + const PasswordCriteriaWidget({ + Key? key, + required this.textEditingController, + }) : super(key: key); + + @override + _PasswordCriteriaWidgetState createState() => _PasswordCriteriaWidgetState(); +} + +class _PasswordCriteriaWidgetState extends State { + bool has8Characters = false; + bool hasUppercase = false; + bool hasLowercase = false; + bool hasNumber = false; + bool hasSpecialCharacter = false; + + @override + void initState() { + super.initState(); + widget.textEditingController.addListener(_updateCriteria); + } + + @override + void dispose() { + widget.textEditingController.removeListener(_updateCriteria); + super.dispose(); + } + + void _updateCriteria() { + final text = widget.textEditingController.text; + setState(() { + has8Characters = text.length >= 8; + hasUppercase = text.contains(RegExp(r'[A-Z]')); + hasLowercase = text.contains(RegExp(r'[a-z]')); + hasNumber = text.contains(RegExp(r'[0-9]')); + hasSpecialCharacter = text.contains(RegExp(r'[!@#$%^&*(),.?":{}|<>]')); + }); + } + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(12.0), + decoration: + BoxDecoration(color: grey1, borderRadius: BorderRadius.circular(8.0)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CText('Password must contain at least:', + style: tsBody2!.copiedWith( + color: black, + fontSize: 14, + fontWeight: FontWeight.w400, + )), + const SizedBox(height: 5), + _buildCriteriaRow('8 or more characters', has8Characters), + _buildCriteriaRow('1 UPPERCASE letter', hasUppercase), + _buildCriteriaRow('1 lowercase letter', hasLowercase), + _buildCriteriaRow('1 number', hasNumber), + _buildCriteriaRow('1 special character', hasSpecialCharacter), + ], + ), + ); + } + + Widget _buildCriteriaRow(String criteria, bool metCriteria) { + return Padding( + padding: const EdgeInsets.only(top: 5), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon( + metCriteria ? Icons.check_circle : Icons.radio_button_unchecked, + color: metCriteria ? Colors.green : Colors.grey, + size: 20, + ), + const SizedBox(width: 8), + CText(criteria, style: tsBody2!.copiedWith(color: grey5)), + ], + ), + ); + } +} diff --git a/lib/common/ui/pin_field.dart b/lib/common/ui/pin_field.dart index 7687f6880..c3b2efeed 100644 --- a/lib/common/ui/pin_field.dart +++ b/lib/common/ui/pin_field.dart @@ -18,42 +18,7 @@ class PinField extends StatelessWidget { @override Widget build(BuildContext context) { return GestureDetector( - onLongPress: () { - Clipboard.getData('text/plain').then((valueFromClipboard) { - if (valueFromClipboard != null && - valueFromClipboard.text!.length == length) { - showDialog( - context: context, - builder: (context) { - return AlertDialog( - content: CText('Paste from clipboard?'.i18n, style: tsBody1), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - }, - child: CText( - 'No'.i18n, - style: tsButtonGrey, - ), - ), - TextButton( - onPressed: () { - controller.text = valueFromClipboard.text!; - Navigator.pop(context); - }, - child: CText( - 'Yes'.i18n, - style: tsButtonPink, - ), - ), - ], - ); - }, - ); - } - }); - }, + onLongPress: () => getClipboardData(context), child: PinCodeTextField( maxLength: length, keyboardType: keyboardType, @@ -85,4 +50,42 @@ class PinField extends StatelessWidget { ), ); } + + Future getClipboardData(BuildContext context) async { + final copiedData = await Clipboard.getData('text/plain'); + if (copiedData?.text != null) { + final code = copiedData!.text; + // ignore: use_build_context_synchronously + showDialog( + context: context, + builder: (context) { + return AlertDialog( + content: CText('Paste from clipboard?'.i18n, style: tsBody1), + actions: [ + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: CText( + 'No'.i18n, + style: tsButtonGrey, + ), + ), + TextButton( + onPressed: () { + controller.text = code!; + Navigator.pop(context); + onDone?.call(controller.text); + }, + child: CText( + 'Yes'.i18n, + style: tsButtonPink, + ), + ), + ], + ); + }, + ); + } + } } diff --git a/lib/common/ui/text_styles.dart b/lib/common/ui/text_styles.dart index b4260efe5..bdf083fbc 100644 --- a/lib/common/ui/text_styles.dart +++ b/lib/common/ui/text_styles.dart @@ -53,7 +53,7 @@ CTextStyle tsSubtitle4 = CTextStyle( fontWeight: FontWeight.w500, ); -CTextStyle tsBody1 = CTextStyle(fontSize: 14, lineHeight: 23); +CTextStyle tsBody1 = CTextStyle(fontSize: 14, lineHeight: 23,); CTextStyle tsBody1Short = CTextStyle(fontSize: 14, lineHeight: 18); diff --git a/lib/core/purchase/app_purchase.dart b/lib/core/purchase/app_purchase.dart new file mode 100644 index 000000000..b811f90b4 --- /dev/null +++ b/lib/core/purchase/app_purchase.dart @@ -0,0 +1,127 @@ +import 'package:in_app_purchase/in_app_purchase.dart'; +import 'package:lantern/replica/common.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; + +import '../../common/common.dart'; + +class AppPurchase { + final InAppPurchase _inAppPurchase = InAppPurchase.instance; + StreamSubscription>? _subscription; + List plansSku = []; + final Set _iosPlansIds = {"1Y", '2Y'}; + VoidCallback? _onSuccess; + Function(dynamic error)? _onError; + String _planId = ""; + String _email = ""; + + void init() { + final purchaseUpdated = _inAppPurchase.purchaseStream; + _subscription = purchaseUpdated.listen( + _onPurchaseUpdate, + onDone: _updateStreamOnDone, + onError: _updateStreamOnError, + ); + getAvailablePlans(); + } + + Future checkForAppStoreIsAvailable() async { + return await InAppPurchase.instance.isAvailable(); + } + + Future getAvailablePlans() async { + final response = await _inAppPurchase.queryProductDetails(_iosPlansIds); + plansSku.clear(); + plansSku.addAll(response.productDetails); + } + + Future startPurchase({ + required String planId, + required String email, + required VoidCallback onSuccess, + required Function(dynamic error) onFailure, + }) async { + if (!(await checkForAppStoreIsAvailable())) { + onFailure("App store is not available"); + return; + } + _email = email; + _planId = planId; + _onSuccess = onSuccess; + _onError = onFailure; + final plan = _normalizePlan(planId); + final purchaseParam = PurchaseParam(productDetails: plan); + try { + await _inAppPurchase.buyConsumable(purchaseParam: purchaseParam); + } on PlatformException catch (e) { + logger.e('Error while calling purchase api', error: e); + Sentry.captureException(e); + _onError?.call(e); + } catch (e) { + logger.e('Payment failed', error: e); + _onError?.call(e); + } + } + + ProductDetails _normalizePlan(String planId) { + /// We have different ids for IOS, Android And servers + /// Convert Server plan to App Store plans + /// For ios we are using plans such as 1Y, 2Y, but server plan is 1y-xx-xx + /// So we split and compare with lowercase + final newPlanId = planId.split('-')[0]; + return plansSku.firstWhere( + (element) => element.id.toLowerCase() == newPlanId.toLowerCase(), + ); + } + + Future _onPurchaseUpdate( + List purchaseDetailsList, + ) async { + for (var purchaseDetails in purchaseDetailsList) { + await _handlePurchase(purchaseDetails); + mainLogger.d('Purchase data: ${purchaseDetails}'); + } + } + + Future _handlePurchase(PurchaseDetails purchaseDetails) async { + if (purchaseDetails.status == PurchaseStatus.canceled) { + /// if user cancels purchase and then try to purchase again it will get penning transaction errr + /// To avoid edge case complete purchase + // User has canceled the purchase + await _inAppPurchase.completePurchase(purchaseDetails); + _onError?.call("Purchase canceled"); + return; + } + if (purchaseDetails.status == PurchaseStatus.purchased) { + try { + await sessionModel.submitApplePlay( + _email, + _planId, + purchaseDetails.verificationData.serverVerificationData, + ); + _onSuccess?.call(); + } catch (e) { + logger.e("purchase error", error: e); + Sentry.captureException(e); + _onError?.call(e); + } + } + if (purchaseDetails.pendingCompletePurchase) { + await _inAppPurchase.completePurchase(purchaseDetails); + } + } + + void _updateStreamOnDone() { + _onError = null; + _onSuccess = null; + _planId = ""; + _subscription?.cancel(); + } + + void _updateStreamOnError(dynamic error) { + //Handle error here + logger.e("purchase error", error: error); + if (_onError != null) { + _onError?.call(error); + } + } +} diff --git a/lib/core/router/router.dart b/lib/core/router/router.dart index 02d265aac..2b8d064f5 100644 --- a/lib/core/router/router.dart +++ b/lib/core/router/router.dart @@ -8,7 +8,10 @@ class AppRouter extends $AppRouter { RouteType get defaultRouteType => const RouteType.adaptive(); @override final List routes = [ - AutoRoute(path: '/', page: Home.page), + AutoRoute( + path: '/', + page: Home.page, + ), CustomRoute( page: FullScreenDialogPage.page, path: '/fullScreenDialogPage', @@ -153,6 +156,12 @@ class AppRouter extends $AppRouter { transitionsBuilder: defaultTransition, durationInMilliseconds: defaultTransitionMillis, reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: ResellerCodeCheckoutLegacy.page, + path: '/resellerCodeCheckoutLegacy', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), CustomRoute( page: StripeCheckout.page, path: '/stripeCheckout', @@ -254,5 +263,75 @@ class AppRouter extends $AppRouter { durationInMilliseconds: defaultTransitionMillis, reverseDurationInMilliseconds: defaultTransitionMillis, ), + CustomRoute( + page: SignIn.page, + path: '/sign_in', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis, + ), + CustomRoute( + page: SignInPassword.page, + path: '/sign_in_password', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis, + ), + CustomRoute( + page: ConfirmEmail.page, + path: '/confirm_email', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis, + ), + CustomRoute( + page: ResetPassword.page, + path: '/reset_password', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis, + ), + CustomRoute( + page: Verification.page, + path: '/verification', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis, + ), + CustomRoute( + page: CreateAccountEmail.page, + path: '/create_account_email', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis, + ), + CustomRoute( + page: CreateAccountPassword.page, + path: '/create_account_password', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis, + ), + CustomRoute( + page: ChangeEmail.page, + path: '/change_email', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis, + ), + CustomRoute( + page: AuthLanding.page, + path: '/auth_landing', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis, + ), + CustomRoute( + page: DeviceLimit.page, + path: '/device_limit', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis, + ) ]; } diff --git a/lib/core/router/router.gr.dart b/lib/core/router/router.gr.dart index 56d1bd8bb..8fdbcb2ed 100644 --- a/lib/core/router/router.gr.dart +++ b/lib/core/router/router.gr.dart @@ -8,66 +8,80 @@ // coverage:ignore-file // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:io' as _i46; +import 'dart:io' as _i58; +import 'dart:ui' as _i56; -import 'package:auto_route/auto_route.dart' as _i42; +import 'package:auto_route/auto_route.dart' as _i53; +import 'package:flutter/cupertino.dart' as _i59; import 'package:lantern/account/account.dart' as _i2; import 'package:lantern/account/account_management.dart' as _i1; -import 'package:lantern/account/blocked_users.dart' as _i9; -import 'package:lantern/account/chat_number_account.dart' as _i10; -import 'package:lantern/account/device_linking/approve_device.dart' as _i5; +import 'package:lantern/account/auth/auth_landing.dart' as _i6; +import 'package:lantern/account/auth/change_email.dart' as _i11; +import 'package:lantern/account/auth/confirm_email.dart' as _i16; +import 'package:lantern/account/auth/create_account_email.dart' as _i19; +import 'package:lantern/account/auth/create_account_password.dart' as _i20; +import 'package:lantern/account/auth/reset_password.dart' as _i45; +import 'package:lantern/account/auth/sign_in.dart' as _i47; +import 'package:lantern/account/auth/sign_in_password.dart' as _i48; +import 'package:lantern/account/auth/verification.dart' as _i52; +import 'package:lantern/account/blocked_users.dart' as _i10; +import 'package:lantern/account/chat_number_account.dart' as _i12; +import 'package:lantern/account/device_linking/add_device.dart' as _i3; import 'package:lantern/account/device_linking/authorize_device_for_pro.dart' - as _i6; -import 'package:lantern/account/device_linking/authorize_device_via_email.dart' as _i7; -import 'package:lantern/account/device_linking/authorize_device_via_email_pin.dart' +import 'package:lantern/account/device_linking/authorize_device_via_email.dart' as _i8; -import 'package:lantern/account/device_linking/link_device.dart' as _i23; -import 'package:lantern/account/invite_friends.dart' as _i20; -import 'package:lantern/account/language.dart' as _i21; -import 'package:lantern/account/lantern_desktop.dart' as _i22; -import 'package:lantern/account/recovery_key.dart' as _i27; -import 'package:lantern/account/report_issue.dart' as _i36; -import 'package:lantern/account/settings.dart' as _i38; -import 'package:lantern/account/split_tunneling.dart' as _i39; -import 'package:lantern/account/support.dart' as _i41; -import 'package:lantern/common/common.dart' as _i44; -import 'package:lantern/common/ui/app_webview.dart' as _i4; -import 'package:lantern/common/ui/full_screen_dialog.dart' as _i16; -import 'package:lantern/home.dart' as _i17; -import 'package:lantern/messaging/contacts/add_contact_number.dart' as _i3; -import 'package:lantern/messaging/contacts/contact_info.dart' as _i14; -import 'package:lantern/messaging/contacts/new_chat.dart' as _i24; -import 'package:lantern/messaging/conversation/conversation.dart' as _i15; -import 'package:lantern/messaging/introductions/introduce.dart' as _i18; -import 'package:lantern/messaging/introductions/introductions.dart' as _i19; -import 'package:lantern/messaging/messaging.dart' as _i43; +import 'package:lantern/account/device_linking/authorize_device_via_email_pin.dart' + as _i9; +import 'package:lantern/account/device_linking/device_limit.dart' as _i21; +import 'package:lantern/account/device_linking/link_device.dart' as _i29; +import 'package:lantern/account/invite_friends.dart' as _i26; +import 'package:lantern/account/language.dart' as _i27; +import 'package:lantern/account/lantern_desktop.dart' as _i28; +import 'package:lantern/account/recovery_key.dart' as _i33; +import 'package:lantern/account/report_issue.dart' as _i42; +import 'package:lantern/account/settings.dart' as _i46; +import 'package:lantern/account/split_tunneling.dart' as _i49; +import 'package:lantern/account/support.dart' as _i51; +import 'package:lantern/common/common.dart' as _i55; +import 'package:lantern/common/ui/app_webview.dart' as _i5; +import 'package:lantern/common/ui/full_screen_dialog.dart' as _i22; +import 'package:lantern/home.dart' as _i23; +import 'package:lantern/messaging/contacts/add_contact_number.dart' as _i4; +import 'package:lantern/messaging/contacts/contact_info.dart' as _i17; +import 'package:lantern/messaging/contacts/new_chat.dart' as _i30; +import 'package:lantern/messaging/conversation/conversation.dart' as _i18; +import 'package:lantern/messaging/introductions/introduce.dart' as _i24; +import 'package:lantern/messaging/introductions/introductions.dart' as _i25; +import 'package:lantern/messaging/messaging.dart' as _i54; import 'package:lantern/messaging/onboarding/chat_number_messaging.dart' - as _i11; -import 'package:lantern/messaging/onboarding/chat_number_recovery.dart' as _i12; -import 'package:lantern/plans/checkout.dart' as _i13; -import 'package:lantern/plans/plans.dart' as _i25; -import 'package:lantern/plans/play_checkout.dart' as _i26; -import 'package:lantern/plans/reseller_checkout.dart' as _i37; -import 'package:lantern/plans/stripe_checkout.dart' as _i40; -import 'package:lantern/replica/common.dart' as _i45; -import 'package:lantern/replica/link_handler.dart' as _i30; -import 'package:lantern/replica/ui/viewers/audio.dart' as _i28; -import 'package:lantern/replica/ui/viewers/image.dart' as _i29; -import 'package:lantern/replica/ui/viewers/misc.dart' as _i31; -import 'package:lantern/replica/ui/viewers/video.dart' as _i35; -import 'package:lantern/replica/upload/description.dart' as _i32; -import 'package:lantern/replica/upload/review.dart' as _i33; -import 'package:lantern/replica/upload/title.dart' as _i34; - -abstract class $AppRouter extends _i42.RootStackRouter { + as _i13; +import 'package:lantern/messaging/onboarding/chat_number_recovery.dart' as _i14; +import 'package:lantern/plans/checkout.dart' as _i15; +import 'package:lantern/plans/plans.dart' as _i31; +import 'package:lantern/plans/play_checkout.dart' as _i32; +import 'package:lantern/plans/reseller_checkout.dart' as _i44; +import 'package:lantern/plans/reseller_checkout_legacy.dart' as _i43; +import 'package:lantern/plans/stripe_checkout.dart' as _i50; +import 'package:lantern/replica/common.dart' as _i57; +import 'package:lantern/replica/link_handler.dart' as _i36; +import 'package:lantern/replica/ui/viewers/audio.dart' as _i34; +import 'package:lantern/replica/ui/viewers/image.dart' as _i35; +import 'package:lantern/replica/ui/viewers/misc.dart' as _i37; +import 'package:lantern/replica/ui/viewers/video.dart' as _i41; +import 'package:lantern/replica/upload/description.dart' as _i38; +import 'package:lantern/replica/upload/review.dart' as _i39; +import 'package:lantern/replica/upload/title.dart' as _i40; +import 'package:lantern/vpn/vpn.dart' as _i60; + +abstract class $AppRouter extends _i53.RootStackRouter { $AppRouter({super.navigatorKey}); @override - final Map pagesMap = { + final Map pagesMap = { AccountManagement.name: (routeData) { final args = routeData.argsAs(); - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, child: _i1.AccountManagement( key: args.key, @@ -76,57 +90,61 @@ abstract class $AppRouter extends _i42.RootStackRouter { ); }, Account.name: (routeData) { - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, child: const _i2.AccountMenu(), ); }, + ApproveDevice.name: (routeData) { + final args = routeData.argsAs( + orElse: () => const ApproveDeviceArgs()); + return _i53.AutoRoutePage( + routeData: routeData, + child: _i3.AddDevice(key: args.key), + ); + }, AddViaChatNumber.name: (routeData) { - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: _i3.AddViaChatNumber(), + child: _i4.AddViaChatNumber(), ); }, AppWebview.name: (routeData) { final args = routeData.argsAs(); - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: _i4.AppWebView( + child: _i5.AppWebView( key: args.key, url: args.url, title: args.title, ), ); }, - ApproveDevice.name: (routeData) { - final args = routeData.argsAs( - orElse: () => const ApproveDeviceArgs()); - return _i42.AutoRoutePage( + AuthLanding.name: (routeData) { + return _i53.AutoRoutePage( routeData: routeData, - child: _i5.ApproveDevice(key: args.key), + child: const _i6.AuthLanding(), ); }, AuthorizePro.name: (routeData) { - final args = routeData.argsAs( - orElse: () => const AuthorizeProArgs()); - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: _i6.AuthorizeDeviceForPro(key: args.key), + child: const _i7.AuthorizeDeviceForPro(), ); }, AuthorizeDeviceEmail.name: (routeData) { final args = routeData.argsAs( orElse: () => const AuthorizeDeviceEmailArgs()); - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: _i7.AuthorizeDeviceViaEmail(key: args.key), + child: _i8.AuthorizeDeviceViaEmail(key: args.key), ); }, AuthorizeDeviceEmailPin.name: (routeData) { final args = routeData.argsAs(); - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: _i8.AuthorizeDeviceViaEmailPin( + child: _i9.AuthorizeDeviceViaEmailPin( key: args.key, email: args.email, ), @@ -135,133 +153,177 @@ abstract class $AppRouter extends _i42.RootStackRouter { BlockedUsers.name: (routeData) { final args = routeData.argsAs( orElse: () => const BlockedUsersArgs()); - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: _i9.BlockedUsers(key: args.key), + child: _i10.BlockedUsers(key: args.key), + ); + }, + ChangeEmail.name: (routeData) { + final args = routeData.argsAs(); + return _i53.AutoRoutePage( + routeData: routeData, + child: _i11.ChangeEmail( + key: args.key, + email: args.email, + ), ); }, ChatNumberAccount.name: (routeData) { - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: _i10.ChatNumberAccount(), + child: _i12.ChatNumberAccount(), ); }, ChatNumberMessaging.name: (routeData) { - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: _i11.ChatNumberMessaging(), + child: _i13.ChatNumberMessaging(), ); }, ChatNumberRecovery.name: (routeData) { - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: _i12.ChatNumberRecovery(), + child: _i14.ChatNumberRecovery(), ); }, Checkout.name: (routeData) { final args = routeData.argsAs(); - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: _i13.Checkout( + child: _i15.Checkout( plan: args.plan, isPro: args.isPro, key: args.key, ), ); }, + ConfirmEmail.name: (routeData) { + return _i53.AutoRoutePage( + routeData: routeData, + child: const _i16.ConfirmEmail(), + ); + }, ContactInfo.name: (routeData) { final args = routeData.argsAs(); - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: _i14.ContactInfo(contact: args.contact), + child: _i17.ContactInfo(contact: args.contact), ); }, Conversation.name: (routeData) { final args = routeData.argsAs(); - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: _i15.Conversation( + child: _i18.Conversation( contactId: args.contactId, initialScrollIndex: args.initialScrollIndex, showContactEditingDialog: args.showContactEditingDialog, ), ); }, + CreateAccountEmail.name: (routeData) { + final args = routeData.argsAs( + orElse: () => const CreateAccountEmailArgs()); + return _i53.AutoRoutePage( + routeData: routeData, + child: _i19.CreateAccountEmail( + key: args.key, + plan: args.plan, + authFlow: args.authFlow, + ), + ); + }, + CreateAccountPassword.name: (routeData) { + final args = routeData.argsAs(); + return _i53.AutoRoutePage( + routeData: routeData, + child: _i20.CreateAccountPassword( + key: args.key, + email: args.email, + code: args.code, + ), + ); + }, + DeviceLimit.name: (routeData) { + return _i53.AutoRoutePage( + routeData: routeData, + child: const _i21.DeviceLimit(), + ); + }, FullScreenDialogPage.name: (routeData) { final args = routeData.argsAs(); - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: _i16.FullScreenDialog( + child: _i22.FullScreenDialog( widget: args.widget, + bgColor: args.bgColor, key: args.key, ), ); }, Home.name: (routeData) { - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: const _i17.HomePage(), + child: const _i23.HomePage(), ); }, Introduce.name: (routeData) { final args = routeData.argsAs(); - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: _i18.Introduce( + child: _i24.Introduce( singleIntro: args.singleIntro, contactToIntro: args.contactToIntro, ), ); }, Introductions.name: (routeData) { - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: _i19.Introductions(), + child: _i25.Introductions(), ); }, InviteFriends.name: (routeData) { - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: _i20.InviteFriends(), + child: _i26.InviteFriends(), ); }, Language.name: (routeData) { - final args = - routeData.argsAs(orElse: () => const LanguageArgs()); - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: _i21.Language(key: args.key), + child: const _i27.Language(), ); }, LanternDesktop.name: (routeData) { - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: const _i22.LanternDesktop(), + child: const _i28.LanternDesktop(), ); }, LinkDevice.name: (routeData) { - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: const _i23.LinkDevice(), + child: const _i29.LinkDevice(), ); }, NewChat.name: (routeData) { - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: _i24.NewChat(), + child: _i30.NewChat(), ); }, PlansPage.name: (routeData) { - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: const _i25.PlansPage(), + child: const _i31.PlansPage(), ); }, PlayCheckout.name: (routeData) { final args = routeData.argsAs(); - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: _i26.PlayCheckout( + child: _i32.PlayCheckout( plan: args.plan, isPro: args.isPro, key: args.key, @@ -271,16 +333,16 @@ abstract class $AppRouter extends _i42.RootStackRouter { RecoveryKey.name: (routeData) { final args = routeData.argsAs( orElse: () => const RecoveryKeyArgs()); - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: _i27.RecoveryKey(key: args.key), + child: _i33.RecoveryKey(key: args.key), ); }, ReplicaAudioViewer.name: (routeData) { final args = routeData.argsAs(); - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: _i28.ReplicaAudioViewer( + child: _i34.ReplicaAudioViewer( replicaApi: args.replicaApi, item: args.item, category: args.category, @@ -289,9 +351,9 @@ abstract class $AppRouter extends _i42.RootStackRouter { }, ReplicaImageViewer.name: (routeData) { final args = routeData.argsAs(); - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: _i29.ReplicaImageViewer( + child: _i35.ReplicaImageViewer( replicaApi: args.replicaApi, item: args.item, category: args.category, @@ -300,9 +362,9 @@ abstract class $AppRouter extends _i42.RootStackRouter { }, ReplicaLinkHandler.name: (routeData) { final args = routeData.argsAs(); - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: _i30.ReplicaLinkHandler( + child: _i36.ReplicaLinkHandler( key: args.key, replicaApi: args.replicaApi, replicaLink: args.replicaLink, @@ -311,9 +373,9 @@ abstract class $AppRouter extends _i42.RootStackRouter { }, ReplicaMiscViewer.name: (routeData) { final args = routeData.argsAs(); - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: _i31.ReplicaMiscViewer( + child: _i37.ReplicaMiscViewer( replicaApi: args.replicaApi, item: args.item, category: args.category, @@ -322,9 +384,9 @@ abstract class $AppRouter extends _i42.RootStackRouter { }, ReplicaUploadDescription.name: (routeData) { final args = routeData.argsAs(); - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: _i32.ReplicaUploadDescription( + child: _i38.ReplicaUploadDescription( key: args.key, fileToUpload: args.fileToUpload, fileTitle: args.fileTitle, @@ -334,9 +396,9 @@ abstract class $AppRouter extends _i42.RootStackRouter { }, ReplicaUploadReview.name: (routeData) { final args = routeData.argsAs(); - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: _i33.ReplicaUploadReview( + child: _i39.ReplicaUploadReview( key: args.key, fileToUpload: args.fileToUpload, fileTitle: args.fileTitle, @@ -346,9 +408,9 @@ abstract class $AppRouter extends _i42.RootStackRouter { }, ReplicaUploadTitle.name: (routeData) { final args = routeData.argsAs(); - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: _i34.ReplicaUploadTitle( + child: _i40.ReplicaUploadTitle( key: args.key, fileToUpload: args.fileToUpload, fileTitle: args.fileTitle, @@ -358,9 +420,9 @@ abstract class $AppRouter extends _i42.RootStackRouter { }, ReplicaVideoViewer.name: (routeData) { final args = routeData.argsAs(); - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: _i35.ReplicaVideoViewer( + child: _i41.ReplicaVideoViewer( replicaApi: args.replicaApi, item: args.item, category: args.category, @@ -370,43 +432,89 @@ abstract class $AppRouter extends _i42.RootStackRouter { ReportIssue.name: (routeData) { final args = routeData.argsAs( orElse: () => const ReportIssueArgs()); - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: _i36.ReportIssue( + child: _i42.ReportIssue( key: args.key, description: args.description, ), ); }, + ResellerCodeCheckoutLegacy.name: (routeData) { + final args = routeData.argsAs(); + return _i53.AutoRoutePage( + routeData: routeData, + child: _i43.ResellerCodeCheckout( + isPro: args.isPro, + key: args.key, + ), + ); + }, ResellerCodeCheckout.name: (routeData) { final args = routeData.argsAs(); - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: _i37.ResellerCodeCheckout( + child: _i44.ResellerCodeCheckout( isPro: args.isPro, + email: args.email, + otp: args.otp, key: args.key, ), ); }, + ResetPassword.name: (routeData) { + final args = routeData.argsAs( + orElse: () => const ResetPasswordArgs()); + return _i53.AutoRoutePage( + routeData: routeData, + child: _i45.ResetPassword( + key: args.key, + email: args.email, + code: args.code, + authFlow: args.authFlow, + ), + ); + }, Settings.name: (routeData) { final args = routeData.argsAs(orElse: () => const SettingsArgs()); - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: _i38.Settings(key: args.key), + child: _i46.Settings(key: args.key), + ); + }, + SignIn.name: (routeData) { + final args = + routeData.argsAs(orElse: () => const SignInArgs()); + return _i53.AutoRoutePage( + routeData: routeData, + child: _i47.SignIn( + key: args.key, + authFlow: args.authFlow, + ), + ); + }, + SignInPassword.name: (routeData) { + final args = routeData.argsAs(); + return _i53.AutoRoutePage( + routeData: routeData, + child: _i48.SignInPassword( + key: args.key, + email: args.email, + ), ); }, SplitTunneling.name: (routeData) { - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: const _i39.SplitTunneling(), + child: const _i49.SplitTunneling(), ); }, StripeCheckout.name: (routeData) { final args = routeData.argsAs(); - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( routeData: routeData, - child: _i40.StripeCheckout( + child: _i50.StripeCheckout( plan: args.plan, email: args.email, refCode: args.refCode, @@ -416,9 +524,23 @@ abstract class $AppRouter extends _i42.RootStackRouter { ); }, Support.name: (routeData) { - return _i42.AutoRoutePage( + return _i53.AutoRoutePage( + routeData: routeData, + child: const _i51.Support(), + ); + }, + Verification.name: (routeData) { + final args = routeData.argsAs(); + return _i53.AutoRoutePage( routeData: routeData, - child: const _i41.Support(), + child: _i52.Verification( + key: args.key, + email: args.email, + authFlow: args.authFlow, + changeEmailArgs: args.changeEmailArgs, + plan: args.plan, + tempPassword: args.tempPassword, + ), ); }, }; @@ -426,11 +548,11 @@ abstract class $AppRouter extends _i42.RootStackRouter { /// generated route for /// [_i1.AccountManagement] -class AccountManagement extends _i42.PageRouteInfo { +class AccountManagement extends _i53.PageRouteInfo { AccountManagement({ - _i43.Key? key, + _i54.Key? key, required bool isPro, - List<_i42.PageRouteInfo>? children, + List<_i53.PageRouteInfo>? children, }) : super( AccountManagement.name, args: AccountManagementArgs( @@ -442,8 +564,8 @@ class AccountManagement extends _i42.PageRouteInfo { static const String name = 'AccountManagement'; - static const _i42.PageInfo page = - _i42.PageInfo(name); + static const _i53.PageInfo page = + _i53.PageInfo(name); } class AccountManagementArgs { @@ -452,7 +574,7 @@ class AccountManagementArgs { required this.isPro, }); - final _i43.Key? key; + final _i54.Key? key; final bool isPro; @@ -464,8 +586,8 @@ class AccountManagementArgs { /// generated route for /// [_i2.AccountMenu] -class Account extends _i42.PageRouteInfo { - const Account({List<_i42.PageRouteInfo>? children}) +class Account extends _i53.PageRouteInfo { + const Account({List<_i53.PageRouteInfo>? children}) : super( Account.name, initialChildren: children, @@ -473,13 +595,42 @@ class Account extends _i42.PageRouteInfo { static const String name = 'Account'; - static const _i42.PageInfo page = _i42.PageInfo(name); + static const _i53.PageInfo page = _i53.PageInfo(name); +} + +/// generated route for +/// [_i3.AddDevice] +class ApproveDevice extends _i53.PageRouteInfo { + ApproveDevice({ + _i55.Key? key, + List<_i53.PageRouteInfo>? children, + }) : super( + ApproveDevice.name, + args: ApproveDeviceArgs(key: key), + initialChildren: children, + ); + + static const String name = 'ApproveDevice'; + + static const _i53.PageInfo page = + _i53.PageInfo(name); +} + +class ApproveDeviceArgs { + const ApproveDeviceArgs({this.key}); + + final _i55.Key? key; + + @override + String toString() { + return 'ApproveDeviceArgs{key: $key}'; + } } /// generated route for -/// [_i3.AddViaChatNumber] -class AddViaChatNumber extends _i42.PageRouteInfo { - const AddViaChatNumber({List<_i42.PageRouteInfo>? children}) +/// [_i4.AddViaChatNumber] +class AddViaChatNumber extends _i53.PageRouteInfo { + const AddViaChatNumber({List<_i53.PageRouteInfo>? children}) : super( AddViaChatNumber.name, initialChildren: children, @@ -487,17 +638,17 @@ class AddViaChatNumber extends _i42.PageRouteInfo { static const String name = 'AddViaChatNumber'; - static const _i42.PageInfo page = _i42.PageInfo(name); + static const _i53.PageInfo page = _i53.PageInfo(name); } /// generated route for -/// [_i4.AppWebView] -class AppWebview extends _i42.PageRouteInfo { +/// [_i5.AppWebView] +class AppWebview extends _i53.PageRouteInfo { AppWebview({ - _i44.Key? key, + _i55.Key? key, required String url, - required String title, - List<_i42.PageRouteInfo>? children, + String title = "", + List<_i53.PageRouteInfo>? children, }) : super( AppWebview.name, args: AppWebviewArgs( @@ -510,18 +661,18 @@ class AppWebview extends _i42.PageRouteInfo { static const String name = 'AppWebview'; - static const _i42.PageInfo page = - _i42.PageInfo(name); + static const _i53.PageInfo page = + _i53.PageInfo(name); } class AppWebviewArgs { const AppWebviewArgs({ this.key, required this.url, - required this.title, + this.title = "", }); - final _i44.Key? key; + final _i55.Key? key; final String url; @@ -534,70 +685,40 @@ class AppWebviewArgs { } /// generated route for -/// [_i5.ApproveDevice] -class ApproveDevice extends _i42.PageRouteInfo { - ApproveDevice({ - _i44.Key? key, - List<_i42.PageRouteInfo>? children, - }) : super( - ApproveDevice.name, - args: ApproveDeviceArgs(key: key), +/// [_i6.AuthLanding] +class AuthLanding extends _i53.PageRouteInfo { + const AuthLanding({List<_i53.PageRouteInfo>? children}) + : super( + AuthLanding.name, initialChildren: children, ); - static const String name = 'ApproveDevice'; + static const String name = 'AuthLanding'; - static const _i42.PageInfo page = - _i42.PageInfo(name); -} - -class ApproveDeviceArgs { - const ApproveDeviceArgs({this.key}); - - final _i44.Key? key; - - @override - String toString() { - return 'ApproveDeviceArgs{key: $key}'; - } + static const _i53.PageInfo page = _i53.PageInfo(name); } /// generated route for -/// [_i6.AuthorizeDeviceForPro] -class AuthorizePro extends _i42.PageRouteInfo { - AuthorizePro({ - _i44.Key? key, - List<_i42.PageRouteInfo>? children, - }) : super( +/// [_i7.AuthorizeDeviceForPro] +class AuthorizePro extends _i53.PageRouteInfo { + const AuthorizePro({List<_i53.PageRouteInfo>? children}) + : super( AuthorizePro.name, - args: AuthorizeProArgs(key: key), initialChildren: children, ); static const String name = 'AuthorizePro'; - static const _i42.PageInfo page = - _i42.PageInfo(name); -} - -class AuthorizeProArgs { - const AuthorizeProArgs({this.key}); - - final _i44.Key? key; - - @override - String toString() { - return 'AuthorizeProArgs{key: $key}'; - } + static const _i53.PageInfo page = _i53.PageInfo(name); } /// generated route for -/// [_i7.AuthorizeDeviceViaEmail] +/// [_i8.AuthorizeDeviceViaEmail] class AuthorizeDeviceEmail - extends _i42.PageRouteInfo { + extends _i53.PageRouteInfo { AuthorizeDeviceEmail({ - _i44.Key? key, - List<_i42.PageRouteInfo>? children, + _i55.Key? key, + List<_i53.PageRouteInfo>? children, }) : super( AuthorizeDeviceEmail.name, args: AuthorizeDeviceEmailArgs(key: key), @@ -606,14 +727,14 @@ class AuthorizeDeviceEmail static const String name = 'AuthorizeDeviceEmail'; - static const _i42.PageInfo page = - _i42.PageInfo(name); + static const _i53.PageInfo page = + _i53.PageInfo(name); } class AuthorizeDeviceEmailArgs { const AuthorizeDeviceEmailArgs({this.key}); - final _i44.Key? key; + final _i55.Key? key; @override String toString() { @@ -622,13 +743,13 @@ class AuthorizeDeviceEmailArgs { } /// generated route for -/// [_i8.AuthorizeDeviceViaEmailPin] +/// [_i9.AuthorizeDeviceViaEmailPin] class AuthorizeDeviceEmailPin - extends _i42.PageRouteInfo { + extends _i53.PageRouteInfo { AuthorizeDeviceEmailPin({ - _i44.Key? key, + _i55.Key? key, required String email, - List<_i42.PageRouteInfo>? children, + List<_i53.PageRouteInfo>? children, }) : super( AuthorizeDeviceEmailPin.name, args: AuthorizeDeviceEmailPinArgs( @@ -640,8 +761,8 @@ class AuthorizeDeviceEmailPin static const String name = 'AuthorizeDeviceEmailPin'; - static const _i42.PageInfo page = - _i42.PageInfo(name); + static const _i53.PageInfo page = + _i53.PageInfo(name); } class AuthorizeDeviceEmailPinArgs { @@ -650,7 +771,7 @@ class AuthorizeDeviceEmailPinArgs { required this.email, }); - final _i44.Key? key; + final _i55.Key? key; final String email; @@ -661,11 +782,11 @@ class AuthorizeDeviceEmailPinArgs { } /// generated route for -/// [_i9.BlockedUsers] -class BlockedUsers extends _i42.PageRouteInfo { +/// [_i10.BlockedUsers] +class BlockedUsers extends _i53.PageRouteInfo { BlockedUsers({ - _i43.Key? key, - List<_i42.PageRouteInfo>? children, + _i54.Key? key, + List<_i53.PageRouteInfo>? children, }) : super( BlockedUsers.name, args: BlockedUsersArgs(key: key), @@ -674,14 +795,14 @@ class BlockedUsers extends _i42.PageRouteInfo { static const String name = 'BlockedUsers'; - static const _i42.PageInfo page = - _i42.PageInfo(name); + static const _i53.PageInfo page = + _i53.PageInfo(name); } class BlockedUsersArgs { const BlockedUsersArgs({this.key}); - final _i43.Key? key; + final _i54.Key? key; @override String toString() { @@ -690,9 +811,47 @@ class BlockedUsersArgs { } /// generated route for -/// [_i10.ChatNumberAccount] -class ChatNumberAccount extends _i42.PageRouteInfo { - const ChatNumberAccount({List<_i42.PageRouteInfo>? children}) +/// [_i11.ChangeEmail] +class ChangeEmail extends _i53.PageRouteInfo { + ChangeEmail({ + _i55.Key? key, + required String email, + List<_i53.PageRouteInfo>? children, + }) : super( + ChangeEmail.name, + args: ChangeEmailArgs( + key: key, + email: email, + ), + initialChildren: children, + ); + + static const String name = 'ChangeEmail'; + + static const _i53.PageInfo page = + _i53.PageInfo(name); +} + +class ChangeEmailArgs { + const ChangeEmailArgs({ + this.key, + required this.email, + }); + + final _i55.Key? key; + + final String email; + + @override + String toString() { + return 'ChangeEmailArgs{key: $key, email: $email}'; + } +} + +/// generated route for +/// [_i12.ChatNumberAccount] +class ChatNumberAccount extends _i53.PageRouteInfo { + const ChatNumberAccount({List<_i53.PageRouteInfo>? children}) : super( ChatNumberAccount.name, initialChildren: children, @@ -700,13 +859,13 @@ class ChatNumberAccount extends _i42.PageRouteInfo { static const String name = 'ChatNumberAccount'; - static const _i42.PageInfo page = _i42.PageInfo(name); + static const _i53.PageInfo page = _i53.PageInfo(name); } /// generated route for -/// [_i11.ChatNumberMessaging] -class ChatNumberMessaging extends _i42.PageRouteInfo { - const ChatNumberMessaging({List<_i42.PageRouteInfo>? children}) +/// [_i13.ChatNumberMessaging] +class ChatNumberMessaging extends _i53.PageRouteInfo { + const ChatNumberMessaging({List<_i53.PageRouteInfo>? children}) : super( ChatNumberMessaging.name, initialChildren: children, @@ -714,13 +873,13 @@ class ChatNumberMessaging extends _i42.PageRouteInfo { static const String name = 'ChatNumberMessaging'; - static const _i42.PageInfo page = _i42.PageInfo(name); + static const _i53.PageInfo page = _i53.PageInfo(name); } /// generated route for -/// [_i12.ChatNumberRecovery] -class ChatNumberRecovery extends _i42.PageRouteInfo { - const ChatNumberRecovery({List<_i42.PageRouteInfo>? children}) +/// [_i14.ChatNumberRecovery] +class ChatNumberRecovery extends _i53.PageRouteInfo { + const ChatNumberRecovery({List<_i53.PageRouteInfo>? children}) : super( ChatNumberRecovery.name, initialChildren: children, @@ -728,17 +887,17 @@ class ChatNumberRecovery extends _i42.PageRouteInfo { static const String name = 'ChatNumberRecovery'; - static const _i42.PageInfo page = _i42.PageInfo(name); + static const _i53.PageInfo page = _i53.PageInfo(name); } /// generated route for -/// [_i13.Checkout] -class Checkout extends _i42.PageRouteInfo { +/// [_i15.Checkout] +class Checkout extends _i53.PageRouteInfo { Checkout({ - required _i44.Plan plan, + required _i55.Plan plan, required bool isPro, - _i44.Key? key, - List<_i42.PageRouteInfo>? children, + _i55.Key? key, + List<_i53.PageRouteInfo>? children, }) : super( Checkout.name, args: CheckoutArgs( @@ -751,8 +910,8 @@ class Checkout extends _i42.PageRouteInfo { static const String name = 'Checkout'; - static const _i42.PageInfo page = - _i42.PageInfo(name); + static const _i53.PageInfo page = + _i53.PageInfo(name); } class CheckoutArgs { @@ -762,11 +921,11 @@ class CheckoutArgs { this.key, }); - final _i44.Plan plan; + final _i55.Plan plan; final bool isPro; - final _i44.Key? key; + final _i55.Key? key; @override String toString() { @@ -775,11 +934,25 @@ class CheckoutArgs { } /// generated route for -/// [_i14.ContactInfo] -class ContactInfo extends _i42.PageRouteInfo { +/// [_i16.ConfirmEmail] +class ConfirmEmail extends _i53.PageRouteInfo { + const ConfirmEmail({List<_i53.PageRouteInfo>? children}) + : super( + ConfirmEmail.name, + initialChildren: children, + ); + + static const String name = 'ConfirmEmail'; + + static const _i53.PageInfo page = _i53.PageInfo(name); +} + +/// generated route for +/// [_i17.ContactInfo] +class ContactInfo extends _i53.PageRouteInfo { ContactInfo({ - required _i43.Contact contact, - List<_i42.PageRouteInfo>? children, + required _i54.Contact contact, + List<_i53.PageRouteInfo>? children, }) : super( ContactInfo.name, args: ContactInfoArgs(contact: contact), @@ -788,14 +961,14 @@ class ContactInfo extends _i42.PageRouteInfo { static const String name = 'ContactInfo'; - static const _i42.PageInfo page = - _i42.PageInfo(name); + static const _i53.PageInfo page = + _i53.PageInfo(name); } class ContactInfoArgs { const ContactInfoArgs({required this.contact}); - final _i43.Contact contact; + final _i54.Contact contact; @override String toString() { @@ -804,13 +977,13 @@ class ContactInfoArgs { } /// generated route for -/// [_i15.Conversation] -class Conversation extends _i42.PageRouteInfo { +/// [_i18.Conversation] +class Conversation extends _i53.PageRouteInfo { Conversation({ - required _i43.ContactId contactId, + required _i54.ContactId contactId, int? initialScrollIndex, bool showContactEditingDialog = false, - List<_i42.PageRouteInfo>? children, + List<_i53.PageRouteInfo>? children, }) : super( Conversation.name, args: ConversationArgs( @@ -823,8 +996,8 @@ class Conversation extends _i42.PageRouteInfo { static const String name = 'Conversation'; - static const _i42.PageInfo page = - _i42.PageInfo(name); + static const _i53.PageInfo page = + _i53.PageInfo(name); } class ConversationArgs { @@ -834,7 +1007,7 @@ class ConversationArgs { this.showContactEditingDialog = false, }); - final _i43.ContactId contactId; + final _i54.ContactId contactId; final int? initialScrollIndex; @@ -847,17 +1020,120 @@ class ConversationArgs { } /// generated route for -/// [_i16.FullScreenDialog] +/// [_i19.CreateAccountEmail] +class CreateAccountEmail extends _i53.PageRouteInfo { + CreateAccountEmail({ + _i55.Key? key, + _i55.Plan? plan, + _i55.AuthFlow authFlow = _i55.AuthFlow.createAccount, + List<_i53.PageRouteInfo>? children, + }) : super( + CreateAccountEmail.name, + args: CreateAccountEmailArgs( + key: key, + plan: plan, + authFlow: authFlow, + ), + initialChildren: children, + ); + + static const String name = 'CreateAccountEmail'; + + static const _i53.PageInfo page = + _i53.PageInfo(name); +} + +class CreateAccountEmailArgs { + const CreateAccountEmailArgs({ + this.key, + this.plan, + this.authFlow = _i55.AuthFlow.createAccount, + }); + + final _i55.Key? key; + + final _i55.Plan? plan; + + final _i55.AuthFlow authFlow; + + @override + String toString() { + return 'CreateAccountEmailArgs{key: $key, plan: $plan, authFlow: $authFlow}'; + } +} + +/// generated route for +/// [_i20.CreateAccountPassword] +class CreateAccountPassword + extends _i53.PageRouteInfo { + CreateAccountPassword({ + _i55.Key? key, + required String email, + required String code, + List<_i53.PageRouteInfo>? children, + }) : super( + CreateAccountPassword.name, + args: CreateAccountPasswordArgs( + key: key, + email: email, + code: code, + ), + initialChildren: children, + ); + + static const String name = 'CreateAccountPassword'; + + static const _i53.PageInfo page = + _i53.PageInfo(name); +} + +class CreateAccountPasswordArgs { + const CreateAccountPasswordArgs({ + this.key, + required this.email, + required this.code, + }); + + final _i55.Key? key; + + final String email; + + final String code; + + @override + String toString() { + return 'CreateAccountPasswordArgs{key: $key, email: $email, code: $code}'; + } +} + +/// generated route for +/// [_i21.DeviceLimit] +class DeviceLimit extends _i53.PageRouteInfo { + const DeviceLimit({List<_i53.PageRouteInfo>? children}) + : super( + DeviceLimit.name, + initialChildren: children, + ); + + static const String name = 'DeviceLimit'; + + static const _i53.PageInfo page = _i53.PageInfo(name); +} + +/// generated route for +/// [_i22.FullScreenDialog] class FullScreenDialogPage - extends _i42.PageRouteInfo { + extends _i53.PageRouteInfo { FullScreenDialogPage({ - required _i44.Widget widget, - _i44.Key? key, - List<_i42.PageRouteInfo>? children, + required _i55.Widget widget, + _i56.Color? bgColor, + _i55.Key? key, + List<_i53.PageRouteInfo>? children, }) : super( FullScreenDialogPage.name, args: FullScreenDialogPageArgs( widget: widget, + bgColor: bgColor, key: key, ), initialChildren: children, @@ -865,30 +1141,33 @@ class FullScreenDialogPage static const String name = 'FullScreenDialogPage'; - static const _i42.PageInfo page = - _i42.PageInfo(name); + static const _i53.PageInfo page = + _i53.PageInfo(name); } class FullScreenDialogPageArgs { const FullScreenDialogPageArgs({ required this.widget, + this.bgColor, this.key, }); - final _i44.Widget widget; + final _i55.Widget widget; + + final _i56.Color? bgColor; - final _i44.Key? key; + final _i55.Key? key; @override String toString() { - return 'FullScreenDialogPageArgs{widget: $widget, key: $key}'; + return 'FullScreenDialogPageArgs{widget: $widget, bgColor: $bgColor, key: $key}'; } } /// generated route for -/// [_i17.HomePage] -class Home extends _i42.PageRouteInfo { - const Home({List<_i42.PageRouteInfo>? children}) +/// [_i23.HomePage] +class Home extends _i53.PageRouteInfo { + const Home({List<_i53.PageRouteInfo>? children}) : super( Home.name, initialChildren: children, @@ -896,16 +1175,16 @@ class Home extends _i42.PageRouteInfo { static const String name = 'Home'; - static const _i42.PageInfo page = _i42.PageInfo(name); + static const _i53.PageInfo page = _i53.PageInfo(name); } /// generated route for -/// [_i18.Introduce] -class Introduce extends _i42.PageRouteInfo { +/// [_i24.Introduce] +class Introduce extends _i53.PageRouteInfo { Introduce({ required bool singleIntro, - _i43.Contact? contactToIntro, - List<_i42.PageRouteInfo>? children, + _i54.Contact? contactToIntro, + List<_i53.PageRouteInfo>? children, }) : super( Introduce.name, args: IntroduceArgs( @@ -917,8 +1196,8 @@ class Introduce extends _i42.PageRouteInfo { static const String name = 'Introduce'; - static const _i42.PageInfo page = - _i42.PageInfo(name); + static const _i53.PageInfo page = + _i53.PageInfo(name); } class IntroduceArgs { @@ -929,7 +1208,7 @@ class IntroduceArgs { final bool singleIntro; - final _i43.Contact? contactToIntro; + final _i54.Contact? contactToIntro; @override String toString() { @@ -938,9 +1217,9 @@ class IntroduceArgs { } /// generated route for -/// [_i19.Introductions] -class Introductions extends _i42.PageRouteInfo { - const Introductions({List<_i42.PageRouteInfo>? children}) +/// [_i25.Introductions] +class Introductions extends _i53.PageRouteInfo { + const Introductions({List<_i53.PageRouteInfo>? children}) : super( Introductions.name, initialChildren: children, @@ -948,13 +1227,13 @@ class Introductions extends _i42.PageRouteInfo { static const String name = 'Introductions'; - static const _i42.PageInfo page = _i42.PageInfo(name); + static const _i53.PageInfo page = _i53.PageInfo(name); } /// generated route for -/// [_i20.InviteFriends] -class InviteFriends extends _i42.PageRouteInfo { - const InviteFriends({List<_i42.PageRouteInfo>? children}) +/// [_i26.InviteFriends] +class InviteFriends extends _i53.PageRouteInfo { + const InviteFriends({List<_i53.PageRouteInfo>? children}) : super( InviteFriends.name, initialChildren: children, @@ -962,42 +1241,27 @@ class InviteFriends extends _i42.PageRouteInfo { static const String name = 'InviteFriends'; - static const _i42.PageInfo page = _i42.PageInfo(name); + static const _i53.PageInfo page = _i53.PageInfo(name); } /// generated route for -/// [_i21.Language] -class Language extends _i42.PageRouteInfo { - Language({ - _i44.Key? key, - List<_i42.PageRouteInfo>? children, - }) : super( +/// [_i27.Language] +class Language extends _i53.PageRouteInfo { + const Language({List<_i53.PageRouteInfo>? children}) + : super( Language.name, - args: LanguageArgs(key: key), initialChildren: children, ); static const String name = 'Language'; - static const _i42.PageInfo page = - _i42.PageInfo(name); -} - -class LanguageArgs { - const LanguageArgs({this.key}); - - final _i44.Key? key; - - @override - String toString() { - return 'LanguageArgs{key: $key}'; - } + static const _i53.PageInfo page = _i53.PageInfo(name); } /// generated route for -/// [_i22.LanternDesktop] -class LanternDesktop extends _i42.PageRouteInfo { - const LanternDesktop({List<_i42.PageRouteInfo>? children}) +/// [_i28.LanternDesktop] +class LanternDesktop extends _i53.PageRouteInfo { + const LanternDesktop({List<_i53.PageRouteInfo>? children}) : super( LanternDesktop.name, initialChildren: children, @@ -1005,13 +1269,13 @@ class LanternDesktop extends _i42.PageRouteInfo { static const String name = 'LanternDesktop'; - static const _i42.PageInfo page = _i42.PageInfo(name); + static const _i53.PageInfo page = _i53.PageInfo(name); } /// generated route for -/// [_i23.LinkDevice] -class LinkDevice extends _i42.PageRouteInfo { - const LinkDevice({List<_i42.PageRouteInfo>? children}) +/// [_i29.LinkDevice] +class LinkDevice extends _i53.PageRouteInfo { + const LinkDevice({List<_i53.PageRouteInfo>? children}) : super( LinkDevice.name, initialChildren: children, @@ -1019,13 +1283,13 @@ class LinkDevice extends _i42.PageRouteInfo { static const String name = 'LinkDevice'; - static const _i42.PageInfo page = _i42.PageInfo(name); + static const _i53.PageInfo page = _i53.PageInfo(name); } /// generated route for -/// [_i24.NewChat] -class NewChat extends _i42.PageRouteInfo { - const NewChat({List<_i42.PageRouteInfo>? children}) +/// [_i30.NewChat] +class NewChat extends _i53.PageRouteInfo { + const NewChat({List<_i53.PageRouteInfo>? children}) : super( NewChat.name, initialChildren: children, @@ -1033,13 +1297,13 @@ class NewChat extends _i42.PageRouteInfo { static const String name = 'NewChat'; - static const _i42.PageInfo page = _i42.PageInfo(name); + static const _i53.PageInfo page = _i53.PageInfo(name); } /// generated route for -/// [_i25.PlansPage] -class PlansPage extends _i42.PageRouteInfo { - const PlansPage({List<_i42.PageRouteInfo>? children}) +/// [_i31.PlansPage] +class PlansPage extends _i53.PageRouteInfo { + const PlansPage({List<_i53.PageRouteInfo>? children}) : super( PlansPage.name, initialChildren: children, @@ -1047,17 +1311,17 @@ class PlansPage extends _i42.PageRouteInfo { static const String name = 'PlansPage'; - static const _i42.PageInfo page = _i42.PageInfo(name); + static const _i53.PageInfo page = _i53.PageInfo(name); } /// generated route for -/// [_i26.PlayCheckout] -class PlayCheckout extends _i42.PageRouteInfo { +/// [_i32.PlayCheckout] +class PlayCheckout extends _i53.PageRouteInfo { PlayCheckout({ - required _i44.Plan plan, + required _i55.Plan plan, required bool isPro, - _i44.Key? key, - List<_i42.PageRouteInfo>? children, + _i55.Key? key, + List<_i53.PageRouteInfo>? children, }) : super( PlayCheckout.name, args: PlayCheckoutArgs( @@ -1070,8 +1334,8 @@ class PlayCheckout extends _i42.PageRouteInfo { static const String name = 'PlayCheckout'; - static const _i42.PageInfo page = - _i42.PageInfo(name); + static const _i53.PageInfo page = + _i53.PageInfo(name); } class PlayCheckoutArgs { @@ -1081,11 +1345,11 @@ class PlayCheckoutArgs { this.key, }); - final _i44.Plan plan; + final _i55.Plan plan; final bool isPro; - final _i44.Key? key; + final _i55.Key? key; @override String toString() { @@ -1094,11 +1358,11 @@ class PlayCheckoutArgs { } /// generated route for -/// [_i27.RecoveryKey] -class RecoveryKey extends _i42.PageRouteInfo { +/// [_i33.RecoveryKey] +class RecoveryKey extends _i53.PageRouteInfo { RecoveryKey({ - _i43.Key? key, - List<_i42.PageRouteInfo>? children, + _i54.Key? key, + List<_i53.PageRouteInfo>? children, }) : super( RecoveryKey.name, args: RecoveryKeyArgs(key: key), @@ -1107,14 +1371,14 @@ class RecoveryKey extends _i42.PageRouteInfo { static const String name = 'RecoveryKey'; - static const _i42.PageInfo page = - _i42.PageInfo(name); + static const _i53.PageInfo page = + _i53.PageInfo(name); } class RecoveryKeyArgs { const RecoveryKeyArgs({this.key}); - final _i43.Key? key; + final _i54.Key? key; @override String toString() { @@ -1123,13 +1387,13 @@ class RecoveryKeyArgs { } /// generated route for -/// [_i28.ReplicaAudioViewer] -class ReplicaAudioViewer extends _i42.PageRouteInfo { +/// [_i34.ReplicaAudioViewer] +class ReplicaAudioViewer extends _i53.PageRouteInfo { ReplicaAudioViewer({ - required _i45.ReplicaApi replicaApi, - required _i45.ReplicaSearchItem item, - required _i45.SearchCategory category, - List<_i42.PageRouteInfo>? children, + required _i57.ReplicaApi replicaApi, + required _i57.ReplicaSearchItem item, + required _i57.SearchCategory category, + List<_i53.PageRouteInfo>? children, }) : super( ReplicaAudioViewer.name, args: ReplicaAudioViewerArgs( @@ -1142,8 +1406,8 @@ class ReplicaAudioViewer extends _i42.PageRouteInfo { static const String name = 'ReplicaAudioViewer'; - static const _i42.PageInfo page = - _i42.PageInfo(name); + static const _i53.PageInfo page = + _i53.PageInfo(name); } class ReplicaAudioViewerArgs { @@ -1153,11 +1417,11 @@ class ReplicaAudioViewerArgs { required this.category, }); - final _i45.ReplicaApi replicaApi; + final _i57.ReplicaApi replicaApi; - final _i45.ReplicaSearchItem item; + final _i57.ReplicaSearchItem item; - final _i45.SearchCategory category; + final _i57.SearchCategory category; @override String toString() { @@ -1166,13 +1430,13 @@ class ReplicaAudioViewerArgs { } /// generated route for -/// [_i29.ReplicaImageViewer] -class ReplicaImageViewer extends _i42.PageRouteInfo { +/// [_i35.ReplicaImageViewer] +class ReplicaImageViewer extends _i53.PageRouteInfo { ReplicaImageViewer({ - required _i45.ReplicaApi replicaApi, - required _i45.ReplicaSearchItem item, - required _i45.SearchCategory category, - List<_i42.PageRouteInfo>? children, + required _i57.ReplicaApi replicaApi, + required _i57.ReplicaSearchItem item, + required _i57.SearchCategory category, + List<_i53.PageRouteInfo>? children, }) : super( ReplicaImageViewer.name, args: ReplicaImageViewerArgs( @@ -1185,8 +1449,8 @@ class ReplicaImageViewer extends _i42.PageRouteInfo { static const String name = 'ReplicaImageViewer'; - static const _i42.PageInfo page = - _i42.PageInfo(name); + static const _i53.PageInfo page = + _i53.PageInfo(name); } class ReplicaImageViewerArgs { @@ -1196,11 +1460,11 @@ class ReplicaImageViewerArgs { required this.category, }); - final _i45.ReplicaApi replicaApi; + final _i57.ReplicaApi replicaApi; - final _i45.ReplicaSearchItem item; + final _i57.ReplicaSearchItem item; - final _i45.SearchCategory category; + final _i57.SearchCategory category; @override String toString() { @@ -1209,13 +1473,13 @@ class ReplicaImageViewerArgs { } /// generated route for -/// [_i30.ReplicaLinkHandler] -class ReplicaLinkHandler extends _i42.PageRouteInfo { +/// [_i36.ReplicaLinkHandler] +class ReplicaLinkHandler extends _i53.PageRouteInfo { ReplicaLinkHandler({ - _i44.Key? key, - required _i45.ReplicaApi replicaApi, - required _i45.ReplicaLink replicaLink, - List<_i42.PageRouteInfo>? children, + _i55.Key? key, + required _i57.ReplicaApi replicaApi, + required _i57.ReplicaLink replicaLink, + List<_i53.PageRouteInfo>? children, }) : super( ReplicaLinkHandler.name, args: ReplicaLinkHandlerArgs( @@ -1228,8 +1492,8 @@ class ReplicaLinkHandler extends _i42.PageRouteInfo { static const String name = 'ReplicaLinkHandler'; - static const _i42.PageInfo page = - _i42.PageInfo(name); + static const _i53.PageInfo page = + _i53.PageInfo(name); } class ReplicaLinkHandlerArgs { @@ -1239,11 +1503,11 @@ class ReplicaLinkHandlerArgs { required this.replicaLink, }); - final _i44.Key? key; + final _i55.Key? key; - final _i45.ReplicaApi replicaApi; + final _i57.ReplicaApi replicaApi; - final _i45.ReplicaLink replicaLink; + final _i57.ReplicaLink replicaLink; @override String toString() { @@ -1252,13 +1516,13 @@ class ReplicaLinkHandlerArgs { } /// generated route for -/// [_i31.ReplicaMiscViewer] -class ReplicaMiscViewer extends _i42.PageRouteInfo { +/// [_i37.ReplicaMiscViewer] +class ReplicaMiscViewer extends _i53.PageRouteInfo { ReplicaMiscViewer({ - required _i45.ReplicaApi replicaApi, - required _i45.ReplicaSearchItem item, - required _i45.SearchCategory category, - List<_i42.PageRouteInfo>? children, + required _i57.ReplicaApi replicaApi, + required _i57.ReplicaSearchItem item, + required _i57.SearchCategory category, + List<_i53.PageRouteInfo>? children, }) : super( ReplicaMiscViewer.name, args: ReplicaMiscViewerArgs( @@ -1271,8 +1535,8 @@ class ReplicaMiscViewer extends _i42.PageRouteInfo { static const String name = 'ReplicaMiscViewer'; - static const _i42.PageInfo page = - _i42.PageInfo(name); + static const _i53.PageInfo page = + _i53.PageInfo(name); } class ReplicaMiscViewerArgs { @@ -1282,11 +1546,11 @@ class ReplicaMiscViewerArgs { required this.category, }); - final _i45.ReplicaApi replicaApi; + final _i57.ReplicaApi replicaApi; - final _i45.ReplicaSearchItem item; + final _i57.ReplicaSearchItem item; - final _i45.SearchCategory category; + final _i57.SearchCategory category; @override String toString() { @@ -1295,15 +1559,15 @@ class ReplicaMiscViewerArgs { } /// generated route for -/// [_i32.ReplicaUploadDescription] +/// [_i38.ReplicaUploadDescription] class ReplicaUploadDescription - extends _i42.PageRouteInfo { + extends _i53.PageRouteInfo { ReplicaUploadDescription({ - _i44.Key? key, - required _i46.File fileToUpload, + _i55.Key? key, + required _i58.File fileToUpload, required String fileTitle, String? fileDescription, - List<_i42.PageRouteInfo>? children, + List<_i53.PageRouteInfo>? children, }) : super( ReplicaUploadDescription.name, args: ReplicaUploadDescriptionArgs( @@ -1317,8 +1581,8 @@ class ReplicaUploadDescription static const String name = 'ReplicaUploadDescription'; - static const _i42.PageInfo page = - _i42.PageInfo(name); + static const _i53.PageInfo page = + _i53.PageInfo(name); } class ReplicaUploadDescriptionArgs { @@ -1329,9 +1593,9 @@ class ReplicaUploadDescriptionArgs { this.fileDescription, }); - final _i44.Key? key; + final _i55.Key? key; - final _i46.File fileToUpload; + final _i58.File fileToUpload; final String fileTitle; @@ -1344,14 +1608,14 @@ class ReplicaUploadDescriptionArgs { } /// generated route for -/// [_i33.ReplicaUploadReview] -class ReplicaUploadReview extends _i42.PageRouteInfo { +/// [_i39.ReplicaUploadReview] +class ReplicaUploadReview extends _i53.PageRouteInfo { ReplicaUploadReview({ - _i44.Key? key, - required _i46.File fileToUpload, + _i55.Key? key, + required _i58.File fileToUpload, required String fileTitle, String? fileDescription, - List<_i42.PageRouteInfo>? children, + List<_i53.PageRouteInfo>? children, }) : super( ReplicaUploadReview.name, args: ReplicaUploadReviewArgs( @@ -1365,8 +1629,8 @@ class ReplicaUploadReview extends _i42.PageRouteInfo { static const String name = 'ReplicaUploadReview'; - static const _i42.PageInfo page = - _i42.PageInfo(name); + static const _i53.PageInfo page = + _i53.PageInfo(name); } class ReplicaUploadReviewArgs { @@ -1377,9 +1641,9 @@ class ReplicaUploadReviewArgs { this.fileDescription, }); - final _i44.Key? key; + final _i55.Key? key; - final _i46.File fileToUpload; + final _i58.File fileToUpload; final String fileTitle; @@ -1392,14 +1656,14 @@ class ReplicaUploadReviewArgs { } /// generated route for -/// [_i34.ReplicaUploadTitle] -class ReplicaUploadTitle extends _i42.PageRouteInfo { +/// [_i40.ReplicaUploadTitle] +class ReplicaUploadTitle extends _i53.PageRouteInfo { ReplicaUploadTitle({ - _i44.Key? key, - required _i46.File fileToUpload, + _i55.Key? key, + required _i58.File fileToUpload, String? fileTitle, String? fileDescription, - List<_i42.PageRouteInfo>? children, + List<_i53.PageRouteInfo>? children, }) : super( ReplicaUploadTitle.name, args: ReplicaUploadTitleArgs( @@ -1413,8 +1677,8 @@ class ReplicaUploadTitle extends _i42.PageRouteInfo { static const String name = 'ReplicaUploadTitle'; - static const _i42.PageInfo page = - _i42.PageInfo(name); + static const _i53.PageInfo page = + _i53.PageInfo(name); } class ReplicaUploadTitleArgs { @@ -1425,9 +1689,9 @@ class ReplicaUploadTitleArgs { this.fileDescription, }); - final _i44.Key? key; + final _i55.Key? key; - final _i46.File fileToUpload; + final _i58.File fileToUpload; final String? fileTitle; @@ -1440,13 +1704,13 @@ class ReplicaUploadTitleArgs { } /// generated route for -/// [_i35.ReplicaVideoViewer] -class ReplicaVideoViewer extends _i42.PageRouteInfo { +/// [_i41.ReplicaVideoViewer] +class ReplicaVideoViewer extends _i53.PageRouteInfo { ReplicaVideoViewer({ - required _i45.ReplicaApi replicaApi, - required _i45.ReplicaSearchItem item, - required _i45.SearchCategory category, - List<_i42.PageRouteInfo>? children, + required _i57.ReplicaApi replicaApi, + required _i57.ReplicaSearchItem item, + required _i57.SearchCategory category, + List<_i53.PageRouteInfo>? children, }) : super( ReplicaVideoViewer.name, args: ReplicaVideoViewerArgs( @@ -1459,8 +1723,8 @@ class ReplicaVideoViewer extends _i42.PageRouteInfo { static const String name = 'ReplicaVideoViewer'; - static const _i42.PageInfo page = - _i42.PageInfo(name); + static const _i53.PageInfo page = + _i53.PageInfo(name); } class ReplicaVideoViewerArgs { @@ -1470,11 +1734,11 @@ class ReplicaVideoViewerArgs { required this.category, }); - final _i45.ReplicaApi replicaApi; + final _i57.ReplicaApi replicaApi; - final _i45.ReplicaSearchItem item; + final _i57.ReplicaSearchItem item; - final _i45.SearchCategory category; + final _i57.SearchCategory category; @override String toString() { @@ -1483,12 +1747,12 @@ class ReplicaVideoViewerArgs { } /// generated route for -/// [_i36.ReportIssue] -class ReportIssue extends _i42.PageRouteInfo { +/// [_i42.ReportIssue] +class ReportIssue extends _i53.PageRouteInfo { ReportIssue({ - _i44.Key? key, + _i55.Key? key, String? description, - List<_i42.PageRouteInfo>? children, + List<_i53.PageRouteInfo>? children, }) : super( ReportIssue.name, args: ReportIssueArgs( @@ -1500,8 +1764,8 @@ class ReportIssue extends _i42.PageRouteInfo { static const String name = 'ReportIssue'; - static const _i42.PageInfo page = - _i42.PageInfo(name); + static const _i53.PageInfo page = + _i53.PageInfo(name); } class ReportIssueArgs { @@ -1510,7 +1774,7 @@ class ReportIssueArgs { this.description, }); - final _i44.Key? key; + final _i55.Key? key; final String? description; @@ -1521,17 +1785,60 @@ class ReportIssueArgs { } /// generated route for -/// [_i37.ResellerCodeCheckout] +/// [_i43.ResellerCodeCheckout] +class ResellerCodeCheckoutLegacy + extends _i53.PageRouteInfo { + ResellerCodeCheckoutLegacy({ + required bool isPro, + _i55.Key? key, + List<_i53.PageRouteInfo>? children, + }) : super( + ResellerCodeCheckoutLegacy.name, + args: ResellerCodeCheckoutLegacyArgs( + isPro: isPro, + key: key, + ), + initialChildren: children, + ); + + static const String name = 'ResellerCodeCheckoutLegacy'; + + static const _i53.PageInfo page = + _i53.PageInfo(name); +} + +class ResellerCodeCheckoutLegacyArgs { + const ResellerCodeCheckoutLegacyArgs({ + required this.isPro, + this.key, + }); + + final bool isPro; + + final _i55.Key? key; + + @override + String toString() { + return 'ResellerCodeCheckoutLegacyArgs{isPro: $isPro, key: $key}'; + } +} + +/// generated route for +/// [_i44.ResellerCodeCheckout] class ResellerCodeCheckout - extends _i42.PageRouteInfo { + extends _i53.PageRouteInfo { ResellerCodeCheckout({ required bool isPro, - _i44.Key? key, - List<_i42.PageRouteInfo>? children, + required String email, + String? otp, + _i55.Key? key, + List<_i53.PageRouteInfo>? children, }) : super( ResellerCodeCheckout.name, args: ResellerCodeCheckoutArgs( isPro: isPro, + email: email, + otp: otp, key: key, ), initialChildren: children, @@ -1539,32 +1846,86 @@ class ResellerCodeCheckout static const String name = 'ResellerCodeCheckout'; - static const _i42.PageInfo page = - _i42.PageInfo(name); + static const _i53.PageInfo page = + _i53.PageInfo(name); } class ResellerCodeCheckoutArgs { const ResellerCodeCheckoutArgs({ required this.isPro, + required this.email, + this.otp, this.key, }); final bool isPro; - final _i44.Key? key; + final String email; + + final String? otp; + + final _i55.Key? key; + + @override + String toString() { + return 'ResellerCodeCheckoutArgs{isPro: $isPro, email: $email, otp: $otp, key: $key}'; + } +} + +/// generated route for +/// [_i45.ResetPassword] +class ResetPassword extends _i53.PageRouteInfo { + ResetPassword({ + _i59.Key? key, + String? email, + String? code, + _i55.AuthFlow authFlow = _i55.AuthFlow.reset, + List<_i53.PageRouteInfo>? children, + }) : super( + ResetPassword.name, + args: ResetPasswordArgs( + key: key, + email: email, + code: code, + authFlow: authFlow, + ), + initialChildren: children, + ); + + static const String name = 'ResetPassword'; + + static const _i53.PageInfo page = + _i53.PageInfo(name); +} + +class ResetPasswordArgs { + const ResetPasswordArgs({ + this.key, + this.email, + this.code, + this.authFlow = _i55.AuthFlow.reset, + }); + + final _i59.Key? key; + + final String? email; + + final String? code; + + final _i55.AuthFlow authFlow; @override String toString() { - return 'ResellerCodeCheckoutArgs{isPro: $isPro, key: $key}'; + return 'ResetPasswordArgs{key: $key, email: $email, code: $code, authFlow: $authFlow}'; } } /// generated route for -/// [_i38.Settings] -class Settings extends _i42.PageRouteInfo { +/// [_i46.Settings] +class Settings extends _i53.PageRouteInfo { Settings({ - _i44.Key? key, - List<_i42.PageRouteInfo>? children, + _i55.Key? key, + List<_i53.PageRouteInfo>? children, }) : super( Settings.name, args: SettingsArgs(key: key), @@ -1573,14 +1934,14 @@ class Settings extends _i42.PageRouteInfo { static const String name = 'Settings'; - static const _i42.PageInfo page = - _i42.PageInfo(name); + static const _i53.PageInfo page = + _i53.PageInfo(name); } class SettingsArgs { const SettingsArgs({this.key}); - final _i44.Key? key; + final _i55.Key? key; @override String toString() { @@ -1589,9 +1950,84 @@ class SettingsArgs { } /// generated route for -/// [_i39.SplitTunneling] -class SplitTunneling extends _i42.PageRouteInfo { - const SplitTunneling({List<_i42.PageRouteInfo>? children}) +/// [_i47.SignIn] +class SignIn extends _i53.PageRouteInfo { + SignIn({ + _i55.Key? key, + _i55.AuthFlow authFlow = _i55.AuthFlow.signIn, + List<_i53.PageRouteInfo>? children, + }) : super( + SignIn.name, + args: SignInArgs( + key: key, + authFlow: authFlow, + ), + initialChildren: children, + ); + + static const String name = 'SignIn'; + + static const _i53.PageInfo page = _i53.PageInfo(name); +} + +class SignInArgs { + const SignInArgs({ + this.key, + this.authFlow = _i55.AuthFlow.signIn, + }); + + final _i55.Key? key; + + final _i55.AuthFlow authFlow; + + @override + String toString() { + return 'SignInArgs{key: $key, authFlow: $authFlow}'; + } +} + +/// generated route for +/// [_i48.SignInPassword] +class SignInPassword extends _i53.PageRouteInfo { + SignInPassword({ + _i55.Key? key, + required String email, + List<_i53.PageRouteInfo>? children, + }) : super( + SignInPassword.name, + args: SignInPasswordArgs( + key: key, + email: email, + ), + initialChildren: children, + ); + + static const String name = 'SignInPassword'; + + static const _i53.PageInfo page = + _i53.PageInfo(name); +} + +class SignInPasswordArgs { + const SignInPasswordArgs({ + this.key, + required this.email, + }); + + final _i55.Key? key; + + final String email; + + @override + String toString() { + return 'SignInPasswordArgs{key: $key, email: $email}'; + } +} + +/// generated route for +/// [_i49.SplitTunneling] +class SplitTunneling extends _i53.PageRouteInfo { + const SplitTunneling({List<_i53.PageRouteInfo>? children}) : super( SplitTunneling.name, initialChildren: children, @@ -1599,19 +2035,19 @@ class SplitTunneling extends _i42.PageRouteInfo { static const String name = 'SplitTunneling'; - static const _i42.PageInfo page = _i42.PageInfo(name); + static const _i53.PageInfo page = _i53.PageInfo(name); } /// generated route for -/// [_i40.StripeCheckout] -class StripeCheckout extends _i42.PageRouteInfo { +/// [_i50.StripeCheckout] +class StripeCheckout extends _i53.PageRouteInfo { StripeCheckout({ - required _i44.Plan plan, + required _i55.Plan plan, required String email, String? refCode, required bool isPro, - _i44.Key? key, - List<_i42.PageRouteInfo>? children, + _i55.Key? key, + List<_i53.PageRouteInfo>? children, }) : super( StripeCheckout.name, args: StripeCheckoutArgs( @@ -1626,8 +2062,8 @@ class StripeCheckout extends _i42.PageRouteInfo { static const String name = 'StripeCheckout'; - static const _i42.PageInfo page = - _i42.PageInfo(name); + static const _i53.PageInfo page = + _i53.PageInfo(name); } class StripeCheckoutArgs { @@ -1639,7 +2075,7 @@ class StripeCheckoutArgs { this.key, }); - final _i44.Plan plan; + final _i55.Plan plan; final String email; @@ -1647,7 +2083,7 @@ class StripeCheckoutArgs { final bool isPro; - final _i44.Key? key; + final _i55.Key? key; @override String toString() { @@ -1656,9 +2092,9 @@ class StripeCheckoutArgs { } /// generated route for -/// [_i41.Support] -class Support extends _i42.PageRouteInfo { - const Support({List<_i42.PageRouteInfo>? children}) +/// [_i51.Support] +class Support extends _i53.PageRouteInfo { + const Support({List<_i53.PageRouteInfo>? children}) : super( Support.name, initialChildren: children, @@ -1666,5 +2102,63 @@ class Support extends _i42.PageRouteInfo { static const String name = 'Support'; - static const _i42.PageInfo page = _i42.PageInfo(name); + static const _i53.PageInfo page = _i53.PageInfo(name); +} + +/// generated route for +/// [_i52.Verification] +class Verification extends _i53.PageRouteInfo { + Verification({ + _i60.Key? key, + required String email, + _i60.AuthFlow authFlow = _i60.AuthFlow.reset, + _i11.ChangeEmailPageArgs? changeEmailArgs, + _i60.Plan? plan, + String? tempPassword, + List<_i53.PageRouteInfo>? children, + }) : super( + Verification.name, + args: VerificationArgs( + key: key, + email: email, + authFlow: authFlow, + changeEmailArgs: changeEmailArgs, + plan: plan, + tempPassword: tempPassword, + ), + initialChildren: children, + ); + + static const String name = 'Verification'; + + static const _i53.PageInfo page = + _i53.PageInfo(name); +} + +class VerificationArgs { + const VerificationArgs({ + this.key, + required this.email, + this.authFlow = _i60.AuthFlow.reset, + this.changeEmailArgs, + this.plan, + this.tempPassword, + }); + + final _i60.Key? key; + + final String email; + + final _i60.AuthFlow authFlow; + + final _i11.ChangeEmailPageArgs? changeEmailArgs; + + final _i60.Plan? plan; + + final String? tempPassword; + + @override + String toString() { + return 'VerificationArgs{key: $key, email: $email, authFlow: $authFlow, changeEmailArgs: $changeEmailArgs, plan: $plan, tempPassword: $tempPassword}'; + } } diff --git a/lib/core/services.dart b/lib/core/services.dart new file mode 100644 index 000000000..e5ac2f5b2 --- /dev/null +++ b/lib/core/services.dart @@ -0,0 +1,9 @@ +import 'package:get_it/get_it.dart'; +import 'package:lantern/core/purchase/app_purchase.dart'; + +final GetIt sl = GetIt.instance; + +void init() { + //Inject + sl.registerLazySingleton(() => AppPurchase()); +} diff --git a/lib/custom_bottom_bar.dart b/lib/custom_bottom_bar.dart index 0e21424c5..44a9142af 100644 --- a/lib/custom_bottom_bar.dart +++ b/lib/custom_bottom_bar.dart @@ -5,12 +5,11 @@ import 'package:lantern/replica/common.dart'; class CustomBottomBar extends StatelessWidget { final String selectedTab; final bool isDevelop; - final bool isTesting; + // final bool isTesting; const CustomBottomBar({ required this.selectedTab, required this.isDevelop, - this.isTesting = false, Key? key, }) : super(key: key); @@ -36,7 +35,7 @@ class CustomBottomBar extends StatelessWidget { } indexToTab[nextIndex] = TAB_ACCOUNT; tabToIndex[TAB_ACCOUNT] = nextIndex++; - if (isDevelop && !isTesting) { + if (isDevelop ) { indexToTab[nextIndex] = TAB_DEVELOPER; tabToIndex[TAB_DEVELOPER] = nextIndex++; } @@ -57,7 +56,7 @@ class CustomBottomBar extends StatelessWidget { replicaEnabled, true, isDevelop, - isTesting, + replicaAddr, ), ); @@ -73,7 +72,6 @@ class CustomBottomBar extends StatelessWidget { bool replicaEnabled, bool hasBeenOnboarded, bool isDevelop, - bool isTesting, String replicaAddr, ) { final items = []; @@ -265,7 +263,7 @@ class CustomBottomBar extends StatelessWidget { ), ); - if (isDevelop && !isTesting) { + if (isDevelop) { items.add( BottomNavigationBarItem( icon: CustomBottomBarItem( diff --git a/lib/ffi.dart b/lib/ffi.dart index cf8e35b79..624f56437 100644 --- a/lib/ffi.dart +++ b/lib/ffi.dart @@ -100,7 +100,6 @@ Future ffiRemoveDevice(String deviceId) async { FutureOr ffiHasPlanUpdateOrBuy(dynamic context) { final json = _bindings.hasPlanUpdatedOrBuy().cast().toDartString(); - print('Result of hasPlanUpdatedOrBuy: $json'); return json == 'true' ? true : throw NoPlansUpdate("No Plans update"); } diff --git a/lib/generated_bindings.dart b/lib/generated_bindings.dart new file mode 100644 index 000000000..4410e4dd4 --- /dev/null +++ b/lib/generated_bindings.dart @@ -0,0 +1,733 @@ +// ignore_for_file: always_specify_types +// ignore_for_file: camel_case_types +// ignore_for_file: non_constant_identifier_names +// ignore_for_file: unused_field +// ignore_for_file: unused_element + +// AUTO GENERATED FILE, DO NOT EDIT. +// +// Generated by `package:ffigen`. +// ignore_for_file: type=lint +import 'dart:ffi' as ffi; + +/// Bindings to `liblantern.h`. +class NativeLibrary { + /// Holds the symbol lookup function. + final ffi.Pointer Function(String symbolName) + _lookup; + + /// The symbols are looked up in [dynamicLibrary]. + NativeLibrary(ffi.DynamicLibrary dynamicLibrary) + : _lookup = dynamicLibrary.lookup; + + /// The symbols are looked up with [lookup]. + NativeLibrary.fromLookup( + ffi.Pointer Function(String symbolName) + lookup) + : _lookup = lookup; + + void start() { + return _start(); + } + + late final _startPtr = + _lookup>('start'); + late final _start = _startPtr.asFunction(); + + ffi.Pointer hasProxyFected() { + return _hasProxyFected(); + } + + late final _hasProxyFectedPtr = + _lookup Function()>>( + 'hasProxyFected'); + late final _hasProxyFected = + _hasProxyFectedPtr.asFunction Function()>(); + + ffi.Pointer hasConfigFected() { + return _hasConfigFected(); + } + + late final _hasConfigFectedPtr = + _lookup Function()>>( + 'hasConfigFected'); + late final _hasConfigFected = + _hasConfigFectedPtr.asFunction Function()>(); + + ffi.Pointer onSuccess() { + return _onSuccess(); + } + + late final _onSuccessPtr = + _lookup Function()>>( + 'onSuccess'); + late final _onSuccess = + _onSuccessPtr.asFunction Function()>(); + + void sysProxyOn() { + return _sysProxyOn(); + } + + late final _sysProxyOnPtr = + _lookup>('sysProxyOn'); + late final _sysProxyOn = _sysProxyOnPtr.asFunction(); + + void sysProxyOff() { + return _sysProxyOff(); + } + + late final _sysProxyOffPtr = + _lookup>('sysProxyOff'); + late final _sysProxyOff = _sysProxyOffPtr.asFunction(); + + ffi.Pointer websocketAddr() { + return _websocketAddr(); + } + + late final _websocketAddrPtr = + _lookup Function()>>( + 'websocketAddr'); + late final _websocketAddr = + _websocketAddrPtr.asFunction Function()>(); + + ffi.Pointer plans() { + return _plans(); + } + + late final _plansPtr = + _lookup Function()>>('plans'); + late final _plans = _plansPtr.asFunction Function()>(); + + ffi.Pointer paymentMethodsV3() { + return _paymentMethodsV3(); + } + + late final _paymentMethodsV3Ptr = + _lookup Function()>>( + 'paymentMethodsV3'); + late final _paymentMethodsV3 = + _paymentMethodsV3Ptr.asFunction Function()>(); + + ffi.Pointer paymentMethodsV4() { + return _paymentMethodsV4(); + } + + late final _paymentMethodsV4Ptr = + _lookup Function()>>( + 'paymentMethodsV4'); + late final _paymentMethodsV4 = + _paymentMethodsV4Ptr.asFunction Function()>(); + + ffi.Pointer proxyAll() { + return _proxyAll(); + } + + late final _proxyAllPtr = + _lookup Function()>>('proxyAll'); + late final _proxyAll = + _proxyAllPtr.asFunction Function()>(); + + void setProxyAll( + ffi.Pointer value, + ) { + return _setProxyAll( + value, + ); + } + + late final _setProxyAllPtr = + _lookup)>>( + 'setProxyAll'); + late final _setProxyAll = + _setProxyAllPtr.asFunction)>(); + + /// this method is reposible for checking if the user has updated plan or bought plans + ffi.Pointer hasPlanUpdatedOrBuy() { + return _hasPlanUpdatedOrBuy(); + } + + late final _hasPlanUpdatedOrBuyPtr = + _lookup Function()>>( + 'hasPlanUpdatedOrBuy'); + late final _hasPlanUpdatedOrBuy = + _hasPlanUpdatedOrBuyPtr.asFunction Function()>(); + + ffi.Pointer devices() { + return _devices(); + } + + late final _devicesPtr = + _lookup Function()>>('devices'); + late final _devices = + _devicesPtr.asFunction Function()>(); + + ffi.Pointer approveDevice( + ffi.Pointer code, + ) { + return _approveDevice( + code, + ); + } + + late final _approveDevicePtr = _lookup< + ffi.NativeFunction< + ffi.Pointer Function( + ffi.Pointer)>>('approveDevice'); + late final _approveDevice = _approveDevicePtr + .asFunction Function(ffi.Pointer)>(); + + ffi.Pointer removeDevice( + ffi.Pointer deviceId, + ) { + return _removeDevice( + deviceId, + ); + } + + late final _removeDevicePtr = _lookup< + ffi.NativeFunction< + ffi.Pointer Function( + ffi.Pointer)>>('removeDevice'); + late final _removeDevice = _removeDevicePtr + .asFunction Function(ffi.Pointer)>(); + + ffi.Pointer expiryDate() { + return _expiryDate(); + } + + late final _expiryDatePtr = + _lookup Function()>>( + 'expiryDate'); + late final _expiryDate = + _expiryDatePtr.asFunction Function()>(); + + ffi.Pointer userData() { + return _userData(); + } + + late final _userDataPtr = + _lookup Function()>>('userData'); + late final _userData = + _userDataPtr.asFunction Function()>(); + + ffi.Pointer serverInfo() { + return _serverInfo(); + } + + late final _serverInfoPtr = + _lookup Function()>>( + 'serverInfo'); + late final _serverInfo = + _serverInfoPtr.asFunction Function()>(); + + ffi.Pointer emailAddress() { + return _emailAddress(); + } + + late final _emailAddressPtr = + _lookup Function()>>( + 'emailAddress'); + late final _emailAddress = + _emailAddressPtr.asFunction Function()>(); + + ffi.Pointer emailExists( + ffi.Pointer email, + ) { + return _emailExists( + email, + ); + } + + late final _emailExistsPtr = _lookup< + ffi.NativeFunction< + ffi.Pointer Function( + ffi.Pointer)>>('emailExists'); + late final _emailExists = _emailExistsPtr + .asFunction Function(ffi.Pointer)>(); + + /// The function returns two C strings: the first represents success, and the second represents an error. + /// If the redemption is successful, the first string contains "true", and the second string is nil. + /// If an error occurs during redemption, the first string is nil, and the second string contains the error message. + ffi.Pointer redeemResellerCode( + ffi.Pointer email, + ffi.Pointer currency, + ffi.Pointer deviceName, + ffi.Pointer resellerCode, + ) { + return _redeemResellerCode( + email, + currency, + deviceName, + resellerCode, + ); + } + + late final _redeemResellerCodePtr = _lookup< + ffi.NativeFunction< + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer)>>('redeemResellerCode'); + late final _redeemResellerCode = _redeemResellerCodePtr.asFunction< + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer)>(); + + ffi.Pointer referral() { + return _referral(); + } + + late final _referralPtr = + _lookup Function()>>('referral'); + late final _referral = + _referralPtr.asFunction Function()>(); + + ffi.Pointer chatEnabled() { + return _chatEnabled(); + } + + late final _chatEnabledPtr = + _lookup Function()>>( + 'chatEnabled'); + late final _chatEnabled = + _chatEnabledPtr.asFunction Function()>(); + + ffi.Pointer playVersion() { + return _playVersion(); + } + + late final _playVersionPtr = + _lookup Function()>>( + 'playVersion'); + late final _playVersion = + _playVersionPtr.asFunction Function()>(); + + ffi.Pointer storeVersion() { + return _storeVersion(); + } + + late final _storeVersionPtr = + _lookup Function()>>( + 'storeVersion'); + late final _storeVersion = + _storeVersionPtr.asFunction Function()>(); + + ffi.Pointer lang() { + return _lang(); + } + + late final _langPtr = + _lookup Function()>>('lang'); + late final _lang = _langPtr.asFunction Function()>(); + + void setSelectLang( + ffi.Pointer lang, + ) { + return _setSelectLang( + lang, + ); + } + + late final _setSelectLangPtr = + _lookup)>>( + 'setSelectLang'); + late final _setSelectLang = + _setSelectLangPtr.asFunction)>(); + + ffi.Pointer country() { + return _country(); + } + + late final _countryPtr = + _lookup Function()>>('country'); + late final _country = + _countryPtr.asFunction Function()>(); + + ffi.Pointer sdkVersion() { + return _sdkVersion(); + } + + late final _sdkVersionPtr = + _lookup Function()>>( + 'sdkVersion'); + late final _sdkVersion = + _sdkVersionPtr.asFunction Function()>(); + + ffi.Pointer vpnStatus() { + return _vpnStatus(); + } + + late final _vpnStatusPtr = + _lookup Function()>>( + 'vpnStatus'); + late final _vpnStatus = + _vpnStatusPtr.asFunction Function()>(); + + ffi.Pointer hasSucceedingProxy() { + return _hasSucceedingProxy(); + } + + late final _hasSucceedingProxyPtr = + _lookup Function()>>( + 'hasSucceedingProxy'); + late final _hasSucceedingProxy = + _hasSucceedingProxyPtr.asFunction Function()>(); + + ffi.Pointer onBoardingStatus() { + return _onBoardingStatus(); + } + + late final _onBoardingStatusPtr = + _lookup Function()>>( + 'onBoardingStatus'); + late final _onBoardingStatus = + _onBoardingStatusPtr.asFunction Function()>(); + + ffi.Pointer acceptedTermsVersion() { + return _acceptedTermsVersion(); + } + + late final _acceptedTermsVersionPtr = + _lookup Function()>>( + 'acceptedTermsVersion'); + late final _acceptedTermsVersion = + _acceptedTermsVersionPtr.asFunction Function()>(); + + ffi.Pointer proUser() { + return _proUser(); + } + + late final _proUserPtr = + _lookup Function()>>('proUser'); + late final _proUser = + _proUserPtr.asFunction Function()>(); + + ffi.Pointer deviceLinkingCode() { + return _deviceLinkingCode(); + } + + late final _deviceLinkingCodePtr = + _lookup Function()>>( + 'deviceLinkingCode'); + late final _deviceLinkingCode = + _deviceLinkingCodePtr.asFunction Function()>(); + + ffi.Pointer paymentRedirect( + ffi.Pointer planID, + ffi.Pointer currency, + ffi.Pointer provider, + ffi.Pointer email, + ffi.Pointer deviceName, + ) { + return _paymentRedirect( + planID, + currency, + provider, + email, + deviceName, + ); + } + + late final _paymentRedirectPtr = _lookup< + ffi.NativeFunction< + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer)>>('paymentRedirect'); + late final _paymentRedirect = _paymentRedirectPtr.asFunction< + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer)>(); + + void exitApp() { + return _exitApp(); + } + + late final _exitAppPtr = + _lookup>('exitApp'); + late final _exitApp = _exitAppPtr.asFunction(); + + ffi.Pointer developmentMode() { + return _developmentMode(); + } + + late final _developmentModePtr = + _lookup Function()>>( + 'developmentMode'); + late final _developmentMode = + _developmentModePtr.asFunction Function()>(); + + ffi.Pointer splitTunneling() { + return _splitTunneling(); + } + + late final _splitTunnelingPtr = + _lookup Function()>>( + 'splitTunneling'); + late final _splitTunneling = + _splitTunnelingPtr.asFunction Function()>(); + + ffi.Pointer chatMe() { + return _chatMe(); + } + + late final _chatMePtr = + _lookup Function()>>('chatMe'); + late final _chatMe = + _chatMePtr.asFunction Function()>(); + + ffi.Pointer replicaAddr() { + return _replicaAddr(); + } + + late final _replicaAddrPtr = + _lookup Function()>>( + 'replicaAddr'); + late final _replicaAddr = + _replicaAddrPtr.asFunction Function()>(); + + reportIssue_return reportIssue( + ffi.Pointer email, + ffi.Pointer issueType, + ffi.Pointer description, + ) { + return _reportIssue( + email, + issueType, + description, + ); + } + + late final _reportIssuePtr = _lookup< + ffi.NativeFunction< + reportIssue_return Function(ffi.Pointer, + ffi.Pointer, ffi.Pointer)>>('reportIssue'); + late final _reportIssue = _reportIssuePtr.asFunction< + reportIssue_return Function(ffi.Pointer, ffi.Pointer, + ffi.Pointer)>(); + + ffi.Pointer checkUpdates() { + return _checkUpdates(); + } + + late final _checkUpdatesPtr = + _lookup Function()>>( + 'checkUpdates'); + late final _checkUpdates = + _checkUpdatesPtr.asFunction Function()>(); +} + +/// mbstate_t is an opaque object to keep conversion state, during multibyte +/// stream conversions. The content must not be referenced by user programs. +final class __mbstate_t extends ffi.Union { + @ffi.Array.multi([128]) + external ffi.Array __mbstate8; + + /// for alignment + @ffi.LongLong() + external int _mbstateL; +} + +final class __darwin_pthread_handler_rec extends ffi.Struct { + /// Routine to call + external ffi + .Pointer)>> + __routine; + + /// Argument to pass + external ffi.Pointer __arg; + + external ffi.Pointer<__darwin_pthread_handler_rec> __next; +} + +final class _opaque_pthread_attr_t extends ffi.Struct { + @ffi.Long() + external int __sig; + + @ffi.Array.multi([56]) + external ffi.Array __opaque; +} + +final class _opaque_pthread_cond_t extends ffi.Struct { + @ffi.Long() + external int __sig; + + @ffi.Array.multi([40]) + external ffi.Array __opaque; +} + +final class _opaque_pthread_condattr_t extends ffi.Struct { + @ffi.Long() + external int __sig; + + @ffi.Array.multi([8]) + external ffi.Array __opaque; +} + +final class _opaque_pthread_mutex_t extends ffi.Struct { + @ffi.Long() + external int __sig; + + @ffi.Array.multi([56]) + external ffi.Array __opaque; +} + +final class _opaque_pthread_mutexattr_t extends ffi.Struct { + @ffi.Long() + external int __sig; + + @ffi.Array.multi([8]) + external ffi.Array __opaque; +} + +final class _opaque_pthread_once_t extends ffi.Struct { + @ffi.Long() + external int __sig; + + @ffi.Array.multi([8]) + external ffi.Array __opaque; +} + +final class _opaque_pthread_rwlock_t extends ffi.Struct { + @ffi.Long() + external int __sig; + + @ffi.Array.multi([192]) + external ffi.Array __opaque; +} + +final class _opaque_pthread_rwlockattr_t extends ffi.Struct { + @ffi.Long() + external int __sig; + + @ffi.Array.multi([16]) + external ffi.Array __opaque; +} + +final class _opaque_pthread_t extends ffi.Struct { + @ffi.Long() + external int __sig; + + external ffi.Pointer<__darwin_pthread_handler_rec> __cleanup_stack; + + @ffi.Array.multi([8176]) + external ffi.Array __opaque; +} + +final class _GoString_ extends ffi.Struct { + external ffi.Pointer p; + + @ptrdiff_t() + external int n; +} + +typedef ptrdiff_t = __darwin_ptrdiff_t; +typedef __darwin_ptrdiff_t = ffi.Long; +typedef Dart__darwin_ptrdiff_t = int; + +final class GoInterface extends ffi.Struct { + external ffi.Pointer t; + + external ffi.Pointer v; +} + +final class GoSlice extends ffi.Struct { + external ffi.Pointer data; + + @GoInt() + external int len; + + @GoInt() + external int cap; +} + +typedef GoInt = GoInt64; +typedef GoInt64 = ffi.LongLong; +typedef DartGoInt64 = int; + +/// Return type for reportIssue +final class reportIssue_return extends ffi.Struct { + external ffi.Pointer r0; + + external ffi.Pointer r1; +} + +const int __has_safe_buffers = 1; + +const int __DARWIN_ONLY_64_BIT_INO_T = 1; + +const int __DARWIN_ONLY_UNIX_CONFORMANCE = 1; + +const int __DARWIN_ONLY_VERS_1050 = 1; + +const int __DARWIN_UNIX03 = 1; + +const int __DARWIN_64_BIT_INO_T = 1; + +const int __DARWIN_VERS_1050 = 1; + +const int __DARWIN_NON_CANCELABLE = 0; + +const String __DARWIN_SUF_EXTSN = '\$DARWIN_EXTSN'; + +const int __DARWIN_C_ANSI = 4096; + +const int __DARWIN_C_FULL = 900000; + +const int __DARWIN_C_LEVEL = 900000; + +const int __STDC_WANT_LIB_EXT1__ = 1; + +const int __DARWIN_NO_LONG_LONG = 0; + +const int _DARWIN_FEATURE_64_BIT_INODE = 1; + +const int _DARWIN_FEATURE_ONLY_64_BIT_INODE = 1; + +const int _DARWIN_FEATURE_ONLY_VERS_1050 = 1; + +const int _DARWIN_FEATURE_ONLY_UNIX_CONFORMANCE = 1; + +const int _DARWIN_FEATURE_UNIX_CONFORMANCE = 3; + +const int __has_ptrcheck = 0; + +const int __DARWIN_NULL = 0; + +const int __PTHREAD_SIZE__ = 8176; + +const int __PTHREAD_ATTR_SIZE__ = 56; + +const int __PTHREAD_MUTEXATTR_SIZE__ = 8; + +const int __PTHREAD_MUTEX_SIZE__ = 56; + +const int __PTHREAD_CONDATTR_SIZE__ = 8; + +const int __PTHREAD_COND_SIZE__ = 40; + +const int __PTHREAD_ONCE_SIZE__ = 8; + +const int __PTHREAD_RWLOCK_SIZE__ = 192; + +const int __PTHREAD_RWLOCKATTR_SIZE__ = 16; + +const int __DARWIN_WCHAR_MAX = 2147483647; + +const int __DARWIN_WCHAR_MIN = -2147483648; + +const int __DARWIN_WEOF = -1; + +const int _FORTIFY_SOURCE = 2; + +const int NULL = 0; + +const int USER_ADDR_NULL = 0; diff --git a/lib/home.dart b/lib/home.dart index a5fe753b3..72b4a75b7 100644 --- a/lib/home.dart +++ b/lib/home.dart @@ -1,4 +1,3 @@ -import 'package:internet_connection_checker_plus/internet_connection_checker_plus.dart'; import 'package:lantern/account/account_tab.dart'; import 'package:lantern/account/developer_settings.dart'; import 'package:lantern/account/privacy_disclosure.dart'; @@ -26,8 +25,9 @@ class HomePage extends StatefulWidget { _HomePageState createState() => _HomePageState(); } -class _HomePageState extends State with TrayListener, WindowListener { +class _HomePageState extends State with WindowListener { Function()? _cancelEventSubscription; + Function userNew = once(); @override void initState() { @@ -47,7 +47,7 @@ class _HomePageState extends State with TrayListener, WindowListener { } void channelListener() { - if(Platform.isIOS) return; + if (Platform.isIOS) return; const mainMethodChannel = MethodChannel('lantern_method_channel'); const navigationChannel = MethodChannel('navigation'); @@ -59,7 +59,7 @@ class _HomePageState extends State with TrayListener, WindowListener { .then((shouldShowModal) async { if (shouldShowModal) { // open VPN tab - sessionModel.setSelectedTab(context,TAB_VPN); + sessionModel.setSelectedTab(context, TAB_VPN); // show Try Lantern Chat dialog await context.router .push(FullScreenDialogPage(widget: TryLanternChat())); @@ -93,11 +93,8 @@ class _HomePageState extends State with TrayListener, WindowListener { }); } - - void _initWindowManager() async { windowManager.addListener(this); - // Add this line to override the default close handler await windowManager.setPreventClose(true); setState(() {}); } @@ -106,6 +103,39 @@ class _HomePageState extends State with TrayListener, WindowListener { trayManager.addListener(TrayHandler.instance); } + Future _checkForFirstTimeVisit() async { + if (!Platform.isIOS) return; + + checkForFirstTimeVisit() async { + if (sessionModel.proUserNotifier.value == null) { + return; + } + if (sessionModel.proUserNotifier.value!) { + sessionModel.setFirstTimeVisit(); + if (sessionModel.proUserNotifier.hasListeners) { + sessionModel.proUserNotifier.removeListener(() {}); + } + return; + } + final isFirstTime = await sessionModel.isUserFirstTimeVisit(); + if (isFirstTime) { + context.router.push(const AuthLanding()); + sessionModel.setFirstTimeVisit(); + if (sessionModel.proUserNotifier.hasListeners) { + sessionModel.proUserNotifier.removeListener(() {}); + } + } + } + + if (sessionModel.proUserNotifier.value != null) { + checkForFirstTimeVisit(); + } else { + sessionModel.proUserNotifier.addListener(() async { + checkForFirstTimeVisit(); + }); + } + } + @override void onWindowClose() async { bool _isPreventClose = await windowManager.isPreventClose(); @@ -185,20 +215,20 @@ class _HomePageState extends State with TrayListener, WindowListener { // not already been accepted return const PrivacyDisclosure(); } - return messagingModel.getOnBoardingStatus((_, isOnboarded, child) { - final isTesting = const String.fromEnvironment( - 'driver', - defaultValue: 'false', - ).toLowerCase() == - 'true'; + if (Platform.isIOS) { + userNew(() { + print("called user new function"); + _checkForFirstTimeVisit(); + }); + } + return messagingModel.getOnBoardingStatus((_, isOnboarded, child) { final tab = tabModel.currentIndex; return Scaffold( body: buildBody(tab, isOnboarded), bottomNavigationBar: CustomBottomBar( selectedTab: tab, isDevelop: developmentMode, - isTesting: isTesting, ), ); }); diff --git a/lib/i18n/i18n.dart b/lib/i18n/i18n.dart index a24578b83..2a1e7dd8f 100644 --- a/lib/i18n/i18n.dart +++ b/lib/i18n/i18n.dart @@ -3,7 +3,7 @@ import 'package:i18n_extension_importer/src/io/import.dart'; import 'package:lantern/common/common.dart'; extension Localization on String { - static String defaultLocale = 'en'; + static String defaultLocale = 'en_us'; static String locale = defaultLocale; static Translations translations = @@ -29,7 +29,7 @@ extension Localization on String { String doLocalize() => localize(this, translations, locale: locale); String get i18n => localize(this, translations, locale: locale.replaceFirst('_', '-').toLowerCase()); - // String get i18n => localize(this, translations, locale: 'hi'); + String fill(List params) => localizeFill(this, params); } diff --git a/lib/main.dart b/lib/main.dart index 536441e67..ac77c317e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,6 +6,7 @@ import 'package:google_mobile_ads/google_mobile_ads.dart'; import 'package:lantern/app.dart'; import 'package:lantern/common/common.dart'; import 'package:lantern/common/common_desktop.dart'; +import 'package:lantern/core/purchase/app_purchase.dart'; import 'package:lantern/replica/ui/utils.dart'; import 'package:window_manager/window_manager.dart'; @@ -14,7 +15,7 @@ import 'catcher_setup.dart'; // IOS issue // https://github.com/flutter/flutter/issues/133465 Future main() async { - // CI will be true only when running appium test +// CI will be true only when running appium test const String flavor = String.fromEnvironment('app.flavor'); print("Running Flavor $flavor"); @@ -22,7 +23,6 @@ Future main() async { print("Flutter extension enabled $flavor"); enableFlutterDriverExtension(); } - WidgetsFlutterBinding.ensureInitialized(); try { // To load the .env file contents into dotenv. @@ -53,10 +53,27 @@ Future main() async { // Due to replica we are using lot of cache // clear if goes to above limit CustomCacheManager().clearCacheIfExceeded(); + if (Platform.isIOS) { + // Inject all the services + init(); + sl().init(); + } } + await Localization.ensureInitialized(); await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); - setupCatcherAndRun(LanternApp()); + +//Todo if catcher is not picking up error and exception then we should switch to sentryFlutter +// SentryFlutter.init((options) { +// options.debug = true; +// options.anrEnabled = true; +// options.autoInitializeNativeSdk = true; +// options.attachScreenshot = true; +// options.dsn = Platform.isAndroid +// ? 'https://4753d78f885f4b79a497435907ce4210@o75725.ingest.sentry.io/5850353' +// : 'https://c14296fdf5a6be272e1ecbdb7cb23f76@o75725.ingest.sentry.io/4506081382694912'; +// }, appRunner: () => setupCatcherAndRun(LanternApp())); + setupCatcherAndRun(const LanternApp()); } Future _initGoogleMobileAds() async { diff --git a/lib/plans/plan_details.dart b/lib/plans/plan_details.dart index c4077bbed..b9131abe9 100644 --- a/lib/plans/plan_details.dart +++ b/lib/plans/plan_details.dart @@ -1,7 +1,8 @@ import 'package:lantern/common/common.dart'; +import 'package:lantern/core/purchase/app_purchase.dart'; import 'package:lantern/plans/utils.dart'; -class PlanCard extends StatelessWidget { +class PlanCard extends StatefulWidget { final Plan plan; final bool isPro; @@ -11,12 +12,17 @@ class PlanCard extends StatelessWidget { Key? key, }) : super(key: key); + @override + State createState() => _PlanCardState(); +} + +class _PlanCardState extends State { @override Widget build(BuildContext context) { - final planName = plan.id.split('-')[0]; - final formattedPricePerYear = plan.totalCostBilledOneTime; - final formattedPricePerMonth = plan.oneMonthCost; - final isBestValue = plan.bestValue; + final planName = widget.plan.id.split('-')[0]; + final formattedPricePerYear = widget.plan.totalCostBilledOneTime; + final formattedPricePerMonth = widget.plan.oneMonthCost; + final isBestValue = widget.plan.bestValue; return Padding( padding: const EdgeInsetsDirectional.only(bottom: 16.0), @@ -123,7 +129,7 @@ class PlanCard extends StatelessWidget { void onPlanTap(BuildContext context) { switch (Platform.operatingSystem) { case 'ios': - throw Exception("Not support at the moment"); + resolveRouteIOS(); break; default: // proceed to the default checkout page on Android and desktop @@ -151,8 +157,8 @@ class PlanCard extends StatelessWidget { if (isPlayVersion && !inRussia) { await context.pushRoute( PlayCheckout( - plan: plan, - isPro: isPro, + plan: widget.plan, + isPro: widget.isPro, ), ); return; @@ -161,11 +167,12 @@ class PlanCard extends StatelessWidget { final providers = paymentProvidersFromMethods(paymentMethods); // if only one payment provider is returned, bypass the last checkout screen // Note: as of now, we only do this for Stripe since it is the only payment provider that collects email - if (providers.length == 1 && providers[0].name.toPaymentEnum() == Providers.stripe) { + if (providers.length == 1 && + providers[0].name.toPaymentEnum() == Providers.stripe) { final providerName = providers[0].name.toPaymentEnum(); final redirectUrl = await sessionModel.paymentRedirectForDesktop( context, - plan.id, + widget.plan.id, "", providerName, ); @@ -174,14 +181,60 @@ class PlanCard extends StatelessWidget { return; } } + // * Proceed to our own Checkout await context.pushRoute( Checkout( - plan: plan, - isPro: isPro, + plan: widget.plan, + isPro: widget.isPro, ), ); } + + void resolveRouteIOS() { + if (widget.isPro ) { + //user is signed in + _proceedToCheckoutIOS(context); + } else { + signUpFlow(); + } + } + + void signUpFlow() { + // If user is new we need to send plans id to create account flow + context.pushRoute(CreateAccountEmail( + authFlow: AuthFlow.createAccount, plan: widget.plan)); + } + + void _proceedToCheckoutIOS(BuildContext context) { + final appPurchase = sl(); + try { + context.loaderOverlay.show(); + appPurchase.startPurchase( + email: sessionModel.userEmail.value ?? "", + planId: widget.plan.id, + onSuccess: () { + context.loaderOverlay.hide(); + showSuccessDialog(context, widget.isPro); + }, + onFailure: (error) { + context.loaderOverlay.hide(); + CDialog.showError( + context, + error: error, + description: error.toString(), + ); + }, + ); + } catch (e) { + context.loaderOverlay.hide(); + CDialog.showError( + context, + error: e, + description: e.toString(), + ); + } + } } class PlanStep extends StatelessWidget { diff --git a/lib/plans/plans.dart b/lib/plans/plans.dart index 47b54623d..e38b6b172 100644 --- a/lib/plans/plans.dart +++ b/lib/plans/plans.dart @@ -1,5 +1,6 @@ import 'package:lantern/common/common.dart'; import 'package:lantern/common/ui/app_loading_dialog.dart'; +import 'package:lantern/core/router/router.gr.dart'; import 'package:lantern/plans/feature_list.dart'; import 'package:lantern/plans/plan_details.dart'; import 'package:lantern/plans/utils.dart'; @@ -15,11 +16,9 @@ class PlansPage extends StatelessWidget { widget: sessionModel .proUser((BuildContext context, bool proUser, Widget? child) { return sessionModel.plans( - builder: ( - context, - Iterable> plans, - Widget? child, - ) { + builder: (context, + Iterable> plans, + Widget? child,) { if (plans.isEmpty) { // show user option to retry return RetryWidget(onRetryTap: () => onRetryTap(context)); @@ -81,8 +80,12 @@ class PlansPage extends StatelessWidget { ), ), // * Card - ...plans.toList().reversed.map( - (plan) => Container( + ...plans + .toList() + .reversed + .map( + (plan) => + Container( color: white, padding: const EdgeInsetsDirectional.only( start: 32.0, @@ -93,10 +96,12 @@ class PlansPage extends StatelessWidget { isPro: proUser, ), ), - ), + ), ], ), ), + // todo need to enable this for other platform soon + _buildFooter(context, proUser), ], ); @@ -106,6 +111,23 @@ class PlansPage extends StatelessWidget { ); } + ///If the user is already so not ask for email + ///f the user is not pro, ask for email + void _onPromoCodeTap(BuildContext context, bool proUser) { + if (!Platform.isIOS) { + context.pushRoute(ResellerCodeCheckoutLegacy(isPro: true)); + return; + } + if (proUser) { + context.pushRoute( + ResellerCodeCheckout(isPro: true, email: sessionModel.userEmail.value!), + ); + } else { + context + .pushRoute(CreateAccountEmail(authFlow: AuthFlow.proCodeActivation)); + } + } + Widget _buildHeader(BuildContext context) { return Container( padding: const EdgeInsetsDirectional.only( @@ -148,9 +170,7 @@ class PlansPage extends StatelessWidget { color: grey1, ), child: GestureDetector( - onTap: () async => await context.pushRoute( - ResellerCodeCheckout(isPro: proUser), - ), + onTap: () => _onPromoCodeTap(context, proUser), child: Text( 'Have a Lantern Pro activation code? Click here.', style: tsBody1.copiedWith(color: grey5), diff --git a/lib/plans/reseller_checkout.dart b/lib/plans/reseller_checkout.dart index 60f3e4a02..f1fd970b2 100644 --- a/lib/plans/reseller_checkout.dart +++ b/lib/plans/reseller_checkout.dart @@ -1,7 +1,7 @@ -import 'package:email_validator/email_validator.dart'; +// ignore_for_file: use_build_context_synchronously + import 'package:intl/intl.dart'; import 'package:lantern/common/common.dart'; -import 'package:lantern/plans/plan_details.dart'; import 'package:lantern/plans/tos.dart'; import 'package:lantern/plans/utils.dart'; @@ -36,9 +36,17 @@ class ResellerCodeFormatter extends TextInputFormatter { @RoutePage(name: "ResellerCodeCheckout") class ResellerCodeCheckout extends StatefulWidget { final bool isPro; + final String email; + + ///This otp is needed to while resting password + /// If otp is null it means user is pro + /// if otp is not null it means user is not pro send them to password screen + final String? otp; - ResellerCodeCheckout({ + const ResellerCodeCheckout({ required this.isPro, + required this.email, + this.otp, Key? key, }) : super(key: key); @@ -47,14 +55,6 @@ class ResellerCodeCheckout extends StatefulWidget { } class _ResellerCodeCheckoutState extends State { - final emailFieldKey = GlobalKey(); - late final emailController = CustomTextEditingController( - formKey: emailFieldKey, - validator: (value) => EmailValidator.validate(value ?? '') - ? null - : 'please_enter_a_valid_email_address'.i18n, - ); - final resellerCodeFieldKey = GlobalKey(); late final resellerCodeController = CustomTextEditingController( formKey: resellerCodeFieldKey, @@ -67,12 +67,6 @@ class _ResellerCodeCheckoutState extends State { : 'your_activation_code_is_invalid'.i18n, ); - @override - void initState() { - WidgetsFlutterBinding.ensureInitialized(); - super.initState(); - } - @override void dispose() { super.dispose(); @@ -80,112 +74,79 @@ class _ResellerCodeCheckoutState extends State { @override Widget build(BuildContext context) { - final copy = 'register_for_pro'.i18n; - return sessionModel.emailAddress(( - BuildContext context, - String emailAddress, - Widget? child, - ) { - return BaseScreen( - resizeToAvoidBottomInset: false, - title: 'lantern_pro_checkout'.i18n, - body: Container( - height: MediaQuery.of(context).size.height, - padding: const EdgeInsetsDirectional.only( - start: 16, - end: 16, - top: 24, - bottom: 24, - ), + return BaseScreen( + resizeToAvoidBottomInset: false, + title: const AppBarProHeader(), + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 0), + child: SafeArea( child: Column( - mainAxisAlignment: MainAxisAlignment.spaceAround, + mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ - // * Step 2 - PlanStep( - stepNum: '2', - description: 'enter_email_and_activation_code'.i18n, + const SizedBox(height: 24), + HeadingText( + title: 'enter_activation_code'.i18n, ), - // * Email field - Container( - padding: const EdgeInsetsDirectional.only( - top: 8, - bottom: 8, - ), - child: Form( - key: emailFieldKey, - child: CTextField( - initialValue: widget.isPro ? emailAddress : '', - controller: emailController, - autovalidateMode: AutovalidateMode.disabled, - label: 'Email'.i18n, - onChanged: (value) { - setState(() {}); - }, - keyboardType: TextInputType.emailAddress, - prefixIcon: const CAssetImage(path: ImagePaths.email), - ), + const SizedBox(height: 24), + _buildEmail(), + const SizedBox(height: 24), + Form( + key: resellerCodeFieldKey, + child: CTextField( + maxLength: 25 + 4, + //accounting for dashes + controller: resellerCodeController, + autovalidateMode: AutovalidateMode.disabled, + inputFormatters: [ResellerCodeFormatter()], + label: 'Activation Code'.i18n, + keyboardType: TextInputType.text, + prefixIcon: const CAssetImage(path: ImagePaths.dots), + textCapitalization: TextCapitalization.characters, ), ), - // * Activation code field - Container( - padding: const EdgeInsetsDirectional.only( - top: 8, - bottom: 8, - ), - child: Form( - key: resellerCodeFieldKey, - child: CTextField( - maxLength: 25 + 4, - //accounting for dashes - controller: resellerCodeController, - autovalidateMode: AutovalidateMode.disabled, - onChanged: (value) { - setState(() {}); - }, - inputFormatters: [ResellerCodeFormatter()], - label: 'Activation Code'.i18n, - keyboardType: TextInputType.text, - prefixIcon: const CAssetImage(path: ImagePaths.dots), - textCapitalization: TextCapitalization.characters, - ), + const SizedBox(height: 24), + SizedBox( + width: double.infinity, + child: Button( + primary: true, + disabled: + resellerCodeFieldKey.currentState?.validate() == false, + text: 'continue'.i18n, + onPressed: onRegisterPro, ), ), - const Spacer(), - Column( - children: [ - TOS(copy: copy), - // * resellerCodeCheckout - Button( - disabled: emailController.value.text.isEmpty || - emailFieldKey.currentState?.validate() == false || - resellerCodeFieldKey.currentState?.validate() == false, - text: copy, - onPressed: onRegisterPro, - ), - ], - ) + const SizedBox(height: 24), + const TOS(), ], ), ), - ); - }); + ), + ); } Future onRegisterPro() async { try { + FocusManager.instance.primaryFocus?.unfocus(); context.loaderOverlay.show(); Locale locale = Localizations.localeOf(context); final format = NumberFormat.simpleCurrency(locale: locale.toString()); final currencyName = format.currencyName ?? "USD"; + await sessionModel.redeemResellerCode( - emailController.text, + widget.email, currencyName, Platform.operatingSystem, resellerCodeController.text, ); context.loaderOverlay.hide(); - showSuccessDialog(context, widget.isPro, true); + if (!widget.isPro) { + // it mean user is coming from signup flow + openPassword(); + } else { + showSuccessDialog(context, widget.isPro, + isReseller: true, barrierDismissible: false); + } } catch (error, stackTrace) { print(stackTrace); appLogger.e(error, stackTrace: stackTrace); @@ -200,4 +161,41 @@ class _ResellerCodeCheckoutState extends State { ); } } + + Widget _buildEmail() { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(100), + color: grey1, + border: Border.all( + width: 1, + color: grey3, + ), + ), + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SvgPicture.asset( + ImagePaths.email, + ), + const SizedBox(width: 8), + CText(widget.email, + textAlign: TextAlign.center, + style: tsBody1!.copiedWith( + leadingDistribution: TextLeadingDistribution.even, + )) + ], + ), + ); + } + + void openPassword() { + context.pushRoute(CreateAccountPassword( + email: widget.email.validateEmail, + code: widget.otp!, + )); + } } diff --git a/lib/plans/reseller_checkout_legacy.dart b/lib/plans/reseller_checkout_legacy.dart new file mode 100644 index 000000000..a3b2987e2 --- /dev/null +++ b/lib/plans/reseller_checkout_legacy.dart @@ -0,0 +1,177 @@ +import 'package:email_validator/email_validator.dart'; +import 'package:intl/intl.dart'; +import 'package:lantern/common/common.dart'; +import 'package:lantern/plans/plan_details.dart'; +import 'package:lantern/plans/reseller_checkout.dart'; +import 'package:lantern/plans/tos.dart'; +import 'package:lantern/plans/utils.dart'; + +@RoutePage(name: "ResellerCodeCheckoutLegacy") +class ResellerCodeCheckout extends StatefulWidget { + final bool isPro; + + ResellerCodeCheckout({ + required this.isPro, + Key? key, + }) : super(key: key); + + @override + State createState() => _ResellerCodeCheckoutState(); +} + +class _ResellerCodeCheckoutState extends State { + final emailFieldKey = GlobalKey(); + late final emailController = CustomTextEditingController( + formKey: emailFieldKey, + validator: (value) => EmailValidator.validate(value ?? '') + ? null + : 'please_enter_a_valid_email_address'.i18n, + ); + + final resellerCodeFieldKey = GlobalKey(); + late final resellerCodeController = CustomTextEditingController( + formKey: resellerCodeFieldKey, + validator: (value) => value != null && + // only allow letters, numbers and hyphens as well as length excluding dashes should be exactly 25 characters + // TODO: reject our own referral code + RegExp(r'^[a-zA-Z0-9-]*$').hasMatch(value) && + value.replaceAll('-', '').length == 25 + ? null + : 'your_activation_code_is_invalid'.i18n, + ); + + @override + void initState() { + WidgetsFlutterBinding.ensureInitialized(); + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final copy = 'register_for_pro'.i18n; + return sessionModel.emailAddress(( + BuildContext context, + String emailAddress, + Widget? child, + ) { + return BaseScreen( + resizeToAvoidBottomInset: false, + title: 'lantern_pro_checkout'.i18n, + body: Container( + height: MediaQuery.of(context).size.height, + padding: const EdgeInsetsDirectional.only( + start: 16, + end: 16, + top: 24, + bottom: 24, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // * Step 2 + PlanStep( + stepNum: '2', + description: 'enter_email_and_activation_code'.i18n, + ), + // * Email field + Container( + padding: const EdgeInsetsDirectional.only( + top: 8, + bottom: 8, + ), + child: Form( + key: emailFieldKey, + child: CTextField( + initialValue: widget.isPro ? emailAddress : '', + controller: emailController, + autovalidateMode: AutovalidateMode.disabled, + label: 'Email'.i18n, + onChanged: (value) { + setState(() {}); + }, + keyboardType: TextInputType.emailAddress, + prefixIcon: const CAssetImage(path: ImagePaths.email), + ), + ), + ), + // * Activation code field + Container( + padding: const EdgeInsetsDirectional.only( + top: 8, + bottom: 8, + ), + child: Form( + key: resellerCodeFieldKey, + child: CTextField( + maxLength: 25 + 4, + //accounting for dashes + controller: resellerCodeController, + autovalidateMode: AutovalidateMode.disabled, + onChanged: (value) { + setState(() {}); + }, + inputFormatters: [ResellerCodeFormatter()], + label: 'Activation Code'.i18n, + keyboardType: TextInputType.text, + prefixIcon: const CAssetImage(path: ImagePaths.dots), + textCapitalization: TextCapitalization.characters, + ), + ), + ), + const Spacer(), + Column( + children: [ + const TOS(), + const SizedBox(height: 16), + // * resellerCodeCheckout + Button( + disabled: emailController.value.text.isEmpty || + emailFieldKey.currentState?.validate() == false || + resellerCodeFieldKey.currentState?.validate() == false, + text: copy, + onPressed: onRegisterPro, + ), + ], + ) + ], + ), + ), + ); + }); + } + + Future onRegisterPro() async { + try { + context.loaderOverlay.show(); + Locale locale = Localizations.localeOf(context); + final format = NumberFormat.simpleCurrency(locale: locale.toString()); + final currencyName = format.currencyName ?? "USD"; + await sessionModel.redeemResellerCode( + emailController.text, + currencyName, + Platform.operatingSystem, + resellerCodeController.text, + ); + context.loaderOverlay.hide(); + showSuccessDialog(context, widget.isPro, isReseller: true); + } catch (error, stackTrace) { + print(stackTrace); + appLogger.e(error, stackTrace: stackTrace); + context.loaderOverlay.hide(); + CDialog.showError( + context, + error: error, + stackTrace: stackTrace, + description: (error as PlatformException) + .message + .toString(), // This is coming localized + ); + } + } +} diff --git a/lib/plans/stripe_checkout.dart b/lib/plans/stripe_checkout.dart index fc76ab649..7c208736c 100644 --- a/lib/plans/stripe_checkout.dart +++ b/lib/plans/stripe_checkout.dart @@ -197,7 +197,7 @@ class _StripeCheckoutState extends State { refCode: widget.refCode, isPro: widget.isPro, ), - TOS(copy: copy), + TOS(), checkoutButton(), ], ), diff --git a/lib/plans/tos.dart b/lib/plans/tos.dart index 35a14bd0d..03ece1866 100644 --- a/lib/plans/tos.dart +++ b/lib/plans/tos.dart @@ -1,50 +1,39 @@ +import 'package:flutter/gestures.dart'; import 'package:lantern/common/common.dart'; -import 'package:url_launcher/url_launcher.dart'; class TOS extends StatelessWidget { const TOS({ Key? key, - required this.copy, }) : super(key: key); - final String copy; - @override Widget build(BuildContext context) { - return GestureDetector( - onTap: () => onUrlTap(context), - child: Container( - padding: const EdgeInsetsDirectional.only(bottom: 24.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - CText( - "${'by_clicking_tos'.i18n.fill([copy])} ", - style: tsOverline, - ), - CText( - 'tos'.i18n, - style: tsOverline.copiedWith( - color: blue3, - ), + return Text.rich( + TextSpan( + children: [ + TextSpan( + text: 'by_creating_an_account'.i18n, + style: tsFloatingLabel, + ), + const TextSpan( + text: ' ', + ), + TextSpan( + text: 'terms_of_service'.i18n, + style: tsFloatingLabel!.copiedWith( + decoration: TextDecoration.underline, ), - ], - ), + recognizer: TapGestureRecognizer() + ..onTap = () => openTermsOfService(context), + ), + ], ), + textAlign: TextAlign.center, ); } - Future onUrlTap(BuildContext context) async { - if (await canLaunchUrl(Uri.parse(AppSecret.tos))) { - CDialog( - title: 'open_url'.i18n, - description: 'are_you_sure_you_want_to_open'.i18n.fill([AppSecret.tos]), - agreeText: 'continue'.i18n, - agreeAction: () async { - await launchUrl(Uri.parse(AppSecret.tos)); - return true; - }, - ).show(context); - } + void openTermsOfService(BuildContext context) { + FocusScope.of(context).unfocus(); + context.pushRoute(AppWebview(url: termsOfService, title: 'TOS')); } } diff --git a/lib/plans/utils.dart b/lib/plans/utils.dart index b8a5bfe40..f6b3ce35a 100644 --- a/lib/plans/utils.dart +++ b/lib/plans/utils.dart @@ -45,7 +45,13 @@ void showError( ); } -void showSuccessDialog(BuildContext context, bool isPro, [bool? isReseller]) { +void showSuccessDialog( + BuildContext context, + bool isPro, { + bool? isReseller, + VoidCallback? onAgree, + bool barrierDismissible = true, +}) { String description, title; if (isReseller != null && isReseller) { title = 'renewal_success'.i18n; @@ -63,7 +69,12 @@ void showSuccessDialog(BuildContext context, bool isPro, [bool? isReseller]) { title: title, description: description, actionLabel: 'continue_to_pro'.i18n, + barrierDismissible: barrierDismissible, agreeAction: () async { + if (onAgree != null) { + onAgree(); + return true; + } // Note: whatever page you need to popUtil // it will pop that page context.router.popUntil((route) => route.settings.name == PlansPage.name); diff --git a/lib/vpn/protos_shared/vpn.pb.dart b/lib/vpn/protos_shared/vpn.pb.dart index 8f5193f73..ef9eb9387 100644 --- a/lib/vpn/protos_shared/vpn.pb.dart +++ b/lib/vpn/protos_shared/vpn.pb.dart @@ -398,6 +398,50 @@ class Devices extends $pb.GeneratedMessage { $core.List get devices => $_getList(0); } +class Plans extends $pb.GeneratedMessage { + factory Plans({ + $core.Iterable? plan, + }) { + final $result = create(); + if (plan != null) { + $result.plan.addAll(plan); + } + return $result; + } + Plans._() : super(); + factory Plans.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory Plans.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Plans', createEmptyInstance: create) + ..pc(1, _omitFieldNames ? '' : 'plan', $pb.PbFieldType.PM, subBuilder: Plan.create) + ..hasRequiredFields = false + ; + + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + Plans clone() => Plans()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + Plans copyWith(void Function(Plans) updates) => super.copyWith((message) => updates(message as Plans)) as Plans; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static Plans create() => Plans._(); + Plans createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static Plans getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static Plans? _defaultInstance; + + @$pb.TagNumber(1) + $core.List get plan => $_getList(0); +} + class Plan extends $pb.GeneratedMessage { factory Plan({ $core.String? id, @@ -411,6 +455,7 @@ class Plan extends $pb.GeneratedMessage { $core.String? totalCost, $core.String? formattedBonus, $core.String? renewalText, + $core.Map<$core.String, $fixnum.Int64>? renewalBonusExpected, }) { final $result = create(); if (id != null) { @@ -446,6 +491,9 @@ class Plan extends $pb.GeneratedMessage { if (renewalText != null) { $result.renewalText = renewalText; } + if (renewalBonusExpected != null) { + $result.renewalBonusExpected.addAll(renewalBonusExpected); + } return $result; } Plan._() : super(); @@ -464,6 +512,7 @@ class Plan extends $pb.GeneratedMessage { ..aOS(9, _omitFieldNames ? '' : 'totalCost', protoName: 'totalCost') ..aOS(10, _omitFieldNames ? '' : 'formattedBonus', protoName: 'formattedBonus') ..aOS(11, _omitFieldNames ? '' : 'renewalText', protoName: 'renewalText') + ..m<$core.String, $fixnum.Int64>(13, _omitFieldNames ? '' : 'renewalBonusExpected', protoName: 'renewalBonusExpected', entryClassName: 'Plan.RenewalBonusExpectedEntry', keyFieldType: $pb.PbFieldType.OS, valueFieldType: $pb.PbFieldType.O6) ..hasRequiredFields = false ; @@ -574,6 +623,9 @@ class Plan extends $pb.GeneratedMessage { $core.bool hasRenewalText() => $_has(10); @$pb.TagNumber(11) void clearRenewalText() => clearField(11); + + @$pb.TagNumber(13) + $core.Map<$core.String, $fixnum.Int64> get renewalBonusExpected => $_getMap(11); } class PaymentProviders extends $pb.GeneratedMessage { @@ -707,6 +759,9 @@ class User extends $pb.GeneratedMessage { $core.String? referral, $core.String? token, $core.bool? yinbiEnabled, + $core.Iterable<$core.String>? inviters, + $core.Iterable<$core.String>? invitees, + $core.Iterable? purchases, }) { final $result = create(); if (userId != null) { @@ -748,6 +803,15 @@ class User extends $pb.GeneratedMessage { if (yinbiEnabled != null) { $result.yinbiEnabled = yinbiEnabled; } + if (inviters != null) { + $result.inviters.addAll(inviters); + } + if (invitees != null) { + $result.invitees.addAll(invitees); + } + if (purchases != null) { + $result.purchases.addAll(purchases); + } return $result; } User._() : super(); @@ -768,6 +832,9 @@ class User extends $pb.GeneratedMessage { ..aOS(11, _omitFieldNames ? '' : 'referral') ..aOS(12, _omitFieldNames ? '' : 'token') ..aOB(13, _omitFieldNames ? '' : 'yinbiEnabled', protoName: 'yinbiEnabled') + ..pPS(14, _omitFieldNames ? '' : 'inviters') + ..pPS(15, _omitFieldNames ? '' : 'invitees') + ..pc(16, _omitFieldNames ? '' : 'purchases', $pb.PbFieldType.PM, subBuilder: Purchase.create) ..hasRequiredFields = false ; @@ -902,6 +969,65 @@ class User extends $pb.GeneratedMessage { $core.bool hasYinbiEnabled() => $_has(12); @$pb.TagNumber(13) void clearYinbiEnabled() => clearField(13); + + @$pb.TagNumber(14) + $core.List<$core.String> get inviters => $_getList(13); + + @$pb.TagNumber(15) + $core.List<$core.String> get invitees => $_getList(14); + + @$pb.TagNumber(16) + $core.List get purchases => $_getList(15); +} + +class Purchase extends $pb.GeneratedMessage { + factory Purchase({ + $core.String? plan, + }) { + final $result = create(); + if (plan != null) { + $result.plan = plan; + } + return $result; + } + Purchase._() : super(); + factory Purchase.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory Purchase.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Purchase', createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'plan') + ..hasRequiredFields = false + ; + + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + Purchase clone() => Purchase()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + Purchase copyWith(void Function(Purchase) updates) => super.copyWith((message) => updates(message as Purchase)) as Purchase; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static Purchase create() => Purchase._(); + Purchase createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static Purchase getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static Purchase? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get plan => $_getSZ(0); + @$pb.TagNumber(1) + set plan($core.String v) { $_setString(0, v); } + @$pb.TagNumber(1) + $core.bool hasPlan() => $_has(0); + @$pb.TagNumber(1) + void clearPlan() => clearField(1); } /// API diff --git a/lib/vpn/protos_shared/vpn.pbjson.dart b/lib/vpn/protos_shared/vpn.pbjson.dart index a437ed37a..6efdcca84 100644 --- a/lib/vpn/protos_shared/vpn.pbjson.dart +++ b/lib/vpn/protos_shared/vpn.pbjson.dart @@ -89,6 +89,18 @@ const Devices$json = { final $typed_data.Uint8List devicesDescriptor = $convert.base64Decode( 'CgdEZXZpY2VzEiEKB2RldmljZXMYASADKAsyBy5EZXZpY2VSB2RldmljZXM='); +@$core.Deprecated('Use plansDescriptor instead') +const Plans$json = { + '1': 'Plans', + '2': [ + {'1': 'plan', '3': 1, '4': 3, '5': 11, '6': '.Plan', '10': 'plan'}, + ], +}; + +/// Descriptor for `Plans`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List plansDescriptor = $convert.base64Decode( + 'CgVQbGFucxIZCgRwbGFuGAEgAygLMgUuUGxhblIEcGxhbg=='); + @$core.Deprecated('Use planDescriptor instead') const Plan$json = { '1': 'Plan', @@ -104,8 +116,9 @@ const Plan$json = { {'1': 'totalCost', '3': 9, '4': 1, '5': 9, '10': 'totalCost'}, {'1': 'formattedBonus', '3': 10, '4': 1, '5': 9, '10': 'formattedBonus'}, {'1': 'renewalText', '3': 11, '4': 1, '5': 9, '10': 'renewalText'}, + {'1': 'renewalBonusExpected', '3': 13, '4': 3, '5': 11, '6': '.Plan.RenewalBonusExpectedEntry', '10': 'renewalBonusExpected'}, ], - '3': [Plan_PriceEntry$json, Plan_ExpectedMonthlyPriceEntry$json], + '3': [Plan_PriceEntry$json, Plan_ExpectedMonthlyPriceEntry$json, Plan_RenewalBonusExpectedEntry$json], }; @$core.Deprecated('Use planDescriptor instead') @@ -128,6 +141,16 @@ const Plan_ExpectedMonthlyPriceEntry$json = { '7': {'7': true}, }; +@$core.Deprecated('Use planDescriptor instead') +const Plan_RenewalBonusExpectedEntry$json = { + '1': 'RenewalBonusExpectedEntry', + '2': [ + {'1': 'key', '3': 1, '4': 1, '5': 9, '10': 'key'}, + {'1': 'value', '3': 2, '4': 1, '5': 3, '10': 'value'}, + ], + '7': {'7': true}, +}; + /// Descriptor for `Plan`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List planDescriptor = $convert.base64Decode( 'CgRQbGFuEg4KAmlkGAEgASgJUgJpZBIgCgtkZXNjcmlwdGlvbhgCIAEoCVILZGVzY3JpcHRpb2' @@ -137,10 +160,13 @@ final $typed_data.Uint8List planDescriptor = $convert.base64Decode( 'ZWN0ZWRNb250aGx5UHJpY2USNgoWdG90YWxDb3N0QmlsbGVkT25lVGltZRgHIAEoCVIWdG90YW' 'xDb3N0QmlsbGVkT25lVGltZRIiCgxvbmVNb250aENvc3QYCCABKAlSDG9uZU1vbnRoQ29zdBIc' 'Cgl0b3RhbENvc3QYCSABKAlSCXRvdGFsQ29zdBImCg5mb3JtYXR0ZWRCb251cxgKIAEoCVIOZm' - '9ybWF0dGVkQm9udXMSIAoLcmVuZXdhbFRleHQYCyABKAlSC3JlbmV3YWxUZXh0GjgKClByaWNl' - 'RW50cnkSEAoDa2V5GAEgASgJUgNrZXkSFAoFdmFsdWUYAiABKANSBXZhbHVlOgI4ARpHChlFeH' - 'BlY3RlZE1vbnRobHlQcmljZUVudHJ5EhAKA2tleRgBIAEoCVIDa2V5EhQKBXZhbHVlGAIgASgD' - 'UgV2YWx1ZToCOAE='); + '9ybWF0dGVkQm9udXMSIAoLcmVuZXdhbFRleHQYCyABKAlSC3JlbmV3YWxUZXh0ElMKFHJlbmV3' + 'YWxCb251c0V4cGVjdGVkGA0gAygLMh8uUGxhbi5SZW5ld2FsQm9udXNFeHBlY3RlZEVudHJ5Uh' + 'RyZW5ld2FsQm9udXNFeHBlY3RlZBo4CgpQcmljZUVudHJ5EhAKA2tleRgBIAEoCVIDa2V5EhQK' + 'BXZhbHVlGAIgASgDUgV2YWx1ZToCOAEaRwoZRXhwZWN0ZWRNb250aGx5UHJpY2VFbnRyeRIQCg' + 'NrZXkYASABKAlSA2tleRIUCgV2YWx1ZRgCIAEoA1IFdmFsdWU6AjgBGkcKGVJlbmV3YWxCb251' + 'c0V4cGVjdGVkRW50cnkSEAoDa2V5GAEgASgJUgNrZXkSFAoFdmFsdWUYAiABKANSBXZhbHVlOg' + 'I4AQ=='); @$core.Deprecated('Use paymentProvidersDescriptor instead') const PaymentProviders$json = { @@ -187,6 +213,9 @@ const User$json = { {'1': 'referral', '3': 11, '4': 1, '5': 9, '10': 'referral'}, {'1': 'token', '3': 12, '4': 1, '5': 9, '10': 'token'}, {'1': 'yinbiEnabled', '3': 13, '4': 1, '5': 8, '10': 'yinbiEnabled'}, + {'1': 'inviters', '3': 14, '4': 3, '5': 9, '10': 'inviters'}, + {'1': 'invitees', '3': 15, '4': 3, '5': 9, '10': 'invitees'}, + {'1': 'purchases', '3': 16, '4': 3, '5': 11, '6': '.Purchase', '10': 'purchases'}, ], }; @@ -198,7 +227,21 @@ final $typed_data.Uint8List userDescriptor = $convert.base64Decode( 'xlEh4KCmV4cGlyYXRpb24YByABKANSCmV4cGlyYXRpb24SIQoHZGV2aWNlcxgIIAMoCzIHLkRl' 'dmljZVIHZGV2aWNlcxISCgRjb2RlGAkgASgJUgRjb2RlEhoKCGV4cGlyZUF0GAogASgDUghleH' 'BpcmVBdBIaCghyZWZlcnJhbBgLIAEoCVIIcmVmZXJyYWwSFAoFdG9rZW4YDCABKAlSBXRva2Vu' - 'EiIKDHlpbmJpRW5hYmxlZBgNIAEoCFIMeWluYmlFbmFibGVk'); + 'EiIKDHlpbmJpRW5hYmxlZBgNIAEoCFIMeWluYmlFbmFibGVkEhoKCGludml0ZXJzGA4gAygJUg' + 'hpbnZpdGVycxIaCghpbnZpdGVlcxgPIAMoCVIIaW52aXRlZXMSJwoJcHVyY2hhc2VzGBAgAygL' + 'MgkuUHVyY2hhc2VSCXB1cmNoYXNlcw=='); + +@$core.Deprecated('Use purchaseDescriptor instead') +const Purchase$json = { + '1': 'Purchase', + '2': [ + {'1': 'plan', '3': 1, '4': 1, '5': 9, '10': 'plan'}, + ], +}; + +/// Descriptor for `Purchase`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List purchaseDescriptor = $convert.base64Decode( + 'CghQdXJjaGFzZRISCgRwbGFuGAEgASgJUgRwbGFu'); @$core.Deprecated('Use baseResponseDescriptor instead') const BaseResponse$json = { diff --git a/lib/vpn/vpn_notifier.dart b/lib/vpn/vpn_notifier.dart index f3008b11f..b1ef2a368 100644 --- a/lib/vpn/vpn_notifier.dart +++ b/lib/vpn/vpn_notifier.dart @@ -47,6 +47,15 @@ class VPNChangeNotifier extends ChangeNotifier { } void initCallbackForMobile() { + //Since IOS config is fetched from the on native side + // We just need to make true for all + if(Platform.isIOS){ + isFlashlightInitialized = true; + isFlashlightInitializedFailed = false; + notifyListeners(); + return; + } + if (timer != null) { return; } diff --git a/lib/vpn/vpn_server_location.dart b/lib/vpn/vpn_server_location.dart index c35a815af..7c1ac3956 100644 --- a/lib/vpn/vpn_server_location.dart +++ b/lib/vpn/vpn_server_location.dart @@ -17,12 +17,12 @@ class ServerLocationWidget extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ CText( - 'Server Location'.i18n, - textAlign: TextAlign.center, + '${'Server Location'.i18n}:', style: tsSubtitle3.copiedWith( color: unselectedTabIconColor, ), ), + const SizedBox(width: 8), Container( padding: const EdgeInsets.only( left: 8, diff --git a/lib/vpn/vpn_status.dart b/lib/vpn/vpn_status.dart index 5f843fe37..263ecf061 100644 --- a/lib/vpn/vpn_status.dart +++ b/lib/vpn/vpn_status.dart @@ -9,7 +9,7 @@ class VPNStatus extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ CText( - 'VPN Status'.i18n, + '${'VPN Status'.i18n}:', style: tsSubtitle3.copiedWith( color: unselectedTabIconColor, ), diff --git a/lib/vpn/vpn_switch.dart b/lib/vpn/vpn_switch.dart index 8fea10c59..5046a21d6 100644 --- a/lib/vpn/vpn_switch.dart +++ b/lib/vpn/vpn_switch.dart @@ -1,4 +1,3 @@ -import 'package:flutter_advanced_switch/flutter_advanced_switch.dart'; import 'package:lantern/ad_helper.dart'; import 'package:lantern/common/common.dart'; import 'package:lantern/common/common_desktop.dart'; @@ -62,33 +61,37 @@ class _VPNSwitchState extends State { @override Widget build(BuildContext context) { - final internetStatusProvider = context.watch(); final vpnNotifier = context.watch(); if (isMobile()) { return sessionModel .shouldShowGoogleAds((context, isGoogleAdsEnable, child) { - adHelper.loadAds(shouldShowGoogleAds: isGoogleAdsEnable); - return Transform.scale( - scale: 2.5, - child: vpnModel.vpnStatus( - (BuildContext context, String vpnStatus, Widget? child) { - return AdvancedSwitch( - width: 60, - disabledOpacity: 1, - enabled: (internetStatusProvider.isConnected && - !vpnNotifier.isFlashlightInitializedFailed), - initialValue: - vpnStatus == 'connected' || vpnStatus == 'disconnecting', - activeColor: onSwitchColor, - inactiveColor: (internetStatusProvider.isConnected && + //Since we don't have feature flag on ios at the moment + // disable ads' + if (Platform.isAndroid) { + adHelper.loadAds(shouldShowGoogleAds: isGoogleAdsEnable); + } + return vpnModel + .vpnStatus((BuildContext context, String vpnStatus, Widget? child) { + // Changes scale on mobile due to hit target + return AdvancedSwitch( + width: 150, + height: 70, + borderRadius: BorderRadius.circular(40), + disabledOpacity: 1, + enabled: (internetStatusProvider.isConnected && + !vpnNotifier.isFlashlightInitializedFailed), + initialValue: + vpnStatus == 'connected' || vpnStatus == 'disconnecting', + activeColor: onSwitchColor, + inactiveColor: (internetStatusProvider.isConnected && !vpnNotifier.isFlashlightInitializedFailed) - ?offSwitchColor - : grey3, - onChanged: (newValue) => - vpnProcessForMobile(newValue, vpnStatus, isGoogleAdsEnable), - ); - })); + ? offSwitchColor + : grey3, + onChanged: (newValue) => + vpnProcessForMobile(newValue, vpnStatus, isGoogleAdsEnable), + ); + }); }); } else { // This ui for desktop @@ -107,9 +110,8 @@ class _VPNSwitchState extends State { activeColor: onSwitchColor, inactiveColor: (internetStatusProvider.isConnected && !vpnNotifier.isFlashlightInitializedFailed) - ?offSwitchColor + ? offSwitchColor : grey3, - onChanged: (newValue) { vpnProcessForDesktop(); setState(() { diff --git a/lib/vpn/vpn_tab.dart b/lib/vpn/vpn_tab.dart index 5e40a4ba8..d3b5e1e5e 100644 --- a/lib/vpn/vpn_tab.dart +++ b/lib/vpn/vpn_tab.dart @@ -5,7 +5,6 @@ import 'package:lantern/vpn/vpn_notifier.dart'; import 'package:shimmer/shimmer.dart'; import '../common/ui/custom/internet_checker.dart'; - import 'vpn_bandwidth.dart'; import 'vpn_pro_banner.dart'; import 'vpn_server_location.dart'; @@ -18,9 +17,8 @@ class VPNTab extends StatelessWidget { @override Widget build(BuildContext context) { final vpnNotifier = context.watch(); - return sessionModel - .proUser((BuildContext context, bool proUser, Widget? child) { - return BaseScreen( + return sessionModel.proUser( + (context, proUser, child) => BaseScreen( title: SvgPicture.asset( proUser ? ImagePaths.pro_logo : ImagePaths.free_logo, height: 16, @@ -34,68 +32,77 @@ class VPNTab extends StatelessWidget { isProUser: proUser, ) : Column( - mainAxisAlignment: MainAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - if (!proUser && !Platform.isIOS) + if (!proUser) const ProBanner() else - const SizedBox(height: 50), - const SizedBox(height: 100), - const VPNSwitch(), - const SizedBox(height: 40), - if (vpnNotifier.isFlashlightInitializedFailed) - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - CText(vpnNotifier.flashlightState, style: tsSubtitle2), - const SizedBox(width: 10), - SizedBox( - height: 20, - width: 20, - child: CircularProgressIndicator( - color: grey5, - ), - ) - ], - ), - const SizedBox(height: 50), - Consumer( - builder: (context, provider, _) { - return provider.isConnected - ? const SizedBox() - : const InternetChecker(); - }, + const SizedBox(height: 10), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + const VPNSwitch(), + const SizedBox(height: 40), + if (vpnNotifier.isFlashlightInitializedFailed) + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CText(vpnNotifier.flashlightState, + style: tsSubtitle2), + const SizedBox(width: 10), + SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator( + color: grey5, + ), + ) + ], + ), + ], ), - const Spacer(), - Container( - padding: const EdgeInsetsDirectional.all(16), - decoration: BoxDecoration( - border: Border.all( - color: borderColor, - width: 1, + Column( + children: [ + Consumer( + builder: (context, provider, _) { + return provider.isConnected + ? const SizedBox() + : const InternetChecker(); + }, ), - borderRadius: const BorderRadius.all( - Radius.circular(borderRadius), + const SizedBox(height: 20), + Container( + padding: const EdgeInsetsDirectional.all(16), + decoration: BoxDecoration( + border: Border.all( + color: borderColor, + width: 1, + ), + borderRadius: const BorderRadius.all( + Radius.circular(borderRadius), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + VPNStatus(), + const CDivider(height: 32.0), + ServerLocationWidget(), + if (Platform.isAndroid) ...{ + const CDivider(height: 32.0), + SplitTunnelingWidget(), + }, + if (!proUser && !Platform.isIOS) + const VPNBandwidth(), + ], + ), ), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - VPNStatus(), - const CDivider(height: 32.0), - ServerLocationWidget(), - if (Platform.isAndroid) ...{ - const CDivider(height: 32.0), - SplitTunnelingWidget(), - }, - if (!proUser) const VPNBandwidth(), - ], - ), + ], ), ], ), - ); - }); + ), + ); } } diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 95ac19d58..e45e07fd2 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -13,6 +13,7 @@ import emoji_picker_flutter import flutter_image_compress_macos import flutter_inappwebview_macos import flutter_local_notifications +import in_app_purchase_storekit import package_info_plus import path_provider_foundation import screen_retriever @@ -34,6 +35,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FlutterImageCompressMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterImageCompressMacosPlugin")) InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) + InAppPurchasePlugin.register(with: registry.registrar(forPlugin: "InAppPurchasePlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin")) diff --git a/macos/Podfile b/macos/Podfile index b52666a10..b6ea4ad91 100644 --- a/macos/Podfile +++ b/macos/Podfile @@ -39,5 +39,8 @@ end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_macos_build_settings(target) + target.build_configurations.each do |config| + config.build_settings['MACOSX_DEPLOYMENT_TARGET'] = '10.15' + end end end diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 3e629c24a..a6a8a61ee 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -18,6 +18,9 @@ PODS: - flutter_local_notifications (0.0.1): - FlutterMacOS - FlutterMacOS (1.0.0) + - in_app_purchase_storekit (0.0.1): + - Flutter + - FlutterMacOS - OrderedSet (5.0.0) - package_info_plus (0.0.1): - FlutterMacOS @@ -59,6 +62,7 @@ DEPENDENCIES: - flutter_inappwebview_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos`) - flutter_local_notifications (from `Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos`) - FlutterMacOS (from `Flutter/ephemeral`) + - in_app_purchase_storekit (from `Flutter/ephemeral/.symlinks/plugins/in_app_purchase_storekit/darwin`) - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`) @@ -95,6 +99,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos FlutterMacOS: :path: Flutter/ephemeral + in_app_purchase_storekit: + :path: Flutter/ephemeral/.symlinks/plugins/in_app_purchase_storekit/darwin package_info_plus: :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos path_provider_foundation: @@ -128,6 +134,7 @@ SPEC CHECKSUMS: flutter_inappwebview_macos: 9600c9df9fdb346aaa8933812009f8d94304203d flutter_local_notifications: 3805ca215b2fb7f397d78b66db91f6a747af52e4 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 + in_app_purchase_storekit: 8c3b0b3eb1b0f04efbff401c3de6266d4258d433 OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 @@ -142,6 +149,6 @@ SPEC CHECKSUMS: video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3 window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8 -PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3 +PODFILE CHECKSUM: b026caf428aef5db8f45e6734a110a98281273f6 -COCOAPODS: 1.15.2 +COCOAPODS: 1.14.3 diff --git a/protos_shared/auth.proto b/protos_shared/auth.proto new file mode 100644 index 000000000..d5e256093 --- /dev/null +++ b/protos_shared/auth.proto @@ -0,0 +1,145 @@ +syntax = "proto3"; +option go_package = "/protos"; + +// POST /users/signup +message SignupRequest { + string email = 1; + bytes salt = 2; + bytes verifier = 3; + bool skip_email_confirmation = 4; +} + + +message EmptyResponse{} + + +// POST /users/signup/resend/email +message SignupEmailResendRequest { + string email = 1; + bytes salt = 2; +} + +// POST /users/signup/complete/email +message ConfirmSignupRequest { + string email = 1; + string code = 2; +} + +// GET /users/salt +message GetSaltResponse { + bytes salt = 1; +} + +// POST /users/prepare +message PrepareRequest { + string email = 1; + bytes a = 2; +} + +message PrepareResponse { + bytes b = 1; + bytes proof = 2; +} + +// POST /users/login +message LoginRequest { + string email = 1; + bytes proof = 2; + string deviceId = 3; +} + +message LoginResponse { + int64 legacyID = 1; + string legacyToken = 2; + string id = 3; + bool emailConfirmed = 4; + message Device { + string id = 1; + string name = 2; + int64 created = 3; + } + message UserData { + int64 userId = 1; + string code = 2; + string token = 3; + string referral = 4; + string phone = 5; + string email = 6; + string userStatus = 7; + string userLevel = 8; + string locale = 9; + int64 expiration = 10; + repeated string servers = 11; + string subscription = 12; + repeated string purchases = 13; + string bonusDays = 14; + string bonusMonths = 15; + repeated string inviters = 16; + repeated string invitees = 17; + repeated Device devices = 18; + bool yinbiEnabled = 19; + } + bool Success = 5; + // this maps to /user-data call in pro-server and is returned only on successful login + UserData legacyUserData = 6; + // list of current user devices. returned only on successful login that is blocked by 'too many devices' + repeated Device devices = 7; +} + + +// POST /users/recovery/start/email +message StartRecoveryByEmailRequest { + string email = 1; +} + +// POST /users/recovery/complete/email +message CompleteRecoveryByEmailRequest { + string email = 1; + string code = 2; + bytes new_salt = 3; + bytes new_verifier = 4; +} + +// POST /users/change_email +message ChangeEmailRequest { + string old_email = 1; + string new_email = 2; + bytes proof = 3; +} + +// POST /users/change_email/complete/email +message CompleteChangeEmailRequest { + string old_email = 1; + string new_email = 2; + string code = 3; + bytes new_salt = 4; + bytes new_verifier = 5; +} + + +// POST /users/delete +message DeleteUserRequest { + string email = 1; + bool permanent = 2; + bytes proof = 3; + string deviceId = 4; +} + +// POST /users/recovery/validate/email +message ValidateRecoveryCodeRequest { + string email = 1; + string code = 2; +} + +message ValidateRecoveryCodeResponse { + bool valid = 1; +} + + +// POST /users/logout +message LogoutRequest { + string email = 1; + string deviceId = 2; + int64 legacyUserID = 3; + string legacyToken = 4; +} diff --git a/protos_shared/vpn.proto b/protos_shared/vpn.proto index 82d9544f4..d25b0d446 100644 --- a/protos_shared/vpn.proto +++ b/protos_shared/vpn.proto @@ -28,10 +28,16 @@ message Device { int64 created = 3; } + + message Devices { repeated Device devices = 1; } +message Plans { + repeated Plan plan = 1; +} + message Plan { string id = 1; string description = 2; @@ -44,6 +50,7 @@ message Plan { string totalCost = 9; string formattedBonus = 10; string renewalText = 11; + map renewalBonusExpected = 13; } message PaymentProviders { @@ -70,8 +77,17 @@ message User { string referral = 11; string token = 12; bool yinbiEnabled = 13; + repeated string inviters = 14; + repeated string invitees = 15; + repeated Purchase purchases = 16; } +message Purchase { + string plan = 1; +} + + + // API message BaseResponse { string status = 1; @@ -80,13 +96,13 @@ message BaseResponse { } message PaymentRedirectRequest { - string plan = 1; - string provider = 2; - string currency = 3; - string email = 4; - string deviceName = 5; - string countryCode = 6; - string locale = 7; + string plan = 1; + string provider = 2; + string currency = 3; + string email = 4; + string deviceName = 5; + string countryCode = 6; + string locale = 7; } message RedeemResellerCodeRequest { diff --git a/pubspec.lock b/pubspec.lock index 202ecd349..8574381ce 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -849,6 +849,14 @@ packages: description: flutter source: sdk version: "0.0.0" + get_it: + dependency: "direct main" + description: + name: get_it + sha256: d85128a5dae4ea777324730dc65edd9c9f43155c109d5cc0a69cab74139fbac1 + url: "https://pub.dev" + source: hosted + version: "7.7.0" gettext_parser: dependency: transitive description: @@ -953,6 +961,38 @@ packages: url: "https://pub.dev" source: hosted version: "0.0.6" + in_app_purchase: + dependency: "direct main" + description: + name: in_app_purchase + sha256: "960f26a08d9351fb8f89f08901f8a829d41b04d45a694b8f776121d9e41dcad6" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + in_app_purchase_android: + dependency: transitive + description: + name: in_app_purchase_android + sha256: b18871cffd4ec8911171864bfc0060c983be96215080a0847c29ef9147f439ef + url: "https://pub.dev" + source: hosted + version: "0.3.6+1" + in_app_purchase_platform_interface: + dependency: transitive + description: + name: in_app_purchase_platform_interface + sha256: "1d353d38251da5b9fea6635c0ebfc6bb17a2d28d0e86ea5e083bf64244f1fb4c" + url: "https://pub.dev" + source: hosted + version: "1.4.0" + in_app_purchase_storekit: + dependency: transitive + description: + name: in_app_purchase_storekit + sha256: "9a087c3c051266642468f031737c5a09f6fd90ff2b21d953e130563f3fd4cb34" + url: "https://pub.dev" + source: hosted + version: "0.3.17" infinite_scroll_pagination: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 20942b49d..a991180f9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -64,15 +64,15 @@ dependencies: # Networking cached_network_image: ^3.3.1 - dio: ^5.3.2 - internet_connection_checker_plus: ^2.2.0 + dio: ^5.4.3+1 + internet_connection_checker_plus: ^2.4.2 # caching - flutter_cache_manager: ^3.3.1 + flutter_cache_manager: ^3.3.2 # change this with flutter_downloader flutter_uploader: ^3.0.0-beta.3 mime: ^1.0.4 - flutter_pdfview: ^1.3.1 + flutter_pdfview: ^1.3.2 # Navigation & Localization auto_route: ^8.2.0 @@ -85,8 +85,8 @@ dependencies: qr_code_scanner: ^1.0.1 # Timer & Overlay - stop_watch_timer: ^3.0.1 - loader_overlay: ^2.3.0 + stop_watch_timer: ^3.1.1 + loader_overlay: ^2.3.2 # Keyboard & Color utilities flutter_keyboard_visibility: ^6.0.0 @@ -102,35 +102,38 @@ dependencies: ref: master # Desktop - window_manager: ^0.3.8 - tray_manager: ^0.2.2 + window_manager: ^0.3.9 + tray_manager: ^0.2.3 flutter_dotenv: ^5.1.0 # Notifications & Logging flutter_local_notifications: ^17.1.2 - logger: ^2.0.1 + logger: ^2.3.0 # Error handling device_info_plus: ^10.1.0 -# flutter_mailer: ^2.0.0 -# fluttertoast: ^8.2.2 # Package information package_info_plus: ^8.0.0 # Path, permission & Markdown handling - path_provider: ^2.1.0 + path_provider: ^2.1.3 permission_handler: ^11.3.1 flutter_markdown: ^0.7.2+1 + # Purchase + in_app_purchase: ^3.2.0 + # Ads - google_mobile_ads: ^5.0.0 + google_mobile_ads: ^5.1.0 retry: ^3.1.2 # Generate bindings to native libraries - ffi: ^2.1.0 + ffi: ^2.1.2 # Deeplink handling app_links: ^6.1.1 + # Service Locator + get_it: ^7.7.0 #Loading animated_loading_border: ^0.0.2 shimmer: ^3.0.0