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.BackendException
|
||||
import com.wireguard.android.backend.WgQuickBackend
|
||||
import com.wireguard.android.backend.Tunnel as WgTunnel
|
||||
import com.wireguard.android.backend.WgQuickBackend
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.di.Kernel
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendMode
|
||||
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.InvalidConfig
|
||||
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.model.TunnelConfig
|
||||
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.automirrored.rounded.ArrowBack
|
||||
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.rounded.*
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -279,12 +280,26 @@ fun currentRouteAsNavbarState(
|
||||
},
|
||||
topTitle = tunnelName ?: "",
|
||||
topTrailing = {
|
||||
IconButton(
|
||||
onClick = {
|
||||
sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges)
|
||||
Row {
|
||||
IconButton(
|
||||
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,
|
||||
|
||||
+36
-16
@@ -11,6 +11,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
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.state.SplitOption
|
||||
import com.zaneschepke.wireguardautotunnel.ui.sideeffect.LocalSideEffect
|
||||
@@ -21,38 +22,57 @@ import org.orbitmvi.orbit.compose.collectSideEffect
|
||||
@Composable
|
||||
fun SplitTunnelScreen(viewModel: SplitTunnelViewModel) {
|
||||
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) {
|
||||
CircularWavyProgressIndicator(waveSpeed = 60.dp, modifier = Modifier.size(48.dp))
|
||||
}
|
||||
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 {
|
||||
mutableStateOf(
|
||||
when {
|
||||
conf.`interface`.excludedApplications.isNotEmpty() ->
|
||||
Pair(SplitOption.EXCLUDE, conf.`interface`.excludedApplications.toSet())
|
||||
conf.`interface`.includedApplications.isNotEmpty() ->
|
||||
Pair(SplitOption.INCLUDE, conf.`interface`.includedApplications.toSet())
|
||||
else -> Pair(SplitOption.ALL, emptySet<String>())
|
||||
}
|
||||
)
|
||||
}
|
||||
val conf by remember(effectiveTunnel) { derivedStateOf { effectiveTunnel.toAmConfig() } }
|
||||
|
||||
var splitConfig by
|
||||
remember(conf) {
|
||||
mutableStateOf(
|
||||
when {
|
||||
conf.`interface`.excludedApplications.isNotEmpty() ->
|
||||
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 ->
|
||||
if (sideEffect is LocalSideEffect.SaveChanges)
|
||||
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(
|
||||
splitConfig = splitConfig,
|
||||
installedPackages = splitTunnelState.installedPackages,
|
||||
installedPackages = uiState.installedPackages,
|
||||
onSplitOptionChange = { splitConfig = Pair(it, splitConfig.second) },
|
||||
onAppSelectionToggle = { appPackage, enabled ->
|
||||
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.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
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
|
||||
|
||||
@Composable
|
||||
@@ -22,16 +26,28 @@ fun SplitTunnelContent(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
SplitOptionSelector(
|
||||
selectedOption = splitConfig.first,
|
||||
onOptionChange = onSplitOptionChange,
|
||||
)
|
||||
if (splitConfig.first != SplitOption.ALL) {
|
||||
AppListSection(
|
||||
installedPackages = installedPackages,
|
||||
onAppSelectionToggle = onAppSelectionToggle,
|
||||
splitConfig = splitConfig,
|
||||
Column {
|
||||
GroupLabel(
|
||||
stringResource(R.string.mode),
|
||||
modifier = Modifier.padding(horizontal = 16.dp).padding(bottom = 8.dp),
|
||||
)
|
||||
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 AddCustomApp : Modal()
|
||||
data object SelectTunnel : Modal()
|
||||
}
|
||||
|
||||
sealed class SelectedTunnels : LocalSideEffect() {
|
||||
|
||||
+4
-1
@@ -4,6 +4,7 @@ import android.net.Uri
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.wireguard.android.backend.WgQuickBackend
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
|
||||
import com.zaneschepke.wireguardautotunnel.data.model.AppMode
|
||||
@@ -166,7 +167,9 @@ constructor(
|
||||
postSideEffect(GlobalSideEffect.Snackbar(message))
|
||||
if (!accepted) return@intent
|
||||
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 {
|
||||
Timber.e("Device does not support kernel backend!")
|
||||
intent {
|
||||
|
||||
@@ -456,4 +456,7 @@
|
||||
<string name="i3" translatable="false">I3</string>
|
||||
<string name="i4" translatable="false">I4</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>
|
||||
|
||||
Reference in New Issue
Block a user