android: connection steps on Home as in the Final 2026 mockup, read from the daemon log

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
DenozordecandClaude Opus 5.5 committed 2026-10-12 14:39:26 +07:00
1 parent a08c193a90
commit 92e2f4f069
5 files changed
+125 -2

No files matched your search

@@ -124,6 +124,7 @@ import kotlinx.coroutines.withContext
import org.strongswan.android.ui.compose.home.CertExpiry
import org.strongswan.android.ui.compose.home.certExpiry
import org.strongswan.android.ui.compose.home.snoozeCertExpiry
import org.strongswan.android.ui.compose.home.connectProgress
import org.strongswan.android.R
import androidx.compose.foundation.layout.widthIn
import org.strongswan.android.logic.routing.RuIpListManager
@@ -255,6 +256,14 @@ fun HomeScreen(
val certExpiry by produceState<CertExpiry?>(null, certAlias, certSnoozes) {
value = withContext(Dispatchers.IO) { certExpiry(context, certAlias) }
}
val connectingNow = state.vpnState == State.CONNECTING
val connectStep by produceState(1, connectingNow) {
while (connectingNow)
{
value = withContext(Dispatchers.IO) { connectProgress(context) }
kotlinx.coroutines.delay(600)
}
}
val model = HomeModel(
state = state,
traffic = traffic,
@@ -270,6 +279,7 @@ fun HomeScreen(
unread = unreadEvents,
findingFastest = findingFastest,
certExpiry = certExpiry,
connectStep = connectStep,
)
val actions = HomeActions(
connect = { selected?.let { viewModel.connect(context, it) } },
@@ -0,0 +1,85 @@
/*
* Copyright (C) 2026 SHX VPN
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
package org.strongswan.android.ui.compose.home
import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import org.strongswan.android.R
import org.strongswan.android.logic.CharonVpnService
import org.strongswan.android.ui.compose.theme.LocalShxColors
import java.io.File
import java.io.RandomAccessFile
/**
* How far the running connection got (F_HomeConnecting), read from the
* daemon log, which starts fresh for every connection:
* 1 the network is there, 2 the server answered, 3 sign-in passed,
* 4 the tunnel is up. Returns the number of finished steps.
*/
internal fun connectProgress(context: Context): Int
{
val text = tail(File(context.filesDir, CharonVpnService.LOG_FILE)) ?: return 1
var done = 1
if (text.contains("received packet"))
{
done = 2
}
if (Regex("authentication of .* successful").containsMatchIn(text) || Regex("IKE_SA .* established").containsMatchIn(text))
{
done = 3
}
if (Regex("CHILD_SA .* established").containsMatchIn(text))
{
done = 4
}
return done
}
/* the last 64 KiB are plenty for one connection attempt */
private fun tail(file: File): String?
{
if (!file.exists())
{
return null
}
return runCatching {
RandomAccessFile(file, "r").use { f ->
val start = (f.length() - 65536).coerceAtLeast(0)
f.seek(start)
val bytes = ByteArray((f.length() - start).toInt())
f.readFully(bytes)
String(bytes, Charsets.UTF_8)
}
}.getOrNull()
}
/** The four steps: ✓ done, … current (purple), empty grey ones ahead. */
@Composable
internal fun ConnectStepsPanel(done: Int, network: String, serverRtt: String)
{
val c = LocalShxColors.current
val steps = listOf(
stringResource(R.string.connect_step_network) to network,
stringResource(R.string.connect_step_server) to serverRtt,
stringResource(R.string.connect_step_auth) to "",
stringResource(R.string.connect_step_tunnel) to "",
)
CheckPanel {
steps.forEachIndexed { i, (title, value) ->
when
{
i < done -> CheckLine("✓", c.successContainer, c.onSuccess, title, value)
i == done -> CheckLine("…", c.accent, androidx.compose.ui.graphics.Color.White, title, "", strong = true)
else -> CheckLine("", c.tile, c.iconMuted, title, "", muted = true)
}
}
}
}
@@ -62,6 +62,8 @@ internal data class HomeModel(
val unread: Int,
val findingFastest: Boolean,
val certExpiry: CertExpiry? = null,
/* finished steps of the running connection (F_HomeConnecting) */
val connectStep: Int = 1,
)
/** What Home can do. */
@@ -187,8 +189,8 @@ internal fun HomeContent(m: HomeModel, a: HomeActions)
{
HeroBlob(heroLookConnecting(), ShxIcons.Power, stringResource(R.string.home_cancel_connect), a.disconnect)
HeroCaption(
stringResource(if (connecting) R.string.status_connecting else R.string.state_disconnecting),
name,
stringResource(if (connecting) R.string.home_connecting else R.string.state_disconnecting),
if (connecting) stringResource(R.string.home_connect_step, name, (m.connectStep + 1).coerceAtMost(4)) else name,
)
}
else ->
@@ -323,6 +325,20 @@ internal fun HomeContent(m: HomeModel, a: HomeActions)
connecting || s.vpnState == State.DISCONNECTING ->
{
server()
if (connecting)
{
val (wifi, cell) = m.networkKinds
ConnectStepsPanel(
m.connectStep,
network = when
{
wifi -> stringResource(R.string.network_wifi)
cell -> stringResource(R.string.network_cellular)
else -> ""
},
serverRtt = if (m.connectStep >= 2) m.serverRttMs?.let { stringResource(R.string.diag_ms, it.toInt()) }.orEmpty() else "",
)
}
ShxButton(stringResource(R.string.home_cancel), a.disconnect, style = ShxButtonStyle.TONAL, height = 48.dp, modifier = Modifier.fillMaxWidth())
}
else ->
@@ -1123,4 +1123,10 @@
<string name="battery_check_again">Проверить снова</string>
<string name="consent_hint_title">Сейчас Android спросит разрешение</string>
<string name="consent_hint_text">Это стандартное окно для любого VPN. Нажмите «ОК» — без этого подключиться нельзя.</string>
<string name="connect_step_network">Сеть</string>
<string name="connect_step_server">Сервер ответил</string>
<string name="connect_step_auth">Проверка входа</string>
<string name="connect_step_tunnel">Туннель и маршруты</string>
<string name="home_connect_step">%1$s · шаг %2$d из 4</string>
<string name="home_connecting">Подключаемся…</string>
</resources>
@@ -1109,4 +1109,10 @@
<string name="battery_check_again">Check again</string>
<string name="consent_hint_title">Android will now ask for permission</string>
<string name="consent_hint_text">This is the standard window for any VPN. Tap «OK» — without it the VPN cannot connect.</string>
<string name="connect_step_network">Network</string>
<string name="connect_step_server">Server answered</string>
<string name="connect_step_auth">Sign-in check</string>
<string name="connect_step_tunnel">Tunnel and routes</string>
<string name="home_connect_step">%1$s · step %2$d of 4</string>
<string name="home_connecting">Connecting…</string>
</resources>