Release 3.8.2.1: reliability, UI probe/logs, CI, signing gates.

Clear sysproxy on core death; probe+reconnect; subscription prune;
Best ignores soft UDP; Windows tray; Android protocol gate; CI workflows.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
M4
2026-07-30 02:44:48 +03:00
co-authored by Cursor
parent 6b6c13c933
commit 621c847cb3
39 changed files with 913 additions and 143 deletions
+4 -4
View File
@@ -1,8 +1,8 @@
# Navis Android
Клиент с UI как на Windows 2.2: список нод, пинг, «лучший», автоподключение, подписка.
Клиент с UI как на Desktop: список нод, пинг, «лучший», автоподключение, подписка.
Версия APK: **2.3.0**
Версия APK: **3.8.2+1** (синхронизируется с Desktop через `scripts/sync-version.py`).
## Протоколы
@@ -10,8 +10,8 @@
|----------|--------------|
| VLESS / VMess / Trojan (Xray) | SOCKS core + VpnService |
| Hysteria 2 | SOCKS core + VpnService |
| NaiveProxy | импорт в UI; Android Chromium-бинарник не бандлится |
| AmneziaWG | импорт в UI; полный Tun — следующий релиз |
| NaiveProxy | импорт в UI; подключение отключено (только Desktop) |
| AmneziaWG | импорт в UI; подключение отключено (только Desktop) |
## Сборка
+2 -2
View File
@@ -13,8 +13,8 @@ android {
minSdk = 26
targetSdk = 35
// versionCode = major*1_000_000 + minor*10_000 + patch*100 + build
versionCode = 2_070_201
versionName = "2.7.3+1"
versionCode = 3080201
versionName = "3.8.2+1"
vectorDrawables.useSupportLibrary = true
ndk {
abiFilters += listOf("arm64-v8a", "armeabi-v7a", "x86_64")
@@ -24,6 +24,12 @@ enum class Proto(val label: String) {
}
}
}
/** Desktop has Naive/AWG; this APK only runs Xray + Hysteria2 in VpnService. */
fun supportedOnAndroid(): Boolean = when (this) {
VLESS, VMESS, TROJAN, HYSTERIA2 -> true
else -> false
}
}
data class Profile(
@@ -59,6 +59,10 @@ class MainActivity : ComponentActivity() {
vm.setMeta("Выберите сервер", true)
return
}
if (!p.protocol.supportedOnAndroid()) {
vm.setMeta("${p.protocol.label}: на Android недоступен — используйте VLESS/VMess/Trojan/Hy2", true)
return
}
prepareVpnThen {
startService(
Intent(this, NavisVpnService::class.java)
@@ -139,12 +139,18 @@ class MainViewModel(app: Application) : AndroidViewModel(app) {
}
val rows = store.pingAll(profiles)
_state.update { it.copy(pings = rows.associateBy { r -> r.id }) }
val best = store.bestOf(rows) ?: run {
setMeta("нет доступных серверов", true)
val supported = profiles.filter { it.protocol.supportedOnAndroid() }
val supportedRows = rows.filter { r -> supported.any { it.id == r.id } }
val best = store.bestOf(supportedRows) ?: run {
setMeta("нет доступных серверов (Android: VLESS/VMess/Trojan/Hy2)", true)
return
}
store.setActiveId(best.id)
val profile = profiles.first { it.id == best.id }
if (!profile.protocol.supportedOnAndroid()) {
setMeta("${profile.protocol.label}: на Android недоступен", true)
return
}
_state.update { it.copy(activeId = best.id) }
setMeta("Лучший: ${profile.name} · ${best.ms} ms")
if (connect && prepare != null) {
@@ -100,7 +100,7 @@ fun NavisScreen(
Text("2.6.1", Modifier.padding(horizontal = 8.dp, vertical = 2.dp), fontSize = 12.sp, fontWeight = FontWeight.Bold, color = AccentDeep)
}
}
Text("Android · Naive · Hy2 · AWG · Xray", color = Muted, fontSize = 13.sp)
Text("Android · Xray · Hy2 (Naive/AWG — только Desktop)", color = Muted, fontSize = 13.sp)
Spacer(Modifier.height(16.dp))
Surface(shape = RoundedCornerShape(22.dp), color = Color.White.copy(alpha = 0.85f), tonalElevation = 2.dp) {