feat: migrate to forked lib

Migrated app to a forked version of wireguard-android to enable development work on features that require changes to the core lib, like #107 #104 #87 #52 #6

Improved first launch flow by change vpn permission to only launch on first tunnel start

Changed to proper database seeding strategy

Updated README to account for GitHub packages auth requirement

Migrated from deprecated UI components and libs

Bump versions
This commit is contained in:
Zane Schepke 2024-02-18 23:28:06 -05:00
parent 500b85f687
commit 2690ce29e1
27 changed files with 286 additions and 197 deletions

View File

@ -19,6 +19,8 @@ jobs:
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
KEY_STORE_FILE: 'android_keystore.jks'
KEY_STORE_LOCATION: ${{ github.workspace }}/app/keystore/
GH_USER: ${{ secrets.GH_USER }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
steps:
- uses: actions/checkout@v4

View File

@ -20,6 +20,8 @@ jobs:
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
KEY_STORE_FILE: 'android_keystore.jks'
KEY_STORE_LOCATION: ${{ github.workspace }}/app/keystore/
GH_USER: ${{ secrets.GH_USER }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
steps:
- uses: actions/checkout@v4

View File

@ -71,6 +71,31 @@ The repository for these docs can be found [here](https://github.com/zaneschepke
```
$ git clone https://github.com/zaneschepke/wgtunnel
$ cd wgtunnel
```
Create a personal access token (classic) in GitHuv to be able to pull the wireguard-android github dependencies from GitHub packages
as documented [here](https://docs.github.com/en/packages/learn-github-packages/introduction-to-github-packages#authenticating-to-github-packages).
Alternatively, you can clone [wireguard-android](https://github.com/zaneschepke/wireguard-android) and run the following command to publish the dependency to your local maven repository (requires you have maven installed). This is the ideal approach
if you intent to make changes to this lib.
```
$ git clone https://github.com/zaneschepke/wireguard-android
$ cd wireguard-android
$ brew install maven
$ ./gradlew publishToMavenLocal
```
The [wireguard-android](https://github.com/zaneschepke/wireguard-android) dependency is a fork of the official [wireguard-android](https://github.com/WireGuard/wireguard-android) library.
Add the following lines to local.properties file:
```
GH_USER=<your github username>
GH_TOKEN=<the personal access token with read package permission you just created>
```
And then build the app:
```
$ ./gradlew assembleDebug
```

View File

@ -156,13 +156,14 @@ dependencies {
debugImplementation(libs.androidx.compose.ui.tooling)
debugImplementation(libs.androidx.compose.manifest)
// wg
// get tunnel lib from github packages or mavenLocal
implementation(libs.tunnel)
coreLibraryDesugaring(libs.desugar.jdk.libs)
// logging
implementation(libs.timber)
// compose navigation
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.hilt.navigation.compose)
@ -172,7 +173,6 @@ dependencies {
ksp(libs.hilt.android.compiler)
// accompanist
implementation(libs.accompanist.systemuicontroller)
implementation(libs.accompanist.permissions)
implementation(libs.accompanist.flowlayout)
implementation(libs.accompanist.drawablepainter)

View File

@ -4,6 +4,7 @@ import androidx.room.testing.MigrationTestHelper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.zaneschepke.wireguardautotunnel.data.AppDatabase
import com.zaneschepke.wireguardautotunnel.data.Queries
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@ -26,33 +27,7 @@ class MigrationTest {
helper.createDatabase(dbName, 4).apply {
// Database has schema version 1. Insert some data using SQL queries.
// You can't use DAO classes because they expect the latest schema.
execSQL(
"INSERT INTO Settings (is_tunnel_enabled," +
"is_tunnel_on_mobile_data_enabled," +
"trusted_network_ssids," +
"default_tunnel," +
"is_always_on_vpn_enabled," +
"is_tunnel_on_ethernet_enabled," +
"is_shortcuts_enabled," +
"is_battery_saver_enabled," +
"is_tunnel_on_wifi_enabled," +
"is_kernel_enabled," +
"is_restore_on_boot_enabled," +
"is_multi_tunnel_enabled)" +
" VALUES " +
"('false'," +
"'false'," +
"'[trustedSSID1,trustedSSID2]'," +
"'defaultTunnel'," +
"'false'," +
"'false'," +
"'false'," +
"'false'," +
"'false'," +
"'false'," +
"'false'," +
"'false')",
)
execSQL(Queries.createDefaultSettings())
execSQL(
"INSERT INTO TunnelConfig (name, wg_quick)" + " VALUES ('hello', 'hello')",
)

View File

@ -0,0 +1,21 @@
package com.zaneschepke.wireguardautotunnel.data
import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteDatabase
import timber.log.Timber
class DatabaseCallback : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) = db.run {
// Notice non-ui thread is here
beginTransaction()
try {
execSQL(Queries.createDefaultSettings())
Timber.i("Bootstrapping settings data")
setTransactionSuccessful()
} catch (e : Exception) {
Timber.e(e)
} finally {
endTransaction()
}
}
}

View File

@ -0,0 +1,33 @@
package com.zaneschepke.wireguardautotunnel.data
object Queries {
fun createDefaultSettings() : String {
return """
INSERT INTO Settings (is_tunnel_enabled,
is_tunnel_on_mobile_data_enabled,
trusted_network_ssids,
default_tunnel,
is_always_on_vpn_enabled,
is_tunnel_on_ethernet_enabled,
is_shortcuts_enabled,
is_battery_saver_enabled,
is_tunnel_on_wifi_enabled,
is_kernel_enabled,
is_restore_on_boot_enabled,
is_multi_tunnel_enabled)
VALUES
('false',
'false',
'[trustedSSID1,trustedSSID2]',
NULL,
'false',
'false',
'false',
'false',
'false',
'false',
'false',
'false')
""".trimIndent()
}
}

View File

@ -4,6 +4,7 @@ import android.content.Context
import androidx.room.Room
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.data.AppDatabase
import com.zaneschepke.wireguardautotunnel.data.DatabaseCallback
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -23,6 +24,7 @@ class DatabaseModule {
context.getString(R.string.db_name),
)
.fallbackToDestructiveMigration()
.addCallback(DatabaseCallback())
.build()
}
}

View File

@ -8,6 +8,7 @@ import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepositor
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.util.goAsync
import dagger.hilt.android.AndroidEntryPoint
import timber.log.Timber
import javax.inject.Inject
@AndroidEntryPoint
@ -21,8 +22,10 @@ class BootReceiver : BroadcastReceiver() {
if (Intent.ACTION_BOOT_COMPLETED != intent?.action) return@goAsync
val settings = settingsRepository.getSettings()
if (settings.isAutoTunnelEnabled) {
Timber.i("Starting watcher service from boot")
ServiceManager.startWatcherServiceForeground(context!!)
} else if(settings.isAlwaysOnVpnEnabled) {
Timber.i("Starting tunnel from boot")
ServiceManager.startVpnServicePrimaryTunnel(context!!, settings, tunnelConfigRepository.getAll().firstOrNull())
}
}

View File

@ -152,12 +152,16 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
wakeLock =
(getSystemService(Context.POWER_SERVICE) as PowerManager).run {
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "$tag::lock").apply {
if (isBatterySaverOn) {
Timber.d("Initiating wakelock with timeout")
acquire(Constants.BATTERY_SAVER_WATCHER_WAKE_LOCK_TIMEOUT)
} else {
Timber.d("Initiating wakelock with zero timeout")
acquire(Constants.DEFAULT_WATCHER_WAKE_LOCK_TIMEOUT)
try {
if (isBatterySaverOn) {
Timber.d("Initiating wakelock with timeout")
acquire(Constants.BATTERY_SAVER_WATCHER_WAKE_LOCK_TIMEOUT)
} else {
Timber.d("Initiating wakelock with zero timeout")
acquire(Constants.DEFAULT_WATCHER_WAKE_LOCK_TIMEOUT)
}
} finally {
release()
}
}
}

View File

@ -25,7 +25,7 @@ import javax.inject.Inject
class WireGuardTunnel
@Inject
constructor(
@Userspace private val userspaceBackend: Backend,
@Userspace private val userspaceBackend : Backend,
@Kernel private val kernelBackend: Backend,
private val settingsRepository: SettingsRepository
) : VpnService {

View File

@ -1,13 +1,12 @@
package com.zaneschepke.wireguardautotunnel.ui
import androidx.lifecycle.ViewModel
import com.zaneschepke.wireguardautotunnel.data.SettingsDao
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@HiltViewModel
class ActivityViewModel
@Inject
constructor(
private val settingsRepo: SettingsDao,
) : ViewModel() {}
constructor() : ViewModel() {
}

View File

@ -6,11 +6,13 @@ import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarData
@ -20,13 +22,12 @@ import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusProperties
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.unit.dp
import androidx.lifecycle.lifecycleScope
import androidx.navigation.compose.NavHost
@ -35,7 +36,6 @@ import androidx.navigation.compose.rememberNavController
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState
import com.wireguard.android.backend.GoBackend
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
import com.zaneschepke.wireguardautotunnel.data.datastore.DataStoreManager
@ -47,7 +47,6 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.config.ConfigScreen
import com.zaneschepke.wireguardautotunnel.ui.screens.main.MainScreen
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.SettingsScreen
import com.zaneschepke.wireguardautotunnel.ui.screens.support.SupportScreen
import com.zaneschepke.wireguardautotunnel.ui.theme.TransparentSystemBars
import com.zaneschepke.wireguardautotunnel.ui.theme.WireguardAutoTunnelTheme
import com.zaneschepke.wireguardautotunnel.util.Constants
import dagger.hilt.android.AndroidEntryPoint
@ -70,6 +69,8 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge(navigationBarStyle = SystemBarStyle.dark(Color.Transparent.toArgb()))
// load preferences into memory and init data
lifecycleScope.launch {
try {
@ -80,13 +81,12 @@ class MainActivity : AppCompatActivity() {
}
}
setContent {
// val activityViewModel = hiltViewModel<ActivityViewModel>()
//val activityViewModel = hiltViewModel<ActivityViewModel>()
val navController = rememberNavController()
val focusRequester = remember { FocusRequester() }
WireguardAutoTunnelTheme {
TransparentSystemBars()
val snackbarHostState = remember { SnackbarHostState() }
@ -134,9 +134,11 @@ class MainActivity : AppCompatActivity() {
)
}
},
modifier = Modifier.focusable().focusProperties { up = focusRequester },
modifier = Modifier
.focusable()
.focusProperties { up = focusRequester },
bottomBar =
if (notificationPermissionState != null && notificationPermissionState.status.isGranted) {
if (notificationPermissionState == null || notificationPermissionState.status.isGranted) {
{
BottomNavBar(
navController,
@ -151,66 +153,64 @@ class MainActivity : AppCompatActivity() {
{}
},
) { padding ->
if (notificationPermissionState != null && !notificationPermissionState.status.isGranted) {
PermissionRequestFailedScreen(
padding = padding,
onRequestAgain = {
val intentSettings =
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
intentSettings.data =
Uri.fromParts(
Constants.URI_PACKAGE_SCHEME,
this.packageName,
null,
)
startActivity(intentSettings)
},
message = getString(R.string.notification_permission_required),
getString(R.string.open_settings),
)
return@Scaffold
}
NavHost(navController, startDestination = Screen.Main.route) {
composable(
Screen.Main.route,
) {
MainScreen(
padding = padding,
focusRequester = focusRequester,
showSnackbarMessage = { message -> showSnackBarMessage(message) },
navController = navController,
Column(modifier = Modifier.padding(padding)) {
if (notificationPermissionState != null && !notificationPermissionState.status.isGranted) {
PermissionRequestFailedScreen(
onRequestAgain = {
val intentSettings =
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
intentSettings.data =
Uri.fromParts(
Constants.URI_PACKAGE_SCHEME,
this@MainActivity.packageName,
null,
)
startActivity(intentSettings)
},
message = getString(R.string.notification_permission_required),
getString(R.string.open_settings),
)
return@Scaffold
}
composable(
Screen.Settings.route,
) {
SettingsScreen(
padding = padding,
showSnackbarMessage = { message -> showSnackBarMessage(message) },
focusRequester = focusRequester,
)
}
composable(
Screen.Support.route,
) {
SupportScreen(
padding = padding,
focusRequester = focusRequester,
showSnackbarMessage = { message -> showSnackBarMessage(message) },
)
}
composable("${Screen.Config.route}/{id}") {
val id = it.arguments?.getString("id")
if (!id.isNullOrBlank()) {
ConfigScreen(
NavHost(navController, startDestination = Screen.Main.route) {
composable(
Screen.Main.route,
) {
MainScreen(
focusRequester = focusRequester,
showSnackbarMessage = { message -> showSnackBarMessage(message) },
navController = navController,
id = id,
showSnackbarMessage = { message ->
showSnackBarMessage(message)
},
)
}
composable(
Screen.Settings.route,
) {
SettingsScreen(
showSnackbarMessage = { message -> showSnackBarMessage(message) },
focusRequester = focusRequester,
)
}
composable(
Screen.Support.route,
) {
SupportScreen(
focusRequester = focusRequester,
showSnackbarMessage = { message -> showSnackBarMessage(message) },
)
}
composable("${Screen.Config.route}/{id}") {
val id = it.arguments?.getString("id")
if (!id.isNullOrBlank()) {
ConfigScreen(
navController = navController,
id = id,
showSnackbarMessage = { message ->
showSnackBarMessage(message)
},
focusRequester = focusRequester,
)
}
}
}
}
}

View File

@ -2,7 +2,6 @@ package com.zaneschepke.wireguardautotunnel.ui.common
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
@ -17,7 +16,6 @@ import kotlinx.coroutines.launch
@Composable
fun PermissionRequestFailedScreen(
padding: PaddingValues,
onRequestAgain: () -> Unit,
message: String,
buttonText: String
@ -26,7 +24,7 @@ fun PermissionRequestFailedScreen(
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxSize().padding(padding),
modifier = Modifier.fillMaxSize(),
) {
Text(message, textAlign = TextAlign.Center, modifier = Modifier.padding(15.dp))
Button(

View File

@ -19,7 +19,6 @@ import androidx.compose.runtime.CompositionLocalProvider
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.platform.LocalLayoutDirection
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.LayoutDirection
@ -33,7 +32,6 @@ fun CustomSnackBar(
isRtl: Boolean = true,
containerColor: Color = MaterialTheme.colorScheme.surface
) {
val context = LocalContext.current
Snackbar(
containerColor = containerColor,
modifier =

View File

@ -1,7 +1,6 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.config
import android.annotation.SuppressLint
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.focusGroup
@ -29,7 +28,7 @@ import androidx.compose.material.icons.rounded.ContentCopy
import androidx.compose.material.icons.rounded.Delete
import androidx.compose.material.icons.rounded.Refresh
import androidx.compose.material.icons.rounded.Save
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.BasicAlertDialog
import androidx.compose.material3.Checkbox
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FabPosition
@ -51,7 +50,6 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
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
@ -86,9 +84,7 @@ import kotlinx.coroutines.delay
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@OptIn(
ExperimentalComposeUiApi::class,
ExperimentalMaterial3Api::class,
ExperimentalFoundationApi::class,
)
@Composable
fun ConfigScreen(
@ -145,7 +141,7 @@ fun ConfigScreen(
showAuthPrompt = false
isAuthenticated = true
},
onError = { error ->
onError = {
showAuthPrompt = false
showSnackbarMessage(Event.Error.AuthenticationFailed.message)
},
@ -161,20 +157,20 @@ fun ConfigScreen(
remember(uiState.packages) {
uiState.packages.sortedBy { viewModel.getPackageLabel(it) }
}
AlertDialog(onDismissRequest = { showApplicationsDialog = false }) {
BasicAlertDialog(onDismissRequest = { showApplicationsDialog = false }) {
Surface(
tonalElevation = 2.dp,
shadowElevation = 2.dp,
shape = RoundedCornerShape(12.dp),
color = MaterialTheme.colorScheme.surface,
modifier =
Modifier.fillMaxWidth()
.fillMaxHeight(if (uiState.isAllApplicationsEnabled) 1 / 5f else 4 / 5f),
Modifier.fillMaxWidth()
.fillMaxHeight(if (uiState.isAllApplicationsEnabled) 1 / 5f else 4 / 5f),
) {
Column(modifier = Modifier.fillMaxWidth()) {
Row(
modifier =
Modifier.fillMaxWidth().padding(horizontal = 20.dp, vertical = 7.dp),
Modifier.fillMaxWidth().padding(horizontal = 20.dp, vertical = 7.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
@ -187,8 +183,8 @@ fun ConfigScreen(
if (!uiState.isAllApplicationsEnabled) {
Row(
modifier =
Modifier.fillMaxWidth()
.padding(horizontal = 20.dp, vertical = 7.dp),
Modifier.fillMaxWidth()
.padding(horizontal = 20.dp, vertical = 7.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
@ -219,8 +215,8 @@ fun ConfigScreen(
}
Row(
modifier =
Modifier.fillMaxWidth()
.padding(horizontal = 20.dp, vertical = 7.dp),
Modifier.fillMaxWidth()
.padding(horizontal = 20.dp, vertical = 7.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
@ -262,9 +258,9 @@ fun ConfigScreen(
Checkbox(
modifier = Modifier.fillMaxSize(),
checked =
(uiState.checkedPackageNames.contains(
pack.packageName
)),
(uiState.checkedPackageNames.contains(
pack.packageName
)),
onCheckedChange = {
if (it) {
viewModel.onAddCheckedPackage(pack.packageName)
@ -299,7 +295,7 @@ fun ConfigScreen(
var fobColor by remember { mutableStateOf(secondaryColor) }
FloatingActionButton(
modifier =
Modifier.padding(bottom = 90.dp).onFocusChanged {
Modifier.onFocusChanged {
if (WireGuardAutoTunnel.isRunningOnAndroidTv()) {
fobColor = if (it.isFocused) hoverColor else secondaryColor
}

View File

@ -1,6 +1,5 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.main
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
@ -20,7 +19,6 @@ import androidx.compose.foundation.gestures.ScrollableDefaults
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.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
@ -45,10 +43,10 @@ import androidx.compose.material.icons.rounded.Edit
import androidx.compose.material.icons.rounded.Info
import androidx.compose.material.icons.rounded.Star
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FabPosition
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@ -110,12 +108,10 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable
fun MainScreen(
viewModel: MainViewModel = hiltViewModel(),
padding: PaddingValues,
focusRequester: FocusRequester,
showSnackbarMessage: (String) -> Unit,
navController: NavController
@ -344,7 +340,6 @@ fun MainScreen(
)
Modifier.focusRequester(focusRequester)
else Modifier)
.padding(bottom = 90.dp)
.onFocusChanged {
if (WireGuardAutoTunnel.isRunningOnAndroidTv()) {
fobColor = if (it.isFocused) hoverColor else secondaryColor
@ -367,7 +362,7 @@ fun MainScreen(
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxSize().padding(padding),
modifier = Modifier.fillMaxSize().padding(innerPadding),
) {
Text(text = stringResource(R.string.no_tunnels), fontStyle = FontStyle.Italic)
}
@ -398,7 +393,7 @@ fun MainScreen(
)
}
if (!WireGuardAutoTunnel.isRunningOnAndroidTv()) {
Divider()
HorizontalDivider()
Row(
modifier =
Modifier.fillMaxWidth()
@ -430,7 +425,7 @@ fun MainScreen(
)
}
}
Divider()
HorizontalDivider()
Row(
modifier =
Modifier.fillMaxWidth()
@ -461,8 +456,7 @@ fun MainScreen(
modifier =
Modifier.fillMaxWidth()
.fillMaxHeight(.90f)
.overscroll(ScrollableDefaults.overscrollEffect())
.padding(innerPadding),
.overscroll(ScrollableDefaults.overscrollEffect()),
state = rememberLazyListState(0, uiState.tunnels.count()),
userScrollEnabled = true,
reverseLayout = true,

View File

@ -20,7 +20,6 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
@ -95,7 +94,6 @@ import java.io.File
@Composable
fun SettingsScreen(
viewModel: SettingsViewModel = hiltViewModel(),
padding: PaddingValues,
showSnackbarMessage: (String) -> Unit,
focusRequester: FocusRequester
) {
@ -127,7 +125,7 @@ fun SettingsScreen(
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK) {
val intent = result.data
result.data
// Handle the Intent
}
viewModel.setBatteryOptimizeDisableShown()
@ -248,7 +246,7 @@ fun SettingsScreen(
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top,
modifier = Modifier.fillMaxSize().verticalScroll(scrollState).padding(padding),
modifier = Modifier.fillMaxSize().verticalScroll(scrollState),
) {
Icon(
Icons.Rounded.LocationOff,
@ -314,7 +312,7 @@ fun SettingsScreen(
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxSize().padding(padding),
modifier = Modifier.fillMaxSize(),
) {
Text(
stringResource(R.string.one_tunnel_required),
@ -347,7 +345,7 @@ fun SettingsScreen(
.fillMaxWidth(fillMaxWidth)
.padding(top = 10.dp)
} else {
Modifier.fillMaxWidth(fillMaxWidth).padding(top = 60.dp)
Modifier.fillMaxWidth(fillMaxWidth).padding(top = 20.dp)
})
.padding(bottom = 10.dp),
) {

View File

@ -8,7 +8,6 @@ import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
@ -20,10 +19,10 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ArrowForward
import androidx.compose.material.icons.automirrored.rounded.ArrowForward
import androidx.compose.material.icons.rounded.Book
import androidx.compose.material.icons.rounded.Mail
import androidx.compose.material3.Divider
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
@ -58,7 +57,6 @@ import com.zaneschepke.wireguardautotunnel.util.Event
@Composable
fun SupportScreen(
viewModel: SupportViewModel = hiltViewModel(),
padding: PaddingValues,
showSnackbarMessage: (String) -> Unit,
focusRequester: FocusRequester
) {
@ -107,7 +105,6 @@ fun SupportScreen(
Modifier.fillMaxSize()
.verticalScroll(rememberScrollState())
.focusable()
.padding(padding),
) {
Surface(
tonalElevation = 2.dp,
@ -155,10 +152,16 @@ fun SupportScreen(
modifier = Modifier.padding(start = 10.dp),
)
}
Icon(Icons.Rounded.ArrowForward, stringResource(id = R.string.go))
Icon(
Icons.AutoMirrored.Rounded.ArrowForward,
stringResource(id = R.string.go)
)
}
}
Divider(color = MaterialTheme.colorScheme.onBackground, thickness = 0.5.dp)
HorizontalDivider(
thickness = 0.5.dp,
color = MaterialTheme.colorScheme.onBackground
)
TextButton(
onClick = { openWebPage(context.resources.getString(R.string.discord_url)) },
modifier = Modifier.padding(vertical = 5.dp),
@ -180,10 +183,16 @@ fun SupportScreen(
modifier = Modifier.padding(start = 10.dp),
)
}
Icon(Icons.Rounded.ArrowForward, stringResource(id = R.string.go))
Icon(
Icons.AutoMirrored.Rounded.ArrowForward,
stringResource(id = R.string.go)
)
}
}
Divider(color = MaterialTheme.colorScheme.onBackground, thickness = 0.5.dp)
HorizontalDivider(
thickness = 0.5.dp,
color = MaterialTheme.colorScheme.onBackground
)
TextButton(
onClick = { openWebPage(context.resources.getString(R.string.github_url)) },
modifier = Modifier.padding(vertical = 5.dp),
@ -205,10 +214,16 @@ fun SupportScreen(
modifier = Modifier.padding(start = 10.dp),
)
}
Icon(Icons.Rounded.ArrowForward, stringResource(id = R.string.go))
Icon(
Icons.AutoMirrored.Rounded.ArrowForward,
stringResource(id = R.string.go)
)
}
}
Divider(color = MaterialTheme.colorScheme.onBackground, thickness = 0.5.dp)
HorizontalDivider(
thickness = 0.5.dp,
color = MaterialTheme.colorScheme.onBackground
)
TextButton(
onClick = { launchEmail() },
modifier = Modifier.padding(vertical = 5.dp),
@ -226,7 +241,10 @@ fun SupportScreen(
modifier = Modifier.padding(start = 10.dp),
)
}
Icon(Icons.Rounded.ArrowForward, stringResource(id = R.string.go))
Icon(
Icons.AutoMirrored.Rounded.ArrowForward,
stringResource(id = R.string.go)
)
}
}
}

View File

@ -1,22 +0,0 @@
package com.zaneschepke.wireguardautotunnel.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.graphics.Color
import com.google.accompanist.systemuicontroller.rememberSystemUiController
@Composable
fun TransparentSystemBars() {
val systemUiController = rememberSystemUiController()
val useDarkIcons = !isSystemInDarkTheme()
DisposableEffect(systemUiController, useDarkIcons) {
systemUiController.setSystemBarsColor(
color = Color.Transparent,
darkIcons = useDarkIcons,
)
onDispose {}
}
}

View File

@ -140,7 +140,7 @@
<string name="exported_configs_message">Exported configs to downloads</string>
<string name="status">status</string>
<string name="tunnel_on_wifi">Tunnel on untrusted wifi</string>
<string name="my_email" translatable="false">zanecschepke@gmail.com</string>
<string name="my_email" translatable="false">support@zaneschepke.com</string>
<string name="email_subject">WG Tunnel Support</string>
<string name="email_chooser">Send an email…</string>
<string name="go">go</string>

View File

@ -1,4 +1,5 @@
import org.gradle.api.invocation.Gradle
import java.io.File
object BuildHelper {
private fun getCurrentFlavor(gradle: Gradle): String {
@ -21,6 +22,17 @@ object BuildHelper {
return flavor
}
fun getLocalProperty(key: String, file: String = "local.properties"): String? {
val properties = java.util.Properties()
val localProperties = File(file)
if (localProperties.isFile) {
java.io.InputStreamReader(java.io.FileInputStream(localProperties), Charsets.UTF_8).use { reader ->
properties.load(reader)
}
} else return null
return properties.getProperty(key)
}
fun isGeneralFlavor(gradle: Gradle): Boolean {
return getCurrentFlavor(gradle) == "general"
}

View File

@ -1,7 +1,7 @@
object Constants {
const val VERSION_NAME = "3.3.5"
const val VERSION_NAME = "3.3.6"
const val JVM_TARGET = "17"
const val VERSION_CODE = 33500
const val VERSION_CODE = 33600
const val TARGET_SDK = 34
const val MIN_SDK = 26
const val APP_ID = "com.zaneschepke.wireguardautotunnel"

View File

@ -0,0 +1,5 @@
What's new:
- Improve first launch flow
- Switch to wireguard lib fork
- Request VPN permission on first VPN start
- Bump versions

View File

@ -10,35 +10,35 @@ datastorePreferences = "1.0.0"
desugar_jdk_libs = "2.0.4"
espressoCore = "3.5.1"
firebase-crashlytics-gradle = "2.9.9"
google-services = "4.4.0"
google-services = "4.4.1"
hiltAndroid = "2.50"
hiltNavigationCompose = "1.1.0"
junit = "4.13.2"
kotlinx-serialization-json = "1.6.2"
lifecycle-runtime-compose = "2.7.0"
material3 = "1.2.0-rc01"
navigationCompose = "2.7.6"
material3 = "1.2.0"
navigationCompose = "2.7.7"
roomVersion = "2.6.1"
timber = "5.0.1"
tunnel = "1.0.20230706"
androidGradlePlugin = "8.3.0-rc01"
tunnel = "1.1.0"
androidGradlePlugin = "8.3.0-rc02"
kotlin = "1.9.22"
ksp = "1.9.22-1.0.16"
composeBom = "2024.01.00"
firebaseBom = "32.7.1"
compose = "1.6.0"
crashlytics = "18.6.1"
analytics = "21.5.0"
composeBom = "2024.02.00"
firebaseBom = "32.7.2"
compose = "1.6.1"
crashlytics = "18.6.2"
analytics = "21.5.1"
zxingAndroidEmbedded = "4.3.0"
zxingCore = "3.5.2"
zxingCore = "3.5.3"
[libraries]
# accompanist
accompanist-drawablepainter = { module = "com.google.accompanist:accompanist-drawablepainter", version.ref = "accompanist" }
accompanist-flowlayout = { module = "com.google.accompanist:accompanist-flowlayout", version.ref = "accompanist" }
accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanist" }
accompanist-systemuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanist" }
#room
androidx-biometric-ktx = { module = "androidx.biometric:biometric-ktx", version.ref = "biometricKtx" }
@ -82,7 +82,7 @@ lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-com
material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "compose" }
timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" }
tunnel = { module = "com.wireguard.android:tunnel", version.ref = "tunnel" }
tunnel = { module = "com.zaneschepke:wireguard-android", version.ref = "tunnel" }
#firebase
google-firebase-crashlytics-ktx = { module = "com.google.firebase:firebase-crashlytics-ktx", version.ref = "crashlytics" }
@ -93,9 +93,9 @@ google-services = { module = "com.google.gms:google-services", version.ref = "go
zxing-core = { module = "com.google.zxing:core", version.ref = "zxingCore" }
zxing-android-embedded = { module = "com.journeyapps:zxing-android-embedded", version.ref = "zxingAndroidEmbedded" }
[plugins]
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hiltAndroid" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
androidLibrary = { id = "com.android.library", version.ref = "androidGradlePlugin" }

View File

@ -2,5 +2,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
distributionSha256Sum=3e1af3ae886920c3ac87f7a91f816c0c7c436f276a6eefdb3da152100fef72ae
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -2,18 +2,42 @@ pluginManagement {
repositories {
google()
mavenCentral()
mavenLocal()
gradlePluginPortal()
}
}
val GITHUB_USER_VAR = "GH_USER"
val GITHUB_TOKEN_VAR = "GH_TOKEN"
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/zaneschepke/wireguard-android")
credentials {
username = getLocalProperty(GITHUB_USER_VAR) ?: System.getenv(GITHUB_USER_VAR)
password = getLocalProperty(GITHUB_TOKEN_VAR) ?: System.getenv(GITHUB_TOKEN_VAR)
}
}
google()
mavenCentral()
mavenLocal()
}
}
fun getLocalProperty(key: String, file: String = "local.properties"): String? {
val properties = java.util.Properties()
val localProperties = File(file)
if (localProperties.isFile) {
java.io.InputStreamReader(java.io.FileInputStream(localProperties), Charsets.UTF_8).use { reader ->
properties.load(reader)
}
} else return null
return properties.getProperty(key)
}
rootProject.name = "WG Tunnel"
include(":app")