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(),
|
new ActivityResultContracts.StartActivityForResult(),
|
||||||
result -> {
|
result -> {
|
||||||
mWaitingForResult = false;
|
mWaitingForResult = false;
|
||||||
|
VpnDialogBus.INSTANCE.setConsentHint(false);
|
||||||
if (result.getResultCode() == RESULT_OK && mProfileInfo != null)
|
if (result.getResultCode() == RESULT_OK && mProfileInfo != null)
|
||||||
{
|
{
|
||||||
onVpnServicePrepared();
|
onVpnServicePrepared();
|
||||||
@@ -224,6 +225,7 @@ public class VpnProfileControlActivity extends AppCompatActivity implements VpnD
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
mWaitingForResult = true;
|
mWaitingForResult = true;
|
||||||
|
VpnDialogBus.INSTANCE.setConsentHint(true);
|
||||||
mPrepareVpnService.launch(intent);
|
mPrepareVpnService.launch(intent);
|
||||||
}
|
}
|
||||||
catch (ActivityNotFoundException ex)
|
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.
|
* don't have the VPN components built into the system image.
|
||||||
* com.android.vpndialogs/com.android.vpndialogs.ConfirmDialog
|
* com.android.vpndialogs/com.android.vpndialogs.ConfirmDialog
|
||||||
* will not be found then */
|
* will not be found then */
|
||||||
|
VpnDialogBus.INSTANCE.setConsentHint(false);
|
||||||
showNotSupported(R.string.vpn_not_supported);
|
showNotSupported(R.string.vpn_not_supported);
|
||||||
mWaitingForResult = false;
|
mWaitingForResult = false;
|
||||||
}
|
}
|
||||||
|
|||||||
+64
@@ -45,6 +45,22 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
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.R
|
||||||
import org.strongswan.android.ui.compose.theme.ShxSpacing
|
import org.strongswan.android.ui.compose.theme.ShxSpacing
|
||||||
import org.strongswan.android.ui.compose.theme.ShxTheme
|
import org.strongswan.android.ui.compose.theme.ShxTheme
|
||||||
@@ -106,6 +122,16 @@ object VpnDialogBus
|
|||||||
{
|
{
|
||||||
_dialog.value = null
|
_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. */
|
/** Renders the connect-flow dialogs in the app theme inside the transparent activity. */
|
||||||
@@ -122,6 +148,11 @@ fun ComponentActivity.installVpnDialogHost()
|
|||||||
fun VpnDialogHost()
|
fun VpnDialogHost()
|
||||||
{
|
{
|
||||||
val dialog by VpnDialogBus.dialog.collectAsStateWithLifecycle()
|
val dialog by VpnDialogBus.dialog.collectAsStateWithLifecycle()
|
||||||
|
val consentHint by VpnDialogBus.consentHint.collectAsStateWithLifecycle()
|
||||||
|
if (consentHint)
|
||||||
|
{
|
||||||
|
ConsentHint()
|
||||||
|
}
|
||||||
when (val d = dialog)
|
when (val d = dialog)
|
||||||
{
|
{
|
||||||
is VpnDialog.Confirmation -> ConfirmationUi(d)
|
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_not_done">Шаг 1 ещё не сделан — VPN может отключаться в фоне</string>
|
||||||
<string name="battery_ok">Готово: Android не будет усыплять VPN</string>
|
<string name="battery_ok">Готово: Android не будет усыплять VPN</string>
|
||||||
<string name="battery_check_again">Проверить снова</string>
|
<string name="battery_check_again">Проверить снова</string>
|
||||||
|
<string name="consent_hint_title">Сейчас Android спросит разрешение</string>
|
||||||
|
<string name="consent_hint_text">Это стандартное окно для любого VPN. Нажмите «ОК» — без этого подключиться нельзя.</string>
|
||||||
</resources>
|
</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_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_ok">Done: Android will not put the VPN to sleep</string>
|
||||||
<string name="battery_check_again">Check again</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>
|
</resources>
|
||||||
Reference in new issue
Block a user