mirror of
https://github.com/wgtunnel/android.git
synced 2026-07-03 14:07:49 +02:00
Compare commits
12 Commits
3.9.1
..
improve-ci
| Author | SHA1 | Date | |
|---|---|---|---|
| 6d8953bf4e | |||
| 375cc978c5 | |||
| 0f4399bb62 | |||
| 00fb8c7d29 | |||
| ab271225de | |||
| 9180344043 | |||
| c5a2f6e311 | |||
| 4780c9bd6f | |||
| 6350ab6752 | |||
| f6cf33b996 | |||
| 9872f4da2b | |||
| b6fd93eae5 |
@@ -110,9 +110,9 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
isCoreLibraryDesugaringEnabled = true
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_17
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
isCoreLibraryDesugaringEnabled = true
|
||||||
}
|
}
|
||||||
kotlinOptions { jvmTarget = Constants.JVM_TARGET }
|
kotlinOptions { jvmTarget = Constants.JVM_TARGET }
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
@@ -124,6 +124,7 @@ android {
|
|||||||
licensee {
|
licensee {
|
||||||
Constants.allowedLicenses.forEach { allow(it) }
|
Constants.allowedLicenses.forEach { allow(it) }
|
||||||
allowUrl(Constants.XZING_LICENSE_URL)
|
allowUrl(Constants.XZING_LICENSE_URL)
|
||||||
|
allowUrl("https://rafaellins.mit-license.org/2021/")
|
||||||
}
|
}
|
||||||
|
|
||||||
applicationVariants.all {
|
applicationVariants.all {
|
||||||
@@ -197,7 +198,6 @@ dependencies {
|
|||||||
implementation(libs.kotlinx.serialization.json)
|
implementation(libs.kotlinx.serialization.json)
|
||||||
|
|
||||||
implementation(libs.zxing.android.embedded)
|
implementation(libs.zxing.android.embedded)
|
||||||
|
|
||||||
implementation(libs.material.icons.extended)
|
implementation(libs.material.icons.extended)
|
||||||
|
|
||||||
implementation(libs.androidx.biometric.ktx)
|
implementation(libs.androidx.biometric.ktx)
|
||||||
@@ -210,7 +210,7 @@ dependencies {
|
|||||||
implementation(libs.androidx.work.runtime)
|
implementation(libs.androidx.work.runtime)
|
||||||
implementation(libs.androidx.hilt.work)
|
implementation(libs.androidx.hilt.work)
|
||||||
|
|
||||||
implementation(libs.qrose)
|
implementation(libs.qrcode.kotlin)
|
||||||
implementation(libs.semver4j)
|
implementation(libs.semver4j)
|
||||||
|
|
||||||
implementation(libs.ktor.client.core)
|
implementation(libs.ktor.client.core)
|
||||||
|
|||||||
@@ -62,10 +62,6 @@
|
|||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.App.Start"
|
android:theme="@style/Theme.App.Start"
|
||||||
tools:targetApi="tiramisu">
|
tools:targetApi="tiramisu">
|
||||||
<activity
|
|
||||||
android:name="com.journeyapps.barcodescanner.CaptureActivity"
|
|
||||||
android:screenOrientation="portrait"
|
|
||||||
tools:replace="screenOrientation" />
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
@@ -82,6 +78,10 @@
|
|||||||
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
|
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="com.journeyapps.barcodescanner.CaptureActivity"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
|
tools:replace="screenOrientation" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".core.shortcut.ShortcutsActivity"
|
android:name=".core.shortcut.ShortcutsActivity"
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import androidx.compose.animation.slideOutVertically
|
|||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.gestures.detectTapGestures
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material.icons.rounded.Settings
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -53,6 +54,7 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.disclosure.Loca
|
|||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.MainScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.MainScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.autotunnel.TunnelAutoTunnelScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.autotunnel.TunnelAutoTunnelScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.ConfigScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.ConfigScreen
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.scanner.ScannerScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.splittunnel.SplitTunnelScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.splittunnel.SplitTunnelScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.tunneloptions.TunnelOptionsScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.tunneloptions.TunnelOptionsScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.pin.PinLockScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.pin.PinLockScreen
|
||||||
@@ -296,10 +298,11 @@ class MainActivity : AppCompatActivity() {
|
|||||||
appUiState.tunnels
|
appUiState.tunnels
|
||||||
.firstOrNull { it.id == args.id }
|
.firstOrNull { it.id == args.id }
|
||||||
?.let { config ->
|
?.let { config ->
|
||||||
TunnelOptionsScreen(config, viewModel, appViewState)
|
TunnelOptionsScreen(config, viewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
composable<Route.Lock> { PinLockScreen(viewModel) }
|
composable<Route.Lock> { PinLockScreen(viewModel) }
|
||||||
|
composable<Route.Scanner> { ScannerScreen(viewModel) }
|
||||||
composable<Route.KillSwitch> {
|
composable<Route.KillSwitch> {
|
||||||
KillSwitchScreen(appUiState, viewModel)
|
KillSwitchScreen(appUiState, viewModel)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ data class TunnelConf(
|
|||||||
val pingIp: String? = null,
|
val pingIp: String? = null,
|
||||||
val isEthernetTunnel: Boolean = false,
|
val isEthernetTunnel: Boolean = false,
|
||||||
val isIpv4Preferred: Boolean = true,
|
val isIpv4Preferred: Boolean = true,
|
||||||
|
val useCache: Boolean = false,
|
||||||
@Transient private var stateChangeCallback: ((Any) -> Unit)? = null,
|
@Transient private var stateChangeCallback: ((Any) -> Unit)? = null,
|
||||||
) : Tunnel, org.amnezia.awg.backend.Tunnel {
|
) : Tunnel, org.amnezia.awg.backend.Tunnel {
|
||||||
|
|
||||||
@@ -110,6 +111,8 @@ data class TunnelConf(
|
|||||||
|
|
||||||
override fun isIpv4ResolutionPreferred(): Boolean = isIpv4Preferred
|
override fun isIpv4ResolutionPreferred(): Boolean = isIpv4Preferred
|
||||||
|
|
||||||
|
override fun useCache(): Boolean = useCache
|
||||||
|
|
||||||
override fun onStateChange(newState: org.amnezia.awg.backend.Tunnel.State) {
|
override fun onStateChange(newState: org.amnezia.awg.backend.Tunnel.State) {
|
||||||
stateChangeCallback?.invoke(newState)
|
stateChangeCallback?.invoke(newState)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.domain.enums
|
package com.zaneschepke.wireguardautotunnel.domain.enums
|
||||||
|
|
||||||
enum class ConfigType {
|
enum class ConfigType {
|
||||||
AM,
|
AMNEZIA,
|
||||||
WG,
|
WG,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ sealed class Route {
|
|||||||
|
|
||||||
@Serializable data object Lock : Route()
|
@Serializable data object Lock : Route()
|
||||||
|
|
||||||
|
@Serializable data object Scanner : Route()
|
||||||
|
|
||||||
@Serializable data object License : Route()
|
@Serializable data object License : Route()
|
||||||
|
|
||||||
@Serializable data class Config(val id: Int) : Route()
|
@Serializable data class Config(val id: Int) : Route()
|
||||||
|
|||||||
+12
-12
@@ -4,7 +4,16 @@ import android.os.Build
|
|||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.*
|
import androidx.compose.material.icons.rounded.Add
|
||||||
|
import androidx.compose.material.icons.rounded.CopyAll
|
||||||
|
import androidx.compose.material.icons.rounded.Delete
|
||||||
|
import androidx.compose.material.icons.rounded.Download
|
||||||
|
import androidx.compose.material.icons.rounded.Edit
|
||||||
|
import androidx.compose.material.icons.rounded.Menu
|
||||||
|
import androidx.compose.material.icons.rounded.PlayArrow
|
||||||
|
import androidx.compose.material.icons.rounded.Save
|
||||||
|
import androidx.compose.material.icons.rounded.SelectAll
|
||||||
|
import androidx.compose.material.icons.rounded.Stop
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@@ -231,17 +240,8 @@ fun currentNavBackStackEntryAsNavBarState(
|
|||||||
showBottom = true,
|
showBottom = true,
|
||||||
topTitle = { tunnel?.name?.let { Text(it) } },
|
topTitle = { tunnel?.name?.let { Text(it) } },
|
||||||
topTrailing = {
|
topTrailing = {
|
||||||
Row {
|
ActionIconButton(Icons.Rounded.Edit, R.string.edit_tunnel) {
|
||||||
ActionIconButton(Icons.Rounded.QrCode2, R.string.show_qr) {
|
tunnel?.id?.let { navController.navigate(Route.Config(it)) }
|
||||||
tunnel?.id?.let {
|
|
||||||
viewModel.handleEvent(
|
|
||||||
AppEvent.SetShowModal(AppViewState.ModalType.QR)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ActionIconButton(Icons.Rounded.Edit, R.string.edit_tunnel) {
|
|
||||||
tunnel?.id?.let { navController.navigate(Route.Config(it)) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
route = args?.let { Route.TunnelOptions(it.id) },
|
route = args?.let { Route.TunnelOptions(it.id) },
|
||||||
|
|||||||
+1
-14
@@ -9,8 +9,6 @@ import androidx.compose.runtime.*
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.journeyapps.barcodescanner.ScanContract
|
|
||||||
import com.journeyapps.barcodescanner.ScanOptions
|
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.Route
|
import com.zaneschepke.wireguardautotunnel.ui.Route
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog
|
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog
|
||||||
@@ -47,17 +45,6 @@ fun MainScreen(appUiState: AppUiState, appViewState: AppViewState, viewModel: Ap
|
|||||||
onData = { data -> viewModel.handleEvent(AppEvent.ImportTunnelFromFile(data)) },
|
onData = { data -> viewModel.handleEvent(AppEvent.ImportTunnelFromFile(data)) },
|
||||||
)
|
)
|
||||||
|
|
||||||
val scanLauncher =
|
|
||||||
rememberLauncherForActivityResult(
|
|
||||||
contract = ScanContract(),
|
|
||||||
onResult = { result ->
|
|
||||||
{
|
|
||||||
if (result != null && result.contents.isNotEmpty())
|
|
||||||
viewModel.handleEvent(AppEvent.ImportTunnelFromQrCode(result.contents))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
val requestPermissionLauncher =
|
val requestPermissionLauncher =
|
||||||
rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted
|
rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted
|
||||||
->
|
->
|
||||||
@@ -69,7 +56,7 @@ fun MainScreen(appUiState: AppUiState, appViewState: AppViewState, viewModel: Ap
|
|||||||
)
|
)
|
||||||
return@rememberLauncherForActivityResult
|
return@rememberLauncherForActivityResult
|
||||||
}
|
}
|
||||||
scanLauncher.launch(ScanOptions().setDesiredBarcodeFormats(ScanOptions.QR_CODE))
|
navController.navigate(Route.Scanner)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appViewState.showModal == AppViewState.ModalType.DELETE) {
|
if (appViewState.showModal == AppViewState.ModalType.DELETE) {
|
||||||
|
|||||||
+1
-1
@@ -93,7 +93,7 @@ fun ExportTunnelsBottomSheet(viewModel: AppViewModel) {
|
|||||||
ExportOptionRow(
|
ExportOptionRow(
|
||||||
label = stringResource(R.string.export_tunnels_amnezia),
|
label = stringResource(R.string.export_tunnels_amnezia),
|
||||||
onClick = {
|
onClick = {
|
||||||
exportConfigType = ConfigType.AM
|
exportConfigType = ConfigType.AMNEZIA
|
||||||
if (!isAuthorized && !isTv) {
|
if (!isAuthorized && !isTv) {
|
||||||
showAuthPrompt = true
|
showAuthPrompt = true
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.ui.screens.main.scanner
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
|
import com.journeyapps.barcodescanner.CompoundBarcodeView
|
||||||
|
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
||||||
|
import com.zaneschepke.wireguardautotunnel.viewmodel.event.AppEvent
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ScannerScreen(viewModel: AppViewModel) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val barcodeView = remember {
|
||||||
|
CompoundBarcodeView(context).apply {
|
||||||
|
this.initializeFromIntent((context as Activity).intent)
|
||||||
|
this.setStatusText("")
|
||||||
|
this.decodeSingle { result ->
|
||||||
|
result.text?.let { barCodeOrQr ->
|
||||||
|
viewModel.handleEvent(AppEvent.ImportTunnelFromQrCode(barCodeOrQr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AndroidView(factory = { barcodeView })
|
||||||
|
DisposableEffect(Unit) {
|
||||||
|
barcodeView.resume()
|
||||||
|
onDispose { barcodeView.pause() }
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-37
@@ -6,53 +6,18 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.runtime.*
|
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.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.SectionDivider
|
import com.zaneschepke.wireguardautotunnel.ui.common.SectionDivider
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.LocalIsAndroidTV
|
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.tunneloptions.components.*
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.tunneloptions.components.*
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.AuthorizationPromptWrapper
|
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.state.AppViewState
|
|
||||||
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
||||||
import com.zaneschepke.wireguardautotunnel.viewmodel.event.AppEvent
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TunnelOptionsScreen(
|
fun TunnelOptionsScreen(tunnelConf: TunnelConf, viewModel: AppViewModel) {
|
||||||
tunnelConf: TunnelConf,
|
|
||||||
viewModel: AppViewModel,
|
|
||||||
appViewState: AppViewState,
|
|
||||||
) {
|
|
||||||
val isTv = LocalIsAndroidTV.current
|
|
||||||
|
|
||||||
var showAuthPrompt by remember { mutableStateOf(!isTv) }
|
|
||||||
var isAuthorized by remember { mutableStateOf(isTv) }
|
|
||||||
|
|
||||||
if (appViewState.showModal == AppViewState.ModalType.QR) {
|
|
||||||
|
|
||||||
// Show authorization prompt if needed
|
|
||||||
if (showAuthPrompt) {
|
|
||||||
AuthorizationPromptWrapper(
|
|
||||||
onDismiss = { showAuthPrompt = false },
|
|
||||||
onSuccess = {
|
|
||||||
showAuthPrompt = false
|
|
||||||
isAuthorized = true
|
|
||||||
},
|
|
||||||
viewModel = viewModel,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (isAuthorized) {
|
|
||||||
QrCodeDialog(
|
|
||||||
tunnelConf = tunnelConf,
|
|
||||||
onDismiss = {
|
|
||||||
viewModel.handleEvent(AppEvent.SetShowModal(AppViewState.ModalType.NONE))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.Start,
|
horizontalAlignment = Alignment.Start,
|
||||||
|
|||||||
-190
@@ -1,190 +0,0 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.ui.screens.main.tunneloptions.components
|
|
||||||
|
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
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.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.outlined.Check
|
|
||||||
import androidx.compose.material.icons.outlined.VpnKey
|
|
||||||
import androidx.compose.material3.AlertDialog
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.MultiChoiceSegmentedButtonRow
|
|
||||||
import androidx.compose.material3.SegmentedButton
|
|
||||||
import androidx.compose.material3.SegmentedButtonDefaults
|
|
||||||
import androidx.compose.material3.Surface
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TextButton
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.DisposableEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.window.DialogProperties
|
|
||||||
import com.zaneschepke.wireguardautotunnel.MainActivity
|
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.ConfigType
|
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.setScreenBrightness
|
|
||||||
import io.github.alexzhirkevich.qrose.options.QrBallShape
|
|
||||||
import io.github.alexzhirkevich.qrose.options.QrBrush
|
|
||||||
import io.github.alexzhirkevich.qrose.options.QrErrorCorrectionLevel
|
|
||||||
import io.github.alexzhirkevich.qrose.options.QrFrameShape
|
|
||||||
import io.github.alexzhirkevich.qrose.options.QrOptions
|
|
||||||
import io.github.alexzhirkevich.qrose.options.QrPixelShape
|
|
||||||
import io.github.alexzhirkevich.qrose.options.circle
|
|
||||||
import io.github.alexzhirkevich.qrose.options.roundCorners
|
|
||||||
import io.github.alexzhirkevich.qrose.options.solid
|
|
||||||
import io.github.alexzhirkevich.qrose.rememberQrCodePainter
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun QrCodeDialog(tunnelConf: TunnelConf, onDismiss: () -> Unit) {
|
|
||||||
val context = LocalContext.current
|
|
||||||
val activity = context as? MainActivity
|
|
||||||
|
|
||||||
// Handle screen brightness
|
|
||||||
DisposableEffect(Unit) {
|
|
||||||
activity?.setScreenBrightness(1.0f)
|
|
||||||
onDispose { activity?.setScreenBrightness(-1f) }
|
|
||||||
}
|
|
||||||
|
|
||||||
QrCodeAlertDialog(tunnelConf = tunnelConf, onDismiss = onDismiss)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun QrCodeAlertDialog(tunnelConf: TunnelConf, onDismiss: () -> Unit) {
|
|
||||||
Surface(color = Color.White, tonalElevation = 0.dp) {
|
|
||||||
AlertDialog(
|
|
||||||
containerColor = Color.White,
|
|
||||||
onDismissRequest = onDismiss,
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(onClick = onDismiss) {
|
|
||||||
Text(stringResource(R.string.done), color = MaterialTheme.colorScheme.surface)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
title = {
|
|
||||||
Text(
|
|
||||||
text = tunnelConf.name,
|
|
||||||
color = Color.Black,
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
text = { QrCodeContent(tunnelConf = tunnelConf) },
|
|
||||||
properties = DialogProperties(usePlatformDefaultWidth = true),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun QrCodeContent(tunnelConf: TunnelConf) {
|
|
||||||
var selectedOption by remember { mutableStateOf(ConfigType.WG) }
|
|
||||||
val qrCodeText =
|
|
||||||
when (selectedOption) {
|
|
||||||
ConfigType.AM -> tunnelConf.toAmConfig().toAwgQuickString(true)
|
|
||||||
ConfigType.WG -> tunnelConf.toWgConfig().toWgQuickString(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
|
||||||
) {
|
|
||||||
val qrCodePainter = rememberQrCodePainter(data = qrCodeText, options = createQrOptions())
|
|
||||||
Image(
|
|
||||||
painter = qrCodePainter,
|
|
||||||
contentDescription = stringResource(R.string.show_qr),
|
|
||||||
modifier =
|
|
||||||
Modifier.size(300.dp)
|
|
||||||
.align(Alignment.CenterHorizontally)
|
|
||||||
.padding(16.dp)
|
|
||||||
.background(Color.White),
|
|
||||||
)
|
|
||||||
ConfigTypeSelector(
|
|
||||||
selectedOption = selectedOption,
|
|
||||||
onOptionSelected = { selectedOption = it },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun ConfigTypeSelector(selectedOption: ConfigType, onOptionSelected: (ConfigType) -> Unit) {
|
|
||||||
MultiChoiceSegmentedButtonRow(modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)) {
|
|
||||||
ConfigType.entries.sortedDescending().forEachIndexed { index, entry ->
|
|
||||||
val isActive = selectedOption == entry
|
|
||||||
val typeName =
|
|
||||||
stringResource(
|
|
||||||
when (entry) {
|
|
||||||
ConfigType.AM -> R.string.amnezia
|
|
||||||
ConfigType.WG -> R.string.wireguard
|
|
||||||
}
|
|
||||||
)
|
|
||||||
SegmentedButton(
|
|
||||||
shape =
|
|
||||||
SegmentedButtonDefaults.itemShape(
|
|
||||||
index = index,
|
|
||||||
count = ConfigType.entries.size,
|
|
||||||
baseShape = RoundedCornerShape(8.dp),
|
|
||||||
),
|
|
||||||
icon = {
|
|
||||||
SegmentedButtonDefaults.Icon(
|
|
||||||
active = isActive,
|
|
||||||
activeContent = {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Outlined.Check,
|
|
||||||
contentDescription = stringResource(R.string.select),
|
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
|
||||||
modifier = Modifier.size(SegmentedButtonDefaults.IconSize),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Outlined.VpnKey,
|
|
||||||
contentDescription = typeName,
|
|
||||||
tint = Color.Black,
|
|
||||||
modifier = Modifier.size(SegmentedButtonDefaults.IconSize),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
colors =
|
|
||||||
SegmentedButtonDefaults.colors()
|
|
||||||
.copy(
|
|
||||||
activeContainerColor = Color.White,
|
|
||||||
inactiveContainerColor = Color.White,
|
|
||||||
),
|
|
||||||
onCheckedChange = { onOptionSelected(entry) },
|
|
||||||
checked = isActive,
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = typeName,
|
|
||||||
color = Color.Black,
|
|
||||||
style = MaterialTheme.typography.labelMedium,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createQrOptions(): QrOptions = QrOptions {
|
|
||||||
shapes {
|
|
||||||
darkPixel = QrPixelShape.circle()
|
|
||||||
ball = QrBallShape.circle()
|
|
||||||
frame = QrFrameShape.roundCorners(0.2f)
|
|
||||||
}
|
|
||||||
colors {
|
|
||||||
dark = QrBrush.solid(Color.Black)
|
|
||||||
frame = QrBrush.solid(Color.Black)
|
|
||||||
ball = QrBrush.solid(Color.Black)
|
|
||||||
}
|
|
||||||
errorCorrectionLevel = QrErrorCorrectionLevel.Medium
|
|
||||||
}
|
|
||||||
@@ -18,7 +18,6 @@ data class AppViewState(
|
|||||||
NONE,
|
NONE,
|
||||||
DELETE,
|
DELETE,
|
||||||
INFO,
|
INFO,
|
||||||
QR,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class BottomSheet {
|
enum class BottomSheet {
|
||||||
|
|||||||
-23
@@ -1,7 +1,6 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.util.extensions
|
package com.zaneschepke.wireguardautotunnel.util.extensions
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.app.Activity
|
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Context.POWER_SERVICE
|
import android.content.Context.POWER_SERVICE
|
||||||
@@ -18,9 +17,6 @@ import android.widget.Toast
|
|||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import androidx.core.location.LocationManagerCompat
|
import androidx.core.location.LocationManagerCompat
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.core.view.WindowCompat
|
|
||||||
import androidx.core.view.WindowInsetsCompat
|
|
||||||
import androidx.core.view.WindowInsetsControllerCompat
|
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.core.service.tile.AutoTunnelControlTile
|
import com.zaneschepke.wireguardautotunnel.core.service.tile.AutoTunnelControlTile
|
||||||
import com.zaneschepke.wireguardautotunnel.core.service.tile.TunnelControlTile
|
import com.zaneschepke.wireguardautotunnel.core.service.tile.TunnelControlTile
|
||||||
@@ -229,22 +225,3 @@ fun Context.installApk(apkFile: File) {
|
|||||||
}
|
}
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Activity.setScreenBrightness(brightness: Float) {
|
|
||||||
window.attributes = window.attributes.apply { screenBrightness = brightness }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Activity.enableImmersiveMode() {
|
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
|
||||||
val controller = WindowCompat.getInsetsController(window, window.decorView)
|
|
||||||
controller.systemBarsBehavior =
|
|
||||||
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
|
||||||
controller.hide(WindowInsetsCompat.Type.systemBars())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Activity.disableImmersiveMode() {
|
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, true)
|
|
||||||
val controller = WindowCompat.getInsetsController(window, window.decorView)
|
|
||||||
controller.show(WindowInsetsCompat.Type.systemBars())
|
|
||||||
window.statusBarColor = android.graphics.Color.TRANSPARENT
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ import java.time.Instant
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Provider
|
import javax.inject.Provider
|
||||||
|
import kotlin.collections.component1
|
||||||
|
import kotlin.collections.component2
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
@@ -400,7 +402,6 @@ constructor(
|
|||||||
|
|
||||||
private suspend fun handleClipboardImport(config: String, tunnels: List<TunnelConf>) {
|
private suspend fun handleClipboardImport(config: String, tunnels: List<TunnelConf>) {
|
||||||
runCatching {
|
runCatching {
|
||||||
Timber.d("Config: $config")
|
|
||||||
val amConfig = TunnelConf.configFromAmQuick(config)
|
val amConfig = TunnelConf.configFromAmQuick(config)
|
||||||
val tunnelConf = TunnelConf.tunnelConfigFromAmConfig(amConfig)
|
val tunnelConf = TunnelConf.tunnelConfigFromAmConfig(amConfig)
|
||||||
saveTunnel(
|
saveTunnel(
|
||||||
@@ -689,7 +690,7 @@ constructor(
|
|||||||
if (tunnels.isEmpty()) return
|
if (tunnels.isEmpty()) return
|
||||||
val (files, shareFileName) =
|
val (files, shareFileName) =
|
||||||
when (configType) {
|
when (configType) {
|
||||||
ConfigType.AM -> {
|
ConfigType.AMNEZIA -> {
|
||||||
val amFiles = fileUtils.createAmFiles(tunnels)
|
val amFiles = fileUtils.createAmFiles(tunnels)
|
||||||
if (amFiles.isEmpty()) {
|
if (amFiles.isEmpty()) {
|
||||||
throw IOException("No valid Amnezia config files created")
|
throw IOException("No valid Amnezia config files created")
|
||||||
|
|||||||
@@ -258,8 +258,4 @@
|
|||||||
<string name="update_check_unsupported">Update check not supported this build type.</string>
|
<string name="update_check_unsupported">Update check not supported this build type.</string>
|
||||||
<string name="darker">Darker</string>
|
<string name="darker">Darker</string>
|
||||||
<string name="amoled">AMOLED</string>
|
<string name="amoled">AMOLED</string>
|
||||||
<string name="show_qr">Show QR</string>
|
|
||||||
<string name="amnezia">Amnezia</string>
|
|
||||||
<string name="wireguard">WireGuard</string>
|
|
||||||
<string name="done">Done</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -6,8 +6,6 @@
|
|||||||
<item name="android:colorPrimary">@color/background</item>
|
<item name="android:colorPrimary">@color/background</item>
|
||||||
<item name="android:windowAllowReturnTransitionOverlap">true</item>
|
<item name="android:windowAllowReturnTransitionOverlap">true</item>
|
||||||
<item name="android:windowAllowEnterTransitionOverlap">true</item>
|
<item name="android:windowAllowEnterTransitionOverlap">true</item>
|
||||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
|
||||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.App.Start" parent="@style/Theme.SplashScreen">
|
<style name="Theme.App.Start" parent="@style/Theme.SplashScreen">
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("org.semver4j:semver4j:5.7.0")
|
implementation("org.semver4j:semver4j:5.6.0")
|
||||||
implementation("org.ajoberstar.grgit:grgit-core:5.3.0")
|
implementation("org.ajoberstar.grgit:grgit-core:5.3.0")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
object Constants {
|
object Constants {
|
||||||
const val VERSION_NAME = "3.9.1"
|
const val VERSION_NAME = "3.9.0"
|
||||||
const val JVM_TARGET = "17"
|
const val JVM_TARGET = "17"
|
||||||
const val VERSION_CODE = 39100
|
const val VERSION_CODE = 38900
|
||||||
const val TARGET_SDK = 35
|
const val TARGET_SDK = 35
|
||||||
const val MIN_SDK = 26
|
const val MIN_SDK = 26
|
||||||
const val APP_ID = "com.zaneschepke.wireguardautotunnel"
|
const val APP_ID = "com.zaneschepke.wireguardautotunnel"
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
What's new:
|
|
||||||
- Fix tunnel no connectivity DNS bug
|
|
||||||
- Fix tunnel stuck starting bug
|
|
||||||
- Add view tunnel QR code
|
|
||||||
- Add resolved endpoint to statistics
|
|
||||||
- Various other bug fixes
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
[versions]
|
[versions]
|
||||||
accompanist = "0.37.3"
|
accompanist = "0.37.2"
|
||||||
activityCompose = "1.10.1"
|
activityCompose = "1.10.1"
|
||||||
amneziawgAndroid = "1.4.0"
|
amneziawgAndroid = "1.3.10"
|
||||||
androidx-junit = "1.2.1"
|
androidx-junit = "1.2.1"
|
||||||
appcompat = "1.7.0"
|
appcompat = "1.7.0"
|
||||||
biometricKtx = "1.2.0-alpha05"
|
biometricKtx = "1.2.0-alpha05"
|
||||||
@@ -18,15 +18,15 @@ lifecycle-runtime-compose = "2.8.7"
|
|||||||
material3 = "1.3.2"
|
material3 = "1.3.2"
|
||||||
navigationCompose = "2.8.9"
|
navigationCompose = "2.8.9"
|
||||||
pinLockCompose = "1.0.4"
|
pinLockCompose = "1.0.4"
|
||||||
qrose = "1.0.1"
|
qrcodeKotlin = "4.4.1"
|
||||||
roomVersion = "2.7.1"
|
roomVersion = "2.7.1"
|
||||||
semver4j = "3.1.0"
|
semver4j = "3.1.0"
|
||||||
slf4jAndroid = "1.7.36"
|
slf4jAndroid = "1.7.36"
|
||||||
timber = "5.0.1"
|
timber = "5.0.1"
|
||||||
tunnel = "1.3.0"
|
tunnel = "1.2.16"
|
||||||
androidGradlePlugin = "8.9.2"
|
androidGradlePlugin = "8.9.2"
|
||||||
kotlin = "2.1.20"
|
kotlin = "2.1.20"
|
||||||
ksp = "2.1.20-2.0.1"
|
ksp = "2.1.20-2.0.0"
|
||||||
composeBom = "2025.04.01"
|
composeBom = "2025.04.01"
|
||||||
compose = "1.8.0"
|
compose = "1.8.0"
|
||||||
icons = "1.7.8"
|
icons = "1.7.8"
|
||||||
@@ -99,7 +99,7 @@ lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-com
|
|||||||
material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "icons" }
|
material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "icons" }
|
||||||
|
|
||||||
pin-lock-compose = { module = "com.zaneschepke:pin_lock_compose", version.ref = "pinLockCompose" }
|
pin-lock-compose = { module = "com.zaneschepke:pin_lock_compose", version.ref = "pinLockCompose" }
|
||||||
qrose = { module = "io.github.alexzhirkevich:qrose", version.ref = "qrose" }
|
qrcode-kotlin = { module = "io.github.g0dkar:qrcode-kotlin", version.ref = "qrcodeKotlin" }
|
||||||
semver4j = { module = "com.vdurmont:semver4j", version.ref = "semver4j" }
|
semver4j = { module = "com.vdurmont:semver4j", version.ref = "semver4j" }
|
||||||
slf4j-android = { module = "org.slf4j:slf4j-android", version.ref = "slf4jAndroid" }
|
slf4j-android = { module = "org.slf4j:slf4j-android", version.ref = "slf4jAndroid" }
|
||||||
timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" }
|
timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" }
|
||||||
|
|||||||
Reference in New Issue
Block a user