Merge branch 'main' into main
This commit is contained in:
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -15,7 +15,7 @@ on:
|
|||||||
env:
|
env:
|
||||||
IS_GITHUB_ACTIONS: 1
|
IS_GITHUB_ACTIONS: 1
|
||||||
CHANNEL: "${{ inputs.channel }}"
|
CHANNEL: "${{ inputs.channel }}"
|
||||||
FLUTTER_VERSION: '3.19.x'
|
FLUTTER_VERSION: '3.22.x'
|
||||||
NDK_VERSION: r26b
|
NDK_VERSION: r26b
|
||||||
UPLOAD_ARTIFACT: "${{ inputs.upload-artifact }}"
|
UPLOAD_ARTIFACT: "${{ inputs.upload-artifact }}"
|
||||||
TAG_NAME: "${{ inputs.tag-name }}"
|
TAG_NAME: "${{ inputs.tag-name }}"
|
||||||
|
|||||||
10
.vscode/launch.json
vendored
10
.vscode/launch.json
vendored
@@ -1,6 +1,16 @@
|
|||||||
{
|
{
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "go Package",
|
||||||
|
"type": "go",
|
||||||
|
"request": "launch",
|
||||||
|
"mode": "auto",
|
||||||
|
"cwd": "./libcore",
|
||||||
|
"program": "./libcore/cli/main.go",
|
||||||
|
"args": ["build","-c","a.txt","-d","b.txt","--full-config"] ,
|
||||||
|
"buildFlags": "-tags with_clash_api,with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Hiddify Dev",
|
"name": "Hiddify Dev",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
|
|||||||
21
.vscode/settings.json
vendored
Normal file
21
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"dart.lineLength": 250,
|
||||||
|
"[dart]": {
|
||||||
|
"editor.defaultFormatter": "Dart-Code.dart-code",
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.formatOnType": true,
|
||||||
|
"editor.tabSize": 2,
|
||||||
|
"editor.rulers": [
|
||||||
|
250
|
||||||
|
],
|
||||||
|
"editor.detectIndentation": false,
|
||||||
|
"editor.selectionHighlight": false,
|
||||||
|
"editor.suggest.snippetsPreventQuickSuggestions": false,
|
||||||
|
"editor.suggestSelection": "first",
|
||||||
|
"editor.tabCompletion": "onlySnippets",
|
||||||
|
"editor.wordBasedSuggestions": "off"
|
||||||
|
},
|
||||||
|
|
||||||
|
"html.format.wrapLineLength": 250,
|
||||||
|
|
||||||
|
}
|
||||||
413
assets/translations/strings_ar.i18n.json
Normal file
413
assets/translations/strings_ar.i18n.json
Normal file
@@ -0,0 +1,413 @@
|
|||||||
|
{
|
||||||
|
"general": {
|
||||||
|
"appTitle": "Hiddify",
|
||||||
|
"reset": "إعادة تعيين",
|
||||||
|
"toggle": {
|
||||||
|
"enabled": "مُفعّل",
|
||||||
|
"disabled": "غير مُفعّل"
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"disable": "تعطيل"
|
||||||
|
},
|
||||||
|
"sort": "فرز",
|
||||||
|
"sortBy": "فرز حسب",
|
||||||
|
"addToClipboard": "إضافة إلى الحافظة",
|
||||||
|
"notSet": "غير مُحدد",
|
||||||
|
"agree": "موافقة",
|
||||||
|
"decline": "رفض",
|
||||||
|
"unknown": "غير معروف",
|
||||||
|
"hidden": "مخفي",
|
||||||
|
"timeout": "انتهاء الوقت",
|
||||||
|
"clipboardExportSuccessMsg": "تمت إضافة البيانات إلى الحافظة",
|
||||||
|
"showMore": "عرض المزيد",
|
||||||
|
"showLess": "عرض أقل",
|
||||||
|
"openAppSettings": "فتح إعدادات التطبيق",
|
||||||
|
"grantPermission": "منح الإذن"
|
||||||
|
},
|
||||||
|
"intro": {
|
||||||
|
"termsAndPolicyCaution(rich)": "بمواصلة استخدامك، فإنك توافق على ${tap(@:about.termsAndConditions)}",
|
||||||
|
"start": "ابدأ"
|
||||||
|
},
|
||||||
|
"home": {
|
||||||
|
"pageTitle": "الصفحة الرئيسية",
|
||||||
|
"emptyProfilesMsg": "ابدأ بإضافة ملف تعريف اشتراك",
|
||||||
|
"noActiveProfileMsg": "اختر ملف تعريف"
|
||||||
|
},
|
||||||
|
"stats": {
|
||||||
|
"traffic": "حركة المرور",
|
||||||
|
"trafficLive": "حركة المرور الحية",
|
||||||
|
"trafficTotal": "إجمالي حركة المرور",
|
||||||
|
"uplink": "الصعود",
|
||||||
|
"downlink": "الهبوط",
|
||||||
|
"connection": "الاتصال",
|
||||||
|
"speed": "السرعة",
|
||||||
|
"totalTransferred": "إجمالي البيانات المنقولة"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"overviewPageTitle": "الملفات الشخصية",
|
||||||
|
"detailsPageTitle": "ملف التعريف",
|
||||||
|
"activeProfileNameSemanticLabel": "اسم ملف التعريف النشط: \"${name}\".",
|
||||||
|
"activeProfileBtnSemanticLabel": "عرض جميع ملفات التعريف",
|
||||||
|
"nonActiveProfileBtnSemanticLabel": "اختر \"${name}\" كملف تعريف نشط",
|
||||||
|
"subscription": {
|
||||||
|
"traffic": "حركة المرور",
|
||||||
|
"updatedTimeAgo": "تم التحديث قبل ${timeago}",
|
||||||
|
"remainingDuration": "تبقى ${duration} يومًا",
|
||||||
|
"remainingTrafficSemanticLabel": "استُهلك ${consumed} من أصل ${total} حركة مرور",
|
||||||
|
"expired": "منتهي الصلاحية",
|
||||||
|
"noTraffic": "غير متاح",
|
||||||
|
"upload": "التحميل",
|
||||||
|
"download": "التنزيل",
|
||||||
|
"total": "إجمالي حركة المرور",
|
||||||
|
"expireDate": "تاريخ انتهاء الصلاحية"
|
||||||
|
},
|
||||||
|
"sortBy": {
|
||||||
|
"lastUpdate": "تم التحديث مؤخرًا",
|
||||||
|
"name": "أبجديًا"
|
||||||
|
},
|
||||||
|
"add": {
|
||||||
|
"buttonText": "ملف تعريف جديد",
|
||||||
|
"shortBtnTxt": "ملف تعريف جديد",
|
||||||
|
"fromClipboard": "إضافة من الحافظة",
|
||||||
|
"scanQr": "مسح رمز الاستجابة السريعة",
|
||||||
|
"qrScanner": {
|
||||||
|
"permissionDeniedError": "تم رفض الإذن",
|
||||||
|
"unexpectedError": "حدث خطأ ما",
|
||||||
|
"torchSemanticLabel": "ضوء فلاش",
|
||||||
|
"facingSemanticLabel": "اتجاه الكاميرا",
|
||||||
|
"permissionRequest": "الإذن للكاميرا لمسح رمز الاستجابة السريعة"
|
||||||
|
},
|
||||||
|
"manually": "إدخال يدوي",
|
||||||
|
"addingProfileMsg": "إضافة ملف التعريف",
|
||||||
|
"failureMsg": "فشل في إضافة ملف التعريف"
|
||||||
|
},
|
||||||
|
"update": {
|
||||||
|
"buttonTxt": "تحديث",
|
||||||
|
"tooltip": "تحديث ملف التعريف",
|
||||||
|
"updateSubscriptions": "تحديث الاشتراكات",
|
||||||
|
"failureMsg": "فشل في تحديث ملف التعريف",
|
||||||
|
"successMsg": "تم تحديث ملف التعريف بنجاح",
|
||||||
|
"namedFailureMsg": "فشل في تحديث \"${name}\"",
|
||||||
|
"namedSuccessMsg": "تم تحديث \"${name}\" بنجاح"
|
||||||
|
},
|
||||||
|
"share": {
|
||||||
|
"buttonText": "مشاركة",
|
||||||
|
"exportToClipboardSuccess": "تم تصدير البيانات إلى الحافظة",
|
||||||
|
"exportSubLinkToClipboard": "تصدير رابط الاشتراك إلى الحافظة",
|
||||||
|
"subLinkQrCode": "رمز الاستجابة السريعة لرابط الاشتراك",
|
||||||
|
"exportConfigToClipboard": "تصدير التكوين إلى الحافظة",
|
||||||
|
"exportConfigToClipboardSuccess": "تم نسخ التكوين إلى الحافظة"
|
||||||
|
},
|
||||||
|
"edit": {
|
||||||
|
"buttonTxt": "تحرير",
|
||||||
|
"selectActiveTxt": "اختر ملف التعريف النشط"
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"buttonTxt": "حذف",
|
||||||
|
"confirmationMsg": "حذف ملف التعريف نهائياً؟",
|
||||||
|
"successMsg": "تم حذف ملف التعريف بنجاح"
|
||||||
|
},
|
||||||
|
"save": {
|
||||||
|
"buttonText": "حفظ",
|
||||||
|
"successMsg": "تم حفظ ملف التعريف بنجاح",
|
||||||
|
"failureMsg": "فشل في حفظ ملف التعريف"
|
||||||
|
},
|
||||||
|
"detailsForm": {
|
||||||
|
"nameLabel": "الاسم",
|
||||||
|
"nameHint": "اسم ملف التعريف",
|
||||||
|
"urlLabel": "العنوان",
|
||||||
|
"urlHint": "عنوان URL للتكوين الكامل",
|
||||||
|
"emptyNameMsg": "الاسم مطلوب",
|
||||||
|
"invalidUrlMsg": "عنوان URL غير صالح",
|
||||||
|
"lastUpdate": "آخر تحديث",
|
||||||
|
"updateInterval": "التحديث التلقائي",
|
||||||
|
"updateIntervalDialogTitle": "مُدة التحديث التلقائي (بالساعات)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"proxies": {
|
||||||
|
"pageTitle": "الخوادم الوكيلية",
|
||||||
|
"emptyProxiesMsg": "لا توجد خوادم وكيلية متاحة",
|
||||||
|
"delayTestTooltip": "اختبار التأخير",
|
||||||
|
"sortTooltip": "فرز الخوادم الوكيلية",
|
||||||
|
"checkIp": "تحقق من عنوان IP",
|
||||||
|
"unknownIp": "عنوان IP غير معروف",
|
||||||
|
"sortOptions": {
|
||||||
|
"unsorted": "افتراضي",
|
||||||
|
"name": "أبجديًا",
|
||||||
|
"delay": "حسب التأخير"
|
||||||
|
},
|
||||||
|
"activeProxySemanticLabel": "الخادم الوكيل النشط",
|
||||||
|
"delaySemantics": {
|
||||||
|
"result": "التأخير: ${delay} مللي ثانية",
|
||||||
|
"timeout": "انتهاء الوقت في اختبار التأخير",
|
||||||
|
"testing": "التأخير: قيد الاختبار..."
|
||||||
|
},
|
||||||
|
"ipInfoSemantics": {
|
||||||
|
"address": "عنوان IP",
|
||||||
|
"country": "الدولة"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"logs": {
|
||||||
|
"pageTitle": "السجلات",
|
||||||
|
"filterHint": "تصفية",
|
||||||
|
"allLevelsFilter": "الكل",
|
||||||
|
"shareCoreLogs": "مشاركة سجلات النواة",
|
||||||
|
"shareAppLogs": "مشاركة سجلات التطبيق",
|
||||||
|
"pauseTooltip": "إيقاف مؤقت",
|
||||||
|
"resumeTooltip": "استئناف",
|
||||||
|
"clearTooltip": "مسح"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"pageTitle": "الإعدادات",
|
||||||
|
"requiresRestartMsg": "لتطبيق هذه التغييرات، أعد تشغيل التطبيق",
|
||||||
|
"experimental": "تجريبي",
|
||||||
|
"experimentalMsg": "الميزات ذات العلامة التجريبية لا تزال قيد التطوير وقد تسبب مشاكل.",
|
||||||
|
"exportOptions": "تصدير الخيارات إلى الحافظة",
|
||||||
|
"exportAllOptions": "تصدير جميع الخيارات إلى الحافظة (تصحيح الأخطاء)",
|
||||||
|
"importOptions": "استيراد الخيارات من الحافظة",
|
||||||
|
"importOptionsMsg": "سيؤدي هذا إلى إعادة كتابة جميع خيارات التكوين بالقيم المحددة. هل أنت متأكد؟",
|
||||||
|
"general": {
|
||||||
|
"sectionTitle": "عام",
|
||||||
|
"locale": "اللغة",
|
||||||
|
"region": "المنطقة",
|
||||||
|
"regionMsg": "يساعد على تعيين الخيارات الافتراضية لتجاوز العناوين المحلية",
|
||||||
|
"regions": {
|
||||||
|
"ir": "إيران (ir)",
|
||||||
|
"cn": "الصين (cn)",
|
||||||
|
"ru": "روسيا (ru)",
|
||||||
|
"af": "أفغانستان (af)",
|
||||||
|
"other": "أخرى"
|
||||||
|
},
|
||||||
|
"themeMode": "وضع السمة",
|
||||||
|
"themeModes": {
|
||||||
|
"system": "اتباع سمة النظام",
|
||||||
|
"dark": "الوضع الداكن",
|
||||||
|
"light": "الوضع الفاتح",
|
||||||
|
"black": "الوضع الأسود"
|
||||||
|
},
|
||||||
|
"enableAnalytics": "تمكين التحليلات",
|
||||||
|
"enableAnalyticsMsg": "منح الإذن بجمع التحليلات وإرسال تقارير الأعطال لتحسين التطبيق",
|
||||||
|
"autoStart": "بدء التشغيل عند تسجيل الدخول",
|
||||||
|
"silentStart": "بدء التشغيل مُصغر",
|
||||||
|
"openWorkingDir": "فتح دليل العمل",
|
||||||
|
"ignoreBatteryOptimizations": "تعطيل تحسينات البطارية",
|
||||||
|
"ignoreBatteryOptimizationsMsg": "إزالة القيود للحصول على أفضل أداء VPN",
|
||||||
|
"dynamicNotification": "عرض السرعة في الإشعار",
|
||||||
|
"hapticFeedback": "ردود فعل اللمس",
|
||||||
|
"autoIpCheck": "التحقق من عنوان IP للاتصال تلقائيًا"
|
||||||
|
},
|
||||||
|
"advanced": {
|
||||||
|
"sectionTitle": "متقدم",
|
||||||
|
"debugMode": "وضع تصحيح الأخطاء",
|
||||||
|
"debugModeMsg": "أعد تشغيل التطبيق لتطبيق هذا التغيير",
|
||||||
|
"memoryLimit": "حد الذاكرة",
|
||||||
|
"memoryLimitMsg": "قم بتمكين هذه الميزة إذا كنت تواجه أخطاء «عدم كفاية الذاكرة» أو تعطل التطبيق بشكل متكرر",
|
||||||
|
"resetTunnel": "إعادة تعيين ملف تعريف VPN"
|
||||||
|
},
|
||||||
|
"network": {
|
||||||
|
"perAppProxyPageTitle": "الوكيل لكل تطبيق",
|
||||||
|
"perAppProxyModes": {
|
||||||
|
"off": "الكل",
|
||||||
|
"offMsg": "وكيل جميع التطبيقات",
|
||||||
|
"include": "وكيل",
|
||||||
|
"includeMsg": "وكيل التطبيقات المحددة فقط",
|
||||||
|
"exclude": "تجاوز",
|
||||||
|
"excludeMsg": "لا تستخدم الوكيل للتطبيقات المحددة"
|
||||||
|
},
|
||||||
|
"showSystemApps": "عرض تطبيقات النظام",
|
||||||
|
"hideSystemApps": "إخفاء تطبيقات النظام",
|
||||||
|
"clearSelection": "مسح الاختيار"
|
||||||
|
},
|
||||||
|
"geoAssets": {
|
||||||
|
"pageTitle": "أصول التوجيه",
|
||||||
|
"geoip": "GeoIP",
|
||||||
|
"geosite": "GeoSite",
|
||||||
|
"version": "الإصدار ${version}",
|
||||||
|
"fileMissing": "الملف مفقود",
|
||||||
|
"update": "تحديث",
|
||||||
|
"download": "تنزيل",
|
||||||
|
"failureMsg": "فشل في تحديث الأصل",
|
||||||
|
"successMsg": "تم تحديث الأصل بنجاح",
|
||||||
|
"addRecommended": "إضافة الأصول الموصى بها",
|
||||||
|
"missingGeoAssetsMsg": "ملفات أصول التوجيه المحددة مفقودة. قم بتنزيلها أو اختر ملفات موجودة"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"about": {
|
||||||
|
"pageTitle": "حول\nتعريب:م. ابراهيم قاسم",
|
||||||
|
"version": "الإصدار",
|
||||||
|
"sourceCode": "رمز المصدر",
|
||||||
|
"telegramChannel": "قناة Telegram",
|
||||||
|
"checkForUpdate": "التحقق من وجود تحديث",
|
||||||
|
"privacyPolicy": "سياسة الخصوصية",
|
||||||
|
"termsAndConditions": "الشروط والأحكام"
|
||||||
|
},
|
||||||
|
"appUpdate": {
|
||||||
|
"notAvailableMsg": "أنت تستخدم أحدث إصدار بالفعل",
|
||||||
|
"dialogTitle": "تحديث متاح",
|
||||||
|
"updateMsg": "إصدار جديد من @:general.appTitle متاح. هل تريد التحديث الآن؟",
|
||||||
|
"currentVersionLbl": "الإصدار الحالي",
|
||||||
|
"newVersionLbl": "الإصدار الجديد",
|
||||||
|
"updateNowBtnTxt": "تحديث الآن",
|
||||||
|
"laterBtnTxt": "لاحقًا",
|
||||||
|
"ignoreBtnTxt": "تجاهل"
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"dashboard": "لوحة التحكم",
|
||||||
|
"quit": "إنهاء",
|
||||||
|
"open": "فتح",
|
||||||
|
"status": {
|
||||||
|
"connect": "الاتصال",
|
||||||
|
"connecting": "جار الاتصال",
|
||||||
|
"disconnect": "فصم الاتصال",
|
||||||
|
"disconnecting": "جار فصم الاتصال"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"failure": {
|
||||||
|
"unexpected": "خطأ غير متوقع",
|
||||||
|
"clash": {
|
||||||
|
"unexpected": "خطأ غير متوقع",
|
||||||
|
"core": "خطأ في Clash ${reason}"
|
||||||
|
},
|
||||||
|
"singbox": {
|
||||||
|
"unexpected": "خطأ في الخدمة غير متوقع",
|
||||||
|
"serviceNotRunning": "الخدمة غير قيد التشغيل",
|
||||||
|
"missingPrivilege": "غياب الامتياز",
|
||||||
|
"missingPrivilegeMsg": "وضع VPN يتطلب امتيازات المسؤول. أعد تشغيل التطبيق كمسؤول أو غيّر وضع الخدمة.",
|
||||||
|
"missingGeoAssets": "أصول Geo مفقودة",
|
||||||
|
"missingGeoAssetsMsg": "أصول Geo مفقودة. فكر في تغيير الأصل النشط أو تنزيل الأصل المحدد في الإعدادات.",
|
||||||
|
"invalidConfigOptions": "خيارات التكوين غير صحيحة",
|
||||||
|
"invalidConfig": "تكوين غير صالح",
|
||||||
|
"create": "خطأ في إنشاء الخدمة",
|
||||||
|
"start": "خطأ في بدء تشغيل الخدمة"
|
||||||
|
},
|
||||||
|
"connectivity": {
|
||||||
|
"unexpected": "فشل غير متوقع",
|
||||||
|
"missingVpnPermission": "غياب إذن VPN",
|
||||||
|
"missingNotificationPermission": "غياب إذن الإشعارات",
|
||||||
|
"core": "خطأ في النواة"
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"unexpected": "خطأ غير متوقع",
|
||||||
|
"notFound": "لم يتم العثور على ملف التعريف",
|
||||||
|
"invalidConfig": "تكوينات غير صحيحة",
|
||||||
|
"invalidUrl": "عنوان URL غير صالح"
|
||||||
|
},
|
||||||
|
"connection": {
|
||||||
|
"unexpected": "خطأ في الاتصال غير متوقع",
|
||||||
|
"timeout": "انتهاء الوقت في الاتصال",
|
||||||
|
"badResponse": "استجابة سيئة",
|
||||||
|
"connectionError": "خطأ في الاتصال",
|
||||||
|
"badCertificate": "شهادة غير صالحة"
|
||||||
|
},
|
||||||
|
"geoAssets": {
|
||||||
|
"unexpected": "خطأ غير متوقع",
|
||||||
|
"notUpdate": "لا يوجد تحديث متاح",
|
||||||
|
"activeNotFound": "لم يتم العثور على أصل Geo النشط"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"play": {
|
||||||
|
"title": "Hiddify (معاينة)",
|
||||||
|
"short_description": "Auto, SSH, VLESS, VMess, Trojan, Reality, Sing-Box, Clash, XRay, Shadowsocks",
|
||||||
|
"full_description": "الهدف الرئيسي لـ Hiddify هو توفير عميل نفق آمن وسهل الاستخدام وكفاءة. يمكّنك من توجيه جميع حركة المرور أو حركة المرور من التطبيق المحدد إلى خادم بعيد من اختيارك، باستخدام إذن VPN-Service. \n\nملاحظة: لا نوفر أي خادم، ويتعين على المستخدمين ضمان بقاء أنشطتهم عبر الإنترنت خاصة باستخدام خادمهم المخصص أو الخوادم الموثوقة. \n \nندعم الخوادم مع:\n- رابط اشتراك V2Ray/XRay عادي \n- رابط اشتراك Clash \n- رابط اشتراك Sing-Box \n\nما هي ميزاتنا الفريدة؟\n - سهل الاستخدام \n - مُحسّن وسريع \n - اختيار أدنى Ping تلقائيًا \n - عرض معلومات استخدام المستخدم \n - استيراد sublink بسهولة بنقرة واحدة باستخدام deeplinking \n - مجاني وخالي من الإعلانات \n - تبديل sublinks بسهولة \n - المزيد والمزيد \n\nالدعم:\n- جميع البروتوكولات التي تدعمها Sing-Box \n- VLESS + XTLS Reality, Vision \n- VMess \n- Trojan \n- ShoadowSocks \n- Reality \n- WireGuard \n- V2Ray \n- Hysteria2 \n- TUICv5 \n- SSH \n- ShadowTLS \n\n\nرمز المصدر موجود في https://github.com/hiddify/Hiddify-Next \nتعتمد نواة التطبيق على Sing-Box مفتوحة المصدر.\n\nوصف الإذن:\n- VPN Service: نظرًا لأن هدف هذا التطبيق هو توفير عميل نفق آمن وسهل الاستخدام وكفاءة، نحتاج إلى هذا الإذن لنتمكن من توجيه حركة المرور عبر النفق إلى الخادم البعيد. \n- QUERY ALL PACKAGES: يستخدم هذا الإذن للسماح للمستخدمين بتضمين أو استبعاد تطبيقات محددة للأنفاق. \n- RECEIVE BOOT COMPLETED: يمكن تمكين أو تعطيل هذا الإذن من إعدادات التطبيق لتنشيط هذا التطبيق عند تشغيل الجهاز. \n- POST NOTIFICATIONS: هذا الإذن ضروري لأننا نستخدم خدمة المقدمة لضمان تشغيل خدمة VPN بشكل مستمر. \n- هذا التطبيق خالي من الإعلانات. يتم جمع التحليلات وبيانات الأعطال فقط بموافقة صريحة من المستخدم في أول استخدام للتطبيق."
|
||||||
|
},
|
||||||
|
"connection": {
|
||||||
|
"tapToConnect": "انقر للاتصال",
|
||||||
|
"connecting": "جار الاتصال",
|
||||||
|
"disconnecting": "جار فصم الاتصال",
|
||||||
|
"connected": "متصل",
|
||||||
|
"reconnect": "أعد الاتصال",
|
||||||
|
"connectAnyWay": "اتصل",
|
||||||
|
"experimentalNotice": "ميزات تجريبية قيد الاستخدام",
|
||||||
|
"experimentalNoticeMsg": "لقد قمت بتمكين بعض الميزات التجريبية التي قد تؤثر على جودة الاتصال وتسبب أخطاء غير متوقعة. يمكنك دائمًا تغيير هذه الخيارات أو إعادة تعيينها من صفحة خيارات التكوين.",
|
||||||
|
"disableExperimentalNotice": "لا تعرض مرة أخرى",
|
||||||
|
"reconnectMsg": "أعد الاتصال ليتم تطبيق التغييرات"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"resetBtn": "إعادة تعيين الخيارات",
|
||||||
|
"serviceMode": "وضع الخدمة",
|
||||||
|
"quickSettings": "الإعدادات السريعة",
|
||||||
|
"setupWarp": "إعداد WARP",
|
||||||
|
"allOptions": "جميع خيارات التكوين",
|
||||||
|
"serviceModes": {
|
||||||
|
"proxy": "خدمة الوكيل فقط",
|
||||||
|
"systemProxy": "تعيين وكيل النظام",
|
||||||
|
"tun": "VPN",
|
||||||
|
"tunService": "خدمة VPN"
|
||||||
|
},
|
||||||
|
"shortServiceModes": {
|
||||||
|
"proxy": "وكيل",
|
||||||
|
"systemProxy": "وكيل النظام",
|
||||||
|
"tun": "VPN",
|
||||||
|
"tunService": "خدمة VPN"
|
||||||
|
},
|
||||||
|
"section": {
|
||||||
|
"route": "خيارات التوجيه",
|
||||||
|
"dns": "خيارات DNS",
|
||||||
|
"inbound": "خيارات الوارد",
|
||||||
|
"mux": "MultiPlexer",
|
||||||
|
"outbound": "خيارات الخارج",
|
||||||
|
"tlsTricks": "حيل TLS",
|
||||||
|
"warp": "خيارات WARP",
|
||||||
|
"misc": "خيارات متنوعة"
|
||||||
|
},
|
||||||
|
"warpConsent": {
|
||||||
|
"title": "موافقة Cloudflare WARP",
|
||||||
|
"description(rich)": "Cloudflare WARP هو مزود VPN مجاني لـ WireGuard. بتمكين هذا الخيار، فإنك توافق على ${tos(Terms of Service)} و ${privacy(Privacy Policy)} لـ Cloudflare WARP."
|
||||||
|
},
|
||||||
|
"generateWarpConfig": "إنشاء تكوين WARP",
|
||||||
|
"missingWarpConfig": "تكوين WARP مفقود",
|
||||||
|
"warpConfigGenerated": "تم إنشاء تكوين WARP",
|
||||||
|
"pageTitle": "خيارات التكوين",
|
||||||
|
"logLevel": "مستوى السجل",
|
||||||
|
"resolveDestination": "حل الوجهة",
|
||||||
|
"ipv6Mode": "توجيه IPv6",
|
||||||
|
"ipv6Modes": {
|
||||||
|
"disable": "تعطيل",
|
||||||
|
"enable": "تمكين",
|
||||||
|
"prefer": "مُفضل",
|
||||||
|
"only": "حصري"
|
||||||
|
},
|
||||||
|
"remoteDnsAddress": "DNS البعيد",
|
||||||
|
"remoteDnsDomainStrategy": "استراتيجية مجال DNS البعيد",
|
||||||
|
"directDnsAddress": "DNS المباشر",
|
||||||
|
"directDnsDomainStrategy": "استراتيجية مجال DNS المباشر",
|
||||||
|
"mixedPort": "منفذ مُختلط",
|
||||||
|
"tproxyPort": "منفذ الوكيل الشفاف",
|
||||||
|
"localDnsPort": "منفذ DNS المحلي",
|
||||||
|
"allowConnectionFromLan": "مشاركة VPN على الشبكة المحلية",
|
||||||
|
"tunImplementation": "تنفيذ TUN",
|
||||||
|
"mtu": "MTU",
|
||||||
|
"connectionTestUrl": "عنوان URL لاختبار الاتصال",
|
||||||
|
"urlTestInterval": "مُدة اختبار عنوان URL",
|
||||||
|
"enableClashApi": "تمكين Clash API",
|
||||||
|
"clashApiPort": "منفذ Clash API",
|
||||||
|
"enableTun": "تمكين TUN",
|
||||||
|
"setSystemProxy": "تعيين وكيل النظام",
|
||||||
|
"enableDnsRouting": "تمكين توجيه DNS",
|
||||||
|
"enableFakeDns": "تمكين DNS المزيف",
|
||||||
|
"bypassLan": "تجاوز LAN",
|
||||||
|
"strictRoute": "توجيه صارم",
|
||||||
|
"enableTlsFragment": "تمكين تجزئة TLS",
|
||||||
|
"tlsFragmentSize": "حجم تجزئة TLS",
|
||||||
|
"tlsFragmentSleep": "وقت تعليق تجزئة TLS",
|
||||||
|
"enableTlsMixedSniCase": "تمكين خلط حالات SNI في TLS",
|
||||||
|
"enableTlsPadding": "تمكين حشو TLS",
|
||||||
|
"tlsPaddingSize": "حشو TLS",
|
||||||
|
"enableMux": "تمكين Mux",
|
||||||
|
"muxProtocol": "بروتوكول Mux",
|
||||||
|
"muxMaxStreams": "أقصى عدد من التدفقات المتزامنة",
|
||||||
|
"enableWarp": "تمكين WARP",
|
||||||
|
"warpDetourMode": "وضع التفاف",
|
||||||
|
"warpDetourModes": {
|
||||||
|
"proxyOverWarp": "لف الخوادم الوكيلية عبر WARP",
|
||||||
|
"warpOverProxy": "لف WARP عبر الخوادم الوكيلية",
|
||||||
|
"inbound": "لف WARP عبر الخوادم الوكيلية",
|
||||||
|
"outbound": "لف الخوادم الوكيلية عبر WARP"
|
||||||
|
},
|
||||||
|
"warpLicenseKey": "مفتاح الترخيص",
|
||||||
|
"warpCleanIp": "عنوان IP نظيف",
|
||||||
|
"warpPort": "المنفذ",
|
||||||
|
"warpNoise": "عدد الضوضاء",
|
||||||
|
"warpNoiseDelay": "تأخير الضوضاء"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -78,8 +78,10 @@
|
|||||||
"permissionRequest": "Permission to camera to scan QR Code"
|
"permissionRequest": "Permission to camera to scan QR Code"
|
||||||
},
|
},
|
||||||
"manually": "Manual Entry",
|
"manually": "Manual Entry",
|
||||||
|
"addWarp": "Add Warp",
|
||||||
"addingProfileMsg": "Adding Profile",
|
"addingProfileMsg": "Adding Profile",
|
||||||
"failureMsg": "Failed to Add Profile"
|
"failureMsg": "Failed to Add Profile"
|
||||||
|
|
||||||
},
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"buttonTxt": "Update",
|
"buttonTxt": "Update",
|
||||||
@@ -162,8 +164,8 @@
|
|||||||
"requiresRestartMsg": "For this to take effect restart the app",
|
"requiresRestartMsg": "For this to take effect restart the app",
|
||||||
"experimental": "Experimental",
|
"experimental": "Experimental",
|
||||||
"experimentalMsg": "Features with Experimental flag are still in development and might cause issues.",
|
"experimentalMsg": "Features with Experimental flag are still in development and might cause issues.",
|
||||||
"exportOptions": "Export Options to Clipboard",
|
"exportOptions": "Copy Anonymous Options to Clipboard",
|
||||||
"exportAllOptions": "Export Options to Clipboard (Debug)",
|
"exportAllOptions": "Copy All Options to Clipboard",
|
||||||
"importOptions": "Import Options From Clipboard",
|
"importOptions": "Import Options From Clipboard",
|
||||||
"importOptionsMsg": "This will rewrite all config options with provided values. Are you sure?",
|
"importOptionsMsg": "This will rewrite all config options with provided values. Are you sure?",
|
||||||
"general": {
|
"general": {
|
||||||
|
|||||||
@@ -162,8 +162,8 @@
|
|||||||
"requiresRestartMsg": "برای اعمال این تنظیم، برنامه را دوباره راهاندازی کنید",
|
"requiresRestartMsg": "برای اعمال این تنظیم، برنامه را دوباره راهاندازی کنید",
|
||||||
"experimental": "آزمایشی",
|
"experimental": "آزمایشی",
|
||||||
"experimentalMsg": "تنظیماتی که عنوان آزمایشی دارند، همچنان در دست توسعه هستند و فعالسازی آنها میتواند باعث بروز مشکلاتی شود. ",
|
"experimentalMsg": "تنظیماتی که عنوان آزمایشی دارند، همچنان در دست توسعه هستند و فعالسازی آنها میتواند باعث بروز مشکلاتی شود. ",
|
||||||
"exportOptions": "صادر کردن تنظیمات به کلیپبورد",
|
"exportOptions": "کپی تنظیمات ساده به کلیپبورد",
|
||||||
"exportAllOptions": "صادر کردن تنظیمات به کلیپبورد (اشکالزدایی)",
|
"exportAllOptions": "کپی همه تنظیمات به کلیپبورد",
|
||||||
"importOptions": "وارد کردن تنظیمات از کلیپبورد",
|
"importOptions": "وارد کردن تنظیمات از کلیپبورد",
|
||||||
"importOptionsMsg": "این اقدام همهی تنظیمات پیکربندی را با مقادیر اولیه بازنویسی میکند. آیا مطمئن هستید؟",
|
"importOptionsMsg": "این اقدام همهی تنظیمات پیکربندی را با مقادیر اولیه بازنویسی میکند. آیا مطمئن هستید؟",
|
||||||
"general": {
|
"general": {
|
||||||
|
|||||||
@@ -16,13 +16,13 @@
|
|||||||
"agree": "Соглашаться",
|
"agree": "Соглашаться",
|
||||||
"decline": "Отклонить",
|
"decline": "Отклонить",
|
||||||
"unknown": "Неизвестный",
|
"unknown": "Неизвестный",
|
||||||
"hidden": "Скрытый",
|
|
||||||
"timeout": "Таймаут",
|
"timeout": "Таймаут",
|
||||||
"clipboardExportSuccessMsg": "Добавлено в буфер обмена",
|
"clipboardExportSuccessMsg": "Скопировано",
|
||||||
"showMore": "Раскрыть",
|
"showMore": "Развернуть ",
|
||||||
"showLess": "Свернуть",
|
"showLess": "Свернуть ",
|
||||||
"openAppSettings": "Открыть настройки приложения",
|
"openAppSettings": "Открыть настройки",
|
||||||
"grantPermission": "Дать разрешение"
|
"grantPermission": "Дать права доступа",
|
||||||
|
"hidden": "Скрытый"
|
||||||
},
|
},
|
||||||
"intro": {
|
"intro": {
|
||||||
"termsAndPolicyCaution(rich)": "Продолжая, Вы соглашаетесь с ${tap(@:about.termsAndConditions)}",
|
"termsAndPolicyCaution(rich)": "Продолжая, Вы соглашаетесь с ${tap(@:about.termsAndConditions)}",
|
||||||
@@ -39,9 +39,10 @@
|
|||||||
"trafficTotal": "Трафик",
|
"trafficTotal": "Трафик",
|
||||||
"uplink": "Скорость отправки",
|
"uplink": "Скорость отправки",
|
||||||
"downlink": "Скорость загрузки",
|
"downlink": "Скорость загрузки",
|
||||||
"connection": "Соединение",
|
"connection": "Соединение ",
|
||||||
"speed": "Скорость",
|
"speed": "Скорость",
|
||||||
"totalTransferred": "В общем передано"
|
"totalTransferred": "Всего передано"
|
||||||
|
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"overviewPageTitle": "Профили",
|
"overviewPageTitle": "Профили",
|
||||||
@@ -56,10 +57,10 @@
|
|||||||
"remainingTrafficSemanticLabel": "Использовано ${consumed} трафика из ${total}",
|
"remainingTrafficSemanticLabel": "Использовано ${consumed} трафика из ${total}",
|
||||||
"expired": "Истекло",
|
"expired": "Истекло",
|
||||||
"noTraffic": "Нет доступного трафика",
|
"noTraffic": "Нет доступного трафика",
|
||||||
"upload": "Загрузка",
|
"upload": "Отправлено ",
|
||||||
"download": "Скачивание",
|
"download": "Скачано",
|
||||||
"total": "Общий трафик",
|
"total": "Всего трафика",
|
||||||
"expireDate": "Истечение срока действия"
|
"expireDate": "Дата окончания подписки "
|
||||||
},
|
},
|
||||||
"sortBy": {
|
"sortBy": {
|
||||||
"lastUpdate": "по последнему обновлению",
|
"lastUpdate": "по последнему обновлению",
|
||||||
@@ -75,7 +76,7 @@
|
|||||||
"unexpectedError": "Неизвестная ошибка",
|
"unexpectedError": "Неизвестная ошибка",
|
||||||
"torchSemanticLabel": "Вспышка",
|
"torchSemanticLabel": "Вспышка",
|
||||||
"facingSemanticLabel": "Фронтальная камера",
|
"facingSemanticLabel": "Фронтальная камера",
|
||||||
"permissionRequest": "Разрешение камере сканировать QR-код"
|
"permissionRequest": "Права на использование камеры для считывания QR"
|
||||||
},
|
},
|
||||||
"manually": "Ввести вручную",
|
"manually": "Ввести вручную",
|
||||||
"addingProfileMsg": "Добавление профиля",
|
"addingProfileMsg": "Добавление профиля",
|
||||||
@@ -138,8 +139,8 @@
|
|||||||
},
|
},
|
||||||
"activeProxySemanticLabel": "Активный прокси",
|
"activeProxySemanticLabel": "Активный прокси",
|
||||||
"delaySemantics": {
|
"delaySemantics": {
|
||||||
"result": "Задержка: ${delay}ms",
|
"result": "Задержка: ${delay}мс",
|
||||||
"timeout": "Тест задержки по таймауту",
|
"timeout": "Тайм-аут при тестировании задержки",
|
||||||
"testing": "Задержка: Тестирование..."
|
"testing": "Задержка: Тестирование..."
|
||||||
},
|
},
|
||||||
"ipInfoSemantics": {
|
"ipInfoSemantics": {
|
||||||
@@ -162,8 +163,7 @@
|
|||||||
"requiresRestartMsg": "Чтобы применить изменения, перезапустите приложение.",
|
"requiresRestartMsg": "Чтобы применить изменения, перезапустите приложение.",
|
||||||
"experimental": "Экспериментальный",
|
"experimental": "Экспериментальный",
|
||||||
"experimentalMsg": "Функции с флагом «Экспериментально» все еще находятся в разработке и могут вызвать проблемы.",
|
"experimentalMsg": "Функции с флагом «Экспериментально» все еще находятся в разработке и могут вызвать проблемы.",
|
||||||
"exportOptions": "Экспорт параметров в буфер обмена",
|
|
||||||
"exportAllOptions": "Экспорт параметров в буфер обмена (отладка)",
|
|
||||||
"importOptions": "Импорт параметров из буфера обмена",
|
"importOptions": "Импорт параметров из буфера обмена",
|
||||||
"importOptionsMsg": "Это перезапишет все параметры конфига предоставленными значениями. Вы уверены?",
|
"importOptionsMsg": "Это перезапишет все параметры конфига предоставленными значениями. Вы уверены?",
|
||||||
"general": {
|
"general": {
|
||||||
@@ -193,7 +193,7 @@
|
|||||||
"ignoreBatteryOptimizations": "Отключить оптимизацию батареи",
|
"ignoreBatteryOptimizations": "Отключить оптимизацию батареи",
|
||||||
"ignoreBatteryOptimizationsMsg": "Отключение ограничений для оптимальной производительности VPN.",
|
"ignoreBatteryOptimizationsMsg": "Отключение ограничений для оптимальной производительности VPN.",
|
||||||
"dynamicNotification": "Отображение скорости в уведомлении",
|
"dynamicNotification": "Отображение скорости в уведомлении",
|
||||||
"autoIpCheck": "Автоматически проверять IP соединение"
|
"autoIpCheck": "Автоматически проверять IP-адрес соединения"
|
||||||
},
|
},
|
||||||
"advanced": {
|
"advanced": {
|
||||||
"sectionTitle": "Расширенные",
|
"sectionTitle": "Расширенные",
|
||||||
@@ -314,8 +314,8 @@
|
|||||||
"connecting": "Подключение",
|
"connecting": "Подключение",
|
||||||
"disconnecting": "Отключение",
|
"disconnecting": "Отключение",
|
||||||
"connected": "Подключено",
|
"connected": "Подключено",
|
||||||
"reconnect": "Переподключение",
|
"reconnect": "Восстановить соединение",
|
||||||
"connectAnyWay": "Подключение",
|
"connectAnyWay": "Подключиться",
|
||||||
"experimentalNotice": "Экспериментальные функции в использовании",
|
"experimentalNotice": "Экспериментальные функции в использовании",
|
||||||
"experimentalNoticeMsg": "Вы включили некоторые экспериментальные функции, которые могут повлиять на качество соединения и вызвать непредвиденные ошибки. Вы всегда можете изменить или сбросить эти параметры на странице параметров конфигурации.",
|
"experimentalNoticeMsg": "Вы включили некоторые экспериментальные функции, которые могут повлиять на качество соединения и вызвать непредвиденные ошибки. Вы всегда можете изменить или сбросить эти параметры на странице параметров конфигурации.",
|
||||||
"disableExperimentalNotice": "Больше не показывать",
|
"disableExperimentalNotice": "Больше не показывать",
|
||||||
@@ -325,8 +325,8 @@
|
|||||||
"resetBtn": "Сбросить параметры",
|
"resetBtn": "Сбросить параметры",
|
||||||
"serviceMode": "Режим работы",
|
"serviceMode": "Режим работы",
|
||||||
"quickSettings": "Быстрые настройки",
|
"quickSettings": "Быстрые настройки",
|
||||||
|
"allOptions": "Все параметры ",
|
||||||
"setupWarp": "Настроить WARP",
|
"setupWarp": "Настроить WARP",
|
||||||
"allOptions": "Все параметры конфига",
|
|
||||||
"serviceModes": {
|
"serviceModes": {
|
||||||
"proxy": "Прокси",
|
"proxy": "Прокси",
|
||||||
"systemProxy": "Системный прокси",
|
"systemProxy": "Системный прокси",
|
||||||
|
|||||||
@@ -162,8 +162,7 @@
|
|||||||
"requiresRestartMsg": "要使其生效,请重新启动应用程序",
|
"requiresRestartMsg": "要使其生效,请重新启动应用程序",
|
||||||
"experimental": "实验性选项",
|
"experimental": "实验性选项",
|
||||||
"experimentalMsg": "带有实验标志的功能仍在开发中,可能会出现问题。",
|
"experimentalMsg": "带有实验标志的功能仍在开发中,可能会出现问题。",
|
||||||
"exportOptions": "导出选项到剪切板",
|
|
||||||
"exportAllOptions": "导出选项到剪切板(用于调试)",
|
|
||||||
"importOptions": "从剪切板导入选项",
|
"importOptions": "从剪切板导入选项",
|
||||||
"importOptionsMsg": "这将使用提供的值重写所有配置选项。您确定吗?",
|
"importOptionsMsg": "这将使用提供的值重写所有配置选项。您确定吗?",
|
||||||
"general": {
|
"general": {
|
||||||
|
|||||||
@@ -12,39 +12,54 @@
|
|||||||
"sort": "排序",
|
"sort": "排序",
|
||||||
"sortBy": "排序方式",
|
"sortBy": "排序方式",
|
||||||
"addToClipboard": "新增至剪貼簿",
|
"addToClipboard": "新增至剪貼簿",
|
||||||
"timeout": "暫停",
|
"notSet": "未設定",
|
||||||
"showMore": "展示更多",
|
"agree": "同意",
|
||||||
|
"decline": "拒絕",
|
||||||
|
"unknown": "不明",
|
||||||
|
"hidden": "隱藏",
|
||||||
|
"timeout": "超時",
|
||||||
|
"clipboardExportSuccessMsg": "已匯出至剪貼簿",
|
||||||
|
"showMore": "顯示更多",
|
||||||
"showLess": "顯示較少",
|
"showLess": "顯示較少",
|
||||||
|
"openAppSettings": "開啟應用程式設定",
|
||||||
"grantPermission": "授予權限"
|
"grantPermission": "授予權限"
|
||||||
},
|
},
|
||||||
"intro": {
|
"intro": {
|
||||||
"termsAndPolicyCaution(rich)": "繼續即表示您同意協議 ${tap(@:about.termsAndConditions)}",
|
"termsAndPolicyCaution(rich)": "繼續即表示您同意合約 ${tap(@:about.termsAndConditions)}",
|
||||||
"start": "開始"
|
"start": "開始"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"pageTitle": "主頁",
|
"pageTitle": "首頁",
|
||||||
"emptyProfilesMsg": "首先新增訂閱設定檔",
|
"emptyProfilesMsg": "首先新增訂閱設定檔",
|
||||||
"noActiveProfileMsg": "選擇設定檔"
|
"noActiveProfileMsg": "選擇設定檔"
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
|
"traffic": "流量",
|
||||||
"trafficLive": "即時流量",
|
"trafficLive": "即時流量",
|
||||||
"trafficTotal": "總流量",
|
"trafficTotal": "總流量",
|
||||||
"uplink": "上行",
|
"uplink": "上行",
|
||||||
"downlink": "下行"
|
"downlink": "下行",
|
||||||
|
"connection": "連線",
|
||||||
|
"speed": "速度",
|
||||||
|
"totalTransferred": "總傳輸量"
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"overviewPageTitle": "設定檔",
|
"overviewPageTitle": "設定檔",
|
||||||
"detailsPageTitle": "設定檔",
|
"detailsPageTitle": "設定檔",
|
||||||
"activeProfileNameSemanticLabel": "活動設定檔名稱:“${name}”。",
|
"activeProfileNameSemanticLabel": "活動設定檔名稱:“${name}”。",
|
||||||
"activeProfileBtnSemanticLabel": "查看所有設定檔。",
|
"activeProfileBtnSemanticLabel": "查看所有設定檔",
|
||||||
"nonActiveProfileBtnSemanticLabel": "選擇“${name}”作為活動設定檔。",
|
"nonActiveProfileBtnSemanticLabel": "選擇“${name}”作為活動設定檔。",
|
||||||
"subscription": {
|
"subscription": {
|
||||||
"traffic": "流量",
|
"traffic": "流量",
|
||||||
"updatedTimeAgo": "更新${timeago}",
|
"updatedTimeAgo": "更新於 ${timeago}",
|
||||||
"remainingDuration": "剩餘 ${duration} 天",
|
"remainingDuration": "剩餘 ${duration} 天",
|
||||||
"remainingTrafficSemanticLabel": "已使用 ${consumed} 流量,總共 ${total} 流量。",
|
"remainingTrafficSemanticLabel": "已使用 ${consumed} 流量,總共 ${total} 流量。",
|
||||||
"expired": "已到期",
|
"expired": "已到期",
|
||||||
"noTraffic": "超過配額"
|
"noTraffic": "超過配額",
|
||||||
|
"upload": "上傳",
|
||||||
|
"download": "下載",
|
||||||
|
"total": "總流量",
|
||||||
|
"expireDate": "到期時間"
|
||||||
},
|
},
|
||||||
"sortBy": {
|
"sortBy": {
|
||||||
"lastUpdate": "最近更新",
|
"lastUpdate": "最近更新",
|
||||||
@@ -53,13 +68,14 @@
|
|||||||
"add": {
|
"add": {
|
||||||
"buttonText": "新的設定檔",
|
"buttonText": "新的設定檔",
|
||||||
"shortBtnTxt": "新的設定檔",
|
"shortBtnTxt": "新的設定檔",
|
||||||
"fromClipboard": "從剪貼簿添加",
|
"fromClipboard": "從剪貼簿新增",
|
||||||
"scanQr": "掃描 QR code",
|
"scanQr": "掃描 QR code",
|
||||||
"qrScanner": {
|
"qrScanner": {
|
||||||
"permissionDeniedError": "沒有權限",
|
"permissionDeniedError": "沒有權限",
|
||||||
"unexpectedError": "出了點問題",
|
"unexpectedError": "出了點問題",
|
||||||
"torchSemanticLabel": "手電筒",
|
"torchSemanticLabel": "手電筒",
|
||||||
"facingSemanticLabel": "相機朝向"
|
"facingSemanticLabel": "相機朝向",
|
||||||
|
"permissionRequest": "授予相機權限已允許掃描 QR code"
|
||||||
},
|
},
|
||||||
"manually": "手動輸入",
|
"manually": "手動輸入",
|
||||||
"addingProfileMsg": "新增設定檔",
|
"addingProfileMsg": "新增設定檔",
|
||||||
@@ -84,11 +100,11 @@
|
|||||||
},
|
},
|
||||||
"edit": {
|
"edit": {
|
||||||
"buttonTxt": "編輯",
|
"buttonTxt": "編輯",
|
||||||
"selectActiveTxt": "選擇並激活設定檔"
|
"selectActiveTxt": "選擇活動設定檔"
|
||||||
},
|
},
|
||||||
"delete": {
|
"delete": {
|
||||||
"buttonTxt": "刪除",
|
"buttonTxt": "刪除",
|
||||||
"confirmationMsg": "永久刪除設定檔嗎?",
|
"confirmationMsg": "要永久刪除設定檔嗎?",
|
||||||
"successMsg": "設定檔刪除成功"
|
"successMsg": "設定檔刪除成功"
|
||||||
},
|
},
|
||||||
"save": {
|
"save": {
|
||||||
@@ -113,18 +129,30 @@
|
|||||||
"emptyProxiesMsg": "沒有可用的代理",
|
"emptyProxiesMsg": "沒有可用的代理",
|
||||||
"delayTestTooltip": "測試延遲",
|
"delayTestTooltip": "測試延遲",
|
||||||
"sortTooltip": "對代理進行排序",
|
"sortTooltip": "對代理進行排序",
|
||||||
|
"checkIp": "檢測 IP 地址",
|
||||||
|
"unknownIp": "不明的 IP",
|
||||||
"sortOptions": {
|
"sortOptions": {
|
||||||
"unsorted": "預設",
|
"unsorted": "預設",
|
||||||
"name": "按字母排序",
|
"name": "按字母排序",
|
||||||
"delay": "按延遲排序"
|
"delay": "按延遲排序"
|
||||||
|
},
|
||||||
|
"activeProxySemanticLabel": "生效中的代理",
|
||||||
|
"delaySemantics": {
|
||||||
|
"result": "延遲: ${delay}ms",
|
||||||
|
"timeout": "延遲測試超時",
|
||||||
|
"testing": "正在測試延遲"
|
||||||
|
},
|
||||||
|
"ipInfoSemantics": {
|
||||||
|
"address": "IP 地址",
|
||||||
|
"country": "國家"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"logs": {
|
"logs": {
|
||||||
"pageTitle": "日誌",
|
"pageTitle": "記錄檔",
|
||||||
"filterHint": "篩選",
|
"filterHint": "篩選",
|
||||||
"allLevelsFilter": "全部",
|
"allLevelsFilter": "全部",
|
||||||
"shareCoreLogs": "分享核心日誌",
|
"shareCoreLogs": "分享核心記錄檔",
|
||||||
"shareAppLogs": "共享應用程式日誌",
|
"shareAppLogs": "分享應用程式記錄檔",
|
||||||
"pauseTooltip": "暫停",
|
"pauseTooltip": "暫停",
|
||||||
"resumeTooltip": "恢復",
|
"resumeTooltip": "恢復",
|
||||||
"clearTooltip": "清除"
|
"clearTooltip": "清除"
|
||||||
@@ -134,17 +162,21 @@
|
|||||||
"requiresRestartMsg": "若要使其生效,請重新啟動應用程式",
|
"requiresRestartMsg": "若要使其生效,請重新啟動應用程式",
|
||||||
"experimental": "實驗性的",
|
"experimental": "實驗性的",
|
||||||
"experimentalMsg": "帶有實驗標誌的功能仍在開發中,可能會導致問題。",
|
"experimentalMsg": "帶有實驗標誌的功能仍在開發中,可能會導致問題。",
|
||||||
|
"exportOptions": "匯出選項至剪貼簿",
|
||||||
|
"exportAllOptions": "匯出選項至剪貼不(用於偵錯) ",
|
||||||
|
"importOptions": "從剪貼簿匯入選項",
|
||||||
|
"importOptionsMsg": "浙江使用提供的值複寫所有配置選項。您確定嗎?",
|
||||||
"general": {
|
"general": {
|
||||||
"sectionTitle": "一般的",
|
"sectionTitle": "一般",
|
||||||
"locale": "語言",
|
"locale": "語言",
|
||||||
"region": "地區",
|
"region": "地區",
|
||||||
"regionMsg": "幫助設定預設選項以繞過國內地址",
|
"regionMsg": "幫助設定預設選項以繞過國內地址",
|
||||||
"regions": {
|
"regions": {
|
||||||
"ir": "伊朗 (ir)",
|
"ir": "伊朗 (ir)",
|
||||||
"cn": "中國大陸 (cn)",
|
"cn": "中國 (cn)",
|
||||||
"ru": "俄羅斯 (ru)",
|
"ru": "俄羅斯 (ru)",
|
||||||
"af": "阿富汗 (af)",
|
"af": "阿富汗 (af)",
|
||||||
"other": "其他地區"
|
"other": "其他"
|
||||||
},
|
},
|
||||||
"themeMode": "主題模式",
|
"themeMode": "主題模式",
|
||||||
"themeModes": {
|
"themeModes": {
|
||||||
@@ -154,18 +186,20 @@
|
|||||||
"black": "黑色"
|
"black": "黑色"
|
||||||
},
|
},
|
||||||
"enableAnalytics": "啟用分析",
|
"enableAnalytics": "啟用分析",
|
||||||
"enableAnalyticsMsg": "授予收集分析並發送崩潰報告以改進應用程式的權限",
|
"enableAnalyticsMsg": "授予收集分析並傳送崩潰報告以改進應用程式的權限",
|
||||||
"autoStart": "隨系統啟動",
|
"autoStart": "隨系統啟動",
|
||||||
"silentStart": "啟動最小化",
|
"silentStart": "啟動最小化",
|
||||||
"openWorkingDir": "開啟工作目錄",
|
"openWorkingDir": "開啟工作目錄",
|
||||||
"ignoreBatteryOptimizations": "停用電池優化",
|
"ignoreBatteryOptimizations": "停用電池最佳化",
|
||||||
"ignoreBatteryOptimizationsMsg": "消除限制以獲得最佳 VPN 效能",
|
"ignoreBatteryOptimizationsMsg": "消除限制以獲得最佳 VPN 效能",
|
||||||
"dynamicNotification": "在通知中顯示速度"
|
"dynamicNotification": "在通知中顯示速度",
|
||||||
|
"hapticFeedback": "觸覺回饋",
|
||||||
|
"autoIpCheck": "自動檢查連線的 IP"
|
||||||
},
|
},
|
||||||
"advanced": {
|
"advanced": {
|
||||||
"sectionTitle": "高級設定",
|
"sectionTitle": "進階設定",
|
||||||
"debugMode": "偵錯模式",
|
"debugMode": "偵錯模式",
|
||||||
"debugModeMsg": "重新啟動應用程式以應用此更改",
|
"debugModeMsg": "重新啟動應用程式以套用此變更",
|
||||||
"memoryLimit": "記憶體限制",
|
"memoryLimit": "記憶體限制",
|
||||||
"memoryLimitMsg": "如果您遇到記憶體不足錯誤或頻繁應用程式崩潰,請啟用",
|
"memoryLimitMsg": "如果您遇到記憶體不足錯誤或頻繁應用程式崩潰,請啟用",
|
||||||
"resetTunnel": "重置 VPN 設定檔"
|
"resetTunnel": "重置 VPN 設定檔"
|
||||||
@@ -189,7 +223,7 @@
|
|||||||
"geoip": "GeoIP",
|
"geoip": "GeoIP",
|
||||||
"geosite": "Geosite",
|
"geosite": "Geosite",
|
||||||
"version": "版本${version}",
|
"version": "版本${version}",
|
||||||
"fileMissing": "文件遺失",
|
"fileMissing": "檔案遺失",
|
||||||
"update": "更新",
|
"update": "更新",
|
||||||
"download": "下載",
|
"download": "下載",
|
||||||
"failureMsg": "更新資源文件失敗",
|
"failureMsg": "更新資源文件失敗",
|
||||||
@@ -201,7 +235,7 @@
|
|||||||
"about": {
|
"about": {
|
||||||
"pageTitle": "關於",
|
"pageTitle": "關於",
|
||||||
"version": "版本",
|
"version": "版本",
|
||||||
"sourceCode": "原始代碼",
|
"sourceCode": "原始碼",
|
||||||
"telegramChannel": "Telegram 頻道",
|
"telegramChannel": "Telegram 頻道",
|
||||||
"checkForUpdate": "檢查更新",
|
"checkForUpdate": "檢查更新",
|
||||||
"privacyPolicy": "隱私政策",
|
"privacyPolicy": "隱私政策",
|
||||||
@@ -220,12 +254,12 @@
|
|||||||
"tray": {
|
"tray": {
|
||||||
"dashboard": "儀表板",
|
"dashboard": "儀表板",
|
||||||
"quit": "退出",
|
"quit": "退出",
|
||||||
"open": "打開",
|
"open": "開啟",
|
||||||
"status": {
|
"status": {
|
||||||
"connect": "連接",
|
"connect": "連線",
|
||||||
"connecting": "連接中",
|
"connecting": "連線中",
|
||||||
"disconnect": "斷開連接",
|
"disconnect": "中斷連線",
|
||||||
"disconnecting": "斷連中"
|
"disconnecting": "中斷連線中"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"failure": {
|
"failure": {
|
||||||
@@ -236,8 +270,8 @@
|
|||||||
},
|
},
|
||||||
"singbox": {
|
"singbox": {
|
||||||
"unexpected": "意外服務錯誤",
|
"unexpected": "意外服務錯誤",
|
||||||
"serviceNotRunning": "服務未運行",
|
"serviceNotRunning": "服務未執行",
|
||||||
"missingPrivilege": "缺乏權限",
|
"missingPrivilege": "缺少權限",
|
||||||
"missingPrivilegeMsg": "VPN 模式需要管理員權限。以管理員身分重新啟動應用程式或變更服務模式。",
|
"missingPrivilegeMsg": "VPN 模式需要管理員權限。以管理員身分重新啟動應用程式或變更服務模式。",
|
||||||
"missingGeoAssets": "缺少 GEO 資源文件",
|
"missingGeoAssets": "缺少 GEO 資源文件",
|
||||||
"missingGeoAssetsMsg": "GEO 資源文件缺失。請考慮更改活動資源文件或在設定中下載選定的資源文件。",
|
"missingGeoAssetsMsg": "GEO 資源文件缺失。請考慮更改活動資源文件或在設定中下載選定的資源文件。",
|
||||||
@@ -277,42 +311,54 @@
|
|||||||
"full_description": "Hiddify 的主要目標是提供安全、使用者友好且高效率的隧道用戶端。它使您能夠利用 VPN 服務權限將所有流量或選定的應用程式流量路由到您選擇的遠端伺服器。\n\n註:我們不提供任何伺服器;使用者需要使用自己的自託管伺服器或受信任的伺服器來確保其線上活動的隱私。\n\n我們透過以下方式支援伺服器:\n - 普通 V2ray/Xray 訂閱連結\n - Clash 訂閱連結\n - Sing-Box 訂閱連結\n\n 我們的獨特功能是什麼?\n - 使用者友善\n - 最佳化且快速\n - 自動選擇最低延遲\n - 顯示使用者使用資訊\n - 使用一鍵連結輕鬆導入\n - 免費且無廣告\n - 輕鬆切換線路\n - 等等\n 支援:\n - Sing-Box 支援的所有協定 \n - VLESS + XTLS Reality、Vision 協定 \n - VMESS\n - Trojan\n - ShoadowSocks\n - Reality\n - WireGuard\n - V2ray\n - Hystria2\n - TUIC \n - SSH\n - ShadowTLS\n\n\n 原始碼位於 https://github.com/hiddify/Hiddify-Next\n 應用程式核心基於開源的 Sing-Box。\n\n權限說明:\n\n - VPN 服務:由於此應用程式的目標是提供安全性、使用者友好且高效的隧道用戶端,因此我們需要此權限才能透過隧道將流量路由到遠端伺服器。\n - 獲取應用程式列表:此權限用於允許使用者包含或排除隧道的特定應用程式。\n - 接收啟動廣播:可以從應用程式設定中啟用或停用此權限,以在裝置啟動時啟動此應用程式。\n - 發送通知:此權限至關重要,因為我們使用前台服務來確保 VPN 服務的持續運作。\n - 該應用程式沒有廣告。分析和崩潰數據僅在用戶首次使用應用程式時明確同意的情況下才會出現。"
|
"full_description": "Hiddify 的主要目標是提供安全、使用者友好且高效率的隧道用戶端。它使您能夠利用 VPN 服務權限將所有流量或選定的應用程式流量路由到您選擇的遠端伺服器。\n\n註:我們不提供任何伺服器;使用者需要使用自己的自託管伺服器或受信任的伺服器來確保其線上活動的隱私。\n\n我們透過以下方式支援伺服器:\n - 普通 V2ray/Xray 訂閱連結\n - Clash 訂閱連結\n - Sing-Box 訂閱連結\n\n 我們的獨特功能是什麼?\n - 使用者友善\n - 最佳化且快速\n - 自動選擇最低延遲\n - 顯示使用者使用資訊\n - 使用一鍵連結輕鬆導入\n - 免費且無廣告\n - 輕鬆切換線路\n - 等等\n 支援:\n - Sing-Box 支援的所有協定 \n - VLESS + XTLS Reality、Vision 協定 \n - VMESS\n - Trojan\n - ShoadowSocks\n - Reality\n - WireGuard\n - V2ray\n - Hystria2\n - TUIC \n - SSH\n - ShadowTLS\n\n\n 原始碼位於 https://github.com/hiddify/Hiddify-Next\n 應用程式核心基於開源的 Sing-Box。\n\n權限說明:\n\n - VPN 服務:由於此應用程式的目標是提供安全性、使用者友好且高效的隧道用戶端,因此我們需要此權限才能透過隧道將流量路由到遠端伺服器。\n - 獲取應用程式列表:此權限用於允許使用者包含或排除隧道的特定應用程式。\n - 接收啟動廣播:可以從應用程式設定中啟用或停用此權限,以在裝置啟動時啟動此應用程式。\n - 發送通知:此權限至關重要,因為我們使用前台服務來確保 VPN 服務的持續運作。\n - 該應用程式沒有廣告。分析和崩潰數據僅在用戶首次使用應用程式時明確同意的情況下才會出現。"
|
||||||
},
|
},
|
||||||
"connection": {
|
"connection": {
|
||||||
"tapToConnect": "點擊以連接",
|
"tapToConnect": "點擊以連線",
|
||||||
"connecting": "連接中",
|
"connecting": "連線中",
|
||||||
"disconnecting": "斷連中",
|
"disconnecting": "中斷連線中",
|
||||||
"connected": "已連接",
|
"connected": "已連線",
|
||||||
|
"reconnect": "重新連線",
|
||||||
|
"connectAnyWay": "連線",
|
||||||
"experimentalNotice": "使用中的實驗性功能",
|
"experimentalNotice": "使用中的實驗性功能",
|
||||||
"experimentalNoticeMsg": "您啟用了一些實驗性功能,這些功能可能會影響連線品質並導致某些意外錯誤。您始終可以從「配置選項」頁面變更或重設這些選項。",
|
"experimentalNoticeMsg": "您啟用了一些實驗性功能,這些功能可能會影響連線品質並導致某些意外錯誤。您始終可以從「配置選項」頁面變更或重設這些選項。",
|
||||||
"disableExperimentalNotice": "不再提示"
|
"disableExperimentalNotice": "不再提示",
|
||||||
|
"reconnectMsg": "重新連線以使變更生效"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"resetBtn": "重置選項",
|
"resetBtn": "重置選項",
|
||||||
"serviceMode": "服務模式",
|
"serviceMode": "服務模式",
|
||||||
"quickSettings": "快速設定",
|
"quickSettings": "快速設定",
|
||||||
"setupWarp": "配置 WARP",
|
"setupWarp": "配置 WARP",
|
||||||
|
"allOptions": "全部配置選項",
|
||||||
"serviceModes": {
|
"serviceModes": {
|
||||||
"proxy": "僅代理",
|
"proxy": "僅代理",
|
||||||
"systemProxy": "系統代理",
|
"systemProxy": "系統代理",
|
||||||
"tun": "VPN",
|
"tun": "VPN",
|
||||||
"tunService": "VPN 服務"
|
"tunService": "VPN 服務"
|
||||||
},
|
},
|
||||||
|
"shortServiceModes": {
|
||||||
|
"proxy": "僅代理",
|
||||||
|
"systemProxy": "系統代理",
|
||||||
|
"tun": "VPN",
|
||||||
|
"tunService": "VPN 服務"
|
||||||
|
},
|
||||||
"section": {
|
"section": {
|
||||||
"route": "路由選項",
|
"route": "路由選項",
|
||||||
"dns": "DNS 選項",
|
"dns": "DNS 選項",
|
||||||
"inbound": "入站選項",
|
"inbound": "入站選項",
|
||||||
"mux": "復用器",
|
"mux": "復用器",
|
||||||
"outbound": "出站選項",
|
"outbound": "出站選項",
|
||||||
"tlsTricks": "TLS Tricks",
|
"tlsTricks": "TLS 特性",
|
||||||
|
"warp": "WARP 選項",
|
||||||
"misc": "其他選項"
|
"misc": "其他選項"
|
||||||
},
|
},
|
||||||
"warpConsent": {
|
"warpConsent": {
|
||||||
"title": "Cloudflare WARP 許可條款"
|
"title": "Cloudflare WARP 授權條款",
|
||||||
|
"description(rich)": "Cloudflare WARP 是一個免費的 WireGuard VPN 提供商。啟用此選項即表示您同意 Cloudflare WARP 的 ${tos(服務條款)} 和 ${privacy(隱私權政策)}"
|
||||||
},
|
},
|
||||||
"generateWarpConfig": "生成 WARP 配置檔案",
|
"generateWarpConfig": "生成 WARP 配置檔案",
|
||||||
"missingWarpConfig": "WARP 配置檔案缺失",
|
"missingWarpConfig": "WARP 配置檔案缺失",
|
||||||
"warpConfigGenerated": "WARP 配置檔案已生成",
|
"warpConfigGenerated": "WARP 配置檔案已生成",
|
||||||
"pageTitle": "配置選項",
|
"pageTitle": "配置選項",
|
||||||
"logLevel": "日誌等級",
|
"logLevel": "紀錄等級",
|
||||||
"resolveDestination": "解析目標地址",
|
"resolveDestination": "解析目標地址",
|
||||||
"ipv6Mode": "IPv6 路由",
|
"ipv6Mode": "IPv6 路由",
|
||||||
"ipv6Modes": {
|
"ipv6Modes": {
|
||||||
@@ -328,11 +374,11 @@
|
|||||||
"mixedPort": "混合連接埠",
|
"mixedPort": "混合連接埠",
|
||||||
"tproxyPort": "透明代理埠",
|
"tproxyPort": "透明代理埠",
|
||||||
"localDnsPort": "本地 DNS 連接埠",
|
"localDnsPort": "本地 DNS 連接埠",
|
||||||
"allowConnectionFromLan": "允許區域網連線",
|
"allowConnectionFromLan": "允許區域網路連線",
|
||||||
"tunImplementation": "TUN 實現",
|
"tunImplementation": "TUN 實現",
|
||||||
"mtu": "MTU",
|
"mtu": "MTU",
|
||||||
"connectionTestUrl": "連接測試網址",
|
"connectionTestUrl": "連線測試網址",
|
||||||
"urlTestInterval": "URL 測試間隔",
|
"urlTestInterval": "網址測試間隔",
|
||||||
"enableClashApi": "啟用 Clash API",
|
"enableClashApi": "啟用 Clash API",
|
||||||
"clashApiPort": "Clash API 連接埠",
|
"clashApiPort": "Clash API 連接埠",
|
||||||
"enableTun": "啟用 TUN",
|
"enableTun": "啟用 TUN",
|
||||||
@@ -358,7 +404,7 @@
|
|||||||
"inbound": "透過代理繞過 WARP",
|
"inbound": "透過代理繞過 WARP",
|
||||||
"outbound": "透過 WARP 繞過代理"
|
"outbound": "透過 WARP 繞過代理"
|
||||||
},
|
},
|
||||||
"warpLicenseKey": "許可證金鑰",
|
"warpLicenseKey": "授權金鑰",
|
||||||
"warpCleanIp": "清理 IP",
|
"warpCleanIp": "清理 IP",
|
||||||
"warpPort": "埠",
|
"warpPort": "埠",
|
||||||
"warpNoise": "噪音計數",
|
"warpNoise": "噪音計數",
|
||||||
|
|||||||
@@ -23,8 +23,7 @@ class AnalyticsController extends _$AnalyticsController with AppLogger {
|
|||||||
return _preferences.getBool(enableAnalyticsPrefKey) ?? true;
|
return _preferences.getBool(enableAnalyticsPrefKey) ?? true;
|
||||||
}
|
}
|
||||||
|
|
||||||
SharedPreferences get _preferences =>
|
SharedPreferences get _preferences => ref.read(sharedPreferencesProvider).requireValue;
|
||||||
ref.read(sharedPreferencesProvider).requireValue;
|
|
||||||
|
|
||||||
Future<void> enableAnalytics() async {
|
Future<void> enableAnalytics() async {
|
||||||
if (state case AsyncData(value: final enabled)) {
|
if (state case AsyncData(value: final enabled)) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ extension AppLocaleX on AppLocale {
|
|||||||
this == AppLocale.fa ? FontFamily.shabnam : FontFamily.emoji;
|
this == AppLocale.fa ? FontFamily.shabnam : FontFamily.emoji;
|
||||||
|
|
||||||
String get localeName => switch (flutterLocale.toString()) {
|
String get localeName => switch (flutterLocale.toString()) {
|
||||||
|
"ar" => "العربية",
|
||||||
"en" => "English",
|
"en" => "English",
|
||||||
"fa" => "فارسی",
|
"fa" => "فارسی",
|
||||||
"ru" => "Русский",
|
"ru" => "Русский",
|
||||||
|
|||||||
@@ -51,8 +51,7 @@ abstract class ConfigOptions {
|
|||||||
validator: (value) => value.isNotBlank,
|
validator: (value) => value.isNotBlank,
|
||||||
);
|
);
|
||||||
|
|
||||||
static final remoteDnsDomainStrategy =
|
static final remoteDnsDomainStrategy = PreferencesNotifier.create<DomainStrategy, String>(
|
||||||
PreferencesNotifier.create<DomainStrategy, String>(
|
|
||||||
"remote-dns-domain-strategy",
|
"remote-dns-domain-strategy",
|
||||||
DomainStrategy.auto,
|
DomainStrategy.auto,
|
||||||
mapFrom: (value) => DomainStrategy.values.firstWhere((e) => e.key == value),
|
mapFrom: (value) => DomainStrategy.values.firstWhere((e) => e.key == value),
|
||||||
@@ -65,8 +64,7 @@ abstract class ConfigOptions {
|
|||||||
validator: (value) => value.isNotBlank,
|
validator: (value) => value.isNotBlank,
|
||||||
);
|
);
|
||||||
|
|
||||||
static final directDnsDomainStrategy =
|
static final directDnsDomainStrategy = PreferencesNotifier.create<DomainStrategy, String>(
|
||||||
PreferencesNotifier.create<DomainStrategy, String>(
|
|
||||||
"direct-dns-domain-strategy",
|
"direct-dns-domain-strategy",
|
||||||
DomainStrategy.auto,
|
DomainStrategy.auto,
|
||||||
mapFrom: (value) => DomainStrategy.values.firstWhere((e) => e.key == value),
|
mapFrom: (value) => DomainStrategy.values.firstWhere((e) => e.key == value),
|
||||||
@@ -91,8 +89,7 @@ abstract class ConfigOptions {
|
|||||||
validator: (value) => isPort(value.toString()),
|
validator: (value) => isPort(value.toString()),
|
||||||
);
|
);
|
||||||
|
|
||||||
static final tunImplementation =
|
static final tunImplementation = PreferencesNotifier.create<TunImplementation, String>(
|
||||||
PreferencesNotifier.create<TunImplementation, String>(
|
|
||||||
"tun-implementation",
|
"tun-implementation",
|
||||||
TunImplementation.mixed,
|
TunImplementation.mixed,
|
||||||
mapFrom: TunImplementation.values.byName,
|
mapFrom: TunImplementation.values.byName,
|
||||||
@@ -101,8 +98,7 @@ abstract class ConfigOptions {
|
|||||||
|
|
||||||
static final mtu = PreferencesNotifier.create<int, int>("mtu", 9000);
|
static final mtu = PreferencesNotifier.create<int, int>("mtu", 9000);
|
||||||
|
|
||||||
static final strictRoute =
|
static final strictRoute = PreferencesNotifier.create<bool, bool>("strict-route", true);
|
||||||
PreferencesNotifier.create<bool, bool>("strict-route", true);
|
|
||||||
|
|
||||||
static final connectionTestUrl = PreferencesNotifier.create<String, String>(
|
static final connectionTestUrl = PreferencesNotifier.create<String, String>(
|
||||||
"connection-test-url",
|
"connection-test-url",
|
||||||
@@ -128,8 +124,7 @@ abstract class ConfigOptions {
|
|||||||
validator: (value) => isPort(value.toString()),
|
validator: (value) => isPort(value.toString()),
|
||||||
);
|
);
|
||||||
|
|
||||||
static final bypassLan =
|
static final bypassLan = PreferencesNotifier.create<bool, bool>("bypass-lan", false);
|
||||||
PreferencesNotifier.create<bool, bool>("bypass-lan", false);
|
|
||||||
|
|
||||||
static final allowConnectionFromLan = PreferencesNotifier.create<bool, bool>(
|
static final allowConnectionFromLan = PreferencesNotifier.create<bool, bool>(
|
||||||
"allow-connection-from-lan",
|
"allow-connection-from-lan",
|
||||||
@@ -156,16 +151,14 @@ abstract class ConfigOptions {
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
static final tlsFragmentSize =
|
static final tlsFragmentSize = PreferencesNotifier.create<OptionalRange, String>(
|
||||||
PreferencesNotifier.create<OptionalRange, String>(
|
|
||||||
"tls-fragment-size",
|
"tls-fragment-size",
|
||||||
const OptionalRange(min: 1, max: 500),
|
const OptionalRange(min: 1, max: 500),
|
||||||
mapFrom: OptionalRange.parse,
|
mapFrom: OptionalRange.parse,
|
||||||
mapTo: const OptionalRangeJsonConverter().toJson,
|
mapTo: const OptionalRangeJsonConverter().toJson,
|
||||||
);
|
);
|
||||||
|
|
||||||
static final tlsFragmentSleep =
|
static final tlsFragmentSleep = PreferencesNotifier.create<OptionalRange, String>(
|
||||||
PreferencesNotifier.create<OptionalRange, String>(
|
|
||||||
"tls-fragment-sleep",
|
"tls-fragment-sleep",
|
||||||
const OptionalRange(min: 0, max: 500),
|
const OptionalRange(min: 0, max: 500),
|
||||||
mapFrom: OptionalRange.parse,
|
mapFrom: OptionalRange.parse,
|
||||||
@@ -182,8 +175,7 @@ abstract class ConfigOptions {
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
static final tlsPaddingSize =
|
static final tlsPaddingSize = PreferencesNotifier.create<OptionalRange, String>(
|
||||||
PreferencesNotifier.create<OptionalRange, String>(
|
|
||||||
"tls-padding-size",
|
"tls-padding-size",
|
||||||
const OptionalRange(min: 1, max: 1500),
|
const OptionalRange(min: 1, max: 1500),
|
||||||
mapFrom: OptionalRange.parse,
|
mapFrom: OptionalRange.parse,
|
||||||
@@ -218,8 +210,7 @@ abstract class ConfigOptions {
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
static final warpDetourMode =
|
static final warpDetourMode = PreferencesNotifier.create<WarpDetourMode, String>(
|
||||||
PreferencesNotifier.create<WarpDetourMode, String>(
|
|
||||||
"warp-detour-mode",
|
"warp-detour-mode",
|
||||||
WarpDetourMode.proxyOverWarp,
|
WarpDetourMode.proxyOverWarp,
|
||||||
mapFrom: WarpDetourMode.values.byName,
|
mapFrom: WarpDetourMode.values.byName,
|
||||||
@@ -230,16 +221,28 @@ abstract class ConfigOptions {
|
|||||||
"warp-license-key",
|
"warp-license-key",
|
||||||
"",
|
"",
|
||||||
);
|
);
|
||||||
|
static final warp2LicenseKey = PreferencesNotifier.create<String, String>(
|
||||||
|
"warp2s-license-key",
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
|
||||||
static final warpAccountId = PreferencesNotifier.create<String, String>(
|
static final warpAccountId = PreferencesNotifier.create<String, String>(
|
||||||
"warp-account-id",
|
"warp-account-id",
|
||||||
"",
|
"",
|
||||||
);
|
);
|
||||||
|
static final warp2AccountId = PreferencesNotifier.create<String, String>(
|
||||||
|
"warp2-account-id",
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
|
||||||
static final warpAccessToken = PreferencesNotifier.create<String, String>(
|
static final warpAccessToken = PreferencesNotifier.create<String, String>(
|
||||||
"warp-access-token",
|
"warp-access-token",
|
||||||
"",
|
"",
|
||||||
);
|
);
|
||||||
|
static final warp2AccessToken = PreferencesNotifier.create<String, String>(
|
||||||
|
"warp2-access-token",
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
|
||||||
static final warpCleanIp = PreferencesNotifier.create<String, String>(
|
static final warpCleanIp = PreferencesNotifier.create<String, String>(
|
||||||
"warp-clean-ip",
|
"warp-clean-ip",
|
||||||
@@ -259,8 +262,7 @@ abstract class ConfigOptions {
|
|||||||
mapTo: const OptionalRangeJsonConverter().toJson,
|
mapTo: const OptionalRangeJsonConverter().toJson,
|
||||||
);
|
);
|
||||||
|
|
||||||
static final warpNoiseDelay =
|
static final warpNoiseDelay = PreferencesNotifier.create<OptionalRange, String>(
|
||||||
PreferencesNotifier.create<OptionalRange, String>(
|
|
||||||
"warp-noise-delay",
|
"warp-noise-delay",
|
||||||
const OptionalRange(min: 20, max: 200),
|
const OptionalRange(min: 20, max: 200),
|
||||||
mapFrom: (value) => OptionalRange.parse(value, allowEmpty: true),
|
mapFrom: (value) => OptionalRange.parse(value, allowEmpty: true),
|
||||||
@@ -271,6 +273,10 @@ abstract class ConfigOptions {
|
|||||||
"warp-wireguard-config",
|
"warp-wireguard-config",
|
||||||
"",
|
"",
|
||||||
);
|
);
|
||||||
|
static final warp2WireguardConfig = PreferencesNotifier.create<String, String>(
|
||||||
|
"warp2-wireguard-config",
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
|
||||||
static final hasExperimentalFeatures = Provider.autoDispose<bool>(
|
static final hasExperimentalFeatures = Provider.autoDispose<bool>(
|
||||||
(ref) {
|
(ref) {
|
||||||
@@ -278,13 +284,7 @@ abstract class ConfigOptions {
|
|||||||
if (PlatformUtils.isDesktop && mode == ServiceMode.tun) {
|
if (PlatformUtils.isDesktop && mode == ServiceMode.tun) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (ref.watch(enableTlsFragment) ||
|
if (ref.watch(enableTlsFragment) || ref.watch(enableTlsMixedSniCase) || ref.watch(enableTlsPadding) || ref.watch(enableMux) || ref.watch(enableWarp) || ref.watch(bypassLan) || ref.watch(allowConnectionFromLan)) {
|
||||||
ref.watch(enableTlsMixedSniCase) ||
|
|
||||||
ref.watch(enableTlsPadding) ||
|
|
||||||
ref.watch(enableMux) ||
|
|
||||||
ref.watch(enableWarp) ||
|
|
||||||
ref.watch(bypassLan) ||
|
|
||||||
ref.watch(allowConnectionFromLan)) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,10 +298,13 @@ abstract class ConfigOptions {
|
|||||||
"warp.access-token",
|
"warp.access-token",
|
||||||
"warp.account-id",
|
"warp.account-id",
|
||||||
"warp.wireguard-config",
|
"warp.wireguard-config",
|
||||||
|
"warp2.license-key",
|
||||||
|
"warp2.access-token",
|
||||||
|
"warp2.account-id",
|
||||||
|
"warp2.wireguard-config",
|
||||||
};
|
};
|
||||||
|
|
||||||
static final Map<String, StateNotifierProvider<PreferencesNotifier, dynamic>>
|
static final Map<String, StateNotifierProvider<PreferencesNotifier, dynamic>> preferences = {
|
||||||
preferences = {
|
|
||||||
"service-mode": serviceMode,
|
"service-mode": serviceMode,
|
||||||
"log-level": logLevel,
|
"log-level": logLevel,
|
||||||
"resolve-destination": resolveDestination,
|
"resolve-destination": resolveDestination,
|
||||||
@@ -348,6 +351,10 @@ abstract class ConfigOptions {
|
|||||||
"warp.noise": warpNoise,
|
"warp.noise": warpNoise,
|
||||||
"warp.noise-delay": warpNoiseDelay,
|
"warp.noise-delay": warpNoiseDelay,
|
||||||
"warp.wireguard-config": warpWireguardConfig,
|
"warp.wireguard-config": warpWireguardConfig,
|
||||||
|
"warp2.license-key": warp2LicenseKey,
|
||||||
|
"warp2.account-id": warp2AccountId,
|
||||||
|
"warp2.access-token": warp2AccessToken,
|
||||||
|
"warp2.wireguard-config": warp2WireguardConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
static final singboxConfigOptions = FutureProvider<SingboxConfigOption>(
|
static final singboxConfigOptions = FutureProvider<SingboxConfigOption>(
|
||||||
@@ -386,8 +393,7 @@ abstract class ConfigOptions {
|
|||||||
};
|
};
|
||||||
|
|
||||||
final geoAssetsRepo = await ref.watch(geoAssetRepositoryProvider.future);
|
final geoAssetsRepo = await ref.watch(geoAssetRepositoryProvider.future);
|
||||||
final geoAssets =
|
final geoAssets = await geoAssetsRepo.getActivePair().getOrElse((l) => throw l).run();
|
||||||
await geoAssetsRepo.getActivePair().getOrElse((l) => throw l).run();
|
|
||||||
|
|
||||||
final mode = ref.watch(serviceMode);
|
final mode = ref.watch(serviceMode);
|
||||||
return SingboxConfigOption(
|
return SingboxConfigOption(
|
||||||
@@ -443,6 +449,18 @@ abstract class ConfigOptions {
|
|||||||
noise: ref.watch(warpNoise),
|
noise: ref.watch(warpNoise),
|
||||||
noiseDelay: ref.watch(warpNoiseDelay),
|
noiseDelay: ref.watch(warpNoiseDelay),
|
||||||
),
|
),
|
||||||
|
warp2: SingboxWarpOption(
|
||||||
|
enable: ref.watch(enableWarp),
|
||||||
|
mode: ref.watch(warpDetourMode),
|
||||||
|
wireguardConfig: ref.watch(warp2WireguardConfig),
|
||||||
|
licenseKey: ref.watch(warp2LicenseKey),
|
||||||
|
accountId: ref.watch(warp2AccountId),
|
||||||
|
accessToken: ref.watch(warp2AccessToken),
|
||||||
|
cleanIp: ref.watch(warpCleanIp),
|
||||||
|
cleanPort: ref.watch(warpPort),
|
||||||
|
noise: ref.watch(warpNoise),
|
||||||
|
noiseDelay: ref.watch(warpNoiseDelay),
|
||||||
|
),
|
||||||
geoipPath: ref.watch(geoAssetPathResolverProvider).relativePath(
|
geoipPath: ref.watch(geoAssetPathResolverProvider).relativePath(
|
||||||
geoAssets.geoip.providerName,
|
geoAssets.geoip.providerName,
|
||||||
geoAssets.geoip.fileName,
|
geoAssets.geoip.fileName,
|
||||||
@@ -470,8 +488,7 @@ class ConfigOptionRepository with ExceptionHandler, InfraLogger {
|
|||||||
final GeoAssetRepository geoAssetRepository;
|
final GeoAssetRepository geoAssetRepository;
|
||||||
final GeoAssetPathResolver geoAssetPathResolver;
|
final GeoAssetPathResolver geoAssetPathResolver;
|
||||||
|
|
||||||
TaskEither<ConfigOptionFailure, SingboxConfigOption>
|
TaskEither<ConfigOptionFailure, SingboxConfigOption> getFullSingboxConfigOption() {
|
||||||
getFullSingboxConfigOption() {
|
|
||||||
return exceptionHandler(
|
return exceptionHandler(
|
||||||
() async {
|
() async {
|
||||||
return right(await getConfigOptions());
|
return right(await getConfigOptions());
|
||||||
|
|||||||
@@ -26,20 +26,14 @@ class WarpOptionNotifier extends _$WarpOptionNotifier with AppLogger {
|
|||||||
|
|
||||||
return WarpOptions(
|
return WarpOptions(
|
||||||
consentGiven: consent,
|
consentGiven: consent,
|
||||||
configGeneration: hasWarpConfig
|
configGeneration: hasWarpConfig ? const AsyncValue.data("") : AsyncError(const MissingWarpConfigFailure(), StackTrace.current),
|
||||||
? const AsyncValue.data("")
|
|
||||||
: AsyncError(const MissingWarpConfigFailure(), StackTrace.current),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
SharedPreferences get _prefs =>
|
SharedPreferences get _prefs => ref.read(sharedPreferencesProvider).requireValue;
|
||||||
ref.read(sharedPreferencesProvider).requireValue;
|
|
||||||
|
|
||||||
Future<void> agree() async {
|
Future<void> agree() async {
|
||||||
await ref
|
await ref.read(sharedPreferencesProvider).requireValue.setBool(warpConsentGiven, true);
|
||||||
.read(sharedPreferencesProvider)
|
|
||||||
.requireValue
|
|
||||||
.setBool(warpConsentGiven, true);
|
|
||||||
state = state.copyWith(consentGiven: true);
|
state = state.copyWith(consentGiven: true);
|
||||||
await generateWarpConfig();
|
await generateWarpConfig();
|
||||||
}
|
}
|
||||||
@@ -59,15 +53,33 @@ class WarpOptionNotifier extends _$WarpOptionNotifier with AppLogger {
|
|||||||
.getOrElse((l) => throw l)
|
.getOrElse((l) => throw l)
|
||||||
.run();
|
.run();
|
||||||
|
|
||||||
await ref
|
await ref.read(ConfigOptions.warpAccountId.notifier).update(warp.accountId);
|
||||||
.read(ConfigOptions.warpAccountId.notifier)
|
await ref.read(ConfigOptions.warpAccessToken.notifier).update(warp.accessToken);
|
||||||
.update(warp.accountId);
|
await ref.read(ConfigOptions.warpWireguardConfig.notifier).update(warp.wireguardConfig);
|
||||||
await ref
|
return warp.log;
|
||||||
.read(ConfigOptions.warpAccessToken.notifier)
|
});
|
||||||
.update(warp.accessToken);
|
|
||||||
await ref
|
state = state.copyWith(configGeneration: result);
|
||||||
.read(ConfigOptions.warpWireguardConfig.notifier)
|
}
|
||||||
.update(warp.wireguardConfig);
|
|
||||||
|
Future<void> generateWarp2Config() async {
|
||||||
|
if (state.configGeneration.isLoading) return;
|
||||||
|
state = state.copyWith(configGeneration: const AsyncLoading());
|
||||||
|
|
||||||
|
final result = await AsyncValue.guard(() async {
|
||||||
|
final warp = await ref
|
||||||
|
.read(singboxServiceProvider)
|
||||||
|
.generateWarpConfig(
|
||||||
|
licenseKey: ref.read(ConfigOptions.warpLicenseKey),
|
||||||
|
previousAccountId: ref.read(ConfigOptions.warp2AccountId),
|
||||||
|
previousAccessToken: ref.read(ConfigOptions.warp2AccessToken),
|
||||||
|
)
|
||||||
|
.getOrElse((l) => throw l)
|
||||||
|
.run();
|
||||||
|
|
||||||
|
await ref.read(ConfigOptions.warp2AccountId.notifier).update(warp.accountId);
|
||||||
|
await ref.read(ConfigOptions.warp2AccessToken.notifier).update(warp.accessToken);
|
||||||
|
await ref.read(ConfigOptions.warp2WireguardConfig.notifier).update(warp.wireguardConfig);
|
||||||
return warp.log;
|
return warp.log;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -23,17 +23,20 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:humanizer/humanizer.dart';
|
import 'package:humanizer/humanizer.dart';
|
||||||
|
|
||||||
enum ConfigOptionSection {
|
enum ConfigOptionSection {
|
||||||
warp;
|
warp,
|
||||||
|
fragment;
|
||||||
|
|
||||||
static final _warpKey = GlobalKey(debugLabel: "warp-section-key");
|
static final _warpKey = GlobalKey(debugLabel: "warp-section-key");
|
||||||
|
static final _fragmentKey = GlobalKey(debugLabel: "fragment-section-key");
|
||||||
|
|
||||||
GlobalKey get key => switch (this) { _ => _warpKey };
|
GlobalKey get key => switch (this) {
|
||||||
|
ConfigOptionSection.warp => _warpKey,
|
||||||
|
ConfigOptionSection.fragment => _fragmentKey,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConfigOptionsPage extends HookConsumerWidget {
|
class ConfigOptionsPage extends HookConsumerWidget {
|
||||||
ConfigOptionsPage({super.key, String? section})
|
ConfigOptionsPage({super.key, String? section}) : section = section != null ? ConfigOptionSection.values.byName(section) : null;
|
||||||
: section =
|
|
||||||
section != null ? ConfigOptionSection.values.byName(section) : null;
|
|
||||||
|
|
||||||
final ConfigOptionSection? section;
|
final ConfigOptionSection? section;
|
||||||
|
|
||||||
@@ -47,14 +50,10 @@ class ConfigOptionsPage extends HookConsumerWidget {
|
|||||||
if (section != null) {
|
if (section != null) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback(
|
WidgetsBinding.instance.addPostFrameCallback(
|
||||||
(_) {
|
(_) {
|
||||||
final box =
|
final box = section!.key.currentContext?.findRenderObject() as RenderBox?;
|
||||||
section!.key.currentContext?.findRenderObject() as RenderBox?;
|
|
||||||
final offset = box?.localToGlobal(Offset.zero);
|
final offset = box?.localToGlobal(Offset.zero);
|
||||||
if (offset == null) return;
|
if (offset == null) return;
|
||||||
final height = scrollController.offset +
|
final height = scrollController.offset + offset.dy - MediaQueryData.fromView(View.of(context)).padding.top - kToolbarHeight;
|
||||||
offset.dy -
|
|
||||||
MediaQueryData.fromView(View.of(context)).padding.top -
|
|
||||||
kToolbarHeight;
|
|
||||||
scrollController.animateTo(
|
scrollController.animateTo(
|
||||||
height,
|
height,
|
||||||
duration: const Duration(milliseconds: 500),
|
duration: const Duration(milliseconds: 500),
|
||||||
@@ -83,36 +82,26 @@ class ConfigOptionsPage extends HookConsumerWidget {
|
|||||||
itemBuilder: (context) {
|
itemBuilder: (context) {
|
||||||
return [
|
return [
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
onTap: () async => ref
|
onTap: () async => ref.read(configOptionNotifierProvider.notifier).exportJsonToClipboard().then((success) {
|
||||||
.read(configOptionNotifierProvider.notifier)
|
|
||||||
.exportJsonToClipboard()
|
|
||||||
.then((success) {
|
|
||||||
if (success) {
|
if (success) {
|
||||||
ref
|
ref.read(inAppNotificationControllerProvider).showSuccessToast(
|
||||||
.read(inAppNotificationControllerProvider)
|
|
||||||
.showSuccessToast(
|
|
||||||
t.general.clipboardExportSuccessMsg,
|
t.general.clipboardExportSuccessMsg,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
child: Text(t.settings.exportOptions),
|
child: Text(t.settings.exportOptions),
|
||||||
),
|
),
|
||||||
if (ref.watch(debugModeNotifierProvider))
|
// if (ref.watch(debugModeNotifierProvider))
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
onTap: () async => ref
|
onTap: () async => ref.read(configOptionNotifierProvider.notifier).exportJsonToClipboard(excludePrivate: false).then((success) {
|
||||||
.read(configOptionNotifierProvider.notifier)
|
if (success) {
|
||||||
.exportJsonToClipboard(excludePrivate: false)
|
ref.read(inAppNotificationControllerProvider).showSuccessToast(
|
||||||
.then((success) {
|
t.general.clipboardExportSuccessMsg,
|
||||||
if (success) {
|
);
|
||||||
ref
|
}
|
||||||
.read(inAppNotificationControllerProvider)
|
}),
|
||||||
.showSuccessToast(
|
child: Text(t.settings.exportAllOptions),
|
||||||
t.general.clipboardExportSuccessMsg,
|
),
|
||||||
);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
child: Text(t.settings.exportAllOptions),
|
|
||||||
),
|
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final shouldImport = await showConfirmationDialog(
|
final shouldImport = await showConfirmationDialog(
|
||||||
@@ -121,9 +110,7 @@ class ConfigOptionsPage extends HookConsumerWidget {
|
|||||||
message: t.settings.importOptionsMsg,
|
message: t.settings.importOptionsMsg,
|
||||||
);
|
);
|
||||||
if (shouldImport) {
|
if (shouldImport) {
|
||||||
await ref
|
await ref.read(configOptionNotifierProvider.notifier).importFromClipboard();
|
||||||
.read(configOptionNotifierProvider.notifier)
|
|
||||||
.importFromClipboard();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Text(t.settings.importOptions),
|
child: Text(t.settings.importOptions),
|
||||||
@@ -131,9 +118,7 @@ class ConfigOptionsPage extends HookConsumerWidget {
|
|||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
child: Text(t.config.resetBtn),
|
child: Text(t.config.resetBtn),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await ref
|
await ref.read(configOptionNotifierProvider.notifier).resetOption();
|
||||||
.read(configOptionNotifierProvider.notifier)
|
|
||||||
.resetOption();
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
@@ -158,15 +143,12 @@ class ConfigOptionsPage extends HookConsumerWidget {
|
|||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
title: Text(experimental(t.config.bypassLan)),
|
title: Text(experimental(t.config.bypassLan)),
|
||||||
value: ref.watch(ConfigOptions.bypassLan),
|
value: ref.watch(ConfigOptions.bypassLan),
|
||||||
onChanged:
|
onChanged: ref.watch(ConfigOptions.bypassLan.notifier).update,
|
||||||
ref.watch(ConfigOptions.bypassLan.notifier).update,
|
|
||||||
),
|
),
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
title: Text(t.config.resolveDestination),
|
title: Text(t.config.resolveDestination),
|
||||||
value: ref.watch(ConfigOptions.resolveDestination),
|
value: ref.watch(ConfigOptions.resolveDestination),
|
||||||
onChanged: ref
|
onChanged: ref.watch(ConfigOptions.resolveDestination.notifier).update,
|
||||||
.watch(ConfigOptions.resolveDestination.notifier)
|
|
||||||
.update,
|
|
||||||
),
|
),
|
||||||
ChoicePreferenceWidget(
|
ChoicePreferenceWidget(
|
||||||
selected: ref.watch(ConfigOptions.ipv6Mode),
|
selected: ref.watch(ConfigOptions.ipv6Mode),
|
||||||
@@ -179,28 +161,24 @@ class ConfigOptionsPage extends HookConsumerWidget {
|
|||||||
SettingsSection(t.config.section.dns),
|
SettingsSection(t.config.section.dns),
|
||||||
ValuePreferenceWidget(
|
ValuePreferenceWidget(
|
||||||
value: ref.watch(ConfigOptions.remoteDnsAddress),
|
value: ref.watch(ConfigOptions.remoteDnsAddress),
|
||||||
preferences:
|
preferences: ref.watch(ConfigOptions.remoteDnsAddress.notifier),
|
||||||
ref.watch(ConfigOptions.remoteDnsAddress.notifier),
|
|
||||||
title: t.config.remoteDnsAddress,
|
title: t.config.remoteDnsAddress,
|
||||||
),
|
),
|
||||||
ChoicePreferenceWidget(
|
ChoicePreferenceWidget(
|
||||||
selected: ref.watch(ConfigOptions.remoteDnsDomainStrategy),
|
selected: ref.watch(ConfigOptions.remoteDnsDomainStrategy),
|
||||||
preferences: ref
|
preferences: ref.watch(ConfigOptions.remoteDnsDomainStrategy.notifier),
|
||||||
.watch(ConfigOptions.remoteDnsDomainStrategy.notifier),
|
|
||||||
choices: DomainStrategy.values,
|
choices: DomainStrategy.values,
|
||||||
title: t.config.remoteDnsDomainStrategy,
|
title: t.config.remoteDnsDomainStrategy,
|
||||||
presentChoice: (value) => value.displayName,
|
presentChoice: (value) => value.displayName,
|
||||||
),
|
),
|
||||||
ValuePreferenceWidget(
|
ValuePreferenceWidget(
|
||||||
value: ref.watch(ConfigOptions.directDnsAddress),
|
value: ref.watch(ConfigOptions.directDnsAddress),
|
||||||
preferences:
|
preferences: ref.watch(ConfigOptions.directDnsAddress.notifier),
|
||||||
ref.watch(ConfigOptions.directDnsAddress.notifier),
|
|
||||||
title: t.config.directDnsAddress,
|
title: t.config.directDnsAddress,
|
||||||
),
|
),
|
||||||
ChoicePreferenceWidget(
|
ChoicePreferenceWidget(
|
||||||
selected: ref.watch(ConfigOptions.directDnsDomainStrategy),
|
selected: ref.watch(ConfigOptions.directDnsDomainStrategy),
|
||||||
preferences: ref
|
preferences: ref.watch(ConfigOptions.directDnsDomainStrategy.notifier),
|
||||||
.watch(ConfigOptions.directDnsDomainStrategy.notifier),
|
|
||||||
choices: DomainStrategy.values,
|
choices: DomainStrategy.values,
|
||||||
title: t.config.directDnsDomainStrategy,
|
title: t.config.directDnsDomainStrategy,
|
||||||
presentChoice: (value) => value.displayName,
|
presentChoice: (value) => value.displayName,
|
||||||
@@ -208,9 +186,7 @@ class ConfigOptionsPage extends HookConsumerWidget {
|
|||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
title: Text(t.config.enableDnsRouting),
|
title: Text(t.config.enableDnsRouting),
|
||||||
value: ref.watch(ConfigOptions.enableDnsRouting),
|
value: ref.watch(ConfigOptions.enableDnsRouting),
|
||||||
onChanged: ref
|
onChanged: ref.watch(ConfigOptions.enableDnsRouting.notifier).update,
|
||||||
.watch(ConfigOptions.enableDnsRouting.notifier)
|
|
||||||
.update,
|
|
||||||
),
|
),
|
||||||
// const SettingsDivider(),
|
// const SettingsDivider(),
|
||||||
// SettingsSection(experimental(t.config.section.mux)),
|
// SettingsSection(experimental(t.config.section.mux)),
|
||||||
@@ -247,13 +223,11 @@ class ConfigOptionsPage extends HookConsumerWidget {
|
|||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
title: Text(t.config.strictRoute),
|
title: Text(t.config.strictRoute),
|
||||||
value: ref.watch(ConfigOptions.strictRoute),
|
value: ref.watch(ConfigOptions.strictRoute),
|
||||||
onChanged:
|
onChanged: ref.watch(ConfigOptions.strictRoute.notifier).update,
|
||||||
ref.watch(ConfigOptions.strictRoute.notifier).update,
|
|
||||||
),
|
),
|
||||||
ChoicePreferenceWidget(
|
ChoicePreferenceWidget(
|
||||||
selected: ref.watch(ConfigOptions.tunImplementation),
|
selected: ref.watch(ConfigOptions.tunImplementation),
|
||||||
preferences:
|
preferences: ref.watch(ConfigOptions.tunImplementation.notifier),
|
||||||
ref.watch(ConfigOptions.tunImplementation.notifier),
|
|
||||||
choices: TunImplementation.values,
|
choices: TunImplementation.values,
|
||||||
title: t.config.tunImplementation,
|
title: t.config.tunImplementation,
|
||||||
presentChoice: (value) => value.name,
|
presentChoice: (value) => value.name,
|
||||||
@@ -287,25 +261,21 @@ class ConfigOptionsPage extends HookConsumerWidget {
|
|||||||
experimental(t.config.allowConnectionFromLan),
|
experimental(t.config.allowConnectionFromLan),
|
||||||
),
|
),
|
||||||
value: ref.watch(ConfigOptions.allowConnectionFromLan),
|
value: ref.watch(ConfigOptions.allowConnectionFromLan),
|
||||||
onChanged: ref
|
onChanged: ref.read(ConfigOptions.allowConnectionFromLan.notifier).update,
|
||||||
.read(ConfigOptions.allowConnectionFromLan.notifier)
|
|
||||||
.update,
|
|
||||||
),
|
),
|
||||||
const SettingsDivider(),
|
const SettingsDivider(),
|
||||||
SettingsSection(
|
SettingsSection(
|
||||||
experimental(t.config.section.tlsTricks),
|
experimental(t.config.section.tlsTricks),
|
||||||
|
key: ConfigOptionSection._fragmentKey,
|
||||||
),
|
),
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
title: Text(t.config.enableTlsFragment),
|
title: Text(t.config.enableTlsFragment),
|
||||||
value: ref.watch(ConfigOptions.enableTlsFragment),
|
value: ref.watch(ConfigOptions.enableTlsFragment),
|
||||||
onChanged: ref
|
onChanged: ref.watch(ConfigOptions.enableTlsFragment.notifier).update,
|
||||||
.watch(ConfigOptions.enableTlsFragment.notifier)
|
|
||||||
.update,
|
|
||||||
),
|
),
|
||||||
ValuePreferenceWidget(
|
ValuePreferenceWidget(
|
||||||
value: ref.watch(ConfigOptions.tlsFragmentSize),
|
value: ref.watch(ConfigOptions.tlsFragmentSize),
|
||||||
preferences:
|
preferences: ref.watch(ConfigOptions.tlsFragmentSize.notifier),
|
||||||
ref.watch(ConfigOptions.tlsFragmentSize.notifier),
|
|
||||||
title: t.config.tlsFragmentSize,
|
title: t.config.tlsFragmentSize,
|
||||||
inputToValue: OptionalRange.tryParse,
|
inputToValue: OptionalRange.tryParse,
|
||||||
presentValue: (value) => value.present(t),
|
presentValue: (value) => value.present(t),
|
||||||
@@ -313,8 +283,7 @@ class ConfigOptionsPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
ValuePreferenceWidget(
|
ValuePreferenceWidget(
|
||||||
value: ref.watch(ConfigOptions.tlsFragmentSleep),
|
value: ref.watch(ConfigOptions.tlsFragmentSleep),
|
||||||
preferences:
|
preferences: ref.watch(ConfigOptions.tlsFragmentSleep.notifier),
|
||||||
ref.watch(ConfigOptions.tlsFragmentSleep.notifier),
|
|
||||||
title: t.config.tlsFragmentSleep,
|
title: t.config.tlsFragmentSleep,
|
||||||
inputToValue: OptionalRange.tryParse,
|
inputToValue: OptionalRange.tryParse,
|
||||||
presentValue: (value) => value.present(t),
|
presentValue: (value) => value.present(t),
|
||||||
@@ -323,21 +292,16 @@ class ConfigOptionsPage extends HookConsumerWidget {
|
|||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
title: Text(t.config.enableTlsMixedSniCase),
|
title: Text(t.config.enableTlsMixedSniCase),
|
||||||
value: ref.watch(ConfigOptions.enableTlsMixedSniCase),
|
value: ref.watch(ConfigOptions.enableTlsMixedSniCase),
|
||||||
onChanged: ref
|
onChanged: ref.watch(ConfigOptions.enableTlsMixedSniCase.notifier).update,
|
||||||
.watch(ConfigOptions.enableTlsMixedSniCase.notifier)
|
|
||||||
.update,
|
|
||||||
),
|
),
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
title: Text(t.config.enableTlsPadding),
|
title: Text(t.config.enableTlsPadding),
|
||||||
value: ref.watch(ConfigOptions.enableTlsPadding),
|
value: ref.watch(ConfigOptions.enableTlsPadding),
|
||||||
onChanged: ref
|
onChanged: ref.watch(ConfigOptions.enableTlsPadding.notifier).update,
|
||||||
.watch(ConfigOptions.enableTlsPadding.notifier)
|
|
||||||
.update,
|
|
||||||
),
|
),
|
||||||
ValuePreferenceWidget(
|
ValuePreferenceWidget(
|
||||||
value: ref.watch(ConfigOptions.tlsPaddingSize),
|
value: ref.watch(ConfigOptions.tlsPaddingSize),
|
||||||
preferences:
|
preferences: ref.watch(ConfigOptions.tlsPaddingSize.notifier),
|
||||||
ref.watch(ConfigOptions.tlsPaddingSize.notifier),
|
|
||||||
title: t.config.tlsPaddingSize,
|
title: t.config.tlsPaddingSize,
|
||||||
inputToValue: OptionalRange.tryParse,
|
inputToValue: OptionalRange.tryParse,
|
||||||
presentValue: (value) => value.format(),
|
presentValue: (value) => value.format(),
|
||||||
@@ -350,38 +314,26 @@ class ConfigOptionsPage extends HookConsumerWidget {
|
|||||||
SettingsSection(t.config.section.misc),
|
SettingsSection(t.config.section.misc),
|
||||||
ValuePreferenceWidget(
|
ValuePreferenceWidget(
|
||||||
value: ref.watch(ConfigOptions.connectionTestUrl),
|
value: ref.watch(ConfigOptions.connectionTestUrl),
|
||||||
preferences:
|
preferences: ref.watch(ConfigOptions.connectionTestUrl.notifier),
|
||||||
ref.watch(ConfigOptions.connectionTestUrl.notifier),
|
|
||||||
title: t.config.connectionTestUrl,
|
title: t.config.connectionTestUrl,
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(t.config.urlTestInterval),
|
title: Text(t.config.urlTestInterval),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
ref
|
ref.watch(ConfigOptions.urlTestInterval).toApproximateTime(isRelativeToNow: false),
|
||||||
.watch(ConfigOptions.urlTestInterval)
|
|
||||||
.toApproximateTime(isRelativeToNow: false),
|
|
||||||
),
|
),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final urlTestInterval = await SettingsSliderDialog(
|
final urlTestInterval = await SettingsSliderDialog(
|
||||||
title: t.config.urlTestInterval,
|
title: t.config.urlTestInterval,
|
||||||
initialValue: ref
|
initialValue: ref.watch(ConfigOptions.urlTestInterval).inMinutes.coerceIn(0, 60).toDouble(),
|
||||||
.watch(ConfigOptions.urlTestInterval)
|
onReset: ref.read(ConfigOptions.urlTestInterval.notifier).reset,
|
||||||
.inMinutes
|
|
||||||
.coerceIn(0, 60)
|
|
||||||
.toDouble(),
|
|
||||||
onReset: ref
|
|
||||||
.read(ConfigOptions.urlTestInterval.notifier)
|
|
||||||
.reset,
|
|
||||||
min: 1,
|
min: 1,
|
||||||
max: 60,
|
max: 60,
|
||||||
divisions: 60,
|
divisions: 60,
|
||||||
labelGen: (value) => Duration(minutes: value.toInt())
|
labelGen: (value) => Duration(minutes: value.toInt()).toApproximateTime(isRelativeToNow: false),
|
||||||
.toApproximateTime(isRelativeToNow: false),
|
|
||||||
).show(context);
|
).show(context);
|
||||||
if (urlTestInterval == null) return;
|
if (urlTestInterval == null) return;
|
||||||
await ref
|
await ref.read(ConfigOptions.urlTestInterval.notifier).update(Duration(minutes: urlTestInterval.toInt()));
|
||||||
.read(ConfigOptions.urlTestInterval.notifier)
|
|
||||||
.update(Duration(minutes: urlTestInterval.toInt()));
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ValuePreferenceWidget(
|
ValuePreferenceWidget(
|
||||||
|
|||||||
@@ -63,17 +63,15 @@ class WarpOptionsTiles extends HookConsumerWidget {
|
|||||||
AsyncLoading() => const LinearProgressIndicator(),
|
AsyncLoading() => const LinearProgressIndicator(),
|
||||||
AsyncError() => Text(
|
AsyncError() => Text(
|
||||||
t.config.missingWarpConfig,
|
t.config.missingWarpConfig,
|
||||||
style:
|
style: TextStyle(color: Theme.of(context).colorScheme.error),
|
||||||
TextStyle(color: Theme.of(context).colorScheme.error),
|
|
||||||
),
|
),
|
||||||
_ => null,
|
_ => null,
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
enabled: canChangeOptions,
|
enabled: canChangeOptions,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await ref
|
await ref.read(warpOptionNotifierProvider.notifier).generateWarpConfig();
|
||||||
.read(warpOptionNotifierProvider.notifier)
|
await ref.read(warpOptionNotifierProvider.notifier).generateWarp2Config();
|
||||||
.generateWarpConfig();
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ChoicePreferenceWidget(
|
ChoicePreferenceWidget(
|
||||||
@@ -111,8 +109,7 @@ class WarpOptionsTiles extends HookConsumerWidget {
|
|||||||
preferences: ref.watch(ConfigOptions.warpNoise.notifier),
|
preferences: ref.watch(ConfigOptions.warpNoise.notifier),
|
||||||
enabled: canChangeOptions,
|
enabled: canChangeOptions,
|
||||||
title: t.config.warpNoise,
|
title: t.config.warpNoise,
|
||||||
inputToValue: (input) =>
|
inputToValue: (input) => OptionalRange.tryParse(input, allowEmpty: true),
|
||||||
OptionalRange.tryParse(input, allowEmpty: true),
|
|
||||||
presentValue: (value) => value.present(t),
|
presentValue: (value) => value.present(t),
|
||||||
formatInputValue: (value) => value.format(),
|
formatInputValue: (value) => value.format(),
|
||||||
),
|
),
|
||||||
@@ -121,8 +118,7 @@ class WarpOptionsTiles extends HookConsumerWidget {
|
|||||||
preferences: ref.watch(ConfigOptions.warpNoiseDelay.notifier),
|
preferences: ref.watch(ConfigOptions.warpNoiseDelay.notifier),
|
||||||
enabled: canChangeOptions,
|
enabled: canChangeOptions,
|
||||||
title: t.config.warpNoiseDelay,
|
title: t.config.warpNoiseDelay,
|
||||||
inputToValue: (input) =>
|
inputToValue: (input) => OptionalRange.tryParse(input, allowEmpty: true),
|
||||||
OptionalRange.tryParse(input, allowEmpty: true),
|
|
||||||
presentValue: (value) => value.present(t),
|
presentValue: (value) => value.present(t),
|
||||||
formatInputValue: (value) => value.format(),
|
formatInputValue: (value) => value.format(),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ class QuickSettingsModal extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final t = ref.watch(translationsProvider);
|
final t = ref.watch(translationsProvider);
|
||||||
|
|
||||||
final warpPrefaceCompleted =
|
final warpPrefaceCompleted = ref.watch(warpOptionNotifierProvider).consentGiven;
|
||||||
ref.watch(warpOptionNotifierProvider).consentGiven;
|
|
||||||
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -33,37 +32,41 @@ class QuickSettingsModal extends HookConsumerWidget {
|
|||||||
e.presentShort(t),
|
e.presentShort(t),
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
tooltip:
|
tooltip: e.isExperimental ? t.settings.experimental : null,
|
||||||
e.isExperimental ? t.settings.experimental : null,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
selected: {ref.watch(ConfigOptions.serviceMode)},
|
selected: {ref.watch(ConfigOptions.serviceMode)},
|
||||||
onSelectionChanged: (newSet) => ref
|
onSelectionChanged: (newSet) => ref.read(ConfigOptions.serviceMode.notifier).update(newSet.first),
|
||||||
.read(ConfigOptions.serviceMode.notifier)
|
|
||||||
.update(newSet.first),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
if (warpPrefaceCompleted)
|
if (warpPrefaceCompleted)
|
||||||
SwitchListTile(
|
GestureDetector(
|
||||||
value: ref.watch(ConfigOptions.enableWarp),
|
onLongPress: () {
|
||||||
onChanged: ref.watch(ConfigOptions.enableWarp.notifier).update,
|
ConfigOptionsRoute(section: ConfigOptionSection.warp.name).go(context);
|
||||||
title: Text(t.config.enableWarp),
|
},
|
||||||
|
child: SwitchListTile(
|
||||||
|
value: ref.watch(ConfigOptions.enableWarp),
|
||||||
|
onChanged: ref.watch(ConfigOptions.enableWarp.notifier).update,
|
||||||
|
title: Text(t.config.enableWarp),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(t.config.setupWarp),
|
title: Text(t.config.setupWarp),
|
||||||
trailing: const Icon(FluentIcons.chevron_right_24_regular),
|
trailing: const Icon(FluentIcons.chevron_right_24_regular),
|
||||||
onTap: () =>
|
onTap: () => ConfigOptionsRoute(section: ConfigOptionSection.warp.name).go(context),
|
||||||
ConfigOptionsRoute(section: ConfigOptionSection.warp.name)
|
),
|
||||||
.go(context),
|
GestureDetector(
|
||||||
|
onLongPress: () {
|
||||||
|
ConfigOptionsRoute(section: ConfigOptionSection.fragment.name).go(context);
|
||||||
|
},
|
||||||
|
child: SwitchListTile(
|
||||||
|
value: ref.watch(ConfigOptions.enableTlsFragment),
|
||||||
|
onChanged: ref.watch(ConfigOptions.enableTlsFragment.notifier).update,
|
||||||
|
title: Text(t.config.enableTlsFragment),
|
||||||
),
|
),
|
||||||
SwitchListTile(
|
|
||||||
value: ref.watch(ConfigOptions.enableTlsFragment),
|
|
||||||
onChanged:
|
|
||||||
ref.watch(ConfigOptions.enableTlsFragment.notifier).update,
|
|
||||||
title: Text(t.config.enableTlsFragment),
|
|
||||||
),
|
),
|
||||||
// SwitchListTile(
|
// SwitchListTile(
|
||||||
// value: ref.watch(ConfigOptions.enableMux),
|
// value: ref.watch(ConfigOptions.enableMux),
|
||||||
|
|||||||
@@ -5,11 +5,16 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hiddify/core/localization/translations.dart';
|
import 'package:hiddify/core/localization/translations.dart';
|
||||||
|
import 'package:hiddify/core/preferences/preferences_provider.dart';
|
||||||
import 'package:hiddify/core/router/router.dart';
|
import 'package:hiddify/core/router/router.dart';
|
||||||
import 'package:hiddify/features/common/qr_code_scanner_screen.dart';
|
import 'package:hiddify/features/common/qr_code_scanner_screen.dart';
|
||||||
|
import 'package:hiddify/features/config_option/notifier/warp_option_notifier.dart';
|
||||||
|
import 'package:hiddify/features/config_option/overview/config_options_page.dart';
|
||||||
|
import 'package:hiddify/features/config_option/overview/warp_options_widgets.dart';
|
||||||
import 'package:hiddify/features/profile/notifier/profile_notifier.dart';
|
import 'package:hiddify/features/profile/notifier/profile_notifier.dart';
|
||||||
import 'package:hiddify/utils/utils.dart';
|
import 'package:hiddify/utils/utils.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
class AddProfileModal extends HookConsumerWidget {
|
class AddProfileModal extends HookConsumerWidget {
|
||||||
const AddProfileModal({
|
const AddProfileModal({
|
||||||
@@ -17,7 +22,7 @@ class AddProfileModal extends HookConsumerWidget {
|
|||||||
this.url,
|
this.url,
|
||||||
this.scrollController,
|
this.scrollController,
|
||||||
});
|
});
|
||||||
|
static const warpConsentGiven = "warp_consent_given";
|
||||||
final String? url;
|
final String? url;
|
||||||
final ScrollController? scrollController;
|
final ScrollController? scrollController;
|
||||||
|
|
||||||
@@ -58,8 +63,7 @@ class AddProfileModal extends HookConsumerWidget {
|
|||||||
child: LayoutBuilder(
|
child: LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
// temporary solution, aspect ratio widget relies on height and in a row there no height!
|
// temporary solution, aspect ratio widget relies on height and in a row there no height!
|
||||||
final buttonWidth =
|
final buttonWidth = constraints.maxWidth / 2 - (buttonsPadding + (buttonsGap / 2));
|
||||||
constraints.maxWidth / 2 - (buttonsPadding + (buttonsGap / 2));
|
|
||||||
|
|
||||||
return AnimatedCrossFade(
|
return AnimatedCrossFade(
|
||||||
firstChild: SizedBox(
|
firstChild: SizedBox(
|
||||||
@@ -93,8 +97,7 @@ class AddProfileModal extends HookConsumerWidget {
|
|||||||
secondChild: Column(
|
secondChild: Column(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding:
|
padding: const EdgeInsets.symmetric(horizontal: buttonsPadding),
|
||||||
const EdgeInsets.symmetric(horizontal: buttonsPadding),
|
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
_Button(
|
_Button(
|
||||||
@@ -103,13 +106,9 @@ class AddProfileModal extends HookConsumerWidget {
|
|||||||
icon: FluentIcons.clipboard_paste_24_regular,
|
icon: FluentIcons.clipboard_paste_24_regular,
|
||||||
size: buttonWidth,
|
size: buttonWidth,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final captureResult =
|
final captureResult = await Clipboard.getData(Clipboard.kTextPlain).then((value) => value?.text ?? '');
|
||||||
await Clipboard.getData(Clipboard.kTextPlain)
|
|
||||||
.then((value) => value?.text ?? '');
|
|
||||||
if (addProfileState.isLoading) return;
|
if (addProfileState.isLoading) return;
|
||||||
ref
|
ref.read(addProfileProvider.notifier).add(captureResult);
|
||||||
.read(addProfileProvider.notifier)
|
|
||||||
.add(captureResult);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const Gap(buttonsGap),
|
const Gap(buttonsGap),
|
||||||
@@ -120,8 +119,7 @@ class AddProfileModal extends HookConsumerWidget {
|
|||||||
icon: FluentIcons.qr_code_24_regular,
|
icon: FluentIcons.qr_code_24_regular,
|
||||||
size: buttonWidth,
|
size: buttonWidth,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final cr =
|
final cr = await QRCodeScannerScreen().open(context);
|
||||||
await QRCodeScannerScreen().open(context);
|
|
||||||
|
|
||||||
if (cr == null) return;
|
if (cr == null) return;
|
||||||
if (addProfileState.isLoading) return;
|
if (addProfileState.isLoading) return;
|
||||||
@@ -142,56 +140,118 @@ class AddProfileModal extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (!PlatformUtils.isDesktop)
|
Padding(
|
||||||
Padding(
|
padding: const EdgeInsets.symmetric(
|
||||||
padding: const EdgeInsets.symmetric(
|
horizontal: buttonsPadding,
|
||||||
horizontal: buttonsPadding,
|
vertical: 16,
|
||||||
vertical: 16,
|
),
|
||||||
),
|
child: Column(
|
||||||
child: Semantics(
|
children: [
|
||||||
button: true,
|
Semantics(
|
||||||
child: SizedBox(
|
button: true,
|
||||||
height: 36,
|
child: SizedBox(
|
||||||
child: Material(
|
height: 36,
|
||||||
key: const ValueKey("add_manually_button"),
|
child: Material(
|
||||||
elevation: 8,
|
key: const ValueKey("add_warp_button"),
|
||||||
color: theme.colorScheme.surface,
|
elevation: 8,
|
||||||
surfaceTintColor: theme.colorScheme.surfaceTint,
|
color: theme.colorScheme.surface,
|
||||||
shadowColor: Colors.transparent,
|
surfaceTintColor: theme.colorScheme.surfaceTint,
|
||||||
borderRadius: BorderRadius.circular(8),
|
shadowColor: Colors.transparent,
|
||||||
clipBehavior: Clip.antiAlias,
|
borderRadius: BorderRadius.circular(8),
|
||||||
child: InkWell(
|
clipBehavior: Clip.antiAlias,
|
||||||
onTap: () async {
|
child: InkWell(
|
||||||
context.pop();
|
onTap: () async {
|
||||||
await const NewProfileRoute().push(context);
|
Future.microtask(() async {
|
||||||
},
|
context.pop();
|
||||||
child: Row(
|
final _prefs = ref.read(sharedPreferencesProvider).requireValue;
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
final consent = _prefs.getBool(warpConsentGiven) ?? false;
|
||||||
children: [
|
if (!consent) {
|
||||||
Icon(
|
final agreed = await showDialog<bool>(
|
||||||
FluentIcons.add_24_regular,
|
context: context,
|
||||||
color: theme.colorScheme.primary,
|
builder: (context) => const WarpLicenseAgreementModal(),
|
||||||
),
|
);
|
||||||
const Gap(8),
|
|
||||||
Text(
|
if (agreed ?? false) {
|
||||||
t.profile.add.manually,
|
await ref.read(warpOptionNotifierProvider.notifier).agree();
|
||||||
style: theme.textTheme.labelLarge?.copyWith(
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final accountId = _prefs.getString("warp2-account-id");
|
||||||
|
final accessToken = _prefs.getString("warp2-access-token");
|
||||||
|
final hasWarp2Config = accountId != null && accessToken != null;
|
||||||
|
|
||||||
|
if (!hasWarp2Config) {
|
||||||
|
await ref.read(warpOptionNotifierProvider.notifier).generateWarp2Config();
|
||||||
|
}
|
||||||
|
await ref.read(addProfileProvider.notifier).add("#profile-title: Hiddify WARP\nwarp://p2@auto#Remote&&detour=warp://p1@auto#Local"); //
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
FluentIcons.add_24_regular,
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(width: 8),
|
||||||
],
|
Text(
|
||||||
|
t.profile.add.addWarp,
|
||||||
|
style: theme.textTheme.labelLarge?.copyWith(
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
if (!PlatformUtils.isDesktop) const SizedBox(height: 16), // Spacing between the buttons
|
||||||
|
if (!PlatformUtils.isDesktop)
|
||||||
|
Semantics(
|
||||||
|
button: true,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 36,
|
||||||
|
child: Material(
|
||||||
|
key: const ValueKey("add_manually_button"),
|
||||||
|
elevation: 8,
|
||||||
|
color: theme.colorScheme.surface,
|
||||||
|
surfaceTintColor: theme.colorScheme.surfaceTint,
|
||||||
|
shadowColor: Colors.transparent,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () async {
|
||||||
|
context.pop();
|
||||||
|
await const NewProfileRoute().push(context);
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
FluentIcons.add_24_regular,
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
t.profile.add.manually,
|
||||||
|
style: theme.textTheme.labelLarge?.copyWith(
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
const Gap(24),
|
const Gap(24),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
crossFadeState: addProfileState.isLoading
|
crossFadeState: addProfileState.isLoading ? CrossFadeState.showFirst : CrossFadeState.showSecond,
|
||||||
? CrossFadeState.showFirst
|
|
||||||
: CrossFadeState.showSecond,
|
|
||||||
duration: const Duration(milliseconds: 250),
|
duration: const Duration(milliseconds: 250),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -47,8 +47,7 @@ class AddProfile extends _$AddProfile with AppLogger {
|
|||||||
return const AsyncData(null);
|
return const AsyncData(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
ProfileRepository get _profilesRepo =>
|
ProfileRepository get _profilesRepo => ref.read(profileRepositoryProvider).requireValue;
|
||||||
ref.read(profileRepositoryProvider).requireValue;
|
|
||||||
CancelToken? _cancelToken;
|
CancelToken? _cancelToken;
|
||||||
|
|
||||||
Future<void> add(String rawInput) async {
|
Future<void> add(String rawInput) async {
|
||||||
@@ -57,8 +56,7 @@ class AddProfile extends _$AddProfile with AppLogger {
|
|||||||
state = await AsyncValue.guard(
|
state = await AsyncValue.guard(
|
||||||
() async {
|
() async {
|
||||||
final activeProfile = await ref.read(activeProfileProvider.future);
|
final activeProfile = await ref.read(activeProfileProvider.future);
|
||||||
final markAsActive =
|
final markAsActive = activeProfile == null || ref.read(Preferences.markNewProfileActive);
|
||||||
activeProfile == null || ref.read(Preferences.markNewProfileActive);
|
|
||||||
final TaskEither<ProfileFailure, Unit> task;
|
final TaskEither<ProfileFailure, Unit> task;
|
||||||
if (LinkParser.parse(rawInput) case (final link)?) {
|
if (LinkParser.parse(rawInput) case (final link)?) {
|
||||||
loggy.debug("adding profile, url: [${link.url}]");
|
loggy.debug("adding profile, url: [${link.url}]");
|
||||||
@@ -70,7 +68,11 @@ class AddProfile extends _$AddProfile with AppLogger {
|
|||||||
} else if (LinkParser.protocol(rawInput) case (final parsed)?) {
|
} else if (LinkParser.protocol(rawInput) case (final parsed)?) {
|
||||||
loggy.debug("adding profile, content");
|
loggy.debug("adding profile, content");
|
||||||
var name = parsed.name;
|
var name = parsed.name;
|
||||||
|
var oldItem = await _profilesRepo.getByName(name);
|
||||||
|
if (name == "Hiddify WARP" && oldItem != null) {
|
||||||
|
_profilesRepo.setAsActive(oldItem.id).run();
|
||||||
|
return unit;
|
||||||
|
}
|
||||||
while (await _profilesRepo.getByName(name) != null) {
|
while (await _profilesRepo.getByName(name) != null) {
|
||||||
name += '${randomInt(0, 9).run()}';
|
name += '${randomInt(0, 9).run()}';
|
||||||
}
|
}
|
||||||
@@ -122,8 +124,7 @@ class UpdateProfile extends _$UpdateProfile with AppLogger {
|
|||||||
return const AsyncData(null);
|
return const AsyncData(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
ProfileRepository get _profilesRepo =>
|
ProfileRepository get _profilesRepo => ref.read(profileRepositoryProvider).requireValue;
|
||||||
ref.read(profileRepositoryProvider).requireValue;
|
|
||||||
|
|
||||||
Future<void> updateProfile(RemoteProfileEntity profile) async {
|
Future<void> updateProfile(RemoteProfileEntity profile) async {
|
||||||
if (state.isLoading) return;
|
if (state.isLoading) return;
|
||||||
@@ -143,9 +144,7 @@ class UpdateProfile extends _$UpdateProfile with AppLogger {
|
|||||||
|
|
||||||
await ref.read(activeProfileProvider.future).then((active) async {
|
await ref.read(activeProfileProvider.future).then((active) async {
|
||||||
if (active != null && active.id == profile.id) {
|
if (active != null && active.id == profile.id) {
|
||||||
await ref
|
await ref.read(connectionNotifierProvider.notifier).reconnect(profile);
|
||||||
.read(connectionNotifierProvider.notifier)
|
|
||||||
.reconnect(profile);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return unit;
|
return unit;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:combine/combine.dart';
|
|
||||||
import 'package:dartx/dartx.dart';
|
import 'package:dartx/dartx.dart';
|
||||||
|
import 'package:fpdart/fpdart.dart';
|
||||||
import 'package:hiddify/core/haptic/haptic_service.dart';
|
import 'package:hiddify/core/haptic/haptic_service.dart';
|
||||||
import 'package:hiddify/core/localization/translations.dart';
|
import 'package:hiddify/core/localization/translations.dart';
|
||||||
import 'package:hiddify/core/preferences/preferences_provider.dart';
|
import 'package:hiddify/core/preferences/preferences_provider.dart';
|
||||||
@@ -85,46 +85,41 @@ class ProxiesOverviewNotifier extends _$ProxiesOverviewNotifier with AppLogger {
|
|||||||
List<ProxyGroupEntity> proxies,
|
List<ProxyGroupEntity> proxies,
|
||||||
ProxiesSort sortBy,
|
ProxiesSort sortBy,
|
||||||
) async {
|
) async {
|
||||||
return CombineWorker().execute(
|
final groupWithSelected = {
|
||||||
() {
|
for (final o in proxies) o.tag: o.selected,
|
||||||
final groupWithSelected = {
|
};
|
||||||
for (final o in proxies) o.tag: o.selected,
|
final sortedProxies = <ProxyGroupEntity>[];
|
||||||
};
|
for (final group in proxies) {
|
||||||
final sortedProxies = <ProxyGroupEntity>[];
|
final sortedItems = switch (sortBy) {
|
||||||
for (final group in proxies) {
|
ProxiesSort.name => group.items.sortedWith((a, b) {
|
||||||
final sortedItems = switch (sortBy) {
|
if (a.type.isGroup && !b.type.isGroup) return -1;
|
||||||
ProxiesSort.name => group.items.sortedWith((a, b) {
|
if (!a.type.isGroup && b.type.isGroup) return 1;
|
||||||
if (a.type.isGroup && !b.type.isGroup) return -1;
|
return a.tag.compareTo(b.tag);
|
||||||
if (!a.type.isGroup && b.type.isGroup) return 1;
|
}),
|
||||||
return a.tag.compareTo(b.tag);
|
ProxiesSort.delay => group.items.sortedWith((a, b) {
|
||||||
}),
|
if (a.type.isGroup && !b.type.isGroup) return -1;
|
||||||
ProxiesSort.delay => group.items.sortedWith((a, b) {
|
if (!a.type.isGroup && b.type.isGroup) return 1;
|
||||||
if (a.type.isGroup && !b.type.isGroup) return -1;
|
|
||||||
if (!a.type.isGroup && b.type.isGroup) return 1;
|
|
||||||
|
|
||||||
final ai = a.urlTestDelay;
|
final ai = a.urlTestDelay;
|
||||||
final bi = b.urlTestDelay;
|
final bi = b.urlTestDelay;
|
||||||
if (ai == 0 && bi == 0) return -1;
|
if (ai == 0 && bi == 0) return -1;
|
||||||
if (ai == 0 && bi > 0) return 1;
|
if (ai == 0 && bi > 0) return 1;
|
||||||
if (ai > 0 && bi == 0) return -1;
|
if (ai > 0 && bi == 0) return -1;
|
||||||
return ai.compareTo(bi);
|
return ai.compareTo(bi);
|
||||||
}),
|
}),
|
||||||
ProxiesSort.unsorted => group.items,
|
ProxiesSort.unsorted => group.items,
|
||||||
};
|
};
|
||||||
final items = <ProxyItemEntity>[];
|
final items = <ProxyItemEntity>[];
|
||||||
for (final item in sortedItems) {
|
for (final item in sortedItems) {
|
||||||
if (groupWithSelected.keys.contains(item.tag)) {
|
if (groupWithSelected.keys.contains(item.tag)) {
|
||||||
items
|
items.add(item.copyWith(selectedTag: groupWithSelected[item.tag]));
|
||||||
.add(item.copyWith(selectedTag: groupWithSelected[item.tag]));
|
} else {
|
||||||
} else {
|
items.add(item);
|
||||||
items.add(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sortedProxies.add(group.copyWith(items: items));
|
|
||||||
}
|
}
|
||||||
return sortedProxies;
|
}
|
||||||
},
|
sortedProxies.add(group.copyWith(items: items));
|
||||||
);
|
}
|
||||||
|
return sortedProxies;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> changeProxy(String groupTag, String outboundTag) async {
|
Future<void> changeProxy(String groupTag, String outboundTag) async {
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ class SingboxConfigOption with _$SingboxConfigOption {
|
|||||||
required SingboxMuxOption mux,
|
required SingboxMuxOption mux,
|
||||||
required SingboxTlsTricks tlsTricks,
|
required SingboxTlsTricks tlsTricks,
|
||||||
required SingboxWarpOption warp,
|
required SingboxWarpOption warp,
|
||||||
|
required SingboxWarpOption warp2,
|
||||||
}) = _SingboxConfigOption;
|
}) = _SingboxConfigOption;
|
||||||
|
|
||||||
String format() {
|
String format() {
|
||||||
@@ -55,8 +56,7 @@ class SingboxConfigOption with _$SingboxConfigOption {
|
|||||||
return encoder.convert(toJson());
|
return encoder.convert(toJson());
|
||||||
}
|
}
|
||||||
|
|
||||||
factory SingboxConfigOption.fromJson(Map<String, dynamic> json) =>
|
factory SingboxConfigOption.fromJson(Map<String, dynamic> json) => _$SingboxConfigOptionFromJson(json);
|
||||||
_$SingboxConfigOptionFromJson(json);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
@@ -75,8 +75,7 @@ class SingboxWarpOption with _$SingboxWarpOption {
|
|||||||
@OptionalRangeJsonConverter() required OptionalRange noiseDelay,
|
@OptionalRangeJsonConverter() required OptionalRange noiseDelay,
|
||||||
}) = _SingboxWarpOption;
|
}) = _SingboxWarpOption;
|
||||||
|
|
||||||
factory SingboxWarpOption.fromJson(Map<String, dynamic> json) =>
|
factory SingboxWarpOption.fromJson(Map<String, dynamic> json) => _$SingboxWarpOptionFromJson(json);
|
||||||
_$SingboxWarpOptionFromJson(json);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
@@ -89,8 +88,7 @@ class SingboxMuxOption with _$SingboxMuxOption {
|
|||||||
required MuxProtocol protocol,
|
required MuxProtocol protocol,
|
||||||
}) = _SingboxMuxOption;
|
}) = _SingboxMuxOption;
|
||||||
|
|
||||||
factory SingboxMuxOption.fromJson(Map<String, dynamic> json) =>
|
factory SingboxMuxOption.fromJson(Map<String, dynamic> json) => _$SingboxMuxOptionFromJson(json);
|
||||||
_$SingboxMuxOptionFromJson(json);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
@@ -105,6 +103,5 @@ class SingboxTlsTricks with _$SingboxTlsTricks {
|
|||||||
@OptionalRangeJsonConverter() required OptionalRange paddingSize,
|
@OptionalRangeJsonConverter() required OptionalRange paddingSize,
|
||||||
}) = _SingboxTlsTricks;
|
}) = _SingboxTlsTricks;
|
||||||
|
|
||||||
factory SingboxTlsTricks.fromJson(Map<String, dynamic> json) =>
|
factory SingboxTlsTricks.fromJson(Map<String, dynamic> json) => _$SingboxTlsTricksFromJson(json);
|
||||||
_$SingboxTlsTricksFromJson(json);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import 'dart:ffi';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:isolate';
|
import 'dart:isolate';
|
||||||
|
|
||||||
import 'package:combine/combine.dart';
|
|
||||||
import 'package:ffi/ffi.dart';
|
import 'package:ffi/ffi.dart';
|
||||||
import 'package:fpdart/fpdart.dart';
|
import 'package:fpdart/fpdart.dart';
|
||||||
import 'package:hiddify/core/model/directories.dart';
|
import 'package:hiddify/core/model/directories.dart';
|
||||||
@@ -52,10 +51,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
|||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
loggy.debug("initializing");
|
loggy.debug("initializing");
|
||||||
_statusReceiver = ReceivePort('service status receiver');
|
_statusReceiver = ReceivePort('service status receiver');
|
||||||
final source = _statusReceiver
|
final source = _statusReceiver.asBroadcastStream().map((event) => jsonDecode(event as String)).map(SingboxStatus.fromEvent);
|
||||||
.asBroadcastStream()
|
|
||||||
.map((event) => jsonDecode(event as String))
|
|
||||||
.map(SingboxStatus.fromEvent);
|
|
||||||
_status = ValueConnectableStream.seeded(
|
_status = ValueConnectableStream.seeded(
|
||||||
source,
|
source,
|
||||||
const SingboxStopped(),
|
const SingboxStopped(),
|
||||||
@@ -69,8 +65,8 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
|||||||
) {
|
) {
|
||||||
final port = _statusReceiver.sendPort.nativePort;
|
final port = _statusReceiver.sendPort.nativePort;
|
||||||
return TaskEither(
|
return TaskEither(
|
||||||
() => CombineWorker().execute(
|
() => Future.microtask(
|
||||||
() {
|
() async {
|
||||||
_box.setupOnce(NativeApi.initializeApiDLData);
|
_box.setupOnce(NativeApi.initializeApiDLData);
|
||||||
final err = _box
|
final err = _box
|
||||||
.setup(
|
.setup(
|
||||||
@@ -98,8 +94,8 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
|||||||
bool debug,
|
bool debug,
|
||||||
) {
|
) {
|
||||||
return TaskEither(
|
return TaskEither(
|
||||||
() => CombineWorker().execute(
|
() => Future.microtask(
|
||||||
() {
|
() async {
|
||||||
final err = _box
|
final err = _box
|
||||||
.parse(
|
.parse(
|
||||||
path.toNativeUtf8().cast(),
|
path.toNativeUtf8().cast(),
|
||||||
@@ -120,13 +116,10 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
|||||||
@override
|
@override
|
||||||
TaskEither<String, Unit> changeOptions(SingboxConfigOption options) {
|
TaskEither<String, Unit> changeOptions(SingboxConfigOption options) {
|
||||||
return TaskEither(
|
return TaskEither(
|
||||||
() => CombineWorker().execute(
|
() => Future.microtask(
|
||||||
() {
|
() async {
|
||||||
final json = jsonEncode(options.toJson());
|
final json = jsonEncode(options.toJson());
|
||||||
final err = _box
|
final err = _box.changeConfigOptions(json.toNativeUtf8().cast()).cast<Utf8>().toDartString();
|
||||||
.changeConfigOptions(json.toNativeUtf8().cast())
|
|
||||||
.cast<Utf8>()
|
|
||||||
.toDartString();
|
|
||||||
if (err.isNotEmpty) {
|
if (err.isNotEmpty) {
|
||||||
return left(err);
|
return left(err);
|
||||||
}
|
}
|
||||||
@@ -141,8 +134,8 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
|||||||
String path,
|
String path,
|
||||||
) {
|
) {
|
||||||
return TaskEither(
|
return TaskEither(
|
||||||
() => CombineWorker().execute(
|
() => Future.microtask(
|
||||||
() {
|
() async {
|
||||||
final response = _box
|
final response = _box
|
||||||
.generateConfig(
|
.generateConfig(
|
||||||
path.toNativeUtf8().cast(),
|
path.toNativeUtf8().cast(),
|
||||||
@@ -166,8 +159,8 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
|||||||
) {
|
) {
|
||||||
loggy.debug("starting, memory limit: [${!disableMemoryLimit}]");
|
loggy.debug("starting, memory limit: [${!disableMemoryLimit}]");
|
||||||
return TaskEither(
|
return TaskEither(
|
||||||
() => CombineWorker().execute(
|
() => Future.microtask(
|
||||||
() {
|
() async {
|
||||||
final err = _box
|
final err = _box
|
||||||
.start(
|
.start(
|
||||||
configPath.toNativeUtf8().cast(),
|
configPath.toNativeUtf8().cast(),
|
||||||
@@ -187,8 +180,8 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
|||||||
@override
|
@override
|
||||||
TaskEither<String, Unit> stop() {
|
TaskEither<String, Unit> stop() {
|
||||||
return TaskEither(
|
return TaskEither(
|
||||||
() => CombineWorker().execute(
|
() => Future.microtask(
|
||||||
() {
|
() async {
|
||||||
final err = _box.stop().cast<Utf8>().toDartString();
|
final err = _box.stop().cast<Utf8>().toDartString();
|
||||||
if (err.isNotEmpty) {
|
if (err.isNotEmpty) {
|
||||||
return left(err);
|
return left(err);
|
||||||
@@ -207,8 +200,8 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
|||||||
) {
|
) {
|
||||||
loggy.debug("restarting, memory limit: [${!disableMemoryLimit}]");
|
loggy.debug("restarting, memory limit: [${!disableMemoryLimit}]");
|
||||||
return TaskEither(
|
return TaskEither(
|
||||||
() => CombineWorker().execute(
|
() => Future.microtask(
|
||||||
() {
|
() async {
|
||||||
final err = _box
|
final err = _box
|
||||||
.restart(
|
.restart(
|
||||||
configPath.toNativeUtf8().cast(),
|
configPath.toNativeUtf8().cast(),
|
||||||
@@ -265,10 +258,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
final err = _box
|
final err = _box.startCommandClient(1, receiver.sendPort.nativePort).cast<Utf8>().toDartString();
|
||||||
.startCommandClient(1, receiver.sendPort.nativePort)
|
|
||||||
.cast<Utf8>()
|
|
||||||
.toDartString();
|
|
||||||
if (err.isNotEmpty) {
|
if (err.isNotEmpty) {
|
||||||
loggy.error("error starting status command: $err");
|
loggy.error("error starting status command: $err");
|
||||||
throw err;
|
throw err;
|
||||||
@@ -310,10 +300,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final err = _box
|
final err = _box.startCommandClient(5, receiver.sendPort.nativePort).cast<Utf8>().toDartString();
|
||||||
.startCommandClient(5, receiver.sendPort.nativePort)
|
|
||||||
.cast<Utf8>()
|
|
||||||
.toDartString();
|
|
||||||
if (err.isNotEmpty) {
|
if (err.isNotEmpty) {
|
||||||
logger.error("error starting group command: $err");
|
logger.error("error starting group command: $err");
|
||||||
throw err;
|
throw err;
|
||||||
@@ -357,10 +344,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final err = _box
|
final err = _box.startCommandClient(13, receiver.sendPort.nativePort).cast<Utf8>().toDartString();
|
||||||
.startCommandClient(13, receiver.sendPort.nativePort)
|
|
||||||
.cast<Utf8>()
|
|
||||||
.toDartString();
|
|
||||||
if (err.isNotEmpty) {
|
if (err.isNotEmpty) {
|
||||||
logger.error("error starting: $err");
|
logger.error("error starting: $err");
|
||||||
throw err;
|
throw err;
|
||||||
@@ -376,8 +360,8 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
|||||||
@override
|
@override
|
||||||
TaskEither<String, Unit> selectOutbound(String groupTag, String outboundTag) {
|
TaskEither<String, Unit> selectOutbound(String groupTag, String outboundTag) {
|
||||||
return TaskEither(
|
return TaskEither(
|
||||||
() => CombineWorker().execute(
|
() => Future.microtask(
|
||||||
() {
|
() async {
|
||||||
final err = _box
|
final err = _box
|
||||||
.selectOutbound(
|
.selectOutbound(
|
||||||
groupTag.toNativeUtf8().cast(),
|
groupTag.toNativeUtf8().cast(),
|
||||||
@@ -397,12 +381,9 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
|||||||
@override
|
@override
|
||||||
TaskEither<String, Unit> urlTest(String groupTag) {
|
TaskEither<String, Unit> urlTest(String groupTag) {
|
||||||
return TaskEither(
|
return TaskEither(
|
||||||
() => CombineWorker().execute(
|
() => Future.microtask(
|
||||||
() {
|
() async {
|
||||||
final err = _box
|
final err = _box.urlTest(groupTag.toNativeUtf8().cast()).cast<Utf8>().toDartString();
|
||||||
.urlTest(groupTag.toNativeUtf8().cast())
|
|
||||||
.cast<Utf8>()
|
|
||||||
.toDartString();
|
|
||||||
if (err.isNotEmpty) {
|
if (err.isNotEmpty) {
|
||||||
return left(err);
|
return left(err);
|
||||||
}
|
}
|
||||||
@@ -418,9 +399,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
|||||||
@override
|
@override
|
||||||
Stream<List<String>> watchLogs(String path) async* {
|
Stream<List<String>> watchLogs(String path) async* {
|
||||||
yield await _readLogFile(File(path));
|
yield await _readLogFile(File(path));
|
||||||
yield* Watcher(path, pollingDelay: const Duration(seconds: 1))
|
yield* Watcher(path, pollingDelay: const Duration(seconds: 1)).events.asyncMap((event) async {
|
||||||
.events
|
|
||||||
.asyncMap((event) async {
|
|
||||||
if (event.type == ChangeType.MODIFY) {
|
if (event.type == ChangeType.MODIFY) {
|
||||||
await _readLogFile(File(path));
|
await _readLogFile(File(path));
|
||||||
}
|
}
|
||||||
@@ -431,17 +410,18 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
|||||||
@override
|
@override
|
||||||
TaskEither<String, Unit> clearLogs() {
|
TaskEither<String, Unit> clearLogs() {
|
||||||
return TaskEither(
|
return TaskEither(
|
||||||
() async {
|
() => Future.microtask(
|
||||||
_logBuffer.clear();
|
() async {
|
||||||
return right(unit);
|
_logBuffer.clear();
|
||||||
},
|
return right(unit);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<String>> _readLogFile(File file) async {
|
Future<List<String>> _readLogFile(File file) async {
|
||||||
if (_logFilePosition == 0 && file.lengthSync() == 0) return [];
|
if (_logFilePosition == 0 && file.lengthSync() == 0) return [];
|
||||||
final content =
|
final content = await file.openRead(_logFilePosition).transform(utf8.decoder).join();
|
||||||
await file.openRead(_logFilePosition).transform(utf8.decoder).join();
|
|
||||||
_logFilePosition = file.lengthSync();
|
_logFilePosition = file.lengthSync();
|
||||||
final lines = const LineSplitter().convert(content);
|
final lines = const LineSplitter().convert(content);
|
||||||
if (lines.length > 300) {
|
if (lines.length > 300) {
|
||||||
@@ -464,8 +444,8 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
|||||||
}) {
|
}) {
|
||||||
loggy.debug("generating warp config");
|
loggy.debug("generating warp config");
|
||||||
return TaskEither(
|
return TaskEither(
|
||||||
() => CombineWorker().execute(
|
() => Future.microtask(
|
||||||
() {
|
() async {
|
||||||
final response = _box
|
final response = _box
|
||||||
.generateWarpConfig(
|
.generateWarpConfig(
|
||||||
licenseKey.toNativeUtf8().cast(),
|
licenseKey.toNativeUtf8().cast(),
|
||||||
|
|||||||
2
libcore
2
libcore
Submodule libcore updated: b265886306...bc48ec07a8
@@ -2,6 +2,7 @@
|
|||||||
"$schema": "https://inlang.com/schema/project-settings",
|
"$schema": "https://inlang.com/schema/project-settings",
|
||||||
"sourceLanguageTag": "en",
|
"sourceLanguageTag": "en",
|
||||||
"languageTags": [
|
"languageTags": [
|
||||||
|
"ar",
|
||||||
"en",
|
"en",
|
||||||
"ckb-KUR",
|
"ckb-KUR",
|
||||||
"es",
|
"es",
|
||||||
|
|||||||
55
pubspec.lock
55
pubspec.lock
@@ -218,14 +218,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "3.0.0"
|
||||||
combine:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: combine
|
|
||||||
sha256: "8b52083c822a614a448fdd307e78c05266080e9747604b61fca5ddfe736a6c1e"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.5.7-0.1.pre"
|
|
||||||
convert:
|
convert:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -801,11 +793,12 @@ packages:
|
|||||||
humanizer:
|
humanizer:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: humanizer
|
path: "."
|
||||||
sha256: "08728a4b6d62accd7d09e668bd54e81e6e09a82c8cfda30553224b3eb868d4f2"
|
ref: up-version
|
||||||
url: "https://pub.dev"
|
resolved-ref: "8ae61d68357fae197be7ee71d67ccb9498b9d5c7"
|
||||||
source: hosted
|
url: "https://github.com/alex-relov/humanizer"
|
||||||
version: "2.2.0"
|
source: git
|
||||||
|
version: "2.3.0"
|
||||||
iconsax_flutter:
|
iconsax_flutter:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -850,10 +843,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: intl
|
name: intl
|
||||||
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
|
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.18.1"
|
version: "0.19.0"
|
||||||
io:
|
io:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -922,26 +915,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker
|
name: leak_tracker
|
||||||
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
|
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.0.0"
|
version: "10.0.4"
|
||||||
leak_tracker_flutter_testing:
|
leak_tracker_flutter_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker_flutter_testing
|
name: leak_tracker_flutter_testing
|
||||||
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
|
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "3.0.3"
|
||||||
leak_tracker_testing:
|
leak_tracker_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker_testing
|
name: leak_tracker_testing
|
||||||
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
|
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "3.0.1"
|
||||||
lint:
|
lint:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@@ -1002,10 +995,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
|
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.11.0"
|
version: "1.12.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1703,26 +1696,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test
|
name: test
|
||||||
sha256: a1f7595805820fcc05e5c52e3a231aedd0b72972cb333e8c738a8b1239448b6f
|
sha256: "7ee446762c2c50b3bd4ea96fe13ffac69919352bd3b4b17bac3f3465edc58073"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.24.9"
|
version: "1.25.2"
|
||||||
test_api:
|
test_api:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
|
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.1"
|
version: "0.7.0"
|
||||||
test_core:
|
test_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_core
|
name: test_core
|
||||||
sha256: a757b14fc47507060a162cc2530d9a4a2f92f5100a952c7443b5cad5ef5b106a
|
sha256: "2bc4b4ecddd75309300d8096f781c0e3280ca1ef85beda558d33fcbedc2eead4"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.9"
|
version: "0.6.0"
|
||||||
time:
|
time:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1935,10 +1928,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vm_service
|
name: vm_service
|
||||||
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
|
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "13.0.0"
|
version: "14.2.1"
|
||||||
watcher:
|
watcher:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
10
pubspec.yaml
10
pubspec.yaml
@@ -12,7 +12,7 @@ dependencies:
|
|||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
cupertino_icons: ^1.0.6
|
cupertino_icons: ^1.0.6
|
||||||
intl: ^0.18.1
|
intl: ^0.19.0
|
||||||
slang: ^3.30.1
|
slang: ^3.30.1
|
||||||
slang_flutter: ^3.30.0
|
slang_flutter: ^3.30.0
|
||||||
fpdart: ^1.1.0
|
fpdart: ^1.1.0
|
||||||
@@ -40,7 +40,7 @@ dependencies:
|
|||||||
launch_at_startup: ^0.2.2
|
launch_at_startup: ^0.2.2
|
||||||
sentry_flutter: ^7.16.1
|
sentry_flutter: ^7.16.1
|
||||||
sentry_dart_plugin: ^1.7.1
|
sentry_dart_plugin: ^1.7.1
|
||||||
combine: ^0.5.7-0.1.pre
|
|
||||||
path: ^1.8.3
|
path: ^1.8.3
|
||||||
loggy: ^2.0.3
|
loggy: ^2.0.3
|
||||||
flutter_loggy: ^2.0.2
|
flutter_loggy: ^2.0.2
|
||||||
@@ -58,7 +58,11 @@ dependencies:
|
|||||||
percent_indicator: ^4.2.3
|
percent_indicator: ^4.2.3
|
||||||
sliver_tools: ^0.2.12
|
sliver_tools: ^0.2.12
|
||||||
flutter_adaptive_scaffold: ^0.1.8
|
flutter_adaptive_scaffold: ^0.1.8
|
||||||
humanizer: ^2.2.0
|
# humanizer: ^2.2.0
|
||||||
|
humanizer:
|
||||||
|
git:
|
||||||
|
url: https://github.com/alex-relov/humanizer
|
||||||
|
ref: up-version
|
||||||
upgrader: ^9.0.0
|
upgrader: ^9.0.0
|
||||||
toastification: ^1.2.1
|
toastification: ^1.2.1
|
||||||
version: ^3.0.2
|
version: ^3.0.2
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ display_name: Hiddify
|
|||||||
executable_name: Hiddify.exe
|
executable_name: Hiddify.exe
|
||||||
output_base_file_name: Hiddify.exe
|
output_base_file_name: Hiddify.exe
|
||||||
create_desktop_icon: true
|
create_desktop_icon: true
|
||||||
install_dir_name: "{autopf64}\\hiddify"
|
install_dir_name: "{autopf64}\\Hiddify"
|
||||||
setup_icon_file: ..\..\windows\runner\resources\app_icon.ico
|
setup_icon_file: ..\..\windows\runner\resources\app_icon.ico
|
||||||
locales:
|
locales:
|
||||||
|
- ar
|
||||||
- en
|
- en
|
||||||
- fa
|
- fa
|
||||||
- ru
|
- ru
|
||||||
|
|||||||
Reference in New Issue
Block a user