Compare commits

..

3 Commits

Author SHA1 Message Date
Zane Schepke b3cb7a7988 docs: update README support screenshot 2023-07-23 00:39:30 -04:00
Zane Schepke 6415f49377 chore: add feature graphic and banner to assets 2023-07-23 00:34:17 -04:00
Zane Schepke baed8ff2e7 feat: support for Always-On VPN and Android TV
Added support for Android TV

Added support for Always-On VPN in settings

Fixes bug where handshake notification is not dismissed after successful handshake

Closes #2,  Closes #5
2023-07-23 00:16:16 -04:00
19 changed files with 124 additions and 281 deletions
-7
View File
@@ -16,13 +16,6 @@ WG Tunnel
</span> </span>
<span align="center">
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/N4N8NMJN2)
</span>
<span align="left"> <span align="left">
This is an alternative Android Application for [WireGuard](https://www.wireguard.com/) with added features. Built using the [wireguard-android](https://github.com/WireGuard/wireguard-android) library and [Jetpack Compose](https://developer.android.com/jetpack/compose), this application was inspired by the official [WireGuard Android](https://github.com/WireGuard/wireguard-android) app. This is an alternative Android Application for [WireGuard](https://www.wireguard.com/) with added features. Built using the [wireguard-android](https://github.com/WireGuard/wireguard-android) library and [Jetpack Compose](https://developer.android.com/jetpack/compose), this application was inspired by the official [WireGuard Android](https://github.com/WireGuard/wireguard-android) app.
+7 -4
View File
@@ -16,13 +16,13 @@ android {
compileSdk = 34 compileSdk = 34
val versionMajor = 2 val versionMajor = 2
val versionMinor = 3 val versionMinor = 2
val versionPatch = 3 val versionPatch = 0
val versionBuild = 0 val versionBuild = 0
defaultConfig { defaultConfig {
applicationId = "com.zaneschepke.wireguardautotunnel" applicationId = "com.zaneschepke.wireguardautotunnel"
minSdk = 28 minSdk = 29
targetSdk = 34 targetSdk = 34
versionCode = versionMajor * 10000 + versionMinor * 1000 + versionPatch * 100 + versionBuild versionCode = versionMajor * 10000 + versionMinor * 1000 + versionPatch * 100 + versionBuild
versionName = "${versionMajor}.${versionMinor}.${versionPatch}" versionName = "${versionMajor}.${versionMinor}.${versionPatch}"
@@ -83,7 +83,7 @@ dependencies {
debugImplementation("androidx.compose.ui:ui-test-manifest") debugImplementation("androidx.compose.ui:ui-test-manifest")
//wireguard tunnel //wireguard tunnel
implementation("com.wireguard.android:tunnel:1.0.20230706") implementation("com.wireguard.android:tunnel:1.0.20230427")
//logging //logging
implementation("com.jakewharton.timber:timber:5.0.1") implementation("com.jakewharton.timber:timber:5.0.1")
@@ -123,6 +123,9 @@ dependencies {
//barcode scanning //barcode scanning
implementation("com.google.android.gms:play-services-code-scanner:16.0.0") implementation("com.google.android.gms:play-services-code-scanner:16.0.0")
} }
kapt { kapt {
+1 -4
View File
@@ -28,9 +28,6 @@
<uses-feature <uses-feature
android:name="android.hardware.location.gps" android:name="android.hardware.location.gps"
android:required="false" /> android:required="false" />
<uses-feature
android:name="android.hardware.screen.portrait"
android:required="false" />
<queries> <queries>
<intent> <intent>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@@ -42,7 +39,7 @@
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:banner="@mipmap/ic_banner" android:banner="@mipmap/ic_launcher_foreground"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
@@ -1,6 +0,0 @@
package com.zaneschepke.wireguardautotunnel
object Constants {
const val VPN_CONNECTIVITY_CHECK_INTERVAL = 3000L;
const val VPN_STATISTIC_CHECK_INTERVAL = 10000L;
}
@@ -1,8 +1,6 @@
package com.zaneschepke.wireguardautotunnel package com.zaneschepke.wireguardautotunnel
import android.app.Application import android.app.Application
import android.content.Context
import android.content.pm.PackageManager
import com.google.firebase.crashlytics.FirebaseCrashlytics import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.zaneschepke.wireguardautotunnel.repository.Repository import com.zaneschepke.wireguardautotunnel.repository.Repository
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
@@ -24,10 +22,4 @@ class WireGuardAutoTunnel : Application() {
} }
settingsRepo.init() settingsRepo.init()
} }
companion object {
fun isRunningOnAndroidTv(context : Context) : Boolean {
return context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
}
}
} }
@@ -8,10 +8,7 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.os.PowerManager import android.os.PowerManager
import android.os.SystemClock import android.os.SystemClock
import androidx.compose.runtime.collectAsState
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.wireguard.android.backend.Tunnel import com.wireguard.android.backend.Tunnel
import com.zaneschepke.wireguardautotunnel.Constants
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.repository.Repository import com.zaneschepke.wireguardautotunnel.repository.Repository
import com.zaneschepke.wireguardautotunnel.service.network.MobileDataService import com.zaneschepke.wireguardautotunnel.service.network.MobileDataService
@@ -25,9 +22,6 @@ import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.single
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@@ -52,14 +46,15 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
@Inject @Inject
lateinit var vpnService : VpnService lateinit var vpnService : VpnService
private var isWifiConnected = false;
private var isMobileDataConnected = false;
private var currentNetworkSSID = "";
private lateinit var watcherJob : Job; private lateinit var watcherJob : Job;
private lateinit var setting : Settings private lateinit var setting : Settings
private lateinit var tunnelId: String private lateinit var tunnelId: String
private var connecting = false
private var disconnecting = false
private var isWifiConnected = false
private var isMobileDataConnected = false
private var wakeLock: PowerManager.WakeLock? = null private var wakeLock: PowerManager.WakeLock? = null
private val tag = this.javaClass.name; private val tag = this.javaClass.name;
@@ -141,9 +136,6 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
watchForMobileDataConnectivityChanges() watchForMobileDataConnectivityChanges()
} }
} }
CoroutineScope(watcherJob).launch {
manageVpn()
}
} }
} }
@@ -157,9 +149,17 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
is NetworkStatus.CapabilitiesChanged -> { is NetworkStatus.CapabilitiesChanged -> {
isMobileDataConnected = true isMobileDataConnected = true
Timber.d("Mobile data capabilities changed") Timber.d("Mobile data capabilities changed")
if(!disconnecting && !connecting) {
if(!isWifiConnected && setting.isTunnelOnMobileDataEnabled
&& vpnService.getState() == Tunnel.State.DOWN)
startVPN()
}
} }
is NetworkStatus.Unavailable -> { is NetworkStatus.Unavailable -> {
isMobileDataConnected = false isMobileDataConnected = false
if(!disconnecting && !connecting) {
if(!isWifiConnected && vpnService.getState() == Tunnel.State.UP) stopVPN()
}
Timber.d("Lost mobile data connection") Timber.d("Lost mobile data connection")
} }
} }
@@ -176,52 +176,61 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
is NetworkStatus.CapabilitiesChanged -> { is NetworkStatus.CapabilitiesChanged -> {
Timber.d("Wifi capabilities changed") Timber.d("Wifi capabilities changed")
isWifiConnected = true isWifiConnected = true
currentNetworkSSID = wifiService.getNetworkName(it.networkCapabilities) ?: ""; if (!connecting && !disconnecting) {
Timber.d("Not connect and not disconnecting")
val ssid = wifiService.getNetworkName(it.networkCapabilities);
Timber.d("SSID: $ssid")
if (!setting.trustedNetworkSSIDs.contains(ssid) && vpnService.getState() == Tunnel.State.DOWN) {
Timber.d("Starting VPN Tunnel for untrusted network: $ssid")
startVPN()
} else if (!disconnecting && vpnService.getState() == Tunnel.State.UP && setting.trustedNetworkSSIDs.contains(
ssid
)
) {
Timber.d("Stopping VPN Tunnel for trusted network with ssid: $ssid")
stopVPN()
}
}
} }
is NetworkStatus.Unavailable -> { is NetworkStatus.Unavailable -> {
isWifiConnected = false isWifiConnected = false
Timber.d("Lost Wi-Fi connection") Timber.d("Lost Wi-Fi connection")
if(!connecting || !disconnecting) {
if(setting.isTunnelOnMobileDataEnabled && vpnService.getState() == Tunnel.State.DOWN
&& isMobileDataConnected){
Timber.d("Wifi not available so starting vpn for mobile data")
startVPN()
}
if(!setting.isTunnelOnMobileDataEnabled && vpnService.getState() == Tunnel.State.UP) {
Timber.d("Lost WiFi connection, disabling vpn")
stopVPN()
}
}
} }
} }
} }
} }
private fun startVPN() {
private suspend fun manageVpn() { if(!connecting) {
while(watcherJob.isActive) { connecting = true
if(setting.isTunnelOnMobileDataEnabled && ServiceTracker.actionOnService(
!isWifiConnected && Action.START,
isMobileDataConnected this.applicationContext as Application,
&& vpnService.getState() == Tunnel.State.DOWN) { WireGuardTunnelService::class.java,
startVPN() mapOf(getString(R.string.tunnel_extras_key) to tunnelId))
} else if(!setting.isTunnelOnMobileDataEnabled && connecting = false
!isWifiConnected &&
vpnService.getState() == Tunnel.State.UP) {
stopVPN()
} else if(isWifiConnected &&
!setting.trustedNetworkSSIDs.contains(currentNetworkSSID) &&
(vpnService.getState() != Tunnel.State.UP)) {
startVPN()
} else if((isWifiConnected &&
setting.trustedNetworkSSIDs.contains(currentNetworkSSID)) &&
(vpnService.getState() == Tunnel.State.UP)) {
stopVPN()
}
delay(Constants.VPN_CONNECTIVITY_CHECK_INTERVAL)
} }
} }
private fun startVPN() {
ServiceTracker.actionOnService(
Action.START,
this.applicationContext as Application,
WireGuardTunnelService::class.java,
mapOf(getString(R.string.tunnel_extras_key) to tunnelId))
}
private fun stopVPN() { private fun stopVPN() {
ServiceTracker.actionOnService( if(!disconnecting) {
Action.STOP, disconnecting = true
this.applicationContext as Application, ServiceTracker.actionOnService(
WireGuardTunnelService::class.java Action.STOP,
) this.applicationContext as Application,
WireGuardTunnelService::class.java
)
disconnecting = false
}
} }
} }
@@ -45,7 +45,6 @@ abstract class BaseNetworkService<T : BaseNetworkService<T>>(val context: Contex
} }
} }
} }
else -> { else -> {
object : ConnectivityManager.NetworkCallback() { object : ConnectivityManager.NetworkCallback() {
@@ -78,8 +77,8 @@ abstract class BaseNetworkService<T : BaseNetworkService<T>>(val context: Contex
override fun getNetworkName(networkCapabilities: NetworkCapabilities): String? { override fun getNetworkName(networkCapabilities: NetworkCapabilities): String? {
var ssid: String? = getWifiNameFromCapabilities(networkCapabilities) var ssid : String? = getWifiNameFromCapabilities(networkCapabilities)
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { if((Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) || (Build.VERSION.SDK_INT == Build.VERSION_CODES.R)) {
val info = wifiManager.connectionInfo val info = wifiManager.connectionInfo
if (info.supplicantState === SupplicantState.COMPLETED) { if (info.supplicantState === SupplicantState.COMPLETED) {
ssid = info.ssid ssid = info.ssid
@@ -90,15 +89,14 @@ abstract class BaseNetworkService<T : BaseNetworkService<T>>(val context: Contex
companion object { companion object {
private fun getWifiNameFromCapabilities(networkCapabilities: NetworkCapabilities): String? { private fun getWifiNameFromCapabilities(networkCapabilities: NetworkCapabilities) : String? {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { val info : WifiInfo
val info: WifiInfo if(networkCapabilities.transportInfo is WifiInfo) {
if (networkCapabilities.transportInfo is WifiInfo) { info = networkCapabilities.transportInfo as WifiInfo
info = networkCapabilities.transportInfo as WifiInfo } else {
return info.ssid return null
}
} }
return null return info.ssid
} }
} }
} }
@@ -5,7 +5,6 @@ import com.wireguard.android.backend.BackendException
import com.wireguard.android.backend.Statistics import com.wireguard.android.backend.Statistics
import com.wireguard.android.backend.Tunnel import com.wireguard.android.backend.Tunnel
import com.wireguard.crypto.Key import com.wireguard.crypto.Key
import com.zaneschepke.wireguardautotunnel.Constants
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.util.NumberUtils import com.zaneschepke.wireguardautotunnel.util.NumberUtils
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -104,7 +103,7 @@ class WireGuardTunnel @Inject constructor(private val backend : Backend,
_handshakeStatus.emit(HandshakeStatus.NOT_STARTED) _handshakeStatus.emit(HandshakeStatus.NOT_STARTED)
} }
if(neverHadHandshakeCounter <= HandshakeStatus.NEVER_CONNECTED_TO_UNHEALTHY_TIME_LIMIT_SEC) { if(neverHadHandshakeCounter <= HandshakeStatus.NEVER_CONNECTED_TO_UNHEALTHY_TIME_LIMIT_SEC) {
neverHadHandshakeCounter += 10 neverHadHandshakeCounter++
} }
return@forEach return@forEach
} }
@@ -115,7 +114,7 @@ class WireGuardTunnel @Inject constructor(private val backend : Backend,
} }
} }
_lastHandshake.emit(handshakeMap) _lastHandshake.emit(handshakeMap)
delay(Constants.VPN_STATISTIC_CHECK_INTERVAL) delay(1000)
} }
} }
} }
@@ -6,7 +6,6 @@ import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.Settings import android.provider.Settings
import android.view.KeyEvent
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
@@ -23,9 +22,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.input.key.onKeyEvent
import com.google.accompanist.navigation.animation.AnimatedNavHost import com.google.accompanist.navigation.animation.AnimatedNavHost
import com.google.accompanist.navigation.animation.composable import com.google.accompanist.navigation.animation.composable
import com.google.accompanist.navigation.animation.rememberAnimatedNavController import com.google.accompanist.navigation.animation.rememberAnimatedNavController
@@ -44,8 +40,6 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.support.SupportScreen
import com.zaneschepke.wireguardautotunnel.ui.theme.TransparentSystemBars import com.zaneschepke.wireguardautotunnel.ui.theme.TransparentSystemBars
import com.zaneschepke.wireguardautotunnel.ui.theme.WireguardAutoTunnelTheme import com.zaneschepke.wireguardautotunnel.ui.theme.WireguardAutoTunnelTheme
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import timber.log.Timber
import java.lang.IllegalStateException
@AndroidEntryPoint @AndroidEntryPoint
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
@@ -57,8 +51,6 @@ class MainActivity : AppCompatActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContent { setContent {
val navController = rememberAnimatedNavController() val navController = rememberAnimatedNavController()
val focusRequester = remember { FocusRequester() }
WireguardAutoTunnelTheme { WireguardAutoTunnelTheme {
TransparentSystemBars() TransparentSystemBars()
@@ -88,25 +80,7 @@ class MainActivity : AppCompatActivity() {
} else requestNotificationPermission() } else requestNotificationPermission()
} }
Scaffold(snackbarHost = { SnackbarHost(snackbarHostState)}, Scaffold(snackbarHost = { SnackbarHost(snackbarHostState) },
modifier = Modifier.onKeyEvent {
if (it.nativeKeyEvent.action == KeyEvent.ACTION_UP) {
when (it.nativeKeyEvent.keyCode) {
KeyEvent.KEYCODE_DPAD_UP -> {
try {
focusRequester.requestFocus()
} catch(e : IllegalStateException) {
Timber.e("No D-Pad focus request modifier added to element on screen")
}
false
} else -> {
false;
}
}
} else {
false
}
},
bottomBar = if (vpnIntent == null && notificationPermissionState.status.isGranted) { bottomBar = if (vpnIntent == null && notificationPermissionState.status.isGranted) {
{ BottomNavBar(navController, Routes.navItems) } { BottomNavBar(navController, Routes.navItems) }
} else { } else {
@@ -152,7 +126,7 @@ class MainActivity : AppCompatActivity() {
} }
} }
}) { }) {
MainScreen(padding = padding, snackbarHostState = snackbarHostState, navController = navController, focusRequester = focusRequester) MainScreen(padding = padding, snackbarHostState = snackbarHostState, navController = navController)
} }
composable(Routes.Settings.name, enterTransition = { composable(Routes.Settings.name, enterTransition = {
when (initialState.destination.route) { when (initialState.destination.route) {
@@ -173,7 +147,7 @@ class MainActivity : AppCompatActivity() {
fadeIn(animationSpec = tween(1000)) fadeIn(animationSpec = tween(1000))
} }
} }
}) { SettingsScreen(padding = padding, snackbarHostState = snackbarHostState, navController = navController, focusRequester = focusRequester) } }) { SettingsScreen(padding = padding, snackbarHostState = snackbarHostState, navController = navController) }
composable(Routes.Support.name, enterTransition = { composable(Routes.Support.name, enterTransition = {
when (initialState.destination.route) { when (initialState.destination.route) {
Routes.Settings.name, Routes.Main.name -> Routes.Settings.name, Routes.Main.name ->
@@ -186,10 +160,10 @@ class MainActivity : AppCompatActivity() {
fadeIn(animationSpec = tween(1000)) fadeIn(animationSpec = tween(1000))
} }
} }
}) { SupportScreen(padding = padding, focusRequester) } }) { SupportScreen(padding = padding) }
composable("${Routes.Config.name}/{id}", enterTransition = { composable("${Routes.Config.name}/{id}", enterTransition = {
fadeIn(animationSpec = tween(1000)) fadeIn(animationSpec = tween(1000))
}) { ConfigScreen(padding = padding, navController = navController, id = it.arguments?.getString("id"), focusRequester = focusRequester)} }) { ConfigScreen(padding = padding, navController = navController, id = it.arguments?.getString("id"))}
composable("${Routes.Detail.name}/{id}", enterTransition = { composable("${Routes.Detail.name}/{id}", enterTransition = {
fadeIn(animationSpec = tween(1000)) fadeIn(animationSpec = tween(1000))
}) { DetailScreen(padding = padding, id = it.arguments?.getString("id")) } }) { DetailScreen(padding = padding, id = it.arguments?.getString("id")) }
@@ -1,6 +1,5 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.config package com.zaneschepke.wireguardautotunnel.ui.screens.config
import android.content.pm.PackageManager
import android.widget.Toast import android.widget.Toast
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@@ -25,13 +24,10 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier 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.LocalContext
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.LocalSoftwareKeyboardController
@@ -52,14 +48,12 @@ import kotlinx.coroutines.launch
fun ConfigScreen( fun ConfigScreen(
viewModel: ConfigViewModel = hiltViewModel(), viewModel: ConfigViewModel = hiltViewModel(),
padding: PaddingValues, padding: PaddingValues,
focusRequester: FocusRequester,
navController: NavController, navController: NavController,
id : String? id : String?
) { ) {
val context = LocalContext.current val context = LocalContext.current
val focusManager = LocalFocusManager.current val focusManager = LocalFocusManager.current
val keyboardController = LocalSoftwareKeyboardController.current val keyboardController = LocalSoftwareKeyboardController.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val tunnel by viewModel.tunnel.collectAsStateWithLifecycle(null) val tunnel by viewModel.tunnel.collectAsStateWithLifecycle(null)
@@ -92,7 +86,6 @@ fun ConfigScreen(
horizontalArrangement = Arrangement.SpaceBetween horizontalArrangement = Arrangement.SpaceBetween
) { ) {
OutlinedTextField( OutlinedTextField(
modifier = Modifier.focusRequester(focusRequester),
value = tunnelName.value, value = tunnelName.value,
onValueChange = { onValueChange = {
viewModel.onTunnelNameChange(it) viewModel.onTunnelNameChange(it)
@@ -165,6 +158,14 @@ fun ConfigScreen(
} }
} }
} }
// LazyColumn(
// modifier = Modifier
// .fillMaxWidth()
// .fillMaxHeight(.75f)
// .padding(horizontal = 14.dp, vertical = 7.dp),
// verticalArrangement = Arrangement.Center,
// horizontalAlignment = Alignment.Start
// ) {
items(packages) { pack -> items(packages) { pack ->
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
@@ -7,7 +7,6 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.focusable
import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -26,7 +25,6 @@ import androidx.compose.material.icons.rounded.Add
import androidx.compose.material.icons.rounded.Circle import androidx.compose.material.icons.rounded.Circle
import androidx.compose.material.icons.rounded.Delete import androidx.compose.material.icons.rounded.Delete
import androidx.compose.material.icons.rounded.Edit import androidx.compose.material.icons.rounded.Edit
import androidx.compose.material.icons.rounded.Info
import androidx.compose.material3.Divider import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FabPosition import androidx.compose.material3.FabPosition
@@ -52,8 +50,6 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.geometry.Offset
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.hapticfeedback.HapticFeedbackType
@@ -71,7 +67,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController import androidx.navigation.NavController
import com.wireguard.android.backend.Tunnel import com.wireguard.android.backend.Tunnel
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.ui.Routes import com.zaneschepke.wireguardautotunnel.ui.Routes
@@ -85,7 +80,6 @@ import kotlinx.coroutines.launch
@Composable @Composable
fun MainScreen( fun MainScreen(
viewModel: MainViewModel = hiltViewModel(), padding: PaddingValues, viewModel: MainViewModel = hiltViewModel(), padding: PaddingValues,
focusRequester: FocusRequester,
snackbarHostState: SnackbarHostState, navController: NavController snackbarHostState: SnackbarHostState, navController: NavController
) { ) {
@@ -245,12 +239,12 @@ fun MainScreen(
.nestedScroll(nestedScrollConnection),) { .nestedScroll(nestedScrollConnection),) {
items(tunnels.toList()) { tunnel -> items(tunnels.toList()) { tunnel ->
RowListItem(leadingIcon = Icons.Rounded.Circle, RowListItem(leadingIcon = Icons.Rounded.Circle,
leadingIconColor = if (tunnelName == tunnel.name) when (handshakeStatus) { leadingIconColor = when (handshakeStatus) {
HandshakeStatus.HEALTHY -> mint HandshakeStatus.HEALTHY -> mint
HandshakeStatus.UNHEALTHY -> brickRed HandshakeStatus.UNHEALTHY -> brickRed
HandshakeStatus.NOT_STARTED -> Color.Gray HandshakeStatus.NOT_STARTED -> Color.Gray
HandshakeStatus.NEVER_CONNECTED -> brickRed HandshakeStatus.NEVER_CONNECTED -> brickRed
} else Color.Gray, },
text = tunnel.name, text = tunnel.name,
onHold = { onHold = {
if (state == Tunnel.State.UP && tunnel.name == tunnelName) { if (state == Tunnel.State.UP && tunnel.name == tunnelName) {
@@ -262,13 +256,7 @@ fun MainScreen(
haptic.performHapticFeedback(HapticFeedbackType.LongPress) haptic.performHapticFeedback(HapticFeedbackType.LongPress)
selectedTunnel = tunnel; selectedTunnel = tunnel;
}, },
onClick = { onClick = { navController.navigate("${Routes.Detail.name}/${tunnel.id}") },
if(!WireGuardAutoTunnel.isRunningOnAndroidTv(context)){
navController.navigate("${Routes.Detail.name}/${tunnel.id}")
} else {
focusRequester.requestFocus()
}
},
rowButton = { rowButton = {
if (tunnel.id == selectedTunnel?.id) { if (tunnel.id == selectedTunnel?.id) {
Row() { Row() {
@@ -277,9 +265,7 @@ fun MainScreen(
}) { }) {
Icon(Icons.Rounded.Edit, stringResource(id = R.string.edit)) Icon(Icons.Rounded.Edit, stringResource(id = R.string.edit))
} }
IconButton( IconButton(onClick = { viewModel.onDelete(tunnel) }) {
modifier = Modifier.focusable(),
onClick = { viewModel.onDelete(tunnel) }) {
Icon( Icon(
Icons.Rounded.Delete, Icons.Rounded.Delete,
stringResource(id = R.string.delete) stringResource(id = R.string.delete)
@@ -287,51 +273,12 @@ fun MainScreen(
} }
} }
} else { } else {
if(WireGuardAutoTunnel.isRunningOnAndroidTv(context)){ Switch(
Row() { checked = (state == Tunnel.State.UP && tunnel.name == tunnelName),
IconButton(modifier = Modifier.focusRequester(focusRequester),onClick = { onCheckedChange = { checked ->
navController.navigate("${Routes.Detail.name}/${tunnel.id}") if (checked) viewModel.onTunnelStart(tunnel) else viewModel.onTunnelStop()
}) {
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()
}
)
}
} }
}) })
} }
@@ -3,7 +3,6 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.settings
import android.Manifest import android.Manifest
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build
import android.provider.Settings import android.provider.Settings
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -13,7 +12,6 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@@ -49,8 +47,6 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
@@ -68,7 +64,6 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState import com.google.accompanist.permissions.rememberPermissionState
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.ui.Routes import com.zaneschepke.wireguardautotunnel.ui.Routes
import com.zaneschepke.wireguardautotunnel.ui.common.ClickableIconButton import com.zaneschepke.wireguardautotunnel.ui.common.ClickableIconButton
@@ -82,7 +77,6 @@ fun SettingsScreen(
viewModel: SettingsViewModel = hiltViewModel(), viewModel: SettingsViewModel = hiltViewModel(),
padding: PaddingValues, padding: PaddingValues,
navController: NavController, navController: NavController,
focusRequester: FocusRequester,
snackbarHostState: SnackbarHostState = remember { SnackbarHostState() } snackbarHostState: SnackbarHostState = remember { SnackbarHostState() }
) { ) {
@@ -98,7 +92,6 @@ fun SettingsScreen(
val tunnels by viewModel.tunnels.collectAsStateWithLifecycle(mutableListOf()) val tunnels by viewModel.tunnels.collectAsStateWithLifecycle(mutableListOf())
val backgroundLocationState = val backgroundLocationState =
rememberPermissionState(Manifest.permission.ACCESS_BACKGROUND_LOCATION) rememberPermissionState(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
val fineLocationState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION)
var currentText by remember { mutableStateOf("") } var currentText by remember { mutableStateOf("") }
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
var isLocationServicesEnabled by remember { mutableStateOf(viewModel.checkLocationServicesEnabled())} var isLocationServicesEnabled by remember { mutableStateOf(viewModel.checkLocationServicesEnabled())}
@@ -126,17 +119,7 @@ fun SettingsScreen(
} }
} }
fun openSettings() { if(!backgroundLocationState.status.isGranted) {
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 && Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
Column(horizontalAlignment = Alignment.CenterHorizontally, Column(horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top, verticalArrangement = Arrangement.Top,
modifier = Modifier modifier = Modifier
@@ -148,10 +131,9 @@ fun SettingsScreen(
.size(128.dp)) .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_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) 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( Row(
modifier = if(WireGuardAutoTunnel.isRunningOnAndroidTv(context)) Modifier modifier = Modifier
.fillMaxWidth()
.padding(10.dp) else Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(30.dp), .padding(30.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
@@ -162,8 +144,14 @@ fun SettingsScreen(
}) { }) {
Text(stringResource(id = R.string.no_thanks)) Text(stringResource(id = R.string.no_thanks))
} }
Button(modifier = Modifier.focusRequester(focusRequester), onClick = { Button(onClick = {
openSettings() scope.launch {
val intentSettings =
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
intentSettings.data =
Uri.fromParts("package", context.packageName, null)
context.startActivity(intentSettings)
}
}) { }) {
Text(stringResource(id = R.string.turn_on)) Text(stringResource(id = R.string.turn_on))
} }
@@ -172,30 +160,6 @@ fun SettingsScreen(
return 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))
}
}
return
}
if (tunnels.isEmpty()) { if (tunnels.isEmpty()) {
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
@@ -213,7 +177,7 @@ fun SettingsScreen(
} }
return return
} }
if(!isLocationServicesEnabled && Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { if(!isLocationServicesEnabled) {
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
@@ -227,7 +191,7 @@ fun SettingsScreen(
modifier = Modifier.padding(15.dp), modifier = Modifier.padding(15.dp),
fontStyle = FontStyle.Italic fontStyle = FontStyle.Italic
) )
Button(modifier = Modifier.focusRequester(focusRequester), onClick = { Button(onClick = {
val locationServicesEnabled = viewModel.checkLocationServicesEnabled() val locationServicesEnabled = viewModel.checkLocationServicesEnabled()
isLocationServicesEnabled = locationServicesEnabled isLocationServicesEnabled = locationServicesEnabled
if(!locationServicesEnabled) { if(!locationServicesEnabled) {
@@ -241,18 +205,11 @@ fun SettingsScreen(
} }
return return
} }
val screenPadding = if(WireGuardAutoTunnel.isRunningOnAndroidTv(context)) 5.dp else 15.dp
Column( Column(
horizontalAlignment = Alignment.Start, horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.Top, verticalArrangement = Arrangement.Top,
modifier = if(WireGuardAutoTunnel.isRunningOnAndroidTv(context)) Modifier modifier = Modifier
.fillMaxHeight(.85f)
.fillMaxWidth()
.verticalScroll(scrollState)
.clickable(indication = null, interactionSource = interactionSource) {
focusManager.clearFocus()
}
.padding(padding) else Modifier
.fillMaxSize() .fillMaxSize()
.verticalScroll(scrollState) .verticalScroll(scrollState)
.clickable(indication = null, interactionSource = interactionSource) { .clickable(indication = null, interactionSource = interactionSource) {
@@ -263,13 +220,12 @@ fun SettingsScreen(
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(screenPadding), .padding(14.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween horizontalArrangement = Arrangement.SpaceBetween
) { ) {
Text(stringResource(R.string.enable_auto_tunnel)) Text(stringResource(R.string.enable_auto_tunnel))
Switch( Switch(
modifier = Modifier.focusRequester(focusRequester),
enabled = !settings.isAlwaysOnVpnEnabled, enabled = !settings.isAlwaysOnVpnEnabled,
checked = settings.isAutoTunnelEnabled, checked = settings.isAutoTunnelEnabled,
onCheckedChange = { onCheckedChange = {
@@ -282,16 +238,14 @@ fun SettingsScreen(
Text( Text(
stringResource(id = R.string.select_tunnel), stringResource(id = R.string.select_tunnel),
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
modifier = Modifier.padding(screenPadding, bottom = 5.dp, top = 5.dp) modifier = Modifier.padding(15.dp, bottom = 5.dp, top = 5.dp)
) )
ExposedDropdownMenuBox( ExposedDropdownMenuBox(
expanded = expanded, expanded = expanded,
onExpandedChange = { onExpandedChange = {
if(!(settings.isAutoTunnelEnabled || settings.isAlwaysOnVpnEnabled)) { if(!(settings.isAutoTunnelEnabled || settings.isAlwaysOnVpnEnabled)) {
expanded = !expanded }}, expanded = !expanded }},
modifier = Modifier.padding(start = 15.dp, top = 5.dp, bottom = 10.dp).clickable { modifier = Modifier.padding(start = 15.dp, top = 5.dp, bottom = 10.dp),
expanded = !expanded
},
) { ) {
TextField( TextField(
enabled = !(settings.isAutoTunnelEnabled || settings.isAlwaysOnVpnEnabled), enabled = !(settings.isAutoTunnelEnabled || settings.isAlwaysOnVpnEnabled),
@@ -330,10 +284,10 @@ fun SettingsScreen(
Text( Text(
stringResource(R.string.trusted_ssid), stringResource(R.string.trusted_ssid),
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
modifier = Modifier.padding(screenPadding, bottom = 5.dp, top = 5.dp) modifier = Modifier.padding(15.dp, bottom = 5.dp, top = 5.dp)
) )
FlowRow( FlowRow(
modifier = Modifier.padding(screenPadding), modifier = Modifier.padding(15.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
@@ -350,7 +304,7 @@ fun SettingsScreen(
value = currentText, value = currentText,
onValueChange = { currentText = it }, onValueChange = { currentText = it },
label = { Text(stringResource(R.string.add_trusted_ssid)) }, label = { Text(stringResource(R.string.add_trusted_ssid)) },
modifier = Modifier.padding(start = screenPadding, top = 5.dp), modifier = Modifier.padding(start = 15.dp, top = 5.dp),
maxLines = 1, maxLines = 1,
keyboardOptions = KeyboardOptions( keyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.None, capitalization = KeyboardCapitalization.None,
@@ -376,7 +330,7 @@ fun SettingsScreen(
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(screenPadding), .padding(14.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween horizontalArrangement = Arrangement.SpaceBetween
) { ) {
@@ -394,7 +348,7 @@ fun SettingsScreen(
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(screenPadding), .padding(14.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween horizontalArrangement = Arrangement.SpaceBetween
) { ) {
@@ -1,10 +1,8 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.support package com.zaneschepke.wireguardautotunnel.ui.screens.support
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
@@ -19,12 +17,8 @@ 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
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@@ -37,7 +31,7 @@ import androidx.compose.ui.unit.sp
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
@Composable @Composable
fun SupportScreen(padding : PaddingValues, focusRequester: FocusRequester) { fun SupportScreen(padding : PaddingValues) {
val context = LocalContext.current val context = LocalContext.current
@@ -52,7 +46,6 @@ fun SupportScreen(padding : PaddingValues, focusRequester: FocusRequester) {
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.focusable()
.padding(padding)) { .padding(padding)) {
Text(stringResource(R.string.support_text), textAlign = TextAlign.Center, modifier = Modifier.padding(30.dp), fontSize = 15.sp) Text(stringResource(R.string.support_text), textAlign = TextAlign.Center, modifier = Modifier.padding(30.dp), fontSize = 15.sp)
Row( Row(
@@ -67,7 +60,7 @@ fun SupportScreen(padding : PaddingValues, focusRequester: FocusRequester) {
}) { }) {
Icon(imageVector = ImageVector.vectorResource(R.drawable.discord), "Discord") Icon(imageVector = ImageVector.vectorResource(R.drawable.discord), "Discord")
} }
IconButton(modifier = Modifier.focusRequester(focusRequester),onClick = { IconButton(onClick = {
openWebPage(context.resources.getString(R.string.github_url)) openWebPage(context.resources.getString(R.string.github_url))
}) { }) {
Icon(imageVector = ImageVector.vectorResource(R.drawable.github), "Github") Icon(imageVector = ImageVector.vectorResource(R.drawable.github), "Github")
@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_banner_background"/>
<foreground android:drawable="@mipmap/ic_banner_foreground"/>
</adaptive-icon>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_banner_background">#121212</color>
</resources>
-2
View File
@@ -82,6 +82,4 @@
<string name="location_services_not_detected">Unable to detect Location Services which are required for this feature. Please enable Location Services.</string> <string name="location_services_not_detected">Unable to detect Location Services which are required for this feature. Please enable Location Services.</string>
<string name="check_again">Check again</string> <string name="check_again">Check again</string>
<string name="detecting_location_services_disabled">Detecting Location Services disabled</string> <string name="detecting_location_services_disabled">Detecting Location Services disabled</string>
<string name="precise_location_message">This feature requires precise location to access Wi-Fi SSID name. Please enable precise location here or in the app settings.</string>
<string name="request">Request</string>
</resources> </resources>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB