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