diff --git a/Android/app/build.gradle.kts b/Android/app/build.gradle.kts index 45e47cac..40bcbfc8 100644 --- a/Android/app/build.gradle.kts +++ b/Android/app/build.gradle.kts @@ -11,8 +11,8 @@ android { applicationId = "com.archipelago.app" minSdk = 26 targetSdk = 35 - versionCode = 38 - versionName = "0.5.18" + versionCode = 39 + versionName = "0.5.19" vectorDrawables { useSupportLibrary = true diff --git a/Android/app/src/main/java/com/archipelago/app/fips/ArchyVpnService.kt b/Android/app/src/main/java/com/archipelago/app/fips/ArchyVpnService.kt index 7badffdb..cfb51e1a 100644 --- a/Android/app/src/main/java/com/archipelago/app/fips/ArchyVpnService.kt +++ b/Android/app/src/main/java/com/archipelago/app/fips/ArchyVpnService.kt @@ -153,19 +153,28 @@ class ArchyVpnService : VpnService() { } catch (_: Exception) { emptyList() }.distinct() - for ((ula, port) in targets) { - try { - java.net.Socket().use { s -> - s.connect( - java.net.InetSocketAddress(java.net.InetAddress.getByName(ula), port), - 20_000, - ) + if (round == 0) Log.i(TAG, "session warmer: ${targets.map { it.first }}") + // Probe all targets CONCURRENTLY with a short timeout — the + // old sequential 20s-per-target loop let one cold node starve + // every other target for the whole aggressive window. + targets.map { (ula, port) -> + launch { + try { + java.net.Socket().use { s -> + s.connect( + java.net.InetSocketAddress( + java.net.InetAddress.getByName(ula), + port, + ), + 5_000, + ) + } + } catch (_: Exception) { + // Cold path / node away — the attempt still drove + // session establishment; try again next round. } - } catch (_: Exception) { - // Cold path / node away — the connect attempt still - // drove session establishment; try again next round. } - } + }.forEach { it.join() } round++ // Aggressive for the first ~minute (session bring-up), then a // slow keep-warm tick that costs nearly nothing. diff --git a/Android/app/src/main/java/com/archipelago/app/fips/FipsManager.kt b/Android/app/src/main/java/com/archipelago/app/fips/FipsManager.kt index a10d473a..398356ec 100644 --- a/Android/app/src/main/java/com/archipelago/app/fips/FipsManager.kt +++ b/Android/app/src/main/java/com/archipelago/app/fips/FipsManager.kt @@ -41,7 +41,15 @@ object FipsManager { ensureIdentity(prefs) prefs.upsertNodePeer(info, alias) peersDirty = true - _consentNeeded.value = true + // Restart the mesh with the new peer RIGHT NOW when consent already + // exists — relying on the consentNeeded collector left a running + // mesh on the OLD peer list whenever the collector wasn't active + // (fresh pairings looked dead until a full app restart). + if (VpnService.prepare(context) == null) { + startService(context) + } else { + _consentNeeded.value = true + } } /** Generate-once mesh identity. Returns null only if the RNG/native fails. */ diff --git a/Android/app/src/main/java/com/archipelago/app/ui/screens/WebViewScreen.kt b/Android/app/src/main/java/com/archipelago/app/ui/screens/WebViewScreen.kt index f92c8484..7f44dfbc 100644 --- a/Android/app/src/main/java/com/archipelago/app/ui/screens/WebViewScreen.kt +++ b/Android/app/src/main/java/com/archipelago/app/ui/screens/WebViewScreen.kt @@ -897,7 +897,17 @@ private fun InAppBrowser( fun isSameNode(u: String): Boolean = isSameHost(u, serverUrl) || (meshUrl != null && isSameHost(u, meshUrl)) var browser by remember { mutableStateOf(null) } - var title by remember { mutableStateOf(android.net.Uri.parse(url).host ?: url) } + // Loader title: never show a raw IP host — a mesh ULA like + // [fd79:1aa:…] is technically the host but reads as garbage on the + // loading screen. Show a neutral name until the page reports its + // real (onReceivedTitle upgrades it). + var title by remember { + mutableStateOf( + android.net.Uri.parse(url).host + ?.takeUnless { it.contains(':') || it.matches(Regex("^\\d+(\\.\\d+){3}$")) } + ?: "Archipelago", + ) + } var favicon by remember { mutableStateOf<Bitmap?>(null) } var progress by remember { mutableIntStateOf(0) } var loading by remember { mutableStateOf(true) } @@ -935,12 +945,21 @@ private fun InAppBrowser( modifier = Modifier .fillMaxSize() .background(SurfaceBlack) - // Bottom inset handled by the touch-shield strip below the bar — - // NOT by padding: a padded area only paints, it doesn't consume, - // so taps in the gesture strip fell straight THROUGH this overlay - // into the kiosk's tab bar behind it (accidental AIUI-tab hits). + // Whole-overlay touch shield: every touch not handled by a child + // (control-bar gaps, inset strips) dies here instead of falling + // through to the kiosk's tab bar behind (a near-miss on Close + // was opening the AIUI tab underneath). + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = {}, + ) + // Bottom inset handled by the touch-shield strip below the bar. + // No TOP inset padding: the WebView draws edge-to-edge behind the + // status bar so the app's own background fills it — the padded + // version painted an opaque black bar there (user-rejected look). .windowInsetsPadding( - WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top) + WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal) ), ) { // WebView + loading overlay fill the area above the bottom control bar. diff --git a/neode-ui/public/packages/archipelago-companion.apk b/neode-ui/public/packages/archipelago-companion.apk index 095d27b8..07cd6748 100644 Binary files a/neode-ui/public/packages/archipelago-companion.apk and b/neode-ui/public/packages/archipelago-companion.apk differ