mirror of
https://github.com/wgtunnel/android.git
synced 2026-07-03 14:07:49 +02:00
feat: copy split config from existing
This commit is contained in:
@@ -2,18 +2,18 @@ package com.zaneschepke.wireguardautotunnel.core.tunnel
|
|||||||
|
|
||||||
import com.wireguard.android.backend.Backend
|
import com.wireguard.android.backend.Backend
|
||||||
import com.wireguard.android.backend.BackendException
|
import com.wireguard.android.backend.BackendException
|
||||||
import com.wireguard.android.backend.WgQuickBackend
|
|
||||||
import com.wireguard.android.backend.Tunnel as WgTunnel
|
import com.wireguard.android.backend.Tunnel as WgTunnel
|
||||||
|
import com.wireguard.android.backend.WgQuickBackend
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
||||||
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.di.Kernel
|
import com.zaneschepke.wireguardautotunnel.di.Kernel
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendMode
|
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendMode
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.events.KernelWireguardNotSupported
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.events.DnsFailure
|
import com.zaneschepke.wireguardautotunnel.domain.events.DnsFailure
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.events.InvalidConfig
|
import com.zaneschepke.wireguardautotunnel.domain.events.InvalidConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.events.KernelTunnelName
|
import com.zaneschepke.wireguardautotunnel.domain.events.KernelTunnelName
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.events.KernelWireguardNotSupported
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.events.UnknownError
|
import com.zaneschepke.wireguardautotunnel.domain.events.UnknownError
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
|
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
|
||||||
|
|||||||
+20
-5
@@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Row
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
||||||
import androidx.compose.material.icons.automirrored.rounded.Sort
|
import androidx.compose.material.icons.automirrored.rounded.Sort
|
||||||
|
import androidx.compose.material.icons.outlined.ContentPasteGo
|
||||||
import androidx.compose.material.icons.outlined.CopyAll
|
import androidx.compose.material.icons.outlined.CopyAll
|
||||||
import androidx.compose.material.icons.rounded.*
|
import androidx.compose.material.icons.rounded.*
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@@ -279,12 +280,26 @@ fun currentRouteAsNavbarState(
|
|||||||
},
|
},
|
||||||
topTitle = tunnelName ?: "",
|
topTitle = tunnelName ?: "",
|
||||||
topTrailing = {
|
topTrailing = {
|
||||||
IconButton(
|
Row {
|
||||||
onClick = {
|
IconButton(
|
||||||
sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges)
|
onClick = {
|
||||||
|
sharedViewModel.postSideEffect(
|
||||||
|
LocalSideEffect.Modal.SelectTunnel
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Outlined.ContentPasteGo,
|
||||||
|
stringResource(R.string.copy_from),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(Icons.Rounded.Save, stringResource(R.string.save))
|
||||||
}
|
}
|
||||||
) {
|
|
||||||
Icon(Icons.Rounded.Save, stringResource(R.string.save))
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
showBottomItems = true,
|
showBottomItems = true,
|
||||||
|
|||||||
+36
-16
@@ -11,6 +11,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
|
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.splittunnel.components.SelectTunnelModal
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.splittunnel.components.SplitTunnelContent
|
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.splittunnel.components.SplitTunnelContent
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.splittunnel.state.SplitOption
|
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.splittunnel.state.SplitOption
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.sideeffect.LocalSideEffect
|
import com.zaneschepke.wireguardautotunnel.ui.sideeffect.LocalSideEffect
|
||||||
@@ -21,38 +22,57 @@ import org.orbitmvi.orbit.compose.collectSideEffect
|
|||||||
@Composable
|
@Composable
|
||||||
fun SplitTunnelScreen(viewModel: SplitTunnelViewModel) {
|
fun SplitTunnelScreen(viewModel: SplitTunnelViewModel) {
|
||||||
val sharedViewModel = LocalSharedVm.current
|
val sharedViewModel = LocalSharedVm.current
|
||||||
val splitTunnelState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
|
||||||
|
|
||||||
if (splitTunnelState.isLoading) {
|
val sharedUiState by sharedViewModel.container.stateFlow.collectAsStateWithLifecycle()
|
||||||
|
val uiState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
var showDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
if (uiState.isLoading) {
|
||||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
CircularWavyProgressIndicator(waveSpeed = 60.dp, modifier = Modifier.size(48.dp))
|
CircularWavyProgressIndicator(waveSpeed = 60.dp, modifier = Modifier.size(48.dp))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val tunnel = splitTunnelState.tunnel ?: return
|
val tunnel = uiState.tunnel ?: return
|
||||||
|
|
||||||
val conf by remember { derivedStateOf { tunnel.toAmConfig() } }
|
var effectiveTunnel by remember { mutableStateOf(tunnel) }
|
||||||
|
|
||||||
var splitConfig by remember {
|
val conf by remember(effectiveTunnel) { derivedStateOf { effectiveTunnel.toAmConfig() } }
|
||||||
mutableStateOf(
|
|
||||||
when {
|
var splitConfig by
|
||||||
conf.`interface`.excludedApplications.isNotEmpty() ->
|
remember(conf) {
|
||||||
Pair(SplitOption.EXCLUDE, conf.`interface`.excludedApplications.toSet())
|
mutableStateOf(
|
||||||
conf.`interface`.includedApplications.isNotEmpty() ->
|
when {
|
||||||
Pair(SplitOption.INCLUDE, conf.`interface`.includedApplications.toSet())
|
conf.`interface`.excludedApplications.isNotEmpty() ->
|
||||||
else -> Pair(SplitOption.ALL, emptySet<String>())
|
Pair(SplitOption.EXCLUDE, conf.`interface`.excludedApplications.toSet())
|
||||||
}
|
conf.`interface`.includedApplications.isNotEmpty() ->
|
||||||
)
|
Pair(SplitOption.INCLUDE, conf.`interface`.includedApplications.toSet())
|
||||||
}
|
else -> Pair(SplitOption.ALL, emptySet<String>())
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
sharedViewModel.collectSideEffect { sideEffect ->
|
sharedViewModel.collectSideEffect { sideEffect ->
|
||||||
if (sideEffect is LocalSideEffect.SaveChanges)
|
if (sideEffect is LocalSideEffect.SaveChanges)
|
||||||
viewModel.saveSplitTunnelSelection(splitConfig)
|
viewModel.saveSplitTunnelSelection(splitConfig)
|
||||||
|
if (sideEffect is LocalSideEffect.Modal.SelectTunnel) showDialog = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SelectTunnelModal(
|
||||||
|
showDialog,
|
||||||
|
sharedUiState.tunnels,
|
||||||
|
onAttest = { conf ->
|
||||||
|
if (conf == null) return@SelectTunnelModal
|
||||||
|
effectiveTunnel = conf
|
||||||
|
showDialog = false
|
||||||
|
},
|
||||||
|
onDismiss = { showDialog = false },
|
||||||
|
)
|
||||||
|
|
||||||
SplitTunnelContent(
|
SplitTunnelContent(
|
||||||
splitConfig = splitConfig,
|
splitConfig = splitConfig,
|
||||||
installedPackages = splitTunnelState.installedPackages,
|
installedPackages = uiState.installedPackages,
|
||||||
onSplitOptionChange = { splitConfig = Pair(it, splitConfig.second) },
|
onSplitOptionChange = { splitConfig = Pair(it, splitConfig.second) },
|
||||||
onAppSelectionToggle = { appPackage, enabled ->
|
onAppSelectionToggle = { appPackage, enabled ->
|
||||||
val updated =
|
val updated =
|
||||||
|
|||||||
+70
@@ -0,0 +1,70 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.splittunnel.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.gestures.ScrollableDefaults
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.foundation.overscroll
|
||||||
|
import androidx.compose.foundation.rememberOverscrollEffect
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Check
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConfig
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SelectTunnelModal(
|
||||||
|
show: Boolean,
|
||||||
|
tunnels: List<TunnelConfig>,
|
||||||
|
onAttest: (tunnelConf: TunnelConfig?) -> Unit,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
) {
|
||||||
|
var selectedTunnel by remember { mutableStateOf<TunnelConfig?>(null) }
|
||||||
|
if (show) {
|
||||||
|
InfoDialog(
|
||||||
|
title = stringResource(R.string.copy_from),
|
||||||
|
body = {
|
||||||
|
LazyColumn(
|
||||||
|
horizontalAlignment = Alignment.Start,
|
||||||
|
verticalArrangement = Arrangement.Top,
|
||||||
|
modifier =
|
||||||
|
Modifier.pointerInput(Unit) { if (tunnels.isEmpty()) return@pointerInput }
|
||||||
|
.overscroll(rememberOverscrollEffect()),
|
||||||
|
state = rememberLazyListState(),
|
||||||
|
userScrollEnabled = true,
|
||||||
|
reverseLayout = false,
|
||||||
|
flingBehavior = ScrollableDefaults.flingBehavior(),
|
||||||
|
) {
|
||||||
|
items(tunnels, key = { it.id }) { tunnel ->
|
||||||
|
SurfaceRow(
|
||||||
|
title = tunnel.name,
|
||||||
|
trailing =
|
||||||
|
if (selectedTunnel?.id == tunnel.id) {
|
||||||
|
{
|
||||||
|
Icon(
|
||||||
|
Icons.Outlined.Check,
|
||||||
|
stringResource(id = R.string.selected),
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else null,
|
||||||
|
onClick = { selectedTunnel = tunnel },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onAttest = { onAttest(selectedTunnel) },
|
||||||
|
onDismiss = { onDismiss() },
|
||||||
|
confirmText = stringResource(R.string.copy),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+25
-9
@@ -3,11 +3,15 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.splittunnel.compo
|
|||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.model.InstalledPackage
|
import com.zaneschepke.wireguardautotunnel.domain.model.InstalledPackage
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.splittunnel.state.SplitOption
|
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.splittunnel.state.SplitOption
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -22,16 +26,28 @@ fun SplitTunnelContent(
|
|||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
) {
|
) {
|
||||||
SplitOptionSelector(
|
Column {
|
||||||
selectedOption = splitConfig.first,
|
GroupLabel(
|
||||||
onOptionChange = onSplitOptionChange,
|
stringResource(R.string.mode),
|
||||||
)
|
modifier = Modifier.padding(horizontal = 16.dp).padding(bottom = 8.dp),
|
||||||
if (splitConfig.first != SplitOption.ALL) {
|
|
||||||
AppListSection(
|
|
||||||
installedPackages = installedPackages,
|
|
||||||
onAppSelectionToggle = onAppSelectionToggle,
|
|
||||||
splitConfig = splitConfig,
|
|
||||||
)
|
)
|
||||||
|
SplitOptionSelector(
|
||||||
|
selectedOption = splitConfig.first,
|
||||||
|
onOptionChange = onSplitOptionChange,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (splitConfig.first != SplitOption.ALL) {
|
||||||
|
Column {
|
||||||
|
GroupLabel(
|
||||||
|
stringResource(R.string.app_selection),
|
||||||
|
modifier = Modifier.padding(16.dp).padding(bottom = 8.dp),
|
||||||
|
)
|
||||||
|
AppListSection(
|
||||||
|
installedPackages = installedPackages,
|
||||||
|
onAppSelectionToggle = onAppSelectionToggle,
|
||||||
|
splitConfig = splitConfig,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -19,7 +19,7 @@ sealed class LocalSideEffect {
|
|||||||
|
|
||||||
data object DeleteTunnels : Modal()
|
data object DeleteTunnels : Modal()
|
||||||
|
|
||||||
data object AddCustomApp : Modal()
|
data object SelectTunnel : Modal()
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class SelectedTunnels : LocalSideEffect() {
|
sealed class SelectedTunnels : LocalSideEffect() {
|
||||||
|
|||||||
+4
-1
@@ -4,6 +4,7 @@ import android.net.Uri
|
|||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import com.wireguard.android.backend.WgQuickBackend
|
import com.wireguard.android.backend.WgQuickBackend
|
||||||
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
|
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
|
||||||
import com.zaneschepke.wireguardautotunnel.data.model.AppMode
|
import com.zaneschepke.wireguardautotunnel.data.model.AppMode
|
||||||
@@ -166,7 +167,9 @@ constructor(
|
|||||||
postSideEffect(GlobalSideEffect.Snackbar(message))
|
postSideEffect(GlobalSideEffect.Snackbar(message))
|
||||||
if (!accepted) return@intent
|
if (!accepted) return@intent
|
||||||
if (WgQuickBackend.hasKernelSupport())
|
if (WgQuickBackend.hasKernelSupport())
|
||||||
Timber.i("Device supports kernel backend. WireGuard module is built in, switching to kernel backend.")
|
Timber.i(
|
||||||
|
"Device supports kernel backend. WireGuard module is built in, switching to kernel backend."
|
||||||
|
)
|
||||||
else {
|
else {
|
||||||
Timber.e("Device does not support kernel backend!")
|
Timber.e("Device does not support kernel backend!")
|
||||||
intent {
|
intent {
|
||||||
|
|||||||
@@ -456,4 +456,7 @@
|
|||||||
<string name="i3" translatable="false">I3</string>
|
<string name="i3" translatable="false">I3</string>
|
||||||
<string name="i4" translatable="false">I4</string>
|
<string name="i4" translatable="false">I4</string>
|
||||||
<string name="i5" translatable="false">I5</string>
|
<string name="i5" translatable="false">I5</string>
|
||||||
|
<string name="copy_from">Copy from</string>
|
||||||
|
<string name="mode">Mode</string>
|
||||||
|
<string name="app_selection">App selection</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user