From b9800e0fe341ae8341f06d579ef65e46a043e075 Mon Sep 17 00:00:00 2001 From: valelaneme Date: Tue, 3 Nov 2026 09:54:44 +0800 Subject: [PATCH 1/6] Update MainActivity.kt --- app/src/main/java/io/nekohasekai/sagernet/ui/MainActivity.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/MainActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/MainActivity.kt index 650a1a3b9..321eef090 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/MainActivity.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/MainActivity.kt @@ -345,7 +345,6 @@ class MainActivity : ThemedActivity(), return true } - @SuppressLint("CommitTransaction") fun displayFragment(fragment: ToolbarFragment) { currentMainFragment = fragment @@ -442,6 +441,9 @@ class MainActivity : ThemedActivity(), showWhenConnected = state == BaseService.State.Connected, animate = animateControls, ) + if (state == BaseService.State.Connected) { + binding.stats.testConnection() + } if (state == BaseService.State.Connecting) errorBar?.dismiss() if (msg == null) return if (msg == DataStore.serviceError) showServiceError(msg) else snackbar(getString(R.string.vpn_error, msg)).show() From 751484588ce70cdb56420d8e53ebfd5bdeb4fe7e Mon Sep 17 00:00:00 2001 From: valelaneme Date: Tue, 3 Nov 2026 09:55:36 +0800 Subject: [PATCH 2/6] Update ProfileRowHolder.kt --- .../sagernet/ui/profiles/ProfileRowHolder.kt | 62 ++++++++++--------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/profiles/ProfileRowHolder.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/profiles/ProfileRowHolder.kt index f3d51cc83..d156597da 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/profiles/ProfileRowHolder.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/profiles/ProfileRowHolder.kt @@ -20,7 +20,6 @@ import io.nekohasekai.sagernet.R import io.nekohasekai.sagernet.database.DataStore import io.nekohasekai.sagernet.database.ProxyEntity import io.nekohasekai.sagernet.database.SettingsRegistry -import io.nekohasekai.sagernet.database.TestShowItems import io.nekohasekai.sagernet.database.preference.OnPreferenceDataStoreChangeListener import io.nekohasekai.sagernet.ktx.alert import io.nekohasekai.sagernet.ktx.getColorAttr @@ -63,16 +62,12 @@ internal class RowContent( private const val LRO = "\u202D" private const val NBSP = "\u00A0" - /** Between items, where the line may break. */ - private const val GAP = "\u2002 " - /** A core rate: "34.13Mbps". */ private val RATE = Regex("""(\d+(?:\.\d+)?)\s*([KMG]bps)""", RegexOption.IGNORE_CASE) fun of(context: Context, profile: ProxyEntity, adapter: ProfileListAdapter): RowContent { val secondary = context.getColorAttr(android.R.attr.textColorSecondary) val brief = adapter.isCompactRow - val showTraffic = !brief && profile.rx + profile.tx != 0L val up = Formatter.formatShortFileSize(context, profile.tx) val down = Formatter.formatShortFileSize(context, profile.rx) val address = if (!brief && adapter.host?.alwaysShowAddress == true && profile.outbound.name.isNotBlank()) { @@ -84,9 +79,10 @@ internal class RowContent( val color = profile.latencyColor().takeIf { it != 0 } ?: secondary val result = SpannableStringBuilder() var description: String? = null - // Profile::DisplayTestResult: a failed or connect-only test shows its verdict alone + when { phase == RowPhase.TESTING -> result.append(context.getString(R.string.connection_test_testing)) + phase == RowPhase.QUEUED -> result.append(context.getString(R.string.profiles_test_queued)) latency == ProxyEntity.LATENCY_CONNECT_ONLY || latency < 0 -> result.append( context.getString( if (latency == ProxyEntity.LATENCY_CONNECT_ONLY) R.string.test_connect_ok else R.string.unavailable @@ -96,28 +92,30 @@ internal class RowContent( ) else -> { - val items = testResult(context, profile, TestShowItems.of(adapter.testItemsToShow), color, secondary) - .let { if (brief) it.take(1) else it } - if (items.isNotEmpty()) { + // 获取覆盖互斥的单一最新测试结果 + val singleItem = singleTestResult(context, profile, color, secondary) + if (singleItem != null) { result.append(LRO) - items.forEachIndexed { index, item -> - if (index > 0) result.append(GAP) - result.append(item.text, ForegroundColorSpan(item.color), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - } - description = items.joinToString(", ") { it.spoken } - } else if (phase == RowPhase.QUEUED) { - result.append(context.getString(R.string.profiles_test_queued)) + result.append(singleItem.text, ForegroundColorSpan(singleItem.color), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + description = singleItem.spoken + } else if (profile.rx + profile.tx > 0L) { + // 无测试结果,但产生了流量,回退显示流量统计 + val trafficText = context.getString(R.string.traffic, up, down) + result.append(trafficText, ForegroundColorSpan(secondary), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + description = context.getString(R.string.profile_row_traffic, up, down) } + // 流量为 0 且无测试结果时,result 保持为空,不会在卡片右侧留有任何占位 } } + val error = if (phase == null) profile.testError?.takeIf { it.isNotBlank() } else null return RowContent( result = result, resultDescription = description, resultDimmed = phase == RowPhase.QUEUED, testing = phase == RowPhase.TESTING, - traffic = if (showTraffic) context.getString(R.string.traffic, up, down) else "", - trafficDescription = if (showTraffic) context.getString(R.string.profile_row_traffic, up, down) else null, + traffic = "", + trafficDescription = null, address = address, error = error, brief = brief, @@ -125,18 +123,20 @@ internal class RowContent( } /** - * The items of Profile::DisplayTestResult (Profile.cpp:36-56): flag and latency in the latency colour, then - * the speeds and the egress IP as the group's test_items_to_show allows. + * 互斥单选展示:测速 > 延迟与国旗 > IP + * 保证只显示一个结果,新测试触发后自动覆盖 */ - private fun testResult( + private fun singleTestResult( context: Context, profile: ProxyEntity, - show: TestShowItems, @ColorInt color: Int, @ColorInt secondary: Int, - ): List { + ): Item? { + // 1. 优先展示上下行速率(如果存在测速结果) + speeds(context, profile.dlSpeed, profile.ulSpeed, secondary)?.let { return it } + + // 2. 其次展示基础:延迟与国家代码 val latency = profile.latency - val items = ArrayList() val head = ArrayList() val headSpoken = ArrayList() profile.testCountry?.takeIf { it.isNotEmpty() }?.let { country -> @@ -148,13 +148,17 @@ internal class RowContent( head += "$latency${NBSP}ms" headSpoken += "$latency ms" } - if (head.isNotEmpty()) items += Item(head.joinToString(NBSP), color, headSpoken.joinToString(", ")) - if (show.showSpeed) speeds(context, profile.dlSpeed, profile.ulSpeed, secondary)?.let { items += it } + if (head.isNotEmpty()) { + return Item(head.joinToString(NBSP), color, headSpoken.joinToString(", ")) + } + + // 3. 兜底展示出口 IP val ip = profile.ipOut.orEmpty() - if (show.showIp && ip.isNotEmpty()) { - items += Item("🌐$NBSP$ip", secondary, context.getString(R.string.profile_row_ip, ip)) + if (ip.isNotEmpty()) { + return Item("🌐$NBSP$ip", secondary, context.getString(R.string.profile_row_ip, ip)) } - return items + + return null } /** "↓34.1 ↑11.6 Mbps": three significant digits, one unit when both rates share it; "N/A" is left out. */ From 22fdb12862824a5f60c2f31c008699c36fc0756e Mon Sep 17 00:00:00 2001 From: valelaneme Date: Tue, 3 Nov 2026 09:56:35 +0800 Subject: [PATCH 3/6] Update StatsBar.kt --- .../java/io/nekohasekai/sagernet/widget/StatsBar.kt | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/io/nekohasekai/sagernet/widget/StatsBar.kt b/app/src/main/java/io/nekohasekai/sagernet/widget/StatsBar.kt index 4c7f309d4..ef14ad8ac 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/widget/StatsBar.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/widget/StatsBar.kt @@ -415,19 +415,13 @@ class StatsBar @JvmOverloads constructor( runOnDefaultDispatcher { try { val elapsed = activity.urlTest() - onMainDispatcher { + onMainDispatcher { isEnabled = true setStatus( if (elapsed == ProxyEntity.LATENCY_CONNECT_ONLY) { app.getText(R.string.test_connect_ok) } else { - app.getString( - if (DataStore.testUrl.startsWith("https://")) { - R.string.connection_test_available - } else { - R.string.connection_test_available_http - }, elapsed - ) + "$elapsed ms" } ) } From 364827ac038f61fff08fd644747467df3b239dc3 Mon Sep 17 00:00:00 2001 From: valelaneme Date: Tue, 3 Nov 2026 09:57:23 +0800 Subject: [PATCH 4/6] Update layout_stats_bar_content.xml --- .../res/layout/layout_stats_bar_content.xml | 56 ++++++++++++------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/app/src/main/res/layout/layout_stats_bar_content.xml b/app/src/main/res/layout/layout_stats_bar_content.xml index a2d96657b..00d4f0d97 100644 --- a/app/src/main/res/layout/layout_stats_bar_content.xml +++ b/app/src/main/res/layout/layout_stats_bar_content.xml @@ -4,7 +4,10 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" - android:padding="16dp"> + android:paddingStart="16dp" + android:paddingEnd="16dp" + android:paddingTop="16dp" + android:paddingBottom="4dp"> + + - + + android:orientation="vertical"> - + - + + + + + From ab42ba90f0f5695214f4e33322eb28fb35085c09 Mon Sep 17 00:00:00 2001 From: valelaneme Date: Thu, 5 Nov 2026 16:38:33 +0800 Subject: [PATCH 5/6] Update CHANGELOG.md --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b3f2c944..6f9e5e56c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,16 @@ #### 2.0.5 +* 配置列表(紧凑模式)信息覆盖显示规则 +* 更改右侧数据展示优先级规则(由高到低): +* 测试中(实时进行中显示「测试中...」) +* 排队中(任务等待队列中半透明显示「排队中」) +* 连接异常 / 特殊状态(连通显示「连接成功」,超时/失败显示「不可用」) +* 测速结果(存在下载/上传速率时优先展示,覆盖延迟与 IP) +* 国旗与延迟(无测速数据时优先展示「国旗 + 延迟 ms」,覆盖出网 IP) +* 出网外部 IP(无测速且无延迟/国旗时显示「🌐 IP」) +* 历史流量统计(无任何测试结果时回退显示流量;流量为 0 时空白不占位) +* 涉及文件: +* app/src/main/java/io/nekohasekai/sagernet/ui/profiles/ProfileRowHolder.kt +#### 2.0.5 * Add per-group routing profiles **1** * Add routing profile import from subscriptions **2** From 74197048ea6d8f6b977a8c11d87e86f9f3cf5cfc Mon Sep 17 00:00:00 2001 From: valelaneme Date: Thu, 5 Nov 2026 16:40:07 +0800 Subject: [PATCH 6/6] Update CHANGELOG for version 2.0.5.8 --- CHANGELOG.md | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f9e5e56c..058762fbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,13 @@ -#### 2.0.5 +#### 2.0.5.8 + +* 底栏左侧区域 + * 1. 单击:快速跳转至配置列表主页面,并高亮定位当前自动选择器 / 活动配置。 + * 2. 长按:一键清空所有节点的历史测试数据(重置 Ping 延迟、测速结果与报错状态)。 +* 底栏右侧区域 + * 1. 单击:测试当前活动出站链路的连通性(保持原有逻辑)。 + * 2. 长按:批量测试当前主页面订阅/分组中全部节点的 Ping 延迟,并同步刷新出站 IP 与归属地国旗信息。 + +#### 2.0.5.7 * 配置列表(紧凑模式)信息覆盖显示规则 * 更改右侧数据展示优先级规则(由高到低): * 测试中(实时进行中显示「测试中...」) @@ -10,6 +19,28 @@ * 历史流量统计(无任何测试结果时回退显示流量;流量为 0 时空白不占位) * 涉及文件: * app/src/main/java/io/nekohasekai/sagernet/ui/profiles/ProfileRowHolder.kt + + +#### 2.0.5.6 + * 1. 删除底栏测试是显示的http https。app/src/main/java/io/nekohasekai/sagernet/widget/StatsBar.kt + * 2. 更新紧凑模式ip与国家测试,显示国旗和延时。app/src/main/java/io/nekohasekai/sagernet/ui/profiles/ProfileRowHolder.kt +#### 2.0.5.3 + +* 更新紧凑模式右侧数据显示规则。app/src/main/java/io/nekohasekai/sagernet/ui/profiles/ProfileRowHolder.kt + * 1. 如果没有测试结果,或者测试结果情况,则显示流量统计信息,如果流量统计信息为0,则显示空。 + * 2. IP与国家、速度、延迟 等测试结果根据时间戳显示最新数据。 + +#### 2.0.5.2 + +**底栏修改** +* 1. 底栏形成“左侧双行网速、右侧国旗加状态”的对称排布。app/src/main/res/layout/layout_stats_bar_content.xml +* 2. 调低底栏高度。 app/src/main/res/layout/layout_stats_bar_content.xml +* 3. 一旦底层核心服务状态切换为 `Connected`(已连接),系统会**立即自动调用 `testConnection()` 发起测速**,实现了连接后全自动测速动作。app/src/main/java/io/nekohasekai/sagernet/ui/MainActivity.kt + +#### 2.0.5.1 + +* 第一个编译版本,只生成 v8a 文件 + #### 2.0.5 * Add per-group routing profiles **1**