fix: qr scanner nav crash
This commit is contained in:
parent
2c0c88baf2
commit
a670931b06
|
@ -55,6 +55,7 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.config.ConfigScreen
|
|||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.MainScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.options.OptionsScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.pinlock.PinLockScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.scanner.ScannerScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.SettingsScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.SupportScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.logs.LogsScreen
|
||||
|
@ -209,6 +210,9 @@ class MainActivity : AppCompatActivity() {
|
|||
appViewModel = viewModel,
|
||||
)
|
||||
}
|
||||
composable<Route.Scanner> {
|
||||
ScannerScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,9 @@ sealed class Route {
|
|||
@Serializable
|
||||
data object Lock : Route()
|
||||
|
||||
@Serializable
|
||||
data object Scanner : Route()
|
||||
|
||||
@Serializable
|
||||
data class Config(
|
||||
val id: Int,
|
||||
|
|
|
@ -36,8 +36,6 @@ import androidx.compose.ui.input.pointer.pointerInput
|
|||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.journeyapps.barcodescanner.ScanContract
|
||||
import com.journeyapps.barcodescanner.ScanOptions
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
import com.zaneschepke.wireguardautotunnel.ui.AppUiState
|
||||
|
@ -105,15 +103,12 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
|
|||
viewModel.onTunnelFileSelected(data, context)
|
||||
})
|
||||
|
||||
val scanLauncher =
|
||||
rememberLauncherForActivityResult(
|
||||
contract = ScanContract(),
|
||||
onResult = {
|
||||
if (it.contents != null) {
|
||||
viewModel.onTunnelQrResult(it.contents)
|
||||
}
|
||||
},
|
||||
)
|
||||
val requestPermissionLauncher = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.RequestPermission(),
|
||||
) { isGranted ->
|
||||
if (!isGranted) return@rememberLauncherForActivityResult snackbar.showMessage("Camera permission required")
|
||||
navController.navigate(Route.Scanner)
|
||||
}
|
||||
|
||||
VpnDeniedDialog(showVpnPermissionDialog, onDismiss = { showVpnPermissionDialog = false })
|
||||
|
||||
|
@ -142,17 +137,6 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
|
|||
}
|
||||
}
|
||||
|
||||
fun launchQrScanner() {
|
||||
val scanOptions = ScanOptions()
|
||||
scanOptions.setDesiredBarcodeFormats(ScanOptions.QR_CODE)
|
||||
scanOptions.setOrientationLocked(true)
|
||||
scanOptions.setPrompt(
|
||||
context.getString(R.string.scanning_qr),
|
||||
)
|
||||
scanOptions.setBeepEnabled(false)
|
||||
scanLauncher.launch(scanOptions)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
modifier =
|
||||
Modifier.pointerInput(Unit) {
|
||||
|
@ -181,7 +165,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
|
|||
showBottomSheet,
|
||||
onDismiss = { showBottomSheet = false },
|
||||
onFileClick = { tunnelFileImportResultLauncher.launch(Constants.ALLOWED_TV_FILE_TYPES) },
|
||||
onQrClick = { launchQrScanner() },
|
||||
onQrClick = { requestPermissionLauncher.launch(android.Manifest.permission.CAMERA) },
|
||||
onManualImportClick = {
|
||||
navController.navigate(
|
||||
Route.Config(Constants.MANUAL_TUNNEL_CONFIG_ID),
|
||||
|
|
|
@ -100,21 +100,6 @@ constructor(
|
|||
return defaultName
|
||||
}
|
||||
|
||||
fun onTunnelQrResult(result: String) = viewModelScope.launch(ioDispatcher) {
|
||||
kotlin.runCatching {
|
||||
val amConfig = TunnelConfig.configFromAmQuick(result)
|
||||
val amQuick = amConfig.toAwgQuickString(true)
|
||||
val wgQuick = amConfig.toWgQuickString()
|
||||
|
||||
val tunnelName = makeTunnelNameUnique(generateQrCodeTunnelName(result))
|
||||
val tunnelConfig = TunnelConfig(name = tunnelName, wgQuick = wgQuick, amQuick = amQuick)
|
||||
saveTunnel(tunnelConfig)
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
SnackbarController.showMessage(StringValue.StringResource(R.string.error_invalid_code))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun makeTunnelNameUnique(name: String): String {
|
||||
return withContext(ioDispatcher) {
|
||||
val tunnels = appDataRepository.tunnels.getAll()
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
package com.zaneschepke.wireguardautotunnel.ui.screens.scanner
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.journeyapps.barcodescanner.CompoundBarcodeView
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
|
||||
|
||||
@OptIn(ExperimentalPermissionsApi::class)
|
||||
@Composable
|
||||
fun ScannerScreen(viewModel: ScannerViewModel = hiltViewModel()) {
|
||||
val context = LocalContext.current
|
||||
val navController = LocalNavController.current
|
||||
|
||||
val success = viewModel.success.collectAsStateWithLifecycle(null)
|
||||
|
||||
LaunchedEffect(success.value) {
|
||||
if (success.value != null) navController.popBackStack()
|
||||
}
|
||||
|
||||
val barcodeView = remember {
|
||||
CompoundBarcodeView(context).apply {
|
||||
this.initializeFromIntent((context as Activity).intent)
|
||||
this.setStatusText("")
|
||||
this.decodeSingle { result ->
|
||||
result.text?.let { barCodeOrQr ->
|
||||
viewModel.onTunnelQrResult(barCodeOrQr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
AndroidView(factory = { barcodeView })
|
||||
DisposableEffect(Unit) {
|
||||
barcodeView.resume()
|
||||
onDispose {
|
||||
barcodeView.pause()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package com.zaneschepke.wireguardautotunnel.ui.screens.scanner
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
||||
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
|
||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.toWgQuickString
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class ScannerViewModel @Inject
|
||||
constructor(
|
||||
private val appDataRepository: AppDataRepository,
|
||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _success = MutableSharedFlow<Boolean>()
|
||||
val success = _success.asSharedFlow()
|
||||
|
||||
private suspend fun makeTunnelNameUnique(name: String): String {
|
||||
return withContext(ioDispatcher) {
|
||||
val tunnels = appDataRepository.tunnels.getAll()
|
||||
var tunnelName = name
|
||||
var num = 1
|
||||
while (tunnels.any { it.name == tunnelName }) {
|
||||
tunnelName = "$name($num)"
|
||||
num++
|
||||
}
|
||||
tunnelName
|
||||
}
|
||||
}
|
||||
|
||||
fun onTunnelQrResult(result: String) = viewModelScope.launch(ioDispatcher) {
|
||||
kotlin.runCatching {
|
||||
val amConfig = TunnelConfig.configFromAmQuick(result)
|
||||
val amQuick = amConfig.toAwgQuickString(true)
|
||||
val wgQuick = amConfig.toWgQuickString()
|
||||
val tunnelName = makeTunnelNameUnique(generateQrCodeDefaultName(result))
|
||||
val tunnelConfig = TunnelConfig(name = tunnelName, wgQuick = wgQuick, amQuick = amQuick)
|
||||
appDataRepository.tunnels.save(tunnelConfig)
|
||||
_success.emit(true)
|
||||
}.onFailure {
|
||||
_success.emit(false)
|
||||
Timber.e(it)
|
||||
SnackbarController.showMessage(StringValue.StringResource(R.string.error_invalid_code))
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateQrCodeDefaultName(config: String): String {
|
||||
return try {
|
||||
TunnelConfig.configFromAmQuick(config).peers[0].endpoint.get().host
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
NumberUtils.generateRandomTunnelName()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package com.zaneschepke.wireguardautotunnel.util.extensions
|
||||
|
||||
import androidx.navigation.NavController
|
||||
import com.zaneschepke.wireguardautotunnel.ui.Route
|
||||
|
||||
fun NavController.navigateAndForget(route: Route) {
|
||||
navigate(route) {
|
||||
popUpTo(0)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue