From 56fd9df2e9644e08f78bc3d85203ad457b218715 Mon Sep 17 00:00:00 2001
From: armv9 <48624112+arm64v8a@users.noreply.github.com>
Date: Tue, 2 Sep 2025 19:47:44 +0900
Subject: [PATCH] dev check update
---
.../java/io/nekohasekai/sagernet/SagerNet.kt | 14 +
.../nekohasekai/sagernet/ui/AboutFragment.kt | 293 +++++++++++-------
.../sagernet/utils/CrashHandler.kt | 8 +-
app/src/main/res/values-zh-rCN/strings.xml | 5 +
app/src/main/res/values/strings.xml | 5 +
buildSrc/src/main/kotlin/Helpers.kt | 38 +--
nb4a.properties | 1 +
7 files changed, 222 insertions(+), 142 deletions(-)
diff --git a/app/src/main/java/io/nekohasekai/sagernet/SagerNet.kt b/app/src/main/java/io/nekohasekai/sagernet/SagerNet.kt
index 2cc881b..02886a6 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/SagerNet.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/SagerNet.kt
@@ -20,6 +20,8 @@ import go.Seq
import io.nekohasekai.sagernet.bg.SagerConnection
import io.nekohasekai.sagernet.database.DataStore
import io.nekohasekai.sagernet.ktx.Logs
+import io.nekohasekai.sagernet.ktx.isOss
+import io.nekohasekai.sagernet.ktx.isPreview
import io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher
import io.nekohasekai.sagernet.ui.MainActivity
import io.nekohasekai.sagernet.utils.*
@@ -194,6 +196,18 @@ class SagerNet : Application(),
var underlyingNetwork: Network? = null
+ var appVersionNameForDisplay = lazy {
+ var n = BuildConfig.VERSION_NAME
+ if (isPreview) {
+ n += " " + BuildConfig.PRE_VERSION_NAME
+ } else if (!isOss) {
+ n += " ${BuildConfig.FLAVOR}"
+ }
+ if (BuildConfig.DEBUG) {
+ n += " DEBUG"
+ }
+ n
+ }
}
}
diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/AboutFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/AboutFragment.kt
index 54ed72a..3609ae6 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/ui/AboutFragment.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/ui/AboutFragment.kt
@@ -10,6 +10,7 @@ import android.os.PowerManager
import android.provider.Settings
import android.text.util.Linkify
import android.view.View
+import android.widget.Toast
import androidx.activity.result.component1
import androidx.activity.result.component2
import androidx.activity.result.contract.ActivityResultContracts
@@ -28,6 +29,12 @@ import io.nekohasekai.sagernet.utils.PackageCache
import io.nekohasekai.sagernet.widget.ListListener
import libcore.Libcore
import moe.matsuri.nb4a.plugin.Plugins
+import androidx.core.net.toUri
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import io.nekohasekai.sagernet.SagerNet
+import io.nekohasekai.sagernet.database.DataStore
+import moe.matsuri.nb4a.utils.Util
+import org.json.JSONObject
class AboutFragment : ToolbarFragment(R.layout.layout_about) {
@@ -65,128 +72,133 @@ class AboutFragment : ToolbarFragment(R.layout.layout_about) {
}
override fun getMaterialAboutList(activityContext: Context): MaterialAboutList {
-
- var versionName = BuildConfig.VERSION_NAME
- if (!isOss) {
- versionName += " ${BuildConfig.FLAVOR}"
- }
- if (BuildConfig.DEBUG) {
- versionName += " DEBUG"
- }
-
return MaterialAboutList.Builder()
.addCard(
MaterialAboutCard.Builder()
- .outline(false)
- .addItem(
- MaterialAboutActionItem.Builder()
- .icon(R.drawable.ic_baseline_update_24)
- .text(R.string.app_version)
- .subText(versionName)
- .setOnClickAction {
- requireContext().launchCustomTab(
- "https://github.com/MatsuriDayo/NekoBoxForAndroid/releases"
- )
- }
- .build())
- .addItem(
- MaterialAboutActionItem.Builder()
- .icon(R.drawable.ic_baseline_layers_24)
- .text(getString(R.string.version_x, "sing-box"))
- .subText(Libcore.versionBox())
- .setOnClickAction { }
- .build())
- .addItem(
- MaterialAboutActionItem.Builder()
- .icon(R.drawable.ic_baseline_card_giftcard_24)
- .text(R.string.donate)
- .subText(R.string.donate_info)
- .setOnClickAction {
- requireContext().launchCustomTab(
- "https://matsuridayo.github.io/index_docs/#donate"
- )
- }
- .build())
- .apply {
- PackageCache.awaitLoadSync()
- for ((_, pkg) in PackageCache.installedPluginPackages) {
- try {
- val pluginId =
- pkg.providers?.get(0)?.loadString(Plugins.METADATA_KEY_ID)
- if (pluginId.isNullOrBlank()) continue
- addItem(
- MaterialAboutActionItem.Builder()
- .icon(R.drawable.ic_baseline_nfc_24)
- .text(
- getString(
- R.string.version_x,
- pluginId
- ) + " (${Plugins.displayExeProvider(pkg.packageName)})"
+ .outline(false)
+ .addItem(
+ MaterialAboutActionItem.Builder()
+ .icon(R.drawable.ic_baseline_update_24)
+ .text(R.string.app_version)
+ .subText(SagerNet.appVersionNameForDisplay.value)
+ .setOnClickAction {
+ requireContext().launchCustomTab(
+ "https://github.com/MatsuriDayo/NekoBoxForAndroid/releases"
)
- .subText("v" + pkg.versionName)
- .setOnClickAction {
- startActivity(Intent().apply {
- action =
- Settings.ACTION_APPLICATION_DETAILS_SETTINGS
- data = Uri.fromParts(
- "package", pkg.packageName, null
+ }
+ .build())
+ .addItem(
+ MaterialAboutActionItem.Builder()
+ .text(R.string.check_update_release)
+ .setOnClickAction {
+ checkUpdate(false)
+ }
+ .build())
+ .addItem(
+ MaterialAboutActionItem.Builder()
+ .text(R.string.check_update_preview)
+ .setOnClickAction {
+ checkUpdate(true)
+ }
+ .build())
+ .addItem(
+ MaterialAboutActionItem.Builder()
+ .icon(R.drawable.ic_baseline_layers_24)
+ .text(getString(R.string.version_x, "sing-box"))
+ .subText(Libcore.versionBox())
+ .setOnClickAction { }
+ .build())
+ .addItem(
+ MaterialAboutActionItem.Builder()
+ .icon(R.drawable.ic_baseline_card_giftcard_24)
+ .text(R.string.donate)
+ .subText(R.string.donate_info)
+ .setOnClickAction {
+ requireContext().launchCustomTab(
+ "https://matsuridayo.github.io/index_docs/#donate"
+ )
+ }
+ .build())
+ .apply {
+ PackageCache.awaitLoadSync()
+ for ((_, pkg) in PackageCache.installedPluginPackages) {
+ try {
+ val pluginId =
+ pkg.providers?.get(0)?.loadString(Plugins.METADATA_KEY_ID)
+ if (pluginId.isNullOrBlank()) continue
+ addItem(
+ MaterialAboutActionItem.Builder()
+ .icon(R.drawable.ic_baseline_nfc_24)
+ .text(
+ getString(
+ R.string.version_x,
+ pluginId
+ ) + " (${Plugins.displayExeProvider(pkg.packageName)})"
)
- })
- }
- .build())
- } catch (e: Exception) {
- Logs.w(e)
+ .subText("v" + pkg.versionName)
+ .setOnClickAction {
+ startActivity(Intent().apply {
+ action =
+ Settings.ACTION_APPLICATION_DETAILS_SETTINGS
+ data = Uri.fromParts(
+ "package", pkg.packageName, null
+ )
+ })
+ }
+ .build())
+ } catch (e: Exception) {
+ Logs.w(e)
+ }
}
}
- }
- .apply {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- val pm = app.getSystemService(Context.POWER_SERVICE) as PowerManager
- if (!pm.isIgnoringBatteryOptimizations(app.packageName)) {
- addItem(
- MaterialAboutActionItem.Builder()
- .icon(R.drawable.ic_baseline_running_with_errors_24)
- .text(R.string.ignore_battery_optimizations)
- .subText(R.string.ignore_battery_optimizations_sum)
- .setOnClickAction {
- requestIgnoreBatteryOptimizations.launch(
- Intent(
- Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
- Uri.parse("package:${app.packageName}")
- )
- )
- }
- .build())
+ .apply {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ val pm = app.getSystemService(Context.POWER_SERVICE) as PowerManager
+ if (!pm.isIgnoringBatteryOptimizations(app.packageName)) {
+ addItem(
+ MaterialAboutActionItem.Builder()
+ .icon(R.drawable.ic_baseline_running_with_errors_24)
+ .text(R.string.ignore_battery_optimizations)
+ .subText(R.string.ignore_battery_optimizations_sum)
+ .setOnClickAction {
+ requestIgnoreBatteryOptimizations.launch(
+ Intent(
+ Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
+ "package:${app.packageName}".toUri()
+ )
+ )
+ }
+ .build())
+ }
}
}
- }
- .build())
+ .build())
.addCard(
MaterialAboutCard.Builder()
- .outline(false)
- .title(R.string.project)
- .addItem(
- MaterialAboutActionItem.Builder()
- .icon(R.drawable.ic_baseline_sanitizer_24)
- .text(R.string.github)
- .setOnClickAction {
- requireContext().launchCustomTab(
- "https://github.com/MatsuriDayo/NekoBoxForAndroid"
+ .outline(false)
+ .title(R.string.project)
+ .addItem(
+ MaterialAboutActionItem.Builder()
+ .icon(R.drawable.ic_baseline_sanitizer_24)
+ .text(R.string.github)
+ .setOnClickAction {
+ requireContext().launchCustomTab(
+ "https://github.com/MatsuriDayo/NekoBoxForAndroid"
- )
- }
+ )
+ }
+ .build())
+ .addItem(
+ MaterialAboutActionItem.Builder()
+ .icon(R.drawable.ic_qu_shadowsocks_foreground)
+ .text(R.string.telegram)
+ .setOnClickAction {
+ requireContext().launchCustomTab(
+ "https://t.me/MatsuriDayo"
+ )
+ }
+ .build())
.build())
- .addItem(
- MaterialAboutActionItem.Builder()
- .icon(R.drawable.ic_qu_shadowsocks_foreground)
- .text(R.string.telegram)
- .setOnClickAction {
- requireContext().launchCustomTab(
- "https://t.me/MatsuriDayo"
- )
- }
- .build())
- .build())
.build()
}
@@ -199,6 +211,69 @@ class AboutFragment : ToolbarFragment(R.layout.layout_about) {
}
}
+ fun checkUpdate(checkPreview: Boolean) {
+ runOnIoDispatcher {
+ try {
+ val client = Libcore.newHttpClient().apply {
+ modernTLS()
+ keepAlive()
+ trySocks5(DataStore.mixedPort)
+ }
+ val response = client.newRequest().apply {
+ if (checkPreview) {
+ setURL("https://api.github.com/repos/MatsuriDayo/NekoBoxForAndroid/releases/tags/preview")
+ } else {
+ setURL("https://api.github.com/repos/MatsuriDayo/NekoBoxForAndroid/releases/latest")
+ }
+ }.execute()
+ val release = JSONObject(Util.getStringBox(response.contentString))
+ val releaseName = release.getString("name")
+ val releaseUrl = release.getString("html_url")
+ var haveUpdate = releaseName.isNotBlank()
+ haveUpdate = if (isPreview) {
+ if (checkPreview) {
+ haveUpdate && releaseName != BuildConfig.PRE_VERSION_NAME
+ } else {
+ // User: 1.3.9 pre-1.4.0 Stable: 1.3.9 -> No update
+ haveUpdate && releaseName != BuildConfig.VERSION_NAME
+ }
+ } else {
+ // User: 1.4.0 Preview: pre-1.4.0 -> No update
+ // User: 1.4.0 Preview: pre-1.4.1 -> Update
+ // User: 1.4.0 Stable: 1.4.0 -> No update
+ // User: 1.4.0 Stable: 1.4.1 -> Update
+ haveUpdate && !releaseName.contains(BuildConfig.VERSION_NAME)
+ }
+ runOnMainDispatcher {
+ if (haveUpdate) {
+ val context = requireContext()
+ MaterialAlertDialogBuilder(context)
+ .setTitle(R.string.update_dialog_title)
+ .setMessage(
+ context.getString(
+ R.string.update_dialog_message,
+ SagerNet.appVersionNameForDisplay.value,
+ releaseName
+ )
+ )
+ .setPositiveButton(R.string.yes) { _, _ ->
+ val intent = Intent(Intent.ACTION_VIEW, releaseUrl.toUri())
+ context.startActivity(intent)
+ }
+ .setNegativeButton(R.string.no, null)
+ .show()
+ } else {
+ Toast.makeText(app, R.string.check_update_no, Toast.LENGTH_SHORT).show()
+ }
+ }
+ } catch (e: Exception) {
+ runOnMainDispatcher {
+ Toast.makeText(app, e.readableMessage, Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+ }
+
}
}
\ No newline at end of file
diff --git a/app/src/main/java/io/nekohasekai/sagernet/utils/CrashHandler.kt b/app/src/main/java/io/nekohasekai/sagernet/utils/CrashHandler.kt
index 292f929..2aa93fa 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/utils/CrashHandler.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/utils/CrashHandler.kt
@@ -6,6 +6,7 @@ import android.os.Build
import android.util.Log
import com.jakewharton.processphoenix.ProcessPhoenix
import io.nekohasekai.sagernet.BuildConfig
+import io.nekohasekai.sagernet.SagerNet
import io.nekohasekai.sagernet.database.preference.PublicDatabase
import io.nekohasekai.sagernet.ktx.Logs
import io.nekohasekai.sagernet.ktx.app
@@ -61,7 +62,7 @@ object CrashHandler : Thread.UncaughtExceptionHandler {
fun buildReportHeader(): String {
var report = ""
- report += "NekoBox for Andoird ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE}) ${BuildConfig.FLAVOR.uppercase()}\n"
+ report += "NekoBox for Android ${SagerNet.appVersionNameForDisplay} (${BuildConfig.VERSION_CODE})\n"
report += "Date: ${getCurrentMilliSecondUTCTimeStamp()}\n\n"
report += "OS_VERSION: ${getSystemPropertyWithAndroidAPI("os.version")}\n"
report += "SDK_INT: ${Build.VERSION.SDK_INT}\n"
@@ -102,7 +103,7 @@ object CrashHandler : Thread.UncaughtExceptionHandler {
report += "\n"
report += pair.key + ": " + pair.toString()
}
- }catch (e: Exception) {
+ } catch (e: Exception) {
report += "Export settings failed: " + formatThrowable(e)
}
@@ -136,7 +137,8 @@ object CrashHandler : Thread.UncaughtExceptionHandler {
if (matcher.matches()) {
key = matcher.group(1)
value = matcher.group(2)
- if (key != null && value != null && !key.isEmpty() && !value.isEmpty()) systemProperties[key] = value
+ if (key != null && value != null && !key.isEmpty() && !value.isEmpty()) systemProperties[key] =
+ value
}
}
bufferedReader.close()
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 4bb98ef..6345801 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -488,4 +488,9 @@
当设备从睡眠状态唤醒时重置出站连接
预览版
本应用为预览版,可能存在诸多问题。若您不愿参与测试,请前往GitHub下载正式发布版本!
+ 检查预览版更新
+ 检查正式版
+ 发现新版本
+ 当前版本:%1$s\n可升级版本:%2$s\n是否前往下载?
+ 检查成功,但没有更新。
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b72a057..b638f05 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -568,4 +568,9 @@
Reset outbound connections when device wake from sleep
Preview version
This application is a preview version and may contain many problems. If you do not want to test it, please go to GitHub to download the Release version!
+ Check for preview version updates
+ Check for release version updates
+ New version available
+ Current version: %1$s\nAvailable version: %2$s\nDo you want to download it?
+ Check successful, but no updates.
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/Helpers.kt b/buildSrc/src/main/kotlin/Helpers.kt
index ff687e6..0c8ec14 100644
--- a/buildSrc/src/main/kotlin/Helpers.kt
+++ b/buildSrc/src/main/kotlin/Helpers.kt
@@ -14,33 +14,6 @@ private val Project.android get() = extensions.getByName("
private lateinit var metadata: Properties
private lateinit var localProperties: Properties
-private lateinit var flavor: String
-
-fun Project.requireFlavor(): String {
- if (::flavor.isInitialized) return flavor
- if (gradle.startParameter.taskNames.isNotEmpty()) {
- val taskName = gradle.startParameter.taskNames[0]
- when {
- taskName.contains("assemble") -> {
- flavor = taskName.substringAfter("assemble")
- return flavor
- }
-
- taskName.contains("install") -> {
- flavor = taskName.substringAfter("install")
- return flavor
- }
-
- taskName.contains("bundle") -> {
- flavor = taskName.substringAfter("bundle")
- return flavor
- }
- }
- }
-
- flavor = ""
- return flavor
-}
fun Project.requireMetadata(): Properties {
if (!::metadata.isInitialized) {
@@ -156,8 +129,6 @@ fun Project.setupAppCommon() {
keyPassword = pwd
}
}
- } else if (requireFlavor().contains("(Oss|Expert|Play)Release".toRegex())) {
- exitProcess(0)
}
buildTypes {
val key = signingConfigs.findByName("release")
@@ -178,6 +149,7 @@ fun Project.setupApp() {
applicationId = pkgName
versionCode = verCode
versionName = verName
+ buildConfigField("String", "PRE_VERSION_NAME", "\"\"")
}
}
setupAppCommon()
@@ -209,7 +181,13 @@ fun Project.setupApp() {
create("oss")
create("fdroid")
create("play")
- create("preview")
+ create("preview") {
+ buildConfigField(
+ "String",
+ "PRE_VERSION_NAME",
+ "\"${requireMetadata().getProperty("PRE_VERSION_NAME")}\""
+ )
+ }
}
applicationVariants.all {
diff --git a/nb4a.properties b/nb4a.properties
index d6dd912..7ced472 100644
--- a/nb4a.properties
+++ b/nb4a.properties
@@ -1,3 +1,4 @@
PACKAGE_NAME=moe.nb4a
VERSION_NAME=1.3.9
+PRE_VERSION_NAME=pre-1.4.0-20250902-1
VERSION_CODE=43