diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..8d56e98b
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,20 @@
+# Changelog
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## Unreleased
+### Added
+- Basic region based routing rules
+- Russian region
+- Logs flow control
+
+### Changed
+- Theme preferences
+- Logs page
+
+### Fixed
+- Localization mistakes in Russian from [solokot](https://github.com/solokot)
+- Localization mistakes in Russian from [Elshad Guseynov](https://github.com/lifeindarkside)
+- Logs filtering
diff --git a/LICENSE.md b/LICENSE.md
index fd7de532..cac21c22 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -2,6 +2,9 @@
# Attribution-NonCommercial-ShareAlike 4.0 International
## Summary:
+- The forks of the app are not allowed to be listed on F-Droid or other app stores under the original name or original design.
+- Any forks should be published open-source under the same license.
+- You need prior consent to publish a fork or use any part of this code in an application published in Apple Store.
- You are free to:
- Share — copy and redistribute the material in any medium or format
- Adapt — remix, transform, and build upon the material
diff --git a/Makefile b/Makefile
index 47420aab..e1eaf072 100644
--- a/Makefile
+++ b/Makefile
@@ -119,9 +119,8 @@ release: # Create a new tag for release.
echo "version: $${VERSION_STR}+$${BUILD_NUMBER}" && \
sed -i "s/version: .*/version: $${VERSION_STR}\+$${BUILD_NUMBER}/g" pubspec.yaml && \
git tag $${TAG} > /dev/null && \
- gitchangelog > changelog.md || { git tag -d $${TAG}; echo "Please run pip install git gitchangelog pystache mustache markdown"; exit 2; } && \
git tag -d $${TAG} > /dev/null && \
- git add pubspec.yaml changelog.md && \
+ git add pubspec.yaml CHANGELOG.md && \
make sync_translate && \
git add assets/translations/* && \
git commit -m "release: version $${TAG}" && \
diff --git a/README.md b/README.md
index c4dc0dfd..934a8192 100644
--- a/README.md
+++ b/README.md
@@ -13,11 +13,6 @@
-
-
-
-
-
## What is Hiddify-Next?
@@ -25,14 +20,11 @@
The app is developed using [Flutter](https://flutter.dev) and [Go](https://go.dev). For more information you can read through our [Contribution Guidelines](https://github.com/hiddify/hiddify-next/blob/main/CONTRIBUTING.md) for development.
-## Improve Translations
-You can easily contribute to this project by using the following links to improve the translations:
- - [English](https://inlang.com/editor/github.com/hiddify/hiddify-next?lang=en)
-- [Persian](https://inlang.com/editor/github.com/hiddify/hiddify-next?lang=en&lang=fa)
-- [Russian](https://inlang.com/editor/github.com/hiddify/hiddify-next?lang=en&lang=ru)
-- [Chinese](https://inlang.com/editor/github.com/hiddify/hiddify-next?lang=en&lang=zh)
+
+
+
## 🚀 Main features
@@ -80,9 +72,9 @@ You can easily contribute to this project by using the following links to improv
Android
-
-
-
+
+
+
@@ -97,7 +89,7 @@ You can easily contribute to this project by using the following links to improv
Linux
-
+
@@ -108,6 +100,15 @@ You can easily contribute to this project by using the following links to improv
## Installation and tutorials
Please find tutorial information on the [wiki page](https://github.com/hiddify/hiddify-next/wiki).
+## Improve Translations
+You can easily contribute to this project by using the following links to improve the translations:
+ - [English](https://inlang.com/editor/github.com/hiddify/hiddify-next?lang=en)
+- [Persian](https://inlang.com/editor/github.com/hiddify/hiddify-next?lang=en&lang=fa)
+- [Russian](https://inlang.com/editor/github.com/hiddify/hiddify-next?lang=en&lang=ru)
+- [Chinese](https://inlang.com/editor/github.com/hiddify/hiddify-next?lang=en&lang=zh)
+
+
+
## Acknowledgements
- [Sing-box](https://github.com/SagerNet/sing-box)
diff --git a/README_cn.md b/README_cn.md
index 7583a9b6..1bf5f6d5 100644
--- a/README_cn.md
+++ b/README_cn.md
@@ -13,7 +13,7 @@
-## 什么是 Hiddify-Next?
+## Hiddify-Next 是什么?
基于 [Sing-box](https://github.com/SagerNet/sing-box) 的多平台客户端,用作通用代理工具链。 该应用程序提供了广泛的功能,如下所列。 它还支持大量协议。 该应用程序免费使用、无广告且开源。 它提供了一个安全且私密的工具来访问免费互联网。
该应用程序是使用 [Flutter](https://flutter.dev/) 和 [Go](https://go.dev/) 开发的。 欲了解更多信息,您可以阅读我们的开发贡献指南。
@@ -31,9 +31,9 @@
🔍 基于延迟的自动选择
-🟡 广泛的协议:ECH、Sing-box、V2ray、Xray、Vless、Vmess、Reality、TUIC、Hysteria、ShadowTLS、SSH、Clash、Clash meta
+🟡 广泛的协议支持:ECH、Sing-box、V2ray、Xray、Vless、Vmess、Trojan、Trojan with websocket、Reality、TUIC、Hysteria、Hysteria2、ShadowTLS、SSH、Clash、Clash meta
-🟡 订阅链接:Clash、Clash meta、Sing-box 和 Shadowsocks
+🟡 支持多种订阅链接导入:Clash、Clash meta、Sing-box 和 Shadowsocks
🔄 自动订阅更新
@@ -45,9 +45,9 @@
🌙 深色和浅色模式
-⚙ 与所有代理管理面板兼容
+⚙ 与所有代理管理面板的节点兼容
-⭐ 适合伊朗、中国、俄罗斯等国家配置
+⭐ 适用于伊朗、中国、俄罗斯等国家配置
📱 可在 Google Play 上获取
@@ -63,7 +63,7 @@
- 安卓
+ Android
@@ -72,13 +72,13 @@
- 视窗
+ Windows
- 苹果系统
+ macOS
@@ -95,16 +95,16 @@
## 致谢
- [Sing-box](https://github.com/SagerNet/sing-box)
-- [Sing-box 适用于安卓](https://github.com/SagerNet/sing-box-for-android)
+- [Sing-box for Android](https://github.com/SagerNet/sing-box-for-android)
- [Clash](https://github.com/Dreamacro/clash)
- [Clash Meta](https://github.com/MetaCubeX/Clash.Meta)
- [FClash](https://github.com/Fclash/Fclash)
-- [其他的](./pubspec.yaml)
+- [其他](./pubspec.yaml)
## 捐赠与支持
-支持我们的最简单方法是点击本页顶部的星号 (⭐)。
+支持我们的最简单方法是单击此页面顶部的 Star (⭐)。
-我们的服务还需要财政支持。 我们所有的活动都是自愿进行的,财政支持将用于项目的开发。 您可以[此处](https://github.com/hiddify/hiddify-server/wiki/support)查看我们的支持地址
+我们的服务也需要资金支持。我们所有的活动都是自愿进行的,资金支持将用于项目的开发和维护。您可以在 [此处](https://github.com/hiddify/hiddify-manager/wiki/support) 查看我们的支持地址。
@@ -119,7 +119,7 @@
- 我们感谢所有参与该项目的人。 这里有一些人,还有 Github 之外的更多人。 这对我们来说意义重大。 ♥
+ 感谢所有参与该项目的人。包括以下列出的人,和更多其他来自 Github 的人。你们对我们的意义非常重大。 ♥
@@ -128,7 +128,7 @@
- 制作与 Contrib.Rocks
+ 使用 Contrib.Rocks 制作
diff --git a/android/app/src/main/kotlin/com/hiddify/hiddify/LogHandler.kt b/android/app/src/main/kotlin/com/hiddify/hiddify/LogHandler.kt
index d1ae2e46..3fc91860 100644
--- a/android/app/src/main/kotlin/com/hiddify/hiddify/LogHandler.kt
+++ b/android/app/src/main/kotlin/com/hiddify/hiddify/LogHandler.kt
@@ -1,5 +1,6 @@
package com.hiddify.hiddify
+import android.util.Log
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.EventChannel
@@ -18,13 +19,15 @@ class LogHandler : FlutterPlugin {
logsChannel.setStreamHandler(object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
- MainActivity.instance.serviceLogs.observeForever {
- if (it == null) return@observeForever
- events?.success(it)
+ val activity = MainActivity.instance
+ events?.success(activity.logList)
+ activity.logCallback = {
+ events?.success(activity.logList)
}
}
override fun onCancel(arguments: Any?) {
+ MainActivity.instance.logCallback = null
}
})
}
diff --git a/android/app/src/main/kotlin/com/hiddify/hiddify/MainActivity.kt b/android/app/src/main/kotlin/com/hiddify/hiddify/MainActivity.kt
index f2253b36..f605144c 100644
--- a/android/app/src/main/kotlin/com/hiddify/hiddify/MainActivity.kt
+++ b/android/app/src/main/kotlin/com/hiddify/hiddify/MainActivity.kt
@@ -39,7 +39,6 @@ class MainActivity : FlutterFragmentActivity(), ServiceConnection.Callback {
var logCallback: ((Boolean) -> Unit)? = null
val serviceStatus = MutableLiveData(Status.Stopped)
val serviceAlerts = MutableLiveData(null)
- val serviceLogs = MutableLiveData(null)
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
@@ -102,37 +101,18 @@ class MainActivity : FlutterFragmentActivity(), ServiceConnection.Callback {
serviceAlerts.postValue(ServiceEvent(Status.Stopped, type, message))
}
- private var paused = false
- override fun onPause() {
- super.onPause()
-
- paused = true
- }
-
- override fun onResume() {
- super.onResume()
-
- paused = false
- logCallback?.invoke(true)
- }
-
override fun onServiceWriteLog(message: String?) {
- if (paused) {
- if (logList.size > 300) {
- logList.removeFirst()
- }
+ if (logList.size > 300) {
+ logList.removeFirst()
}
logList.addLast(message)
- if (!paused) {
- logCallback?.invoke(false)
- serviceLogs.postValue(message)
- }
+ logCallback?.invoke(false)
}
override fun onServiceResetLogs(messages: MutableList) {
logList.clear()
logList.addAll(messages)
- if (!paused) logCallback?.invoke(true)
+ logCallback?.invoke(true)
}
override fun onDestroy() {
diff --git a/android/app/src/main/kotlin/com/hiddify/hiddify/MethodHandler.kt b/android/app/src/main/kotlin/com/hiddify/hiddify/MethodHandler.kt
index 944c52f7..36e01b2a 100644
--- a/android/app/src/main/kotlin/com/hiddify/hiddify/MethodHandler.kt
+++ b/android/app/src/main/kotlin/com/hiddify/hiddify/MethodHandler.kt
@@ -29,6 +29,7 @@ class MethodHandler(private val scope: CoroutineScope) : FlutterPlugin,
Restart("restart"),
SelectOutbound("select_outbound"),
UrlTest("url_test"),
+ ClearLogs("clear_logs"),
}
}
@@ -63,38 +64,44 @@ class MethodHandler(private val scope: CoroutineScope) : FlutterPlugin,
}
Trigger.ChangeConfigOptions.method -> {
- result.runCatching {
- val args = call.arguments as String
- Settings.configOptions = args
- success(true)
+ scope.launch {
+ result.runCatching {
+ val args = call.arguments as String
+ Settings.configOptions = args
+ success(true)
+ }
}
}
Trigger.Start.method -> {
- result.runCatching {
- val args = call.arguments as Map<*, *>
- Settings.activeConfigPath = args["path"] as String? ?: ""
- val mainActivity = MainActivity.instance
- val started = mainActivity.serviceStatus.value == Status.Started
- if (started) {
- Log.w(TAG, "service is already running")
- return success(true)
+ scope.launch {
+ result.runCatching {
+ val args = call.arguments as Map<*, *>
+ Settings.activeConfigPath = args["path"] as String? ?: ""
+ val mainActivity = MainActivity.instance
+ val started = mainActivity.serviceStatus.value == Status.Started
+ if (started) {
+ Log.w(TAG, "service is already running")
+ return@launch success(true)
+ }
+ mainActivity.startService()
+ success(true)
}
- mainActivity.startService()
- success(true)
}
}
Trigger.Stop.method -> {
- result.runCatching {
- val mainActivity = MainActivity.instance
- val started = mainActivity.serviceStatus.value == Status.Started
- if (!started) {
- Log.w(TAG, "service is not running")
- return success(true)
+ scope.launch {
+ result.runCatching {
+ val mainActivity = MainActivity.instance
+ val started = mainActivity.serviceStatus.value == Status.Started
+ if (!started) {
+ Log.w(TAG, "service is not running")
+ return@launch success(true)
+ }
+ BoxService.stop()
+ success(true)
}
- BoxService.stop()
- success(true)
}
}
@@ -151,6 +158,15 @@ class MethodHandler(private val scope: CoroutineScope) : FlutterPlugin,
}
}
+ Trigger.ClearLogs.method -> {
+ scope.launch {
+ result.runCatching {
+ MainActivity.instance.onServiceResetLogs(mutableListOf())
+ success(true)
+ }
+ }
+ }
+
else -> result.notImplemented()
}
}
diff --git a/assets/translations/strings_en.i18n.json b/assets/translations/strings_en.i18n.json
index ddf1dbca..8a5e9056 100644
--- a/assets/translations/strings_en.i18n.json
+++ b/assets/translations/strings_en.i18n.json
@@ -46,7 +46,7 @@
"remainingDuration": "${duration} Days Remaining",
"remainingTrafficSemanticLabel": "${consumed} of ${total} traffic consumed.",
"expired": "Expired",
- "noTraffic": "No more traffic"
+ "noTraffic": "Out of Quota"
},
"sortBy": {
"lastUpdate": "Recently updated",
@@ -106,11 +106,13 @@
},
"logs": {
"pageTitle": "Logs",
- "clearLogsButtonText": "Clear Logs",
"filterHint": "Filter",
"allLevelsFilter": "All",
"shareCoreLogs": "Share Core Logs",
- "shareAppLogs": "Share App logs"
+ "shareAppLogs": "Share App logs",
+ "pauseTooltip": "Pause",
+ "resumeTooltip": "Resume",
+ "clearTooltip": "Clear"
},
"settings": {
"pageTitle": "Settings",
@@ -123,17 +125,18 @@
"regions": {
"ir": "Iran (ir)",
"cn": "China (cn)",
+ "ru": "Russia (ru)",
"other": "Other"
},
"themeMode": "Theme Mode",
"themeModes": {
"system": "Follow system theme",
"dark": "Dark mode",
- "light": "Light mode"
+ "light": "Light mode",
+ "black": "Black mode"
},
"enableAnalytics": "Enable Analytics",
"enableAnalyticsMsg": "Give permission to collect analytics and send crash reports to improve the app",
- "trueBlack": "Pure Black",
"autoStart": "Start on Boot",
"silentStart": "Silent Start",
"openWorkingDir": "Open Working Directory",
@@ -257,4 +260,4 @@
"short_description": "Auto, SSH, VLESS, Vmess, Trojan, Reality, Sing-Box, Clash, Xray, Shadowsocks",
"full_description": "The key goal of HiddifyNext is to provide a secure, user-friendly and efficient tunneling client. It enables you to route all traffic or selected app traffic to a remote server of your choose, utilizing VPN-Service permission.\n\nNote: We do not provide any server; users are required to ensure their online activities stay private by using use their own self-hosted server or trusted servers. \n \nWe support servers with:\n- Normal V2ray/Xray Subscription Link\n- Clash Subscription Link\n- Sing-Box Subscription Link\n\nWhat is our unique features?\n - User Friendly\n - Optimized and Fast\n - Automatically select LowestPing \n - Show user usage information\n - Easily import sublink by one click using deeplinking \n - Free and No ADS\n - Easily switch user sublinks\n - more and more\n\nSupport:\n- All Protocols supported by Sing-Box \n- VLESS + xtls reality, vision\n- VMESS\n- Trojan\n- ShoadowSocks\n- Reality\n- V2ray\n- Hystria2\n- TUIC\n- SSH\n- ShadowTLS\n\n\nThe source code exist in https://github.com/hiddify/Hiddify-Next\nThe application core is based on open-source sing-box.\n\nPermission Description:\n- VPN Service: As the goal of this application is to provide a secure, user-friendly and efficient tunneling client, we need this permission to be able to route the traffic via tunnel to the remote server. \n- QUERY ALL PACKAGES: This permission is used to allow users to include or exclude specific applications for tunneling.\n- RECEIVE BOOT COMPLETED: This permission can be enabled or disabled from app settings to activate this application upon device boot.\n- POST NOTIFICATIONS: This permission is essential as we employ a foreground service to ensure the continuous operation of the VPN service.\n- This application is free from advertisements. The analytics and crash data only occurs with the explicit consent of the user in the first use of application."
}
-}
+}
\ No newline at end of file
diff --git a/assets/translations/strings_fa.i18n.json b/assets/translations/strings_fa.i18n.json
index 9fe3206c..cebc0b0a 100644
--- a/assets/translations/strings_fa.i18n.json
+++ b/assets/translations/strings_fa.i18n.json
@@ -106,11 +106,13 @@
},
"logs": {
"pageTitle": "لاگها",
- "clearLogsButtonText": "پاکسازی",
"filterHint": "فیلتر",
"allLevelsFilter": "همه",
"shareCoreLogs": "اشتراکگذاری لاگ هسته",
- "shareAppLogs": "اشتراکگذاری لاگ برنامه"
+ "shareAppLogs": "اشتراکگذاری لاگ برنامه",
+ "pauseTooltip": "مکث",
+ "resumeTooltip": "از سرگیری",
+ "clearTooltip": "پاکسازی"
},
"settings": {
"pageTitle": "تنظیمات",
@@ -123,17 +125,18 @@
"regions": {
"ir": "ایران (ir)",
"cn": "چین (cn)",
+ "ru": "روسیه (ru)",
"other": "سایر"
},
"themeMode": "تم مود",
"themeModes": {
"system": "پیروی از تم دستگاه",
"dark": "تم تیره",
- "light": "تم روشن"
+ "light": "تم روشن",
+ "black": "تم سیاه"
},
"enableAnalytics": "فعالسازی آنالیتیکز",
"enableAnalyticsMsg": "ارائه دسترسی آنالیز و گزارش خطا برای بهبود عملکرد برنامه",
- "trueBlack": "کاملا سیاه",
"autoStart": "اجرا با روشن شدن سیستم",
"silentStart": "اجرای ساکت",
"openWorkingDir": "باز کردن دایرکتوری کاری",
@@ -257,4 +260,4 @@
"short_description": "Auto, SSH, VLESS, Vmess, Trojan, Reality, Sing-Box, Clash, Xray, Shadowsocks",
"full_description": "هدف اصلی HiddifyNext ارائه یک کلاینت تونل زنی ایمن، کاربرپسند و کارآمد است. این به شما امکان می دهد تا با استفاده از مجوز VPN-Service، تمام ترافیک یا ترافیک برنامه انتخابی را به یک سرور راه دور مورد نظر خود هدایت کنید.\n\nتوجه: ما هیچ سروری ارائه نمی دهیم. کاربران موظفند با استفاده از سرورهای خود میزبان یا سرورهای مورد اعتماد، فعالیتهای آنلاین خود را خصوصی نگه دارند.\n \nما از سرورهایی با موارد زیر پشتیبانی می کنیم:\n- لینک اشتراک V2ray/Xray معمولی\n- لینک اشتراک کلش\n- لینک اشتراک Sing-Box\n\nویژگی های منحصر به فرد ما چیست؟\n - کاربر پسند\n - بهینه و سریع\n - به طور خودکار LowestPing را انتخاب کنید\n - نمایش اطلاعات استفاده کاربر\n - به راحتی لینک فرعی را با یک کلیک با استفاده از دیپ لینک وارد کنید\n - رایگان و بدون تبلیغات\n - به راحتی پیوندهای فرعی کاربر را تغییر دهید\n - بیشتر و بیشتر\n\nحمایت کردن:\n- تمام پروتکل های پشتیبانی شده توسط Sing-Box\n- VLESS + xtls \n- VMESS\n- تروجان\n- ShoadowSocks\n- ریالیتی\n- V2ray\n- هیستریا 2\n- TUIC\n- SSH\n- ShadowTLS\n\n\nکد منبع در https://github.com/hiddify/Hiddify-Next وجود دارد\nهسته برنامه بر اساس sing-box منبع باز است.\n\nتوضیحات مجوز:\n- سرویس VPN: از آنجا که هدف این برنامه ارائه یک کلاینت تونل زنی ایمن، کاربر پسند و کارآمد است، ما به این مجوز نیاز داریم تا بتوانیم ترافیک را از طریق تونل به سرور راه دور هدایت کنیم.\n- QUERY ALL PACKAGES: این مجوز برای اجازه دادن به کاربران برای گنجاندن یا حذف برنامه های کاربردی خاص برای تونل زدن استفاده می شود.\n- دریافت بوت تکمیل شد: این مجوز را می توان از تنظیمات برنامه فعال یا غیرفعال کرد تا این برنامه پس از بوت شدن دستگاه فعال شود.\n- اعلان های ارسالی: این مجوز ضروری است زیرا ما از یک سرویس پیش زمینه برای اطمینان از عملکرد مداوم سرویس VPN استفاده می کنیم.\n- این برنامه بدون تبلیغات است. تجزیه و تحلیل و داده های اشکال فقط با رضایت صریح کاربر در اولین استفاده از برنامه اتفاق می افتد."
}
-}
+}
\ No newline at end of file
diff --git a/assets/translations/strings_ru.i18n.json b/assets/translations/strings_ru.i18n.json
index d798d217..a5c627c7 100644
--- a/assets/translations/strings_ru.i18n.json
+++ b/assets/translations/strings_ru.i18n.json
@@ -1,6 +1,6 @@
{
"general": {
- "appTitle": "HiddifyNext",
+ "appTitle": "Hiddify Next",
"reset": "Сброс",
"toggle": {
"enabled": "Включено",
@@ -30,8 +30,8 @@
"stats": {
"traffic": "Скорость",
"trafficTotal": "Трафик",
- "uplink": "Входящий канал",
- "downlink": "Исходящий канал"
+ "uplink": "Исходящий канал",
+ "downlink": "Входящий канал"
}
},
"profile": {
@@ -106,11 +106,13 @@
},
"logs": {
"pageTitle": "Журналы",
- "clearLogsButtonText": "Очистить журналы",
"filterHint": "Фильтр",
"allLevelsFilter": "Все",
"shareCoreLogs": "Поделиться журналами ядра",
- "shareAppLogs": "Поделиться журналами приложения"
+ "shareAppLogs": "Поделиться журналами приложения",
+ "pauseTooltip": "Приостановить",
+ "resumeTooltip": "Возобновить",
+ "clearTooltip": "Очистить"
},
"settings": {
"pageTitle": "Настройки",
@@ -123,17 +125,18 @@
"regions": {
"ir": "Иран (ir)",
"cn": "Китай (cn)",
+ "ru": "Россия (ru)",
"other": "Другой"
},
"themeMode": "Оформление",
"themeModes": {
"system": "Системная тема",
"dark": "Тёмная тема",
- "light": "Светлая тема"
+ "light": "Светлая тема",
+ "black": "Чёрная тема"
},
"enableAnalytics": "Сбор аналитики",
"enableAnalyticsMsg": "Сбор аналитических данных и отправка отчётов о сбоях для улучшения приложения.",
- "trueBlack": "Чистый чёрный цвет",
"autoStart": "Запуск при загрузке",
"silentStart": "Тихий запуск",
"openWorkingDir": "Открыть рабочую папку",
@@ -198,7 +201,7 @@
"pageTitle": "О программе",
"version": "Версия",
"sourceCode": "Исходный код",
- "telegramChannel": "Telegram канал",
+ "telegramChannel": "Telegram-канал",
"checkForUpdate": "Проверка обновления",
"privacyPolicy": "Политика конфиденциальности",
"termsAndConditions": "Условия и положения"
@@ -209,7 +212,7 @@
"updateMsg": "Доступна новая версия @:general.appTitle. Обновить сейчас?",
"currentVersionLbl": "Текущая версия",
"newVersionLbl": "Новая версия",
- "updateNowBtnTxt": "Обновить сейчас",
+ "updateNowBtnTxt": "Обновить",
"laterBtnTxt": "Позже",
"ignoreBtnTxt": "Пропустить"
},
@@ -255,6 +258,6 @@
"play": {
"title": "Hiddify Next (Preview)",
"short_description": "Автовыбор, SSH, VLESS, Vmess, Trojan, Reality, Sing-Box, Clash, Xray, Shadowsocks",
- "full_description": "Основная цель HiddifyNext — предоставить безопасный, удобный и эффективный клиент туннелирования. Он позволяет направлять весь трафик или трафик выбранного приложения на выбранный вами удалённый сервер, используя разрешение 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Поддержка:\n• Все протоколы, поддерживаемые Sing-Box\n• VLESS + xtls reality, vision\n• VMESS\n• Trojan\n• ShoadowSocks\n• Reality\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— СЛУЖБА VPN: поскольку целью данного приложения является предоставление безопасного, удобного и эффективного клиента туннелирования, это разрешение необходимо, чтобы иметь возможность направлять трафик через туннель на удалённый сервер.\n— ЗАПРОС ВСЕХ ПАКЕТОВ: это разрешение позволяет включать или исключать определённые приложения для туннелирования.\n— ИНФОРМИРОВАНИЕ О ЗАВЕРШЕНИИ ЗАГРУЗКИ: это разрешение можно включить или отключить в настройках приложения, чтобы (де)активировать запуск приложения при загрузке устройства.\n- ПОСТОЯННОЕ УВЕДОМЛЕНИЕ: это разрешение необходимо, поскольку используется приоритетная служба для обеспечения непрерывной работы службы VPN.\n— Приложение не содержит рекламы. Сбор аналитики и данных о сбоях происходят только с явного согласия пользователя при первом использовании приложения."
+ "full_description": "Основная цель HiddifyNext — предоставить безопасный, удобный и эффективный клиент туннелирования. Он позволяет направлять весь трафик или трафик выбранного приложения на выбранный вами удалённый сервер, используя разрешение 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Поддержка:\n• Все протоколы, поддерживаемые Sing-Box\n• VLESS + xtls reality, vision\n• VMESS\n• Trojan\n• ShoadowSocks\n• Reality\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— СЛУЖБА VPN: поскольку целью данного приложения является предоставление безопасного, удобного и эффективного клиента туннелирования, это разрешение необходимо, чтобы иметь возможность направлять трафик через туннель на удалённый сервер.\n— ЗАПРОС ВСЕХ ПАКЕТОВ: это разрешение позволяет включать или исключать определённые приложения для туннелирования.\n— ИНФОРМИРОВАНИЕ О ЗАВЕРШЕНИИ ЗАГРУЗКИ: это разрешение можно включить или отключить в настройках приложения, чтобы (де)активировать запуск приложения при загрузке устройства.\n— ПОСТОЯННОЕ УВЕДОМЛЕНИЕ: это разрешение необходимо, поскольку используется приоритетная служба для обеспечения непрерывной работы службы VPN.\n— Приложение не содержит рекламы. Сбор аналитики и данных о сбоях происходят только с явного согласия пользователя при первом использовании приложения."
}
-}
+}
\ No newline at end of file
diff --git a/assets/translations/strings_zh.i18n.json b/assets/translations/strings_zh.i18n.json
index d7610172..a1ec9bd4 100644
--- a/assets/translations/strings_zh.i18n.json
+++ b/assets/translations/strings_zh.i18n.json
@@ -106,11 +106,13 @@
},
"logs": {
"pageTitle": "日志",
- "clearLogsButtonText": "清除日志",
"filterHint": "筛选",
"allLevelsFilter": "全部",
"shareCoreLogs": "分享核心日志",
- "shareAppLogs": "分享日志"
+ "shareAppLogs": "分享日志",
+ "pauseTooltip": "暂停",
+ "resumeTooltip": "恢复",
+ "clearTooltip": "清除"
},
"settings": {
"pageTitle": "设置",
@@ -123,17 +125,18 @@
"regions": {
"ir": "伊朗 (ir)",
"cn": "中国 (cn)",
+ "ru": "俄罗斯 (ru)",
"other": "其他"
},
"themeMode": "主题模式",
"themeModes": {
"system": "遵循系统主题",
"dark": "深色模式",
- "light": "灯光模式"
+ "light": "灯光模式",
+ "black": "黑色模式"
},
"enableAnalytics": "启用分析",
"enableAnalyticsMsg": "授予收集分析并发送崩溃报告以改进应用程序的权限",
- "trueBlack": "纯黑",
"autoStart": "开机启动",
"silentStart": "无声启动",
"openWorkingDir": "打开工作目录",
@@ -257,4 +260,4 @@
"short_description": "自动,SSH, VLESS, Vmess, Trojan, Reality, Sing-Box, Clash, Xray, Shadowsocks",
"full_description": "HiddifyNext 的主要目标是提供安全、用户友好且高效的隧道客户端。它使您能够利用 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\n支持:\n- Sing-Box 支持的所有协议\n- VLESS + xtls 现实、愿景\n- VMESS\n- Trojan\n- ShoadowSocks\n- Reality\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\nVPN服务:由于此应用程序的目标是提供安全、用户友好和高效的隧道客户端,我们需要此权限以能够通过隧道将流量路由到远程服务器。\n查询所有包:此权限用于允许用户包括或排除特定应用程序以进行隧道传输。\n接收启动完成:此权限可以从应用程序设置中启用或禁用,以在设备启动时激活此应用程序。\n发送通知:此权限是必需的,因为我们使用前台服务来确保VPN服务的持续运行。\n此应用程序没有广告。分析和崩溃数据仅在用户在首次使用应用程序时明确同意的情况下发生。"
}
-}
+}
\ No newline at end of file
diff --git a/changelog.md b/changelog.md
deleted file mode 100644
index 1ead2d96..00000000
--- a/changelog.md
+++ /dev/null
@@ -1,1265 +0,0 @@
-# Changelog
-
-
-## 0.9.2 (2023-10-15)
-
-#### Other
-
-* Fix ndk setup.
-
-* Fix android arm bug.
-
-
-
-## v0.9.1 (2023-10-15)
-
-#### Other
-
-* Update README_cn.md.
-
-* Update README.md.
-
-* Update README_fa.md.
-
-* Update README.md.
-
-* Update README.md.
-
-* Update README_ru.md.
-
-* Update README_cn.md.
-
-* Update README_fa.md.
-
-* Update README.md.
-
-* Delete docs/file.
-
-* Google play badge.
-
-* Delete docs/google-play-badge1.png.
-
-* Upload google play badge.
-
-* Create file.
-
-* Delete docs/google-play-badge.png.
-
-* Fix ci.
-
-* Change ndk setup.
-
-* Update dependencies.
-
-* Update README_ru.md.
-
-* Update README_cn.md.
-
-* Update README_ru.md.
-
-* Update README_ru.md.
-
-* Change desktop error handling.
-
-* Fix android bugs.
-
-* Update README.md.
-
-* Update README_cn.md.
-
-* Update README_ru.md.
-
-* Update README_ru.md.
-
-* Update README_cn.md.
-
-* Update README_fa.md.
-
-* Update README.md.
-
-* Delete Russian_Flag.png.
-
-* Add Russian flag.
-
-* Delete docs/file.
-
-* Create README_ru.md.
-
-* Delete REAMME_ru.md.
-
-* Create REAMME_ru.md.
-
-* Update README_cn.md.
-
-* Update README_fa.md.
-
-* Update README_fa.md.
-
-* Update README.md.
-
-* Update README_cn.md.
-
-* Update README_cn.md.
-
-* Update README_fa.md.
-
-* Update README.md.
-
-* Update README.md.
-
-* Update file.
-
-* Uplaod a badge for google play.
-
-* Delete google-play-badge.png.
-
-* Add files via upload.
-
-* Create file.
-
-* Delete docs/google-play-badge.png.
-
-* Delete google-play-badge.png.
-
-* Add files via upload.
-
-* Update README_fa.md.
-
-* Update README.md.
-
-* Update README.md.
-
-* Create Chinese README_cn.md.
-
-
-
-## v0.8.12 (2023-10-13)
-
-#### Fix
-
-* Typo.
-
-* Bug.
-
-* Release names.
-
-#### Other
-
-* Update readme.
-
-* Update core.
-
-* Update README_fa.md.
-
-* Update README_fa.md.
-
-* Update README.md.
-
-* Merge pull request #56 from solokot/main.
- _Improvement of Russian translation_
-
-* Improvement of Russian translation.
- _Basically a replacement for machine automatic translation_
-
-* Update README.md.
-
-* Update README_fa.md.
-
-* Update README_fa.md.
-
-* Update README.md.
-
-* Update README_fa.md.
-
-* Update README.md.
-
-* Update README.md.
-
-* Update README_fa.md.
-
-* Update README_fa.md.
-
-* Update README_fa.md.
-
-* Update README.md.
-
-* Update README_fa.md.
-
-* Update README.md.
-
-* Update README_fa.md.
-
-* Update README_fa.md.
-
-* Update README.md.
-
-* Update README_fa.md.
-
-* Update README_fa.md.
-
-* Update README.md.
-
-* Update README.md.
-
-* Update README_fa.md.
-
-* Update README.md.
-
-* Update README.md.
-
-* Update README_fa.md.
-
-* Update README_fa.md.
-
-* Update README_fa.md.
-
-* Fix bugs.
-
-* Change android signing.
-
-* Update README.md.
-
-* Update readme.
-
-* Update contribution guide.
-
-* Update README_fa.md.
-
-* Update README.md.
-
-* Update README_fa.md.
-
-* Update README_fa.md.
-
-* Update release_message.md.
-
-* Update README.md.
-
-* Update README_fa.md.
-
-* Update README_fa.md.
-
-* Update README.md.
-
-* Update README_fa.md.
-
-* Update release template.
-
-
-
-## v0.8.11 (2023-10-08)
-
-#### Changes
-
-* Remove auto release message.
-
-
-
-## v0.8.10 (2023-10-08)
-
-#### Fix
-
-* Release changelog.
-
-
-
-## v0.8.9 (2023-10-08)
-
-#### Fix
-
-* Missing libs.
-
-
-
-## v0.8.8 (2023-10-08)
-
-#### Fix
-
-* Release bug.
-
-
-
-## v0.8.7 (2023-10-08)
-
-#### Fix
-
-* Release message.
-
-
-
-## v0.8.6 (2023-10-08)
-
-#### Fix
-
-* Windows build.
-
-* Build issue.
-
-#### Other
-
-* Merge branch 'main' of hiddify-github:hiddify/hiddify-next.
-
-* Update release_message.md.
-
-* Merge branch 'main' of hiddify-github:hiddify/hiddify-next.
-
-* Update release_message.md.
-
-* Update README.md.
-
-* Update README_fa.md.
-
-* Delete docs/note.
-
-* Add files via upload.
-
-* Create note.
-
-* Update contribute.md.
-
-* Update README.md.
-
-* Delete assets/images/google-play-badge.png.
-
-* Update README_fa.md.
-
-* Update README.md.
-
-* Update release_message.md.
-
-* Update release_message.md.
-
-* Update release_message.md.
-
-* Fix build.
-
-
-
-## v0.8.5 (2023-10-07)
-
-#### Fix
-
-* Bug.
-
-
-
-## v0.8.4 (2023-10-07)
-
-#### Fix
-
-* Translate.
-
-
-
-## v0.8.3 (2023-10-07)
-
-#### Other
-
-* Add release message and help.
-
-* Merge branch 'main' of hiddify-github:hiddify/hiddify-next.
-
-* Fix bugs.
-
-* Update release_message.md.
-
-* Update release_message.md.
-
-* Update README_fa.md.
-
-* Update README.md.
-
-* Update release_message.md.
-
-* Update release_message.md.
-
-* Create release_message.md.
-
-* Add google play badge to assets.
-
-* Update README.md.
-
-* Update README_fa.md.
-
-* Update README_fa.md.
-
-* Update README.md.
-
-* Update README.md.
-
-* Create contribute.md.
-
-* Update README_fa.md.
-
-* Update README.md.
-
-* Update README_fa.md.
-
-* Update README_fa.md.
-
-* Update README.md.
-
-* Update README.md.
-
-* Update README_fa.md.
-
-* Add feature request template.
-
-* Fix issue template.
-
-* Add issue template.
-
-
-
-## v0.8.2 (2023-10-07)
-
-#### Fix
-
-* Hysteria2 and some links.
-
-#### Other
-
-* Update README_fa.md.
-
-* Update README_fa.md.
-
-* Update README_fa.md.
-
-* Update README.md.
-
-* Update README.md.
-
-* Update README.md.
-
-* Update README.md.
-
-* Update README.md.
-
-* Update README.md.
-
-* Update README.md.
-
-* Update README.md.
-
-* Update README.md.
-
-* Update README.md.
-
-* Update README.md.
-
-* Update README.md.
-
-* Create README_fa.md.
-
-* Update README.md.
-
-* Update README.md.
-
-* Update README.md.
- _Add some features to the readme_
-
-* Add debug export to clipboard.
-
-
-
-## v0.8.1 (2023-10-06)
-
-#### New
-
-* Add chinese lang.
-
-#### Fix
-
-* Chinese translation.
-
-#### Other
-
-* Update core 0.5.1.
-
-* Fix floating number sub info header.
-
-* Add russian.
-
-* Add google play descriptions.
-
-* Merge branch 'main' of hiddify-github:hiddify/hiddify-next.
-
-
-
-## v0.8.0 (2023-10-05)
-
-#### New
-
-* Add russian lang.
-
-#### Other
-
-* Add proxy tag sanitization.
-
-* Fix bugs.
-
-* Add ignore app update version.
-
-* Add new protocols to link parser.
-
-* Auto update translations on release.
-
-* Update translation.
-
-* Remove param in ru translation.
-
-
-
-## v0.7.2 (2023-10-04)
-
-#### Other
-
-* Fix bugs.
-
-
-
-## v0.7.1 (2023-10-03)
-
-#### Other
-
-* Fix log and analytics bugs.
-
-
-
-## v0.7.0 (2023-10-03)
-
-#### New
-
-* Add support of some exception panel with zero usage.
-
-#### Other
-
-* Fix translation bug.
-
-* Improve error handling and presentation.
-
-* Add retry for network ops.
-
-* Merge branch 'main' of hiddify-github:hiddify/hiddify-next.
-
-* Add local profile.
-
-* Add auto translator.
-
-* Add auto translate.
-
-
-
-## v0.6.0 (2023-09-30)
-
-#### Other
-
-* Fix minor bugs.
-
-* Add scheduled profile update.
-
-* Update dependencies.
-
-* Refactor profile details page.
-
-
-
-## v0.5.11 (2023-09-22)
-
-#### Other
-
-* Fix android bundle abi.
-
-* Bump core version.
-
-
-
-## v0.5.10 (2023-09-22)
-
-#### Other
-
-* Fix minor bugs.
-
-* Fix profile update bug.
-
-
-
-## v0.5.9 (2023-09-22)
-
-#### Other
-
-* Add build number.
-
-
-
-## v0.5.8 (2023-09-22)
-
-#### New
-
-* Add crashlytics.
-
-* Add support for base64 sublink for header from content.
-
-* Add profile headers from comments in response! good for hosting in github and show information.
-
-* Automated version release.
-
-* Send release versions only to play market add pre-release version.
-
-#### Changes
-
-* Change invalid dns 235.5.5.5 to 8.8.8.8.
-
-#### Fix
-
-* Improve routing accessibility and logs.
-
-* Minor bugs.
-
-* Prefs persistence.
-
-* Crashlytics.
-
-* App update url.
-
-* Small profiles.
-
-* Makefile vars.
-
-* Adaptive icon.
-
-* Pre-release.
-
-* Typo in adaptive icon.
-
-* If .dev is exist in the version do not show update needed.
-
-* Keep the link as it is. fix the issue with &
-
-* Dependency issue.
-
-* Remove extra print.
-
-* Bug in get headers from body.
-
-* Bug ini ci to google play.
-
-* Tag version issue.
-
-* Ci bug.
-
-* Remove comments.
-
-* Bug.
-
-#### Other
-
-* Fix ci.
-
-* Fix false-positive error reports.
-
-* Change build setup.
-
-* Fix minor bugs.
-
-* Refactor app update.
-
-* Fix sentry dart plugin upload.
-
-* Fix ci debug symbols upload.
-
-* Add sentry provider observer.
-
-* Ci: add sentry debug info upload.
-
-* Update dependencies and general fixes.
-
-* Chore: bump agp version.
-
-* Ci: fix env.
-
-* Ci: add dsn env.
-
-* Feat: add region and terms to intro.
-
-* Update ci.yml.
-
-* Build: add sentry dsn.
-
-* Feat: add intro screen.
-
-* Feat: add sentry.
-
-* Ci: bump macos version.
-
-* Feat: update profile when adding preexisting url.
-
-* Publish draft even with error.
-
-* Update version of core.
-
-* Merge branch 'main' of hiddify-github:hiddify/hiddify-next.
-
-* Add firebase.
-
-* Update translation.
-
-* Refactor: version presentation.
-
-* Perf: improve header parser.
-
-* Feat: remove check for updates in market releases.
-
-* Better manage the market release.
-
-* Update.
-
-* Merge branch 'main' of https://github.com/hiddify/hiddify-next.
-
-* Improve accessability.
-
-* Fix per-app proxy selection.
-
-* Add android per-app proxy.
-
-* Add basic flavors.
-
-* Update Makefile.
-
-* Update ci.yml.
-
-* Update ci.yml.
-
-* Update Makefile.
-
-* Update ci.yml.
-
-* Update ci.yml.
-
-* Update README.md.
-
-* Add accessability semantics.
-
-* Add support for fragment in the url.
-
-
-
-## v0.1.0 (2023-09-11)
-
-#### New
-
-* Add signing to android.
-
-* Add android universal build also.
-
-* Add change in network entitlements.
-
-* Change logo icon to next.
-
-* Add cache for speed up build process.
-
-* Add windows portable version.
-
-* Add libclash.
-
-* Add as draft release.
-
-* Make better ci and building applications.
-
-#### Changes
-
-* Change windows logo.
-
-* Change macos build to flutter_distributor.
-
-* Remove x86 builds since flutter does not support.
-
-* Add x64 to the name.
-
-* Update makefile.
-
-#### Fix
-
-* Space bugs in some panels.
-
-* Aab.
-
-* Aab build.
-
-* Universal sign.
-
-* KeystoreProperties.
-
-* Name issue.
-
-* Revert package name change.
-
-* Name.
-
-* Geosite download.
-
-* Windows portable bug.
-
-* Change name bug.
-
-* Setup exe files.
-
-* Libclash.so.
-
-* Linux AppImage.
-
-* Error.
-
-* Category in linux.
-
-* Add fuse for linux.
-
-* Icon.
-
-* Linux logo.
-
-* Android.
-
-* Makefile error.
-
-#### Other
-
-* Release v0.1.0.
-
-* Fix ci build.
-
-* Update ci.
-
-* Add core version.
-
-* Fix barrel file.
-
-* Change proxies lifecycle.
-
-* Add service restart.
-
-* Remove notification service.
-
-* Change android notification permission.
-
-* Add android service restart.
-
-* Change mark new profile active.
-
-* Handle unlimited.
-
-* Update upload download link stats.
-
-* Update Translations.
-
-* Remove // TODO add content disposition parsing.
-
-* Add content-disposition for profile title.
-
-* Update README.md.
-
-* Add hysteria2.
-
-* Fix android build connection.
-
-* Add android connection shortcut.
-
-* Add desktop autostart.
-
-* Add android boot receiver.
-
-* Add android proxy service.
-
-* Add android battery optimizations settings.
-
-* Change sharedpreferences to unify with android.
-
-* Add vclibs.
-
-* Update dependencies.
-
-* Add submodule.
-
-* Fix translation code gen.
-
-* Change prefs.
-
-* Change default config options.
-
-* Remove string casing.
-
-* Update ci.yml.
-
-* Remove caching.
-
-* Change core prefs to use code generation.
-
-* Fix custom lint.
-
-* Fix general issues.
-
-* Remove vclibs.
-
-* Update README.md.
-
-* Fix blank screen.
-
-* Add proxies sort.
-
-* Refactor preferences.
-
-* Update dependencies.
-
-* Add more config options to settings.
-
-* Add tun implementation option.
-
-* Add android power manager.
-
-* Add android quick tile.
-
-* Fix macos dependencies.
-
-* Fix mac build.
-
-* Fix build.
-
-* Change system tray icon.
-
-* Fix riverpod code generation.
-
-* Remove unnecessary config options.
-
-* Add accessability semantics.
-
-* Update dependencies.
-
-* Add config options.
-
-* Add pref utilities.
-
-* Add stats overview.
-
-* Update Translations.
-
-* Fix android outbounds view.
-
-* Remove unnecessary options.
-
-* Change proxies flow.
-
-* Add android command client support.
-
-* Update README.md.
-
-* Add status command receiver.
-
-* Add hiddify deeplink.
-
-* Add macos deeplink support.
-
-* Add singbox deeplink.
-
-* Change error prompts.
-
-* Fix url parser.
-
-* Remove unnecessary prefs.
-
-* Update dependencies.
-
-* Make AppImage zipped for preserving permission in linux.
-
-* Add user agent.
-
-* Fix uri launch.
-
-* Update ci flutter version.
-
-* Fix macos silent start.
-
-* Change proxies page.
-
-* Fix desktop connection error msg.
-
-* Add button tooltips.
-
-* Fix logging.
-
-* Create dependabot.yml.
-
-* Create CODE_OF_CONDUCT.md.
-
-* Fix aab.
-
-* Add aab file.
-
-* Change windows-portable name to HiddifyNext-portable.
-
-* Add debug mode.
-
-* Add misc settings ui.
-
-* Remote unnecessary logs.
-
-* Add misc preferences.
-
-* Add directory options.
-
-* Fix linux build.
-
-* Merge branch 'main' of https://github.com/hiddify/hiddify-next.
-
-* Fix android builds.
-
-* Fix android splash screen.
-
-* Update icons.
-
-* Fix windows executable build.
-
-* Remove unnecessary build steps.
-
-* Remove build number from appbar.
-
-* Update readme.
-
-* Change desktop directories.
-
-* Remove desktop notifications.
-
-* Force same version code for all platforms.
-
-* Add more logs.
-
-* Add log timestamp.
-
-* Update icons.
-
-* Remove drawer branding.
-
-* Add download section in readme.
-
-* Fix name of universal apk.
-
-* Fix order.
-
-* Update readme.
-
-* Update logging.
-
-* Update geo assets url.
-
-* Change linux directories.
-
-* Update icon.
-
-* Update icon.
-
-* Update logo for all platforms.
-
-* Revert name in macos.
-
-* Temporary disable app sandbox.
-
-* Change name.
-
-* Update Release.entitlements to fix binding issue.
-
-* Merge pull request #5 from evstegneych/main.
- _add macos option_
-
-* Add macos option.
-
-* Update ci flutter version.
-
-* Update kotlin version.
-
-* Update other dependencies.
-
-* Update dependencies.
-
-* Update flutter version.
-
-* Update LICENSE.md.
-
-* Add build option for ios but not tested.
-
-* Return: build for all.
-
-* Fix build for macos.
-
-* Fix ci.
-
-* Migrate to singbox.
-
-* Fix routing.
-
-* Fix connection button text casing.
-
-* Add update checking.
-
-* Add separate page for clash overrides.
-
-* Add version number to appbar.
-
-* Add more icons.
-
-* Add sort limited profiles last.
-
-* Fix profile sort icon.
-
-* Fix profile traffic ratio.
-
-* Add profiles sort option.
-
-* Refactor profile addition flow.
-
-* Add extra profile metadata.
-
-* Fix linux deep link service.
-
-* Refactor profile tile.
-
-* Add locale based font.
-
-* Fix linux startup.
-
-* Update dependencies.
-
-* Add about page.
-
-* Fix linux notifications.
-
-* Build all.
-
-* Hard coding.
-
-* Add png.
-
-* Update ci.yml.
-
-* Update ci.yml.
-
-* Update ci.yml.
-
-* Update ci.yml.
-
-* Update ci.yml.
-
-* Update ci.yml.
-
-* Update ci.yml.
-
-* Update ci.yml.
-
-* Update Makefile.
-
-* Update ci.yml.
-
-* Update Makefile.
-
-* Update ci.yml.
-
-* Update ci.yml.
-
-* Update Makefile.
-
-* Update ci.yml.
-
-* Update Makefile.
-
-* Fix makefile.
-
-* Fix actions.
-
-* Fix actions.
-
-* Fix actions.
-
-* Update build setup.
-
-* Merge branch 'main' of https://github.com/hiddify/hiddify-next.
-
-* Create LICENSE.md.
-
-* Add core submodule.
-
-* Remove core libs.
-
-* Update Makefile.
-
-* Update Makefile.
-
-* Remove c install.
-
-* Update make.
-
-* Merge branch 'main' of https://github.com/hiddify/hiddify-next.
-
-* Add Persian font.
-
-* Update dependencies.
-
-* Update to libclash.
-
-* Add cache.
-
-* Add: new action.
-
-* Add write permission.
-
-* Fix.
-
-* Modify.
-
-* Fix.
-
-* Update.
-
-* Fix android build.
-
-* Fix code gen bug.
-
-* Update ci.yml.
-
-* Update ci.yml.
-
-* Fix CI.
-
-* Fix CI.
-
-* Merge branch 'main' of https://github.com/hiddify/hiddify-next.
-
-* Update ci.yml.
-
-* Update ci.yml.
-
-* Fix CI.
-
-* Update ci.yml.
-
-* Update ci.yml.
-
-* Add CI.
-
-* Add silent start for desktop.
-
-* Update readme.
-
-* Add headless mode to desktop.
-
-* Update dependencies.
-
-* Add distribution setup for windows.
-
-* Add Farsi(fa) language.
-
-* Merge branch 'main' of https://github.com/hiddify/hiddify-next.
-
-* Initial commit.
-
-* Initial.
-
-
-
diff --git a/lib/core/app/app_view.dart b/lib/core/app/app_view.dart
index 69549dc6..35129467 100644
--- a/lib/core/app/app_view.dart
+++ b/lib/core/app/app_view.dart
@@ -33,7 +33,7 @@ class AppView extends HookConsumerWidget with PresLogger {
supportedLocales: AppLocaleUtils.supportedLocales,
localizationsDelegates: GlobalMaterialLocalizations.delegates,
debugShowCheckedModeBanner: false,
- themeMode: theme.mode,
+ themeMode: theme.mode.flutterThemeMode,
theme: theme.light(),
darkTheme: theme.dark(),
title: Constants.appName,
diff --git a/lib/core/core_providers.dart b/lib/core/core_providers.dart
index 9cd598ff..35ce17e4 100644
--- a/lib/core/core_providers.dart
+++ b/lib/core/core_providers.dart
@@ -19,6 +19,5 @@ TranslationsEn translations(TranslationsRef ref) =>
@Riverpod(keepAlive: true)
AppTheme theme(ThemeRef ref) => AppTheme(
ref.watch(themeModeNotifierProvider),
- ref.watch(trueBlackThemeNotifierProvider),
ref.watch(localeNotifierProvider).preferredFontFamily,
);
diff --git a/lib/core/prefs/app_theme.dart b/lib/core/prefs/app_theme.dart
index 8e6c8601..786be4f5 100644
--- a/lib/core/prefs/app_theme.dart
+++ b/lib/core/prefs/app_theme.dart
@@ -1,16 +1,38 @@
import 'package:flex_color_scheme/flex_color_scheme.dart';
import 'package:flutter/material.dart';
+import 'package:hiddify/core/prefs/locale_prefs.dart';
+
+enum AppThemeMode {
+ system,
+ light,
+ dark,
+ black;
+
+ String present(TranslationsEn t) => switch (this) {
+ system => t.settings.general.themeModes.system,
+ light => t.settings.general.themeModes.light,
+ dark => t.settings.general.themeModes.dark,
+ black => t.settings.general.themeModes.black,
+ };
+
+ ThemeMode get flutterThemeMode => switch (this) {
+ system => ThemeMode.system,
+ light => ThemeMode.light,
+ dark => ThemeMode.dark,
+ black => ThemeMode.dark,
+ };
+
+ bool get trueBlack => this == black;
+}
// mostly exact copy of flex color scheme 7.1's fabulous 12 theme
class AppTheme {
AppTheme(
this.mode,
- this.trueBlack,
this.fontFamily,
);
- final ThemeMode mode;
- final bool trueBlack;
+ final AppThemeMode mode;
final String fontFamily;
ThemeData light() {
@@ -81,7 +103,7 @@ class AppTheme {
useMaterial3: true,
swapLegacyOnMaterial3: true,
useMaterial3ErrorColors: true,
- darkIsTrueBlack: trueBlack,
+ darkIsTrueBlack: mode.trueBlack,
surfaceMode: FlexSurfaceMode.highScaffoldLowSurface,
// blendLevel: 1,
subThemesData: const FlexSubThemesData(
diff --git a/lib/core/prefs/theme_prefs.dart b/lib/core/prefs/theme_prefs.dart
index 8a0eca45..a9f21b3b 100644
--- a/lib/core/prefs/theme_prefs.dart
+++ b/lib/core/prefs/theme_prefs.dart
@@ -1,4 +1,4 @@
-import 'package:flutter/material.dart';
+import 'package:hiddify/core/prefs/app_theme.dart';
import 'package:hiddify/data/data_providers.dart';
import 'package:hiddify/utils/pref_notifier.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -10,29 +10,15 @@ class ThemeModeNotifier extends _$ThemeModeNotifier {
late final _pref = Pref(
ref.watch(sharedPreferencesProvider),
"theme_mode",
- ThemeMode.system,
- mapFrom: ThemeMode.values.byName,
+ AppThemeMode.system,
+ mapFrom: AppThemeMode.values.byName,
mapTo: (value) => value.name,
);
@override
- ThemeMode build() => _pref.getValue();
+ AppThemeMode build() => _pref.getValue();
- Future update(ThemeMode value) {
- state = value;
- return _pref.update(value);
- }
-}
-
-@Riverpod(keepAlive: true)
-class TrueBlackThemeNotifier extends _$TrueBlackThemeNotifier {
- late final _pref =
- Pref(ref.watch(sharedPreferencesProvider), "true_black_theme", false);
-
- @override
- bool build() => _pref.getValue();
-
- Future update(bool value) {
+ Future update(AppThemeMode value) {
state = value;
return _pref.update(value);
}
diff --git a/lib/data/repository/config_options_store.dart b/lib/data/repository/config_options_store.dart
index afc10987..fb7b9432 100644
--- a/lib/data/repository/config_options_store.dart
+++ b/lib/data/repository/config_options_store.dart
@@ -1,6 +1,6 @@
// ignore_for_file: avoid_manual_providers_as_generated_provider_dependency
import 'package:hiddify/core/prefs/prefs.dart';
-import 'package:hiddify/domain/singbox/config_options.dart';
+import 'package:hiddify/domain/singbox/singbox.dart';
import 'package:hiddify/utils/pref_notifier.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -67,6 +67,42 @@ final enableTunStore = PrefNotifier.provider("enable-tun", _default.enableTun);
final setSystemProxyStore =
PrefNotifier.provider("set-system-proxy", _default.setSystemProxy);
+// HACK temporary
+@riverpod
+List rules(RulesRef ref) => switch (ref.watch(regionNotifierProvider)) {
+ Region.ir => [
+ const Rule(
+ id: "id",
+ name: "name",
+ enabled: true,
+ domains: "domain:.ir",
+ ip: "geoip:ir",
+ outbound: RuleOutbound.bypass,
+ ),
+ ],
+ Region.cn => [
+ const Rule(
+ id: "id",
+ name: "name",
+ enabled: true,
+ domains: "domain:.cn,geosite:cn",
+ ip: "geoip:cn",
+ outbound: RuleOutbound.bypass,
+ ),
+ ],
+ Region.ru => [
+ const Rule(
+ id: "id",
+ name: "name",
+ enabled: true,
+ domains: "domain:.ru",
+ ip: "geoip:ru",
+ outbound: RuleOutbound.bypass,
+ ),
+ ],
+ _ => [],
+ };
+
@riverpod
ConfigOptions configOptions(ConfigOptionsRef ref) => ConfigOptions(
executeConfigAsIs:
@@ -88,4 +124,5 @@ ConfigOptions configOptions(ConfigOptionsRef ref) => ConfigOptions(
clashApiPort: ref.watch(clashApiPortStore),
enableTun: ref.watch(enableTunStore),
setSystemProxy: ref.watch(setSystemProxyStore),
+ rules: ref.watch(rulesProvider),
);
diff --git a/lib/data/repository/core_facade_impl.dart b/lib/data/repository/core_facade_impl.dart
index b344809f..fa89af87 100644
--- a/lib/data/repository/core_facade_impl.dart
+++ b/lib/data/repository/core_facade_impl.dart
@@ -179,10 +179,21 @@ class CoreFacadeImpl with ExceptionHandler, InfraLogger implements CoreFacade {
}
@override
- Stream> watchLogs() {
- return singbox
- .watchLogs(filesEditor.coreLogsPath)
- .handleExceptions(CoreServiceFailure.unexpected);
+ Stream>> watchLogs() {
+ return singbox.watchLogs(filesEditor.coreLogsPath).handleExceptions(
+ (error, stackTrace) {
+ loggy.warning("error watching logs", error, stackTrace);
+ return CoreServiceFailure.unexpected(error, stackTrace);
+ },
+ );
+ }
+
+ @override
+ TaskEither clearLogs() {
+ return exceptionHandler(
+ () => singbox.clearLogs().mapLeft(CoreServiceFailure.other).run(),
+ CoreServiceFailure.unexpected,
+ );
}
@override
diff --git a/lib/domain/singbox/box_log.dart b/lib/domain/singbox/box_log.dart
new file mode 100644
index 00000000..84ee25c1
--- /dev/null
+++ b/lib/domain/singbox/box_log.dart
@@ -0,0 +1,62 @@
+import 'package:dartx/dartx.dart';
+import 'package:flutter/material.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+import 'package:tint/tint.dart';
+
+part 'box_log.freezed.dart';
+
+enum LogLevel {
+ trace,
+ debug,
+ info,
+ warn,
+ error,
+ fatal,
+ panic;
+
+ static List get choices => values.takeFirst(4);
+
+ Color? get color => switch (this) {
+ trace => Colors.lightBlueAccent,
+ debug => Colors.grey,
+ info => Colors.lightGreen,
+ warn => Colors.orange,
+ error => Colors.redAccent,
+ fatal => Colors.red,
+ panic => Colors.red,
+ };
+}
+
+@freezed
+class BoxLog with _$BoxLog {
+ const factory BoxLog({
+ LogLevel? level,
+ DateTime? time,
+ required String message,
+ }) = _BoxLog;
+
+ factory BoxLog.parse(String log) {
+ log = log.strip();
+ DateTime? time;
+ if (log.length > 25) {
+ time = DateTime.tryParse(log.substring(6, 25));
+ }
+ if (time != null) {
+ log = log.substring(26);
+ }
+ final level = LogLevel.values.firstOrNullWhere(
+ (e) {
+ if (log.startsWith(e.name.toUpperCase())) {
+ log = log.removePrefix(e.name.toUpperCase());
+ return true;
+ }
+ return false;
+ },
+ );
+ return BoxLog(
+ level: level,
+ time: time,
+ message: log.trim(),
+ );
+ }
+}
diff --git a/lib/domain/singbox/config_options.dart b/lib/domain/singbox/config_options.dart
index 1890d8a9..4206a840 100644
--- a/lib/domain/singbox/config_options.dart
+++ b/lib/domain/singbox/config_options.dart
@@ -2,6 +2,8 @@ import 'dart:convert';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hiddify/core/prefs/prefs.dart';
+import 'package:hiddify/domain/singbox/box_log.dart';
+import 'package:hiddify/domain/singbox/rules.dart';
import 'package:hiddify/utils/platform_utils.dart';
part 'config_options.freezed.dart';
@@ -19,13 +21,13 @@ class ConfigOptions with _$ConfigOptions {
@Default(IPv6Mode.disable) IPv6Mode ipv6Mode,
@Default("tcp://8.8.8.8") String remoteDnsAddress,
@Default(DomainStrategy.auto) DomainStrategy remoteDnsDomainStrategy,
- @Default("8.8.8.8") String directDnsAddress,
+ @Default("local") String directDnsAddress,
@Default(DomainStrategy.auto) DomainStrategy directDnsDomainStrategy,
@Default(2334) int mixedPort,
@Default(6450) int localDnsPort,
@Default(TunImplementation.mixed) TunImplementation tunImplementation,
@Default(9000) int mtu,
- @Default("https://www.gstatic.com/generate_204") String connectionTestUrl,
+ @Default("http://cp.cloudflare.com/") String connectionTestUrl,
@IntervalConverter()
@Default(Duration(minutes: 10))
Duration urlTestInterval,
@@ -33,6 +35,9 @@ class ConfigOptions with _$ConfigOptions {
@Default(6756) int clashApiPort,
@Default(false) bool enableTun,
@Default(true) bool setSystemProxy,
+ @Default(false) bool bypassLan,
+ @Default(false) bool enableFakeDns,
+ List? rules,
}) = _ConfigOptions;
static ConfigOptions initial = ConfigOptions(
@@ -49,13 +54,6 @@ class ConfigOptions with _$ConfigOptions {
_$ConfigOptionsFromJson(json);
}
-enum LogLevel {
- warn,
- info,
- debug,
- trace,
-}
-
@JsonEnum(valueField: 'key')
enum IPv6Mode {
disable("ipv4_only"),
diff --git a/lib/domain/singbox/rules.dart b/lib/domain/singbox/rules.dart
index f30f024f..96c53c9e 100644
--- a/lib/domain/singbox/rules.dart
+++ b/lib/domain/singbox/rules.dart
@@ -1,5 +1,40 @@
+import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hiddify/core/prefs/locale_prefs.dart';
+part 'rules.freezed.dart';
+part 'rules.g.dart';
+
+@freezed
+class Rule with _$Rule {
+ @JsonSerializable(fieldRename: FieldRename.kebab)
+ const factory Rule({
+ required String id,
+ required String name,
+ @Default(false) bool enabled,
+ String? domains,
+ String? ip,
+ String? port,
+ String? protocol,
+ @Default(RuleNetwork.tcpAndUdp) RuleNetwork network,
+ @Default(RuleOutbound.proxy) RuleOutbound outbound,
+ }) = _Rule;
+
+ factory Rule.fromJson(Map json) => _$RuleFromJson(json);
+}
+
+enum RuleOutbound { proxy, bypass, block }
+
+@JsonEnum(valueField: 'key')
+enum RuleNetwork {
+ tcpAndUdp(""),
+ tcp("tcp"),
+ udp("udp");
+
+ const RuleNetwork(this.key);
+
+ final String? key;
+}
+
enum PerAppProxyMode {
off,
include,
@@ -26,11 +61,13 @@ enum PerAppProxyMode {
enum Region {
ir,
cn,
+ ru,
other;
String present(TranslationsEn t) => switch (this) {
ir => t.settings.general.regions.ir,
cn => t.settings.general.regions.cn,
+ ru => t.settings.general.regions.ru,
other => t.settings.general.regions.other,
};
}
diff --git a/lib/domain/singbox/singbox.dart b/lib/domain/singbox/singbox.dart
index 39bb5774..ea665c8f 100644
--- a/lib/domain/singbox/singbox.dart
+++ b/lib/domain/singbox/singbox.dart
@@ -1,3 +1,4 @@
+export 'box_log.dart';
export 'config_options.dart';
export 'core_status.dart';
export 'outbounds.dart';
diff --git a/lib/domain/singbox/singbox_facade.dart b/lib/domain/singbox/singbox_facade.dart
index bcd40f80..6a8d9541 100644
--- a/lib/domain/singbox/singbox_facade.dart
+++ b/lib/domain/singbox/singbox_facade.dart
@@ -37,5 +37,7 @@ abstract interface class SingboxFacade {
Stream> watchCoreStatus();
- Stream> watchLogs();
+ Stream>> watchLogs();
+
+ TaskEither clearLogs();
}
diff --git a/lib/features/common/common_controllers.dart b/lib/features/common/common_controllers.dart
index 8cfbb413..db6e7e2c 100644
--- a/lib/features/common/common_controllers.dart
+++ b/lib/features/common/common_controllers.dart
@@ -2,7 +2,6 @@ import 'package:hiddify/core/prefs/general_prefs.dart';
import 'package:hiddify/features/common/app_update_notifier.dart';
import 'package:hiddify/features/common/connectivity/connectivity_controller.dart';
import 'package:hiddify/features/common/window/window_controller.dart';
-import 'package:hiddify/features/logs/notifier/notifier.dart';
import 'package:hiddify/features/profiles/notifier/notifier.dart';
import 'package:hiddify/features/system_tray/controller/system_tray_controller.dart';
import 'package:hiddify/services/service_providers.dart';
@@ -24,10 +23,6 @@ void commonControllers(CommonControllersRef ref) {
},
fireImmediately: true,
);
- ref.listen(
- logsNotifierProvider,
- (previous, next) {},
- );
ref.listen(
connectivityControllerProvider,
(previous, next) {},
diff --git a/lib/features/logs/notifier/logs_notifier.dart b/lib/features/logs/notifier/logs_notifier.dart
index 6c7cfd43..b089840d 100644
--- a/lib/features/logs/notifier/logs_notifier.dart
+++ b/lib/features/logs/notifier/logs_notifier.dart
@@ -1,66 +1,133 @@
import 'dart:async';
-import 'package:dartx/dartx.dart';
import 'package:hiddify/data/data_providers.dart';
-import 'package:hiddify/domain/clash/clash.dart';
+import 'package:hiddify/domain/singbox/singbox.dart';
import 'package:hiddify/features/logs/notifier/logs_state.dart';
+import 'package:hiddify/utils/riverpod_utils.dart';
import 'package:hiddify/utils/utils.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
+import 'package:rxdart/rxdart.dart';
part 'logs_notifier.g.dart';
-// TODO: rewrite
-@Riverpod(keepAlive: true)
+@riverpod
class LogsNotifier extends _$LogsNotifier with AppLogger {
- static const maxLength = 1000;
-
@override
- Stream build() {
- state = const AsyncData(LogsState());
- return ref.read(coreFacadeProvider).watchLogs().asyncMap(
- (event) async {
- _logs = [
- event.getOrElse((l) => throw l),
- ..._logs.takeFirst(maxLength - 1),
- ];
- return switch (state) {
- // ignore: unused_result
- AsyncData(:final value) => value.copyWith(logs: await _computeLogs()),
- _ => LogsState(logs: await _computeLogs()),
- };
+ LogsState build() {
+ ref.disposeDelay(const Duration(seconds: 20));
+ state = const LogsState();
+ ref.onDispose(
+ () {
+ loggy.debug("disposing");
+ _listener?.cancel();
+ _listener = null;
},
);
+ ref.onCancel(
+ () {
+ if (_listener?.isPaused != true) {
+ loggy.debug("pausing");
+ _listener?.pause();
+ }
+ },
+ );
+ ref.onResume(
+ () {
+ if (!state.paused && (_listener?.isPaused ?? false)) {
+ loggy.debug("resuming");
+ _listener?.resume();
+ }
+ },
+ );
+
+ _addListeners();
+ return const LogsState();
}
- var _logs = [];
+ StreamSubscription? _listener;
+
+ Future _addListeners() async {
+ loggy.debug("adding listeners");
+ await _listener?.cancel();
+ _listener = ref
+ .read(coreFacadeProvider)
+ .watchLogs()
+ .throttle(
+ (_) => Stream.value(_listener?.isPaused ?? false),
+ leading: false,
+ trailing: true,
+ )
+ .throttleTime(
+ const Duration(milliseconds: 250),
+ leading: false,
+ trailing: true,
+ )
+ .asyncMap(
+ (event) async {
+ await event.fold(
+ (f) {
+ _logs = [];
+ state = state.copyWith(logs: AsyncError(f, StackTrace.current));
+ },
+ (a) async {
+ _logs = a.reversed;
+ state = state.copyWith(logs: AsyncData(await _computeLogs()));
+ },
+ );
+ },
+ ).listen((event) {});
+ }
+
+ Iterable _logs = [];
final _debouncer = CallbackDebouncer(const Duration(milliseconds: 200));
LogLevel? _levelFilter;
String _filter = "";
- Future> _computeLogs() async {
- if (_levelFilter == null && _filter.isEmpty) return _logs;
- return _logs.where((e) {
- return _filter.isEmpty || e.contains(_filter);
+ Future> _computeLogs() async {
+ final logs = _logs.map(BoxLog.parse);
+ if (_levelFilter == null && _filter.isEmpty) return logs.toList();
+ return logs.where((e) {
+ return (_filter.isEmpty || e.message.contains(_filter)) &&
+ (_levelFilter == null ||
+ e.level == null ||
+ e.level!.index >= _levelFilter!.index);
}).toList();
}
- void clear() {
- if (state case AsyncData(:final value)) {
- state = AsyncData(value.copyWith(logs: [])).copyWithPrevious(state);
- }
+ void pause() {
+ loggy.debug("pausing");
+ _listener?.pause();
+ state = state.copyWith(paused: true);
+ }
+
+ void resume() {
+ loggy.debug("resuming");
+ _listener?.resume();
+ state = state.copyWith(paused: false);
+ }
+
+ Future clear() async {
+ loggy.debug("clearing");
+ await ref.read(coreFacadeProvider).clearLogs().match(
+ (l) {
+ loggy.warning("error clearing logs", l);
+ },
+ (_) {
+ _logs = [];
+ state = state.copyWith(logs: const AsyncData([]));
+ },
+ ).run();
}
void filterMessage(String? filter) {
_filter = filter ?? '';
_debouncer(
() async {
- if (state case AsyncData(:final value)) {
- state = AsyncData(
- value.copyWith(
- filter: _filter,
- logs: await _computeLogs(),
- ),
- ).copyWithPrevious(state);
+ if (state.logs case AsyncData()) {
+ state = state.copyWith(
+ filter: _filter,
+ logs: AsyncData(await _computeLogs()),
+ );
}
},
);
@@ -68,13 +135,11 @@ class LogsNotifier extends _$LogsNotifier with AppLogger {
Future filterLevel(LogLevel? level) async {
_levelFilter = level;
- if (state case AsyncData(:final value)) {
- state = AsyncData(
- value.copyWith(
- levelFilter: _levelFilter,
- logs: await _computeLogs(),
- ),
- ).copyWithPrevious(state);
+ if (state.logs case AsyncData()) {
+ state = state.copyWith(
+ levelFilter: _levelFilter,
+ logs: AsyncData(await _computeLogs()),
+ );
}
}
}
diff --git a/lib/features/logs/notifier/logs_state.dart b/lib/features/logs/notifier/logs_state.dart
index ba34a075..4318870a 100644
--- a/lib/features/logs/notifier/logs_state.dart
+++ b/lib/features/logs/notifier/logs_state.dart
@@ -1,5 +1,6 @@
import 'package:freezed_annotation/freezed_annotation.dart';
-import 'package:hiddify/domain/clash/clash.dart';
+import 'package:hiddify/domain/singbox/singbox.dart';
+import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'logs_state.freezed.dart';
@@ -8,7 +9,8 @@ class LogsState with _$LogsState {
const LogsState._();
const factory LogsState({
- @Default([]) List logs,
+ @Default(AsyncLoading()) AsyncValue> logs,
+ @Default(false) bool paused,
@Default("") String filter,
LogLevel? levelFilter,
}) = _LogsState;
diff --git a/lib/features/logs/view/logs_page.dart b/lib/features/logs/view/logs_page.dart
index af2e7954..7c5d093c 100644
--- a/lib/features/logs/view/logs_page.dart
+++ b/lib/features/logs/view/logs_page.dart
@@ -1,31 +1,31 @@
-import 'package:dartx/dartx.dart';
+
import 'package:flutter/material.dart';
+import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:fpdart/fpdart.dart';
import 'package:gap/gap.dart';
import 'package:hiddify/core/core_providers.dart';
import 'package:hiddify/core/prefs/prefs.dart';
-import 'package:hiddify/domain/clash/clash.dart';
import 'package:hiddify/domain/failures.dart';
-import 'package:hiddify/features/common/common.dart';
+import 'package:hiddify/domain/singbox/singbox.dart';
import 'package:hiddify/features/logs/notifier/notifier.dart';
import 'package:hiddify/services/service_providers.dart';
import 'package:hiddify/utils/utils.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
-import 'package:tint/tint.dart';
-
class LogsPage extends HookConsumerWidget with PresLogger {
const LogsPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final t = ref.watch(translationsProvider);
- final asyncState = ref.watch(logsNotifierProvider);
+ final state = ref.watch(logsNotifierProvider);
final notifier = ref.watch(logsNotifierProvider.notifier);
final debug = ref.watch(debugModeNotifierProvider);
final filesEditor = ref.watch(filesEditorServiceProvider);
+ final filterController = useTextEditingController(text: state.filter);
+
final List popupButtons = debug || PlatformUtils.isDesktop
? [
PopupMenuItem(
@@ -49,115 +49,146 @@ class LogsPage extends HookConsumerWidget with PresLogger {
]
: [];
- switch (asyncState) {
- case AsyncData(value: final state):
- return Scaffold(
- appBar: AppBar(
- // TODO: fix height
- toolbarHeight: 90,
- title: Text(t.logs.pageTitle),
- actions: [
- if (popupButtons.isNotEmpty)
- PopupMenuButton(
- itemBuilder: (context) {
- return popupButtons;
- },
- ),
- ],
- bottom: PreferredSize(
- preferredSize: const Size.fromHeight(36),
- child: Padding(
- padding:
- const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
- child: Row(
- children: [
- Flexible(
- child: TextFormField(
- onChanged: notifier.filterMessage,
- decoration: InputDecoration(
- isDense: true,
- hintText: t.logs.filterHint,
- ),
- ),
+ return Scaffold(
+ appBar: AppBar(
+ // TODO: fix height
+ toolbarHeight: 90,
+ title: Text(t.logs.pageTitle),
+ actions: [
+ if (state.paused)
+ IconButton(
+ onPressed: notifier.resume,
+ icon: const Icon(Icons.play_arrow),
+ tooltip: t.logs.resumeTooltip,
+ )
+ else
+ IconButton(
+ onPressed: notifier.pause,
+ icon: const Icon(Icons.pause),
+ tooltip: t.logs.pauseTooltip,
+ ),
+ IconButton(
+ onPressed: notifier.clear,
+ icon: const Icon(Icons.clear_all),
+ tooltip: t.logs.clearTooltip,
+ ),
+ if (popupButtons.isNotEmpty)
+ PopupMenuButton(
+ itemBuilder: (context) {
+ return popupButtons;
+ },
+ ),
+ ],
+ bottom: PreferredSize(
+ preferredSize: const Size.fromHeight(36),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
+ child: Row(
+ children: [
+ Flexible(
+ child: TextFormField(
+ controller: filterController,
+ onChanged: notifier.filterMessage,
+ decoration: InputDecoration(
+ isDense: true,
+ hintText: t.logs.filterHint,
),
- const Gap(16),
- DropdownButton>(
- value: optionOf(state.levelFilter),
- onChanged: (v) {
- if (v == null) return;
- notifier.filterLevel(v.toNullable());
- },
- padding: const EdgeInsets.symmetric(horizontal: 8),
- borderRadius: BorderRadius.circular(4),
- items: [
- DropdownMenuItem(
- value: none(),
- child: Text(t.logs.allLevelsFilter),
- ),
- ...LogLevel.values.takeFirst(3).map(
- (e) => DropdownMenuItem(
- value: some(e),
- child: Text(e.name),
- ),
- ),
- ],
+ ),
+ ),
+ const Gap(16),
+ DropdownButton >(
+ value: optionOf(state.levelFilter),
+ onChanged: (v) {
+ if (v == null) return;
+ notifier.filterLevel(v.toNullable());
+ },
+ padding: const EdgeInsets.symmetric(horizontal: 8),
+ borderRadius: BorderRadius.circular(4),
+ items: [
+ DropdownMenuItem(
+ value: none(),
+ child: Text(t.logs.allLevelsFilter),
+ ),
+ ...LogLevel.choices.map(
+ (e) => DropdownMenuItem(
+ value: some(e),
+ child: Text(e.name),
+ ),
),
],
),
- ),
+ ],
),
),
- body: ListView.builder(
- itemCount: state.logs.length,
- reverse: true,
- itemBuilder: (context, index) {
- final log = state.logs[index];
- return Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- ListTile(
- dense: true,
- subtitle: Text(log.strip()),
- ),
- if (index != 0)
- const Divider(
- indent: 16,
- endIndent: 16,
- height: 4,
+ ),
+ ),
+ body: switch (state.logs) {
+ AsyncData(value: final logs) => SelectionArea(
+ child: ListView.builder(
+ itemCount: logs.length,
+ reverse: true,
+ itemBuilder: (context, index) {
+ final log = logs[index];
+ return Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 16,
+ vertical: 4,
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ if (log.level != null)
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ log.level!.name.toUpperCase(),
+ style: Theme.of(context)
+ .textTheme
+ .labelMedium
+ ?.copyWith(color: log.level!.color),
+ ),
+ if (log.time != null)
+ Text(
+ log.time!.toString(),
+ style:
+ Theme.of(context).textTheme.labelSmall,
+ ),
+ ],
+ ),
+ Text(
+ log.message,
+ style: Theme.of(context).textTheme.bodySmall,
+ ),
+ ],
+ ),
),
- ],
- );
- },
+ if (index != 0)
+ const Divider(
+ indent: 16,
+ endIndent: 16,
+ height: 4,
+ ),
+ ],
+ );
+ },
+ ),
),
- );
-
- case AsyncError(:final error):
- return Scaffold(
- body: CustomScrollView(
+ AsyncError(:final error) => CustomScrollView(
slivers: [
- NestedTabAppBar(
- title: Text(t.logs.pageTitle),
- ),
SliverErrorBodyPlaceholder(t.presentShortError(error)),
],
),
- );
-
- case AsyncLoading():
- return Scaffold(
- body: CustomScrollView(
+ _ => const CustomScrollView(
slivers: [
- NestedTabAppBar(
- title: Text(t.logs.pageTitle),
- ),
- const SliverLoadingBodyPlaceholder(),
+ SliverLoadingBodyPlaceholder(),
],
),
- );
-
- // TODO: remove
- default:
- return const Scaffold();
- }
+ },
+ );
}
}
diff --git a/lib/features/settings/view/config_options_page.dart b/lib/features/settings/view/config_options_page.dart
index a677fd7a..87e804f5 100644
--- a/lib/features/settings/view/config_options_page.dart
+++ b/lib/features/settings/view/config_options_page.dart
@@ -52,13 +52,13 @@ class ConfigOptionsPage extends HookConsumerWidget {
),
ListTile(
title: Text(t.settings.config.logLevel),
- subtitle: Text(options.logLevel.name),
+ subtitle: Text(options.logLevel.name.toUpperCase()),
onTap: () async {
final logLevel = await SettingsPickerDialog(
title: t.settings.config.logLevel,
selected: options.logLevel,
- options: LogLevel.values,
- getTitle: (e) => e.name,
+ options: LogLevel.choices,
+ getTitle: (e) => e.name.toUpperCase(),
resetValue: _default.logLevel,
).show(context);
if (logLevel == null) return;
diff --git a/lib/features/settings/widgets/advanced_setting_tiles.dart b/lib/features/settings/widgets/advanced_setting_tiles.dart
index 82b94bdb..64274c21 100644
--- a/lib/features/settings/widgets/advanced_setting_tiles.dart
+++ b/lib/features/settings/widgets/advanced_setting_tiles.dart
@@ -6,6 +6,7 @@ import 'package:hiddify/core/core_providers.dart';
import 'package:hiddify/core/prefs/prefs.dart';
import 'package:hiddify/core/router/routes/routes.dart';
import 'package:hiddify/domain/singbox/singbox.dart';
+import 'package:hiddify/features/common/common.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
class AdvancedSettingTiles extends HookConsumerWidget {
@@ -20,6 +21,7 @@ class AdvancedSettingTiles extends HookConsumerWidget {
return Column(
children: [
+ const RegionPrefTile(),
ListTile(
title: Text(t.settings.config.pageTitle),
leading: const Icon(Icons.edit_document),
diff --git a/lib/features/settings/widgets/general_setting_tiles.dart b/lib/features/settings/widgets/general_setting_tiles.dart
index f8b76249..a66cb81e 100644
--- a/lib/features/settings/widgets/general_setting_tiles.dart
+++ b/lib/features/settings/widgets/general_setting_tiles.dart
@@ -3,7 +3,6 @@ import 'package:go_router/go_router.dart';
import 'package:hiddify/core/core_providers.dart';
import 'package:hiddify/core/prefs/prefs.dart';
import 'package:hiddify/features/common/common.dart';
-import 'package:hiddify/features/settings/widgets/theme_mode_switch_button.dart';
import 'package:hiddify/services/auto_start_service.dart';
import 'package:hiddify/utils/utils.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -44,31 +43,34 @@ class GeneralSettingTiles extends HookConsumerWidget {
),
ListTile(
title: Text(t.settings.general.themeMode),
- subtitle: Text(
- switch (theme.mode) {
- ThemeMode.system => t.settings.general.themeModes.system,
- ThemeMode.light => t.settings.general.themeModes.light,
- ThemeMode.dark => t.settings.general.themeModes.dark,
- },
- ),
- trailing: ThemeModeSwitch(
- themeMode: theme.mode,
- onChanged: ref.read(themeModeNotifierProvider.notifier).update,
- ),
+ subtitle: Text(theme.mode.present(t)),
leading: const Icon(Icons.light_mode),
onTap: () async {
- await ref.read(themeModeNotifierProvider.notifier).update(
- Theme.of(context).brightness == Brightness.light
- ? ThemeMode.dark
- : ThemeMode.light,
+ final selectedThemeMode = await showDialog(
+ context: context,
+ builder: (context) {
+ return SimpleDialog(
+ title: Text(t.settings.general.themeMode),
+ children: AppThemeMode.values
+ .map(
+ (e) => RadioListTile(
+ title: Text(e.present(t)),
+ value: e,
+ groupValue: theme.mode,
+ onChanged: (e) => context.pop(e),
+ ),
+ )
+ .toList(),
);
+ },
+ );
+ if (selectedThemeMode != null) {
+ await ref
+ .read(themeModeNotifierProvider.notifier)
+ .update(selectedThemeMode);
+ }
},
),
- SwitchListTile(
- title: Text(t.settings.general.trueBlack),
- value: theme.trueBlack,
- onChanged: ref.read(trueBlackThemeNotifierProvider.notifier).update,
- ),
if (PlatformUtils.isDesktop) ...[
SwitchListTile(
title: Text(t.settings.general.autoStart),
diff --git a/lib/features/settings/widgets/theme_mode_switch_button.dart b/lib/features/settings/widgets/theme_mode_switch_button.dart
deleted file mode 100644
index 580a633b..00000000
--- a/lib/features/settings/widgets/theme_mode_switch_button.dart
+++ /dev/null
@@ -1,51 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:hiddify/core/core_providers.dart';
-import 'package:hooks_riverpod/hooks_riverpod.dart';
-
-class ThemeModeSwitch extends HookConsumerWidget {
- const ThemeModeSwitch({
- super.key,
- required this.themeMode,
- required this.onChanged,
- });
- final ThemeMode themeMode;
- final ValueChanged onChanged;
-
- @override
- Widget build(BuildContext context, WidgetRef ref) {
- final t = ref.watch(translationsProvider);
-
- final List isSelected = [
- themeMode == ThemeMode.light,
- themeMode == ThemeMode.system,
- themeMode == ThemeMode.dark,
- ];
-
- return ToggleButtons(
- isSelected: isSelected,
- onPressed: (int newIndex) {
- if (newIndex == 0) {
- onChanged(ThemeMode.light);
- } else if (newIndex == 1) {
- onChanged(ThemeMode.system);
- } else {
- onChanged(ThemeMode.dark);
- }
- },
- children: [
- Icon(
- Icons.wb_sunny,
- semanticLabel: t.settings.general.themeModes.light,
- ),
- Icon(
- Icons.phone_iphone,
- semanticLabel: t.settings.general.themeModes.system,
- ),
- Icon(
- Icons.bedtime,
- semanticLabel: t.settings.general.themeModes.dark,
- ),
- ],
- );
- }
-}
diff --git a/lib/services/singbox/ffi_singbox_service.dart b/lib/services/singbox/ffi_singbox_service.dart
index e2f75d9c..8b9b3aff 100644
--- a/lib/services/singbox/ffi_singbox_service.dart
+++ b/lib/services/singbox/ffi_singbox_service.dart
@@ -8,7 +8,7 @@ import 'package:combine/combine.dart';
import 'package:ffi/ffi.dart';
import 'package:fpdart/fpdart.dart';
import 'package:hiddify/domain/connectivity/connectivity.dart';
-import 'package:hiddify/domain/singbox/config_options.dart';
+import 'package:hiddify/domain/singbox/singbox.dart';
import 'package:hiddify/gen/singbox_generated_bindings.dart';
import 'package:hiddify/services/singbox/shared.dart';
import 'package:hiddify/services/singbox/singbox_service.dart';
@@ -16,6 +16,7 @@ import 'package:hiddify/utils/utils.dart';
import 'package:loggy/loggy.dart';
import 'package:path/path.dart' as p;
import 'package:rxdart/rxdart.dart';
+import 'package:watcher/watcher.dart';
final _logger = Loggy('FFISingboxService');
@@ -301,33 +302,47 @@ class FFISingboxService
);
}
+ final _logBuffer = [];
+ int _logFilePosition = 0;
+
@override
- Stream watchLogs(String path) {
- var linesRead = 0;
- return Stream.periodic(
- const Duration(seconds: 1),
- ).asyncMap((_) async {
- final result = await _readLogs(path, linesRead);
- linesRead = result.$2;
- return result.$1;
- }).transform(
- StreamTransformer.fromHandlers(
- handleData: (data, sink) {
- for (final item in data) {
- sink.add(item);
- }
- },
- ),
- );
+ Stream> watchLogs(String path) async* {
+ yield await _readLogFile(File(path));
+ yield* Watcher(path, pollingDelay: const Duration(seconds: 1))
+ .events
+ .asyncMap((event) async {
+ if (event.type == ChangeType.MODIFY) {
+ await _readLogFile(File(path));
+ }
+ return _logBuffer;
+ });
}
- Future<(List, int)> _readLogs(String path, int from) async {
- return CombineWorker().execute(
+ @override
+ TaskEither clearLogs() {
+ return TaskEither(
() async {
- final lines = await File(path).readAsLines();
- final to = lines.length;
- return (lines.sublist(from), to);
+ _logBuffer.clear();
+ return right(unit);
},
);
}
+
+ Future> _readLogFile(File file) async {
+ if (_logFilePosition == 0 && file.lengthSync() == 0) return [];
+ final content =
+ await file.openRead(_logFilePosition).transform(utf8.decoder).join();
+ _logFilePosition = file.lengthSync();
+ final lines = const LineSplitter().convert(content);
+ if (lines.length > 300) {
+ lines.removeRange(0, lines.length - 300);
+ }
+ for (final line in lines) {
+ _logBuffer.add(line);
+ if (_logBuffer.length > 300) {
+ _logBuffer.removeAt(0);
+ }
+ }
+ return _logBuffer;
+ }
}
diff --git a/lib/services/singbox/mobile_singbox_service.dart b/lib/services/singbox/mobile_singbox_service.dart
index 83d1e1cc..aaf2b399 100644
--- a/lib/services/singbox/mobile_singbox_service.dart
+++ b/lib/services/singbox/mobile_singbox_service.dart
@@ -163,11 +163,18 @@ class MobileSingboxService
}
@override
- Stream watchLogs(String path) {
- return _logsChannel.receiveBroadcastStream().map(
- (event) {
- // loggy.debug("received log: $event");
- return event as String;
+ Stream> watchLogs(String path) async* {
+ yield* _logsChannel
+ .receiveBroadcastStream()
+ .map((event) => (event as List).map((e) => e as String).toList());
+ }
+
+ @override
+ TaskEither clearLogs() {
+ return TaskEither(
+ () async {
+ await _methodChannel.invokeMethod("clear_logs");
+ return right(unit);
},
);
}
diff --git a/lib/services/singbox/singbox_service.dart b/lib/services/singbox/singbox_service.dart
index 5f1c31f4..eb181695 100644
--- a/lib/services/singbox/singbox_service.dart
+++ b/lib/services/singbox/singbox_service.dart
@@ -48,5 +48,7 @@ abstract interface class SingboxService {
Stream watchStats();
- Stream watchLogs(String path);
+ Stream> watchLogs(String path);
+
+ TaskEither clearLogs();
}
diff --git a/libcore b/libcore
index d410fe1c..7b367fe7 160000
--- a/libcore
+++ b/libcore
@@ -1 +1 @@
-Subproject commit d410fe1c4b0d90f545716737d53e0002be9504cd
+Subproject commit 7b367fe70c9ecbf0dda2b73289905565e2451745
diff --git a/pubspec.lock b/pubspec.lock
index 609db473..68535a90 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -1555,7 +1555,7 @@ packages:
source: hosted
version: "11.10.0"
watcher:
- dependency: transitive
+ dependency: "direct main"
description:
name: watcher
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
diff --git a/pubspec.yaml b/pubspec.yaml
index 5016d264..f2ef1f46 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -10,8 +10,6 @@ dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.6
-
- # internationalization
flutter_localizations:
sdk: flutter
intl: ^0.18.1
@@ -19,28 +17,18 @@ dependencies:
slang_flutter: ^3.24.0
timeago: ^3.5.0
flutter_localized_locales: ^2.0.5
-
- # data & serialization
fpdart: ^1.1.0
freezed_annotation: ^2.4.1
json_annotation: ^4.8.1
-
- # state management
hooks_riverpod: ^2.4.3
flutter_hooks: ^0.20.3
riverpod_annotation: ^2.2.0
rxdart: ^0.27.7
-
- # persistence
drift: ^2.12.1
sqlite3_flutter_libs: ^0.5.16
shared_preferences: ^2.2.2
-
- # networking
dio: ^5.3.3
web_socket_channel: ^2.4.0
-
- # native
ffi: ^2.1.0
path_provider: ^2.1.1
flutter_local_notifications: ^15.1.1
@@ -54,13 +42,9 @@ dependencies:
url_launcher: ^6.1.14
vclibs: ^0.1.0
launch_at_startup: ^0.2.2
-
- # analytics
sentry_flutter: ^7.10.1
sentry_dart_plugin: ^1.6.2
sentry_dio: ^7.10.1
-
- # utils
combine: ^0.5.6
path: ^1.8.3
loggy: ^2.0.3
@@ -73,8 +57,7 @@ dependencies:
accessibility_tools: ^1.0.0
neat_periodic_task: ^2.0.1
retry: ^3.1.2
-
- # widgets
+ watcher: ^1.1.0
go_router: ^11.1.4
flex_color_scheme: ^7.3.1
flutter_animate: ^4.2.0+1
@@ -170,3 +153,8 @@ sentry:
upload_sources: true
log_level: info
ignore_missing: true
+
+cider:
+ link_template:
+ tag: https://github.com/hiddify/hiddify-next/releases/tag/%tag%
+ diff: https://github.com/hiddify/hiddify-next/compare/%from%...%to%