dev check update

This commit is contained in:
armv9 2025-09-02 19:47:44 +09:00
parent 7736548e4f
commit 56fd9df2e9
7 changed files with 222 additions and 142 deletions

View File

@ -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
}
}
}

View File

@ -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()
}
}
}
}
}
}

View File

@ -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()

View File

@ -488,4 +488,9 @@
<string name="wake_reset_connections">当设备从睡眠状态唤醒时重置出站连接</string>
<string name="preview_version">预览版</string>
<string name="preview_version_hint">本应用为预览版可能存在诸多问题。若您不愿参与测试请前往GitHub下载正式发布版本</string>
<string name="check_update_preview">检查预览版更新</string>
<string name="check_update_release">检查正式版</string>
<string name="update_dialog_title">发现新版本</string>
<string name="update_dialog_message">当前版本:%1$s\n可升级版本%2$s\n是否前往下载</string>
<string name="check_update_no">检查成功,但没有更新。</string>
</resources>

View File

@ -568,4 +568,9 @@
<string name="wake_reset_connections">Reset outbound connections when device wake from sleep</string>
<string name="preview_version">Preview version</string>
<string name="preview_version_hint">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!</string>
<string name="check_update_preview">Check for preview version updates</string>
<string name="check_update_release">Check for release version updates</string>
<string name="update_dialog_title">New version available</string>
<string name="update_dialog_message">Current version: %1$s\nAvailable version: %2$s\nDo you want to download it?</string>
<string name="check_update_no">Check successful, but no updates.</string>
</resources>

View File

@ -14,33 +14,6 @@ private val Project.android get() = extensions.getByName<ApplicationExtension>("
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 {

View File

@ -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