fix: auto-tunnel screen not loading without wifi

Fixes auto tunnel screen failing to load if you haven't connected to wifi once.

Fixes import via url.

Closes #1108
Closes #1105
This commit is contained in:
Zane Schepke
2025-12-19 11:30:39 -05:00
parent 394188b55f
commit eac674c996
4 changed files with 51 additions and 35 deletions
@@ -1,42 +1,47 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.*
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.ui.common.dialog.InfoDialog
import com.zaneschepke.wireguardautotunnel.ui.common.textbox.ConfigurationTextBox
@Composable
fun UrlImportDialog(onDismiss: () -> Unit, onConfirm: (String) -> Unit) {
var url by remember { mutableStateOf("") }
var isError by remember { mutableStateOf(false) }
AlertDialog(
onDismissRequest = onDismiss,
title = { Text(stringResource(R.string.add_from_url)) },
text = {
Column(modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp)) {
OutlinedTextField(
LaunchedEffect(url) { isError = false }
InfoDialog(
onDismiss = onDismiss,
title = stringResource(R.string.add_from_url),
body = {
Column(verticalArrangement = Arrangement.spacedBy(24.dp)) {
Text(
stringResource(R.string.import_url_description),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface,
)
ConfigurationTextBox(
value = url,
label = stringResource(R.string.enter_config_url),
hint = stringResource(R.string.example_import_url),
onValueChange = { url = it },
label = { Text(stringResource(R.string.enter_config_url)) },
modifier = Modifier.fillMaxWidth(),
isError = isError,
)
}
},
confirmButton = {
TextButton(onClick = { onConfirm(url) }, enabled = url.isNotBlank()) {
Text(stringResource(R.string.okay))
}
},
dismissButton = {
TextButton(onClick = onDismiss) { Text(stringResource(R.string.cancel)) }
confirmText = stringResource(R.string.okay),
onAttest = {
if (url.isNotBlank() && url.startsWith("https://")) {
onConfirm(url)
} else isError = true
},
)
}
@@ -1,7 +1,6 @@
package com.zaneschepke.wireguardautotunnel.viewmodel
import android.net.Uri
import androidx.core.net.toUri
import androidx.lifecycle.ViewModel
import com.wireguard.android.backend.WgQuickBackend
import com.zaneschepke.wireguardautotunnel.R
@@ -24,9 +23,11 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelName
import com.zaneschepke.wireguardautotunnel.util.extensions.asStringValue
import com.zaneschepke.wireguardautotunnel.util.extensions.saveTunnelsUniquely
import dagger.hilt.android.lifecycle.HiltViewModel
import io.ktor.client.HttpClient
import io.ktor.client.request.prepareGet
import io.ktor.client.statement.bodyAsText
import java.io.File
import java.io.IOException
import java.net.URL
import java.time.Instant
import javax.inject.Inject
import kotlinx.coroutines.flow.combine
@@ -50,6 +51,7 @@ constructor(
private val settingsRepository: GeneralSettingRepository,
private val monitoringSettingsRepository: MonitoringSettingsRepository,
private val rootShellUtils: RootShellUtils,
private val httpClient: HttpClient,
private val fileUtils: FileUtils,
) : ContainerHost<SharedAppUiState, LocalSideEffect>, ViewModel() {
@@ -239,18 +241,23 @@ constructor(
fun importFromQr(conf: String) = intent { importFromClipboard(conf) }
fun importFromUrl(url: String) = intent {
runCatching {
val url = URL(url)
val uri = url.toURI().toString().toUri()
importFromUri(uri)
}
.onFailure {
postSideEffect(
GlobalSideEffect.Toast(
StringValue.StringResource(R.string.error_download_failed)
try {
httpClient.prepareGet(url).execute { response ->
if (response.status.value in 200..299) {
val body = response.bodyAsText()
importFromClipboard(body)
} else {
throw IOException(
"Failed to download file with error status: ${response.status.value}"
)
)
}
}
} catch (e: Exception) {
Timber.e(e)
postSideEffect(
GlobalSideEffect.Toast(StringValue.StringResource(R.string.error_download_failed))
)
}
}
fun importFromUri(uri: Uri) = intent {
+2
View File
@@ -459,4 +459,6 @@
<string name="copy_from">Copy from</string>
<string name="mode">Mode</string>
<string name="app_selection">App selection</string>
<string name="example_import_url" translatable="false">https://123.com/tun.conf</string>
<string name="import_url_description">The URL must be secure and serve a .conf file.</string>
</resources>
@@ -15,8 +15,6 @@ import com.wireguard.android.util.RootShell
import com.zaneschepke.networkmonitor.AndroidNetworkMonitor.WifiDetectionMethod.*
import com.zaneschepke.networkmonitor.shizuku.ShizukuShell
import com.zaneschepke.networkmonitor.util.*
import kotlin.concurrent.atomics.AtomicReference
import kotlin.concurrent.atomics.ExperimentalAtomicApi
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
@@ -24,6 +22,8 @@ import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.withTimeoutOrNull
import timber.log.Timber
import kotlin.concurrent.atomics.AtomicReference
import kotlin.concurrent.atomics.ExperimentalAtomicApi
class AndroidNetworkMonitor(
private val appContext: Context,
@@ -212,6 +212,8 @@ class AndroidNetworkMonitor(
connectivityManager?.registerNetworkCallback(request, wifiCallback!!)
trySend(TransportEvent.Unknown)
awaitClose {
runCatching { connectivityManager?.unregisterNetworkCallback(wifiCallback!!) }
.onFailure { Timber.e(it, "Error unregistering WiFi network callback") }