android: routing - advanced block (RU details, GeoIP, force prefixes), kill switch vs excluded apps note
This commit is contained in:
1 parent
c5585df135
commit
8bd65d535e
4 files changed
+108
-50
No files matched your search
+103
-49
@@ -61,6 +61,12 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.LinearWavyProgressIndicator
|
import androidx.compose.material3.LinearWavyProgressIndicator
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.material.icons.rounded.ExpandLess
|
||||||
|
import androidx.compose.material.icons.rounded.ExpandMore
|
||||||
|
import androidx.compose.ui.semantics.heading
|
||||||
|
import androidx.compose.ui.semantics.semantics
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
@@ -119,6 +125,7 @@ import org.strongswan.android.ui.compose.components.ShxSection
|
|||||||
import org.strongswan.android.ui.compose.components.ShxSectionDivider
|
import org.strongswan.android.ui.compose.components.ShxSectionDivider
|
||||||
import org.strongswan.android.ui.compose.theme.LocalShxStatusColors
|
import org.strongswan.android.ui.compose.theme.LocalShxStatusColors
|
||||||
import org.strongswan.android.ui.compose.theme.ScreenPadding
|
import org.strongswan.android.ui.compose.theme.ScreenPadding
|
||||||
|
import org.strongswan.android.ui.compose.theme.ShxSpacing
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RoutingScreen()
|
fun RoutingScreen()
|
||||||
@@ -246,6 +253,19 @@ private fun RoutingContent(snackbarHostState: SnackbarHostState)
|
|||||||
modifier = Modifier.padding(horizontal = ScreenPadding),
|
modifier = Modifier.padding(horizontal = ScreenPadding),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/* kill switch + excluded apps: excluded apps are added to the VPN's disallowed list
|
||||||
|
* (CharonVpnService.addDisallowedApplication), so the system lockdown does not
|
||||||
|
* cover them - on a tunnel drop they keep using the normal network */
|
||||||
|
if (config.enabled && excludedApps > 0 && remember { context.isVpnLockdownEnabled() })
|
||||||
|
{
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.routing_killswitch_note, excludedApps),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.padding(horizontal = ScreenPadding + ShxSpacing.xs, vertical = ShxSpacing.sm),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/* ---- dead direct destinations the engine keeps refusing ---- */
|
/* ---- dead direct destinations the engine keeps refusing ---- */
|
||||||
val deadTargets by produceState(initialValue = emptyList<DeadTargetUi>())
|
val deadTargets by produceState(initialValue = emptyList<DeadTargetUi>())
|
||||||
{
|
{
|
||||||
@@ -322,55 +342,6 @@ private fun RoutingContent(snackbarHostState: SnackbarHostState)
|
|||||||
onCheckedChange = { ruList.setEnabled(it) },
|
onCheckedChange = { ruList.setEnabled(it) },
|
||||||
onUpdate = { refreshGeoip() },
|
onUpdate = { refreshGeoip() },
|
||||||
)
|
)
|
||||||
if (ruState.enabled)
|
|
||||||
{
|
|
||||||
ShxSectionDivider()
|
|
||||||
RuModeSelector(
|
|
||||||
mode = ruState.mode,
|
|
||||||
onModeChange = { ruList.setMode(it) },
|
|
||||||
)
|
|
||||||
if (ruState.status == RuIpListStatus.UPDATING)
|
|
||||||
{
|
|
||||||
LinearWavyProgressIndicator(
|
|
||||||
Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 16.dp, vertical = 4.dp),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
GeoipChipsRow(state = ruState)
|
|
||||||
RuDetailsDisclosure(mode = ruState.mode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- force-tunnel prefixes: always through the tunnel ---- */
|
|
||||||
ShxSection(title = stringResource(R.string.routing_force_section))
|
|
||||||
{
|
|
||||||
if (config.forceTunnelPrefixes.isEmpty())
|
|
||||||
{
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.routing_force_summary),
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 6.dp),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
config.forceTunnelPrefixes.forEachIndexed { index, entry ->
|
|
||||||
ForcePrefixRow(
|
|
||||||
entry = entry,
|
|
||||||
onToggle = { manager.setForcePrefixEnabled(entry.id, it) },
|
|
||||||
onEdit = { forceDialogId = entry.id },
|
|
||||||
onDelete = { manager.deleteForcePrefix(entry.id) },
|
|
||||||
)
|
|
||||||
if (index < config.forceTunnelPrefixes.lastIndex)
|
|
||||||
{
|
|
||||||
ShxSectionDivider()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ShxSectionDivider()
|
|
||||||
AddForcePrefixRow(onClick = { forceDialogId = "" })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- rule sets ---- */
|
/* ---- rule sets ---- */
|
||||||
@@ -410,6 +381,89 @@ private fun RoutingContent(snackbarHostState: SnackbarHostState)
|
|||||||
AddRuleRow(onClick = { addNewSet() })
|
AddRuleRow(onClick = { addNewSet() })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- advanced: userspace mode, GeoIP list, force-tunnel prefixes ---- */
|
||||||
|
var advancedOpen by rememberSaveable { mutableStateOf(false) }
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable { advancedOpen = !advancedOpen }
|
||||||
|
.padding(horizontal = ScreenPadding + 16.dp, vertical = 16.dp),
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.routing_advanced).uppercase(),
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.weight(1f).semantics { heading() },
|
||||||
|
)
|
||||||
|
Icon(
|
||||||
|
if (advancedOpen) Icons.Rounded.ExpandLess else Icons.Rounded.ExpandMore,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
AnimatedVisibility(visible = advancedOpen)
|
||||||
|
{
|
||||||
|
Column {
|
||||||
|
if (ruState.enabled)
|
||||||
|
{
|
||||||
|
ShxSection(title = stringResource(R.string.routing_section_ru))
|
||||||
|
{
|
||||||
|
if (ruState.enabled)
|
||||||
|
{
|
||||||
|
RuModeSelector(
|
||||||
|
mode = ruState.mode,
|
||||||
|
onModeChange = { ruList.setMode(it) },
|
||||||
|
)
|
||||||
|
if (ruState.status == RuIpListStatus.UPDATING)
|
||||||
|
{
|
||||||
|
LinearWavyProgressIndicator(
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 4.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
GeoipChipsRow(state = ruState)
|
||||||
|
RuDetailsDisclosure(mode = ruState.mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* ---- force-tunnel prefixes: always through the tunnel ---- */
|
||||||
|
ShxSection(title = stringResource(R.string.routing_force_section))
|
||||||
|
{
|
||||||
|
if (config.forceTunnelPrefixes.isEmpty())
|
||||||
|
{
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.routing_force_summary),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp, vertical = 6.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
config.forceTunnelPrefixes.forEachIndexed { index, entry ->
|
||||||
|
ForcePrefixRow(
|
||||||
|
entry = entry,
|
||||||
|
onToggle = { manager.setForcePrefixEnabled(entry.id, it) },
|
||||||
|
onEdit = { forceDialogId = entry.id },
|
||||||
|
onDelete = { manager.deleteForcePrefix(entry.id) },
|
||||||
|
)
|
||||||
|
if (index < config.forceTunnelPrefixes.lastIndex)
|
||||||
|
{
|
||||||
|
ShxSectionDivider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ShxSectionDivider()
|
||||||
|
AddForcePrefixRow(onClick = { forceDialogId = "" })
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- rule set editor, hosted outside the scrolling column ---- */
|
/* ---- rule set editor, hosted outside the scrolling column ---- */
|
||||||
|
|||||||
+1
-1
@@ -612,7 +612,7 @@ private fun updateStatusText(state: UpdateState): String
|
|||||||
* Checks whether the system-level lockdown ("Block connections without VPN") is
|
* Checks whether the system-level lockdown ("Block connections without VPN") is
|
||||||
* active for our package. Returns false if not determinable.
|
* active for our package. Returns false if not determinable.
|
||||||
*/
|
*/
|
||||||
private fun Context.isVpnLockdownEnabled(): Boolean
|
internal fun Context.isVpnLockdownEnabled(): Boolean
|
||||||
{
|
{
|
||||||
return try
|
return try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -491,4 +491,6 @@
|
|||||||
<string name="home_manage_profiles">Управление профилями</string>
|
<string name="home_manage_profiles">Управление профилями</string>
|
||||||
<string name="availability_expand">Показать детали</string>
|
<string name="availability_expand">Показать детали</string>
|
||||||
<string name="availability_collapse">Скрыть детали</string>
|
<string name="availability_collapse">Скрыть детали</string>
|
||||||
|
<string name="routing_advanced">Дополнительно</string>
|
||||||
|
<string name="routing_killswitch_note">Исключённые приложения (%1$d) всегда идут мимо VPN, в том числе при обрыве туннеля: kill switch их не блокирует.</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -496,4 +496,6 @@
|
|||||||
<string name="home_manage_profiles">Manage profiles</string>
|
<string name="home_manage_profiles">Manage profiles</string>
|
||||||
<string name="availability_expand">Show details</string>
|
<string name="availability_expand">Show details</string>
|
||||||
<string name="availability_collapse">Hide details</string>
|
<string name="availability_collapse">Hide details</string>
|
||||||
|
<string name="routing_advanced">Advanced</string>
|
||||||
|
<string name="routing_killswitch_note">%1$d excluded app(s) always use the normal network, even when the tunnel drops: the kill switch does not block them.</string>
|
||||||
</resources>
|
</resources>
|
||||||
Reference in new issue
Block a user