android: hint behind the system VPN consent window as in the Final 2026 mockup
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8fbebed140
commit
a08c193a90
4 files changed
+71
No files matched your search
+3
@@ -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;
|
||||
}
|
||||
|
||||
+64
@@ -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<Boolean> = _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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1121,4 +1121,6 @@
|
||||
<string name="battery_not_done">Шаг 1 ещё не сделан — VPN может отключаться в фоне</string>
|
||||
<string name="battery_ok">Готово: Android не будет усыплять VPN</string>
|
||||
<string name="battery_check_again">Проверить снова</string>
|
||||
<string name="consent_hint_title">Сейчас Android спросит разрешение</string>
|
||||
<string name="consent_hint_text">Это стандартное окно для любого VPN. Нажмите «ОК» — без этого подключиться нельзя.</string>
|
||||
</resources>
|
||||
@@ -1107,4 +1107,6 @@
|
||||
<string name="battery_not_done">Step 1 is not done yet — the VPN may stop in the background</string>
|
||||
<string name="battery_ok">Done: Android will not put the VPN to sleep</string>
|
||||
<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>
|
||||
</resources>
|
||||
Reference in new issue
Block a user