diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 842779e..58ad66a 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -16,7 +16,7 @@ android {
compileSdk = 34
val versionMajor = 2
- val versionMinor = 2
+ val versionMinor = 3
val versionPatch = 0
val versionBuild = 0
@@ -123,9 +123,6 @@ dependencies {
//barcode scanning
implementation("com.google.android.gms:play-services-code-scanner:16.0.0")
-
-
-
}
kapt {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c10fa56..951bb88 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -28,6 +28,9 @@
+
@@ -39,7 +42,7 @@
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
- android:banner="@mipmap/ic_launcher_foreground"
+ android:banner="@mipmap/ic_banner"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigScreen.kt
index f22727c..7c5c2ad 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigScreen.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigScreen.kt
@@ -1,5 +1,6 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.config
+import android.content.pm.PackageManager
import android.widget.Toast
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
@@ -24,10 +25,13 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
@@ -54,6 +58,8 @@ fun ConfigScreen(
val context = LocalContext.current
val focusManager = LocalFocusManager.current
+ val focusRequester = remember { FocusRequester() }
+
val keyboardController = LocalSoftwareKeyboardController.current
val scope = rememberCoroutineScope()
val tunnel by viewModel.tunnel.collectAsStateWithLifecycle(null)
@@ -86,6 +92,7 @@ fun ConfigScreen(
horizontalArrangement = Arrangement.SpaceBetween
) {
OutlinedTextField(
+ modifier = Modifier.focusRequester(focusRequester),
value = tunnelName.value,
onValueChange = {
viewModel.onTunnelNameChange(it)
@@ -158,14 +165,6 @@ fun ConfigScreen(
}
}
}
-// LazyColumn(
-// modifier = Modifier
-// .fillMaxWidth()
-// .fillMaxHeight(.75f)
-// .padding(horizontal = 14.dp, vertical = 7.dp),
-// verticalArrangement = Arrangement.Center,
-// horizontalAlignment = Alignment.Start
-// ) {
items(packages) { pack ->
Row(
verticalAlignment = Alignment.CenterVertically,
@@ -229,5 +228,10 @@ fun ConfigScreen(
}
}
}
+ LaunchedEffect(Unit) {
+ if(context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
+ focusRequester.requestFocus()
+ }
+ }
}
}
\ No newline at end of file
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 ebdb8d5..a1747bb 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
@@ -1,12 +1,14 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.main
import android.annotation.SuppressLint
+import android.content.pm.PackageManager
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.focusable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -25,6 +27,7 @@ import androidx.compose.material.icons.rounded.Add
import androidx.compose.material.icons.rounded.Circle
import androidx.compose.material.icons.rounded.Delete
import androidx.compose.material.icons.rounded.Edit
+import androidx.compose.material.icons.rounded.Info
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FabPosition
@@ -49,7 +52,10 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
@@ -76,7 +82,7 @@ import com.zaneschepke.wireguardautotunnel.ui.theme.mint
import kotlinx.coroutines.launch
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
-@OptIn(ExperimentalMaterial3Api::class)
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
@Composable
fun MainScreen(
viewModel: MainViewModel = hiltViewModel(), padding: PaddingValues,
@@ -86,6 +92,7 @@ fun MainScreen(
val haptic = LocalHapticFeedback.current
val context = LocalContext.current
val isVisible = rememberSaveable { mutableStateOf(true) }
+ val focusRequester = remember { FocusRequester() }
val scope = rememberCoroutineScope()
val sheetState = rememberModalBottomSheetState()
@@ -256,7 +263,13 @@ fun MainScreen(
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
selectedTunnel = tunnel;
},
- onClick = { navController.navigate("${Routes.Detail.name}/${tunnel.id}") },
+ onClick = {
+ if(!context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)){
+ navController.navigate("${Routes.Detail.name}/${tunnel.id}")
+ } else {
+ focusRequester.requestFocus()
+ }
+ },
rowButton = {
if (tunnel.id == selectedTunnel?.id) {
Row() {
@@ -265,7 +278,9 @@ fun MainScreen(
}) {
Icon(Icons.Rounded.Edit, stringResource(id = R.string.edit))
}
- IconButton(onClick = { viewModel.onDelete(tunnel) }) {
+ IconButton(
+ modifier = Modifier.focusable(),
+ onClick = { viewModel.onDelete(tunnel) }) {
Icon(
Icons.Rounded.Delete,
stringResource(id = R.string.delete)
@@ -273,12 +288,51 @@ fun MainScreen(
}
}
} else {
- Switch(
- checked = (state == Tunnel.State.UP && tunnel.name == tunnelName),
- onCheckedChange = { checked ->
- if (checked) viewModel.onTunnelStart(tunnel) else viewModel.onTunnelStop()
+ if(context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)){
+ Row() {
+ IconButton(modifier = Modifier.focusRequester(focusRequester),onClick = {
+ navController.navigate("${Routes.Detail.name}/${tunnel.id}")
+ }) {
+ Icon(Icons.Rounded.Info, "Info")
+ }
+ IconButton(onClick = {
+ if (state == Tunnel.State.UP && tunnel.name == tunnelName)
+ scope.launch {
+ viewModel.showSnackBarMessage(context.resources.getString(R.string.turn_off_tunnel))
+ } else {
+ navController.navigate("${Routes.Config.name}/${tunnel.id}")
+ }
+ }) {
+ Icon(Icons.Rounded.Edit, stringResource(id = R.string.edit))
+ }
+ IconButton(onClick = {
+ if (state == Tunnel.State.UP && tunnel.name == tunnelName)
+ scope.launch {
+ viewModel.showSnackBarMessage(context.resources.getString(R.string.turn_off_tunnel))
+ } else {
+ viewModel.onDelete(tunnel)
+ }
+ }) {
+ Icon(
+ Icons.Rounded.Delete,
+ stringResource(id = R.string.delete)
+ )
+ }
+ Switch(
+ checked = (state == Tunnel.State.UP && tunnel.name == tunnelName),
+ onCheckedChange = { checked ->
+ if (checked) viewModel.onTunnelStart(tunnel) else viewModel.onTunnelStop()
+ }
+ )
}
- )
+ } else {
+ Switch(
+ checked = (state == Tunnel.State.UP && tunnel.name == tunnelName),
+ onCheckedChange = { checked ->
+ if (checked) viewModel.onTunnelStart(tunnel) else viewModel.onTunnelStop()
+ }
+ )
+ }
}
})
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsScreen.kt
index 8feb52e..87cfdd4 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsScreen.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsScreen.kt
@@ -2,6 +2,7 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.settings
import android.Manifest
import android.content.Intent
+import android.content.pm.PackageManager
import android.net.Uri
import android.provider.Settings
import androidx.compose.foundation.clickable
@@ -47,6 +48,8 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
@@ -82,6 +85,7 @@ fun SettingsScreen(
val scope = rememberCoroutineScope()
val context = LocalContext.current
+ val focusRequester = remember { FocusRequester() }
val focusManager = LocalFocusManager.current
val interactionSource = remember { MutableInteractionSource() }
@@ -92,6 +96,7 @@ fun SettingsScreen(
val tunnels by viewModel.tunnels.collectAsStateWithLifecycle(mutableListOf())
val backgroundLocationState =
rememberPermissionState(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
+ val fineLocationState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION)
var currentText by remember { mutableStateOf("") }
val scrollState = rememberScrollState()
var isLocationServicesEnabled by remember { mutableStateOf(viewModel.checkLocationServicesEnabled())}
@@ -119,6 +124,16 @@ fun SettingsScreen(
}
}
+ fun openSettings() {
+ scope.launch {
+ val intentSettings =
+ Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
+ intentSettings.data =
+ Uri.fromParts("package", context.packageName, null)
+ context.startActivity(intentSettings)
+ }
+ }
+
if(!backgroundLocationState.status.isGranted) {
Column(horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top,
@@ -131,7 +146,6 @@ fun SettingsScreen(
.size(128.dp))
Text(stringResource(R.string.prominent_background_location_title), textAlign = TextAlign.Center, modifier = Modifier.padding(30.dp), fontSize = 20.sp)
Text(stringResource(R.string.prominent_background_location_message), textAlign = TextAlign.Center, modifier = Modifier.padding(30.dp), fontSize = 15.sp)
- //Spacer(modifier = Modifier.weight(1f))
Row(
modifier = Modifier
.fillMaxWidth()
@@ -144,22 +158,50 @@ fun SettingsScreen(
}) {
Text(stringResource(id = R.string.no_thanks))
}
- Button(onClick = {
- scope.launch {
- val intentSettings =
- Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
- intentSettings.data =
- Uri.fromParts("package", context.packageName, null)
- context.startActivity(intentSettings)
- }
+ Button(modifier = Modifier.focusRequester(focusRequester), onClick = {
+ openSettings()
}) {
Text(stringResource(id = R.string.turn_on))
}
+ LaunchedEffect(Unit) {
+ if(context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
+ focusRequester.requestFocus()
+ }
+ }
}
}
return
}
+ if(!fineLocationState.status.isGranted) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center,
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(padding)
+ ) {
+ Text(
+ stringResource(id = R.string.precise_location_message),
+ textAlign = TextAlign.Center,
+ modifier = Modifier.padding(15.dp),
+ fontStyle = FontStyle.Italic
+ )
+ Button(modifier = Modifier.focusRequester(focusRequester),onClick = {
+ fineLocationState.launchPermissionRequest()
+ }) {
+ Text(stringResource(id = R.string.request))
+ }
+ LaunchedEffect(Unit) {
+ if(context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
+ focusRequester.requestFocus()
+ }
+ }
+
+ }
+ return
+ }
+
if (tunnels.isEmpty()) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
@@ -191,7 +233,7 @@ fun SettingsScreen(
modifier = Modifier.padding(15.dp),
fontStyle = FontStyle.Italic
)
- Button(onClick = {
+ Button(modifier = Modifier.focusRequester(focusRequester), onClick = {
val locationServicesEnabled = viewModel.checkLocationServicesEnabled()
isLocationServicesEnabled = locationServicesEnabled
if(!locationServicesEnabled) {
@@ -202,6 +244,11 @@ fun SettingsScreen(
}) {
Text(stringResource(id = R.string.check_again))
}
+ LaunchedEffect(Unit) {
+ if(context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
+ focusRequester.requestFocus()
+ }
+ }
}
return
}
@@ -226,6 +273,7 @@ fun SettingsScreen(
) {
Text(stringResource(R.string.enable_auto_tunnel))
Switch(
+ modifier = Modifier.focusRequester(focusRequester),
enabled = !settings.isAlwaysOnVpnEnabled,
checked = settings.isAutoTunnelEnabled,
onCheckedChange = {
@@ -234,6 +282,11 @@ fun SettingsScreen(
}
}
)
+ LaunchedEffect(Unit) {
+ if(context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
+ focusRequester.requestFocus()
+ }
+ }
}
Text(
stringResource(id = R.string.select_tunnel),
@@ -245,7 +298,9 @@ fun SettingsScreen(
onExpandedChange = {
if(!(settings.isAutoTunnelEnabled || settings.isAlwaysOnVpnEnabled)) {
expanded = !expanded }},
- modifier = Modifier.padding(start = 15.dp, top = 5.dp, bottom = 10.dp),
+ modifier = Modifier.padding(start = 15.dp, top = 5.dp, bottom = 10.dp).clickable {
+ expanded = !expanded
+ },
) {
TextField(
enabled = !(settings.isAutoTunnelEnabled || settings.isAlwaysOnVpnEnabled),
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/SupportScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/SupportScreen.kt
index 93147e4..3c264a9 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/SupportScreen.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/SupportScreen.kt
@@ -1,8 +1,10 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.support
import android.content.Intent
+import android.content.pm.PackageManager
import android.net.Uri
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
@@ -17,8 +19,12 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
@@ -34,6 +40,7 @@ import com.zaneschepke.wireguardautotunnel.R
fun SupportScreen(padding : PaddingValues) {
val context = LocalContext.current
+ val focusRequester = remember { FocusRequester() }
fun openWebPage(url: String) {
val webpage: Uri = Uri.parse(url)
@@ -46,6 +53,7 @@ fun SupportScreen(padding : PaddingValues) {
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
+ .focusable()
.padding(padding)) {
Text(stringResource(R.string.support_text), textAlign = TextAlign.Center, modifier = Modifier.padding(30.dp), fontSize = 15.sp)
Row(
@@ -60,11 +68,16 @@ fun SupportScreen(padding : PaddingValues) {
}) {
Icon(imageVector = ImageVector.vectorResource(R.drawable.discord), "Discord")
}
- IconButton(onClick = {
+ IconButton(modifier = Modifier.focusRequester(focusRequester),onClick = {
openWebPage(context.resources.getString(R.string.github_url))
}) {
Icon(imageVector = ImageVector.vectorResource(R.drawable.github), "Github")
}
+ LaunchedEffect(Unit) {
+ if(context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
+ focusRequester.requestFocus()
+ }
+ }
}
Spacer(modifier = Modifier.weight(1f))
Text(stringResource(id = R.string.privacy_policy), style = TextStyle(textDecoration = TextDecoration.Underline),
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_banner.xml b/app/src/main/res/mipmap-anydpi-v26/ic_banner.xml
new file mode 100644
index 0000000..cf3108b
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_banner.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-xhdpi/ic_banner.png b/app/src/main/res/mipmap-xhdpi/ic_banner.png
new file mode 100644
index 0000000..b85f129
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_banner.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_banner_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_banner_foreground.png
new file mode 100644
index 0000000..7df06a4
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_banner_foreground.png differ
diff --git a/app/src/main/res/values/ic_banner_background.xml b/app/src/main/res/values/ic_banner_background.xml
new file mode 100644
index 0000000..5274b8a
--- /dev/null
+++ b/app/src/main/res/values/ic_banner_background.xml
@@ -0,0 +1,4 @@
+
+
+ #121212
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index ddee92f..a06e840 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -82,4 +82,6 @@
Unable to detect Location Services which are required for this feature. Please enable Location Services.
Check again
Detecting Location Services disabled
+ This feature requires precise location to access Wi-Fi SSID name. Please enable precise location here or in the app settings.
+ Request
\ No newline at end of file