From a08c193a908eed56cbbced6cb9f864cb99a9b68e Mon Sep 17 00:00:00 2001 From: Denozordec Date: Mon, 12 Oct 2026 14:36:09 +0700 Subject: [PATCH] android: hint behind the system VPN consent window as in the Final 2026 mockup Co-Authored-By: Claude Opus 5.5 --- .../android/ui/VpnProfileControlActivity.java | 3 + .../android/ui/compose/VpnDialogs.kt | 64 +++++++++++++++++++ .../app/src/main/res/values-ru/strings.xml | 2 + .../app/src/main/res/values/strings.xml | 2 + 4 files changed, 71 insertions(+) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java index b77f115bd..c12045817 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java @@ -102,6 +102,7 @@ public class VpnProfileControlActivity extends AppCompatActivity implements VpnD new ActivityResultContracts.StartActivityForResult(), result -> { mWaitingForResult = false; + VpnDialogBus.INSTANCE.setConsentHint(false); if (result.getResultCode() == RESULT_OK && mProfileInfo != null) { onVpnServicePrepared(); @@ -224,6 +225,7 @@ public class VpnProfileControlActivity extends AppCompatActivity implements VpnD try { mWaitingForResult = true; + VpnDialogBus.INSTANCE.setConsentHint(true); mPrepareVpnService.launch(intent); } catch (ActivityNotFoundException ex) @@ -232,6 +234,7 @@ public class VpnProfileControlActivity extends AppCompatActivity implements VpnD * don't have the VPN components built into the system image. * com.android.vpndialogs/com.android.vpndialogs.ConfirmDialog * will not be found then */ + VpnDialogBus.INSTANCE.setConsentHint(false); showNotSupported(R.string.vpn_not_supported); mWaitingForResult = false; } diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/VpnDialogs.kt b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/VpnDialogs.kt index b2de1679c..b7c9e03bb 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/VpnDialogs.kt +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/VpnDialogs.kt @@ -45,6 +45,22 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.ui.Alignment +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.strongswan.android.ui.compose.design.ShxIcons +import org.strongswan.android.ui.compose.theme.LocalShxColors +import org.strongswan.android.ui.compose.theme.ShxType import org.strongswan.android.R import org.strongswan.android.ui.compose.theme.ShxSpacing import org.strongswan.android.ui.compose.theme.ShxTheme @@ -106,6 +122,16 @@ object VpnDialogBus { _dialog.value = null } + + /* F_VpnConsent: the hint behind the system VPN consent window */ + private val _consentHint = MutableStateFlow(false) + val consentHint: StateFlow = _consentHint + + @JvmStatic + fun setConsentHint(shown: Boolean) + { + _consentHint.value = shown + } } /** Renders the connect-flow dialogs in the app theme inside the transparent activity. */ @@ -122,6 +148,11 @@ fun ComponentActivity.installVpnDialogHost() fun VpnDialogHost() { val dialog by VpnDialogBus.dialog.collectAsStateWithLifecycle() + val consentHint by VpnDialogBus.consentHint.collectAsStateWithLifecycle() + if (consentHint) + { + ConsentHint() + } when (val d = dialog) { is VpnDialog.Confirmation -> ConfirmationUi(d) @@ -289,3 +320,36 @@ private fun NotSupportedUi(dialog: VpnDialog.NotSupported) }, ) } + +/** + * Shown by the transparent host while Android's own consent dialog is on + * top (F_VpnConsent): a dimmed screen with a card at the top explaining it. + */ +@Composable +private fun ConsentHint() +{ + val c = LocalShxColors.current + Box( + Modifier.fillMaxSize().background(Color(0x8C1C1B20)).statusBarsPadding().padding(start = 20.dp, end = 20.dp, top = 24.dp), + ) + { + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp), + modifier = Modifier.fillMaxWidth().clip(RoundedCornerShape(22.dp)).background(c.background).padding(16.dp), + ) + { + Box(contentAlignment = Alignment.Center, modifier = Modifier.size(36.dp).clip(RoundedCornerShape(12.dp)).background(c.accent)) + { + Icon(ShxIcons.Shield, contentDescription = null, tint = Color.White, modifier = Modifier.size(20.dp)) + } + Column(verticalArrangement = Arrangement.spacedBy(3.dp)) + { + Text(stringResource(R.string.consent_hint_title), style = ShxType.cardTitle, color = c.text) + Text( + stringResource(R.string.consent_hint_text), + style = ShxType.note.copy(fontSize = 13.sp, lineHeight = 19.sp, fontWeight = FontWeight.Normal), color = c.textSoft, + ) + } + } + } +} diff --git a/src/frontends/android/app/src/main/res/values-ru/strings.xml b/src/frontends/android/app/src/main/res/values-ru/strings.xml index 46b646772..c7fb31f43 100644 --- a/src/frontends/android/app/src/main/res/values-ru/strings.xml +++ b/src/frontends/android/app/src/main/res/values-ru/strings.xml @@ -1121,4 +1121,6 @@ Шаг 1 ещё не сделан — VPN может отключаться в фоне Готово: Android не будет усыплять VPN Проверить снова + Сейчас Android спросит разрешение + Это стандартное окно для любого VPN. Нажмите «ОК» — без этого подключиться нельзя. diff --git a/src/frontends/android/app/src/main/res/values/strings.xml b/src/frontends/android/app/src/main/res/values/strings.xml index cb10ab28f..607af9f1c 100644 --- a/src/frontends/android/app/src/main/res/values/strings.xml +++ b/src/frontends/android/app/src/main/res/values/strings.xml @@ -1107,4 +1107,6 @@ Step 1 is not done yet — the VPN may stop in the background Done: Android will not put the VPN to sleep Check again + Android will now ask for permission + This is the standard window for any VPN. Tap «OK» — without it the VPN cannot connect.