diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 5a1f4bc..110dbc3 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -16,8 +16,8 @@ android {
compileSdk = 33
val versionMajor = 1
- val versionMinor = 1
- val versionPatch = 6
+ val versionMinor = 2
+ val versionPatch = 0
val versionBuild = 0
defaultConfig {
@@ -71,7 +71,7 @@ dependencies {
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
- implementation("androidx.compose.material3:material3")
+ implementation("androidx.compose.material3:material3:1.1.1")
implementation("androidx.appcompat:appcompat:1.6.1")
testImplementation("junit:junit:4.13.2")
@@ -89,7 +89,7 @@ dependencies {
implementation("com.jakewharton.timber:timber:5.0.1")
// compose navigation
- implementation("androidx.navigation:navigation-compose:2.5.3")
+ implementation("androidx.navigation:navigation-compose:2.6.0")
implementation("androidx.hilt:hilt-navigation-compose:1.0.0")
// hilt
@@ -120,6 +120,11 @@ dependencies {
implementation("com.google.firebase:firebase-crashlytics-ktx")
implementation("com.google.firebase:firebase-analytics-ktx")
+ //barcode scanning
+ implementation("com.google.android.gms:play-services-code-scanner:16.0.0")
+
+
+
}
kapt {
correctErrorTypes = true
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2b8ef75..b38fe4a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,6 +1,7 @@
+
@@ -59,5 +60,8 @@
+
\ No newline at end of file
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/ScannerModule.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/ScannerModule.kt
new file mode 100644
index 0000000..dc506ac
--- /dev/null
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/ScannerModule.kt
@@ -0,0 +1,41 @@
+package com.zaneschepke.wireguardautotunnel.module
+
+import android.content.Context
+import com.google.mlkit.vision.barcode.common.Barcode
+import com.google.mlkit.vision.codescanner.GmsBarcodeScanner
+import com.google.mlkit.vision.codescanner.GmsBarcodeScannerOptions
+import com.google.mlkit.vision.codescanner.GmsBarcodeScanning
+import com.zaneschepke.wireguardautotunnel.service.barcode.CodeScanner
+import com.zaneschepke.wireguardautotunnel.service.barcode.QRScanner
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.components.ViewModelComponent
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.android.scopes.ViewModelScoped
+
+@Module
+@InstallIn(ViewModelComponent::class)
+class ScannerModule {
+
+ @ViewModelScoped
+ @Provides
+ fun provideBarCodeOptions() : GmsBarcodeScannerOptions {
+ return GmsBarcodeScannerOptions.Builder()
+ .setBarcodeFormats(Barcode.FORMAT_QR_CODE)
+ .build()
+ }
+
+ @ViewModelScoped
+ @Provides
+ fun provideBarCodeScanner(@ApplicationContext context: Context, options: GmsBarcodeScannerOptions) : GmsBarcodeScanner {
+ return GmsBarcodeScanning.getClient(context, options)
+ }
+
+ @ViewModelScoped
+ @Provides
+ fun provideQRScanner(gmsBarcodeScanner: GmsBarcodeScanner) : CodeScanner {
+ return QRScanner(gmsBarcodeScanner)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/ServiceModule.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/ServiceModule.kt
index 59f6644..c6d3b4c 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/ServiceModule.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/ServiceModule.kt
@@ -1,5 +1,7 @@
package com.zaneschepke.wireguardautotunnel.module
+import com.zaneschepke.wireguardautotunnel.service.barcode.CodeScanner
+import com.zaneschepke.wireguardautotunnel.service.barcode.QRScanner
import com.zaneschepke.wireguardautotunnel.service.network.MobileDataService
import com.zaneschepke.wireguardautotunnel.service.network.NetworkService
import com.zaneschepke.wireguardautotunnel.service.network.WifiService
@@ -10,6 +12,7 @@ import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ServiceComponent
import dagger.hilt.android.scopes.ServiceScoped
+import dagger.hilt.android.scopes.ViewModelScoped
@Module
@InstallIn(ServiceComponent::class)
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/barcode/CodeScanner.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/barcode/CodeScanner.kt
new file mode 100644
index 0000000..2c0a5ad
--- /dev/null
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/barcode/CodeScanner.kt
@@ -0,0 +1,7 @@
+package com.zaneschepke.wireguardautotunnel.service.barcode
+
+import kotlinx.coroutines.flow.Flow
+
+interface CodeScanner {
+ fun scan() : Flow
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/barcode/QRScanner.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/barcode/QRScanner.kt
new file mode 100644
index 0000000..ab71f7c
--- /dev/null
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/barcode/QRScanner.kt
@@ -0,0 +1,22 @@
+package com.zaneschepke.wireguardautotunnel.service.barcode
+
+import com.google.mlkit.vision.codescanner.GmsBarcodeScanner
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import timber.log.Timber
+import javax.inject.Inject
+
+class QRScanner @Inject constructor(private val gmsBarcodeScanner: GmsBarcodeScanner) : CodeScanner {
+ override fun scan(): Flow {
+ return callbackFlow {
+ gmsBarcodeScanner.startScan().addOnSuccessListener {
+ trySend(it.rawValue)
+ }.addOnFailureListener {
+ Timber.e(it.message)
+ }
+ awaitClose {
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt
index 56b8a79..def01b1 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt
@@ -24,7 +24,7 @@ class WireGuardTunnel @Inject constructor(private val backend : Backend) : VpnSe
override val state get() = _state.asSharedFlow()
override suspend fun startTunnel(tunnelConfig: TunnelConfig) : Tunnel.State{
- try {
+ return try {
if(getState() == Tunnel.State.UP && _tunnelName.value != tunnelConfig.name) {
stopTunnel()
}
@@ -33,10 +33,10 @@ class WireGuardTunnel @Inject constructor(private val backend : Backend) : VpnSe
val state = backend.setState(
this, Tunnel.State.UP, config)
_state.emit(state)
- return state;
+ state;
} catch (e : Exception) {
Timber.e("Failed to start tunnel with error: ${e.message}")
- return Tunnel.State.DOWN
+ Tunnel.State.DOWN
}
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt
index 415987e..b45b62a 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt
@@ -4,28 +4,38 @@ import android.annotation.SuppressLint
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.FileOpen
+import androidx.compose.material.icons.filled.QrCode
import androidx.compose.material.icons.rounded.Add
import androidx.compose.material.icons.rounded.Delete
import androidx.compose.material.icons.rounded.Edit
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
+import androidx.compose.material3.Divider
+import androidx.compose.material3.DrawerValue
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FabPosition
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.ModalBottomSheet
+import androidx.compose.material3.ModalDrawerSheet
+import androidx.compose.material3.ModalNavigationDrawer
+import androidx.compose.material3.NavigationDrawerItem
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarDuration
@@ -33,6 +43,8 @@ import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
+import androidx.compose.material3.rememberDrawerState
+import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -45,6 +57,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.modifier.modifierLocalConsumer
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.stringResource
@@ -68,6 +81,8 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), padding : PaddingValu
val context = LocalContext.current
val scope = rememberCoroutineScope()
+ val sheetState = rememberModalBottomSheetState()
+ var showBottomSheet by remember { mutableStateOf(false) }
val tunnels by viewModel.tunnels.collectAsStateWithLifecycle(mutableListOf())
val viewState = viewModel.viewState.collectAsStateWithLifecycle()
var showAlertDialog by remember { mutableStateOf(false) }
@@ -109,7 +124,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), padding : PaddingValu
FloatingActionButton(
modifier = Modifier.padding(bottom = 90.dp),
onClick = {
- pickFileLauncher.launch("*/*")
+ showBottomSheet = true
},
containerColor = MaterialTheme.colorScheme.secondary,
shape = RoundedCornerShape(16.dp),
@@ -133,6 +148,36 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), padding : PaddingValu
Text(text = stringResource(R.string.no_tunnels), fontStyle = FontStyle.Italic)
}
}
+ if (showBottomSheet) {
+ ModalBottomSheet(
+ onDismissRequest = {
+ showBottomSheet = false
+ },
+ sheetState = sheetState
+ ) {
+ // Sheet content
+ Row(
+ modifier = Modifier.fillMaxWidth().clickable {
+ showBottomSheet = false
+ pickFileLauncher.launch("*/*")
+ }.padding(10.dp)
+ ) {
+ Icon(Icons.Filled.FileOpen, contentDescription = "File Open", modifier = Modifier.padding(10.dp))
+ Text("Add tunnel from files", modifier = Modifier.padding(10.dp))
+ }
+ Divider()
+ Row(modifier = Modifier.fillMaxWidth().clickable {
+ scope.launch {
+ showBottomSheet = false
+ viewModel.onTunnelQRSelected()
+ }
+ }.padding(10.dp)
+ ) {
+ Icon(Icons.Filled.QrCode, contentDescription = "QR Scan", modifier = Modifier.padding(10.dp))
+ Text("Add tunnel from QR code", modifier = Modifier.padding(10.dp))
+ }
+ }
+ }
Column(
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.Top,
@@ -181,7 +226,11 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), padding : PaddingValu
}, confirmButton = {
Button(onClick = {
if (tunnels.any { it.name == selectedTunnel?.name }) {
- Toast.makeText(context, context.resources.getString(R.string.tunnel_exists), Toast.LENGTH_LONG)
+ Toast.makeText(
+ context,
+ context.resources.getString(R.string.tunnel_exists),
+ Toast.LENGTH_LONG
+ )
.show()
return@Button
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainViewModel.kt
index 3fa0e45..9d652f9 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainViewModel.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainViewModel.kt
@@ -10,6 +10,7 @@ import androidx.lifecycle.viewModelScope
import com.wireguard.config.Config
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.repository.Repository
+import com.zaneschepke.wireguardautotunnel.service.barcode.CodeScanner
import com.zaneschepke.wireguardautotunnel.service.foreground.Action
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceState
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceTracker
@@ -32,7 +33,8 @@ import javax.inject.Inject
class MainViewModel @Inject constructor(private val application : Application,
private val tunnelRepo : Repository,
private val settingsRepo : Repository,
- private val vpnService: VpnService
+ private val vpnService: VpnService,
+ private val codeScanner: CodeScanner
) : ViewModel() {
private val _viewState = MutableStateFlow(ViewState())
@@ -43,7 +45,9 @@ class MainViewModel @Inject constructor(private val application : Application,
private val _settings = MutableStateFlow(Settings())
val settings get() = _settings.asStateFlow()
- private val defaultConfigName = "tunnel${(Math.random() * 1000).toInt()}"
+ private val defaultConfigName = {
+ "tunnel${(Math.random() * 100000).toInt()}"
+ }
init {
@@ -111,6 +115,15 @@ class MainViewModel @Inject constructor(private val application : Application,
ServiceTracker.actionOnService( Action.STOP, application, WireGuardTunnelService::class.java)
}
+ suspend fun onTunnelQRSelected() {
+ codeScanner.scan().collect {
+ Timber.d(it)
+ if(!it.isNullOrEmpty()) {
+ tunnelRepo.save(TunnelConfig(name = defaultConfigName(), wgQuick = it))
+ }
+ }
+ }
+
fun onTunnelFileSelected(uri : Uri) {
val fileName = getFileName(application.applicationContext, uri)
val extension = getFileExtensionFromFileName(fileName)
@@ -135,14 +148,14 @@ class MainViewModel @Inject constructor(private val application : Application,
private fun getFileName(context: Context, uri: Uri): String {
if (uri.scheme == "content") {
val cursor = context.contentResolver.query(uri, null, null, null, null)
- cursor ?: return defaultConfigName
+ cursor ?: return defaultConfigName()
cursor.use {
if(cursor.moveToFirst()) {
return cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME))
}
}
}
- return defaultConfigName
+ return defaultConfigName()
}
suspend fun showSnackBarMessage(message : String) {
diff --git a/build.gradle.kts b/build.gradle.kts
index 84e9717..3ecb7a8 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -4,6 +4,7 @@ buildscript {
val objectBoxVersion by extra("3.5.1")
val hiltVersion by extra("2.44")
val accompanistVersion by extra("0.31.2-alpha")
+ val cameraVersion by extra("1.3.0-beta01")
dependencies {
classpath("io.objectbox:objectbox-gradle-plugin:$objectBoxVersion")