feat: add tunnel from clipboard

closes #431
This commit is contained in:
Zane Schepke 2024-11-23 15:48:11 -05:00
parent 9a2d77c8bf
commit 9bb30069fe
7 changed files with 60 additions and 10 deletions

View File

@ -5,6 +5,7 @@ import androidx.room.Entity
import androidx.room.Index import androidx.room.Index
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.wireguard.config.Config import com.wireguard.config.Config
import com.zaneschepke.wireguardautotunnel.util.extensions.toWgQuickString
import java.io.InputStream import java.io.InputStream
@Entity(indices = [Index(value = ["name"], unique = true)]) @Entity(indices = [Index(value = ["name"], unique = true)])
@ -79,6 +80,12 @@ data class TunnelConfig(
} }
} }
fun tunnelConfigFromAmConfig(config: org.amnezia.awg.config.Config, name: String): TunnelConfig {
val amQuick = config.toAwgQuickString(true)
val wgQuick = config.toWgQuickString()
return TunnelConfig(name = name, wgQuick = wgQuick, amQuick = amQuick)
}
const val AM_QUICK_DEFAULT = "" const val AM_QUICK_DEFAULT = ""
} }
} }

View File

@ -37,6 +37,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -68,6 +69,7 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState) { fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState) {
val context = LocalContext.current val context = LocalContext.current
val navController = LocalNavController.current val navController = LocalNavController.current
val clipboard = LocalClipboardManager.current
val snackbar = SnackbarController.current val snackbar = SnackbarController.current
var showBottomSheet by remember { mutableStateOf(false) } var showBottomSheet by remember { mutableStateOf(false) }
@ -201,12 +203,17 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
) )
} }
}, },
) { ) { padding ->
TunnelImportSheet( TunnelImportSheet(
showBottomSheet, showBottomSheet,
onDismiss = { showBottomSheet = false }, onDismiss = { showBottomSheet = false },
onFileClick = { tunnelFileImportResultLauncher.launch(Constants.ALLOWED_TV_FILE_TYPES) }, onFileClick = { tunnelFileImportResultLauncher.launch(Constants.ALLOWED_TV_FILE_TYPES) },
onQrClick = { requestPermissionLauncher.launch(android.Manifest.permission.CAMERA) }, onQrClick = { requestPermissionLauncher.launch(android.Manifest.permission.CAMERA) },
onClipboardClick = {
clipboard.getText()?.text?.let {
viewModel.onClipboardImport(it)
}
},
onManualImportClick = { onManualImportClick = {
navController.navigate( navController.navigate(
Route.Config(Constants.MANUAL_TUNNEL_CONFIG_ID), Route.Config(Constants.MANUAL_TUNNEL_CONFIG_ID),
@ -218,7 +225,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
verticalArrangement = Arrangement.spacedBy(5.dp.scaledHeight(), Alignment.Top), verticalArrangement = Arrangement.spacedBy(5.dp.scaledHeight(), Alignment.Top),
modifier = modifier =
Modifier Modifier
.fillMaxSize().padding(it) .fillMaxSize().padding(padding)
.overscroll(ScrollableDefaults.overscrollEffect()) .overscroll(ScrollableDefaults.overscrollEffect())
.nestedScroll(nestedScrollConnection), .nestedScroll(nestedScrollConnection),
state = rememberLazyListState(0, uiState.tunnels.count()), state = rememberLazyListState(0, uiState.tunnels.count()),

View File

@ -261,4 +261,14 @@ constructor(
), ),
) )
} }
fun onClipboardImport(config: String) = viewModelScope.launch(ioDispatcher) {
runCatching {
val amConfig = TunnelConfig.configFromAmQuick(config)
val tunnelConfig = TunnelConfig.tunnelConfigFromAmConfig(amConfig, makeTunnelNameUnique(generateQrCodeDefaultName(config)))
saveTunnel(tunnelConfig)
}.onFailure {
SnackbarController.showMessage(StringValue.StringResource(R.string.error_file_format))
}
}
} }

View File

@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ContentPasteGo
import androidx.compose.material.icons.filled.Create import androidx.compose.material.icons.filled.Create
import androidx.compose.material.icons.filled.FileOpen import androidx.compose.material.icons.filled.FileOpen
import androidx.compose.material.icons.filled.QrCode import androidx.compose.material.icons.filled.QrCode
@ -22,9 +23,17 @@ import androidx.compose.ui.unit.dp
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
// TODO refactor this component
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun TunnelImportSheet(show: Boolean, onDismiss: () -> Unit, onFileClick: () -> Unit, onQrClick: () -> Unit, onManualImportClick: () -> Unit) { fun TunnelImportSheet(
show: Boolean,
onDismiss: () -> Unit,
onFileClick: () -> Unit,
onQrClick: () -> Unit,
onManualImportClick: () -> Unit,
onClipboardClick: () -> Unit,
) {
val sheetState = rememberModalBottomSheetState() val sheetState = rememberModalBottomSheetState()
val context = LocalContext.current val context = LocalContext.current
@ -77,6 +86,28 @@ fun TunnelImportSheet(show: Boolean, onDismiss: () -> Unit, onFileClick: () -> U
modifier = Modifier.padding(10.dp), modifier = Modifier.padding(10.dp),
) )
} }
HorizontalDivider()
Row(
modifier =
Modifier
.fillMaxWidth()
.clickable {
onDismiss()
onClipboardClick()
}
.padding(10.dp),
) {
val icon = Icons.Filled.ContentPasteGo
Icon(
icon,
contentDescription = icon.name,
modifier = Modifier.padding(10.dp),
)
Text(
stringResource(id = R.string.add_from_clipboard),
modifier = Modifier.padding(10.dp),
)
}
} }
HorizontalDivider() HorizontalDivider()
Row( Row(

View File

@ -9,11 +9,9 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.journeyapps.barcodescanner.CompoundBarcodeView import com.journeyapps.barcodescanner.CompoundBarcodeView
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
@OptIn(ExperimentalPermissionsApi::class)
@Composable @Composable
fun ScannerScreen(viewModel: ScannerViewModel = hiltViewModel()) { fun ScannerScreen(viewModel: ScannerViewModel = hiltViewModel()) {
val context = LocalContext.current val context = LocalContext.current

View File

@ -9,7 +9,6 @@ import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
import com.zaneschepke.wireguardautotunnel.util.NumberUtils import com.zaneschepke.wireguardautotunnel.util.NumberUtils
import com.zaneschepke.wireguardautotunnel.util.StringValue import com.zaneschepke.wireguardautotunnel.util.StringValue
import com.zaneschepke.wireguardautotunnel.util.extensions.toWgQuickString
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
@ -45,10 +44,7 @@ constructor(
fun onTunnelQrResult(result: String) = viewModelScope.launch(ioDispatcher) { fun onTunnelQrResult(result: String) = viewModelScope.launch(ioDispatcher) {
kotlin.runCatching { kotlin.runCatching {
val amConfig = TunnelConfig.configFromAmQuick(result) val amConfig = TunnelConfig.configFromAmQuick(result)
val amQuick = amConfig.toAwgQuickString(true) val tunnelConfig = TunnelConfig.tunnelConfigFromAmConfig(amConfig, makeTunnelNameUnique(generateQrCodeDefaultName(result)))
val wgQuick = amConfig.toWgQuickString()
val tunnelName = makeTunnelNameUnique(generateQrCodeDefaultName(result))
val tunnelConfig = TunnelConfig(name = tunnelName, wgQuick = wgQuick, amQuick = amQuick)
appDataRepository.tunnels.save(tunnelConfig) appDataRepository.tunnels.save(tunnelConfig)
_success.emit(true) _success.emit(true)
}.onFailure { }.onFailure {

View File

@ -177,4 +177,5 @@
<string name="enable_local_logging">Enable local logging</string> <string name="enable_local_logging">Enable local logging</string>
<string name="configuration_change">Configuration change</string> <string name="configuration_change">Configuration change</string>
<string name="requires_app_relaunch">This change requires an app relaunch. Would you like to proceed?</string> <string name="requires_app_relaunch">This change requires an app relaunch. Would you like to proceed?</string>
<string name="add_from_clipboard">Add from clipboard</string>
</resources> </resources>