Compare commits

..

11 Commits

Author SHA1 Message Date
dependabot[bot] bab469f776 build(deps): bump androidx.navigation:navigation-compose
Bumps androidx.navigation:navigation-compose from 2.8.3 to 2.8.4.

---
updated-dependencies:
- dependency-name: androidx.navigation:navigation-compose
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-14 13:39:31 +00:00
GitHub Actions c7a45845d6 Automated build update 2024-11-14 02:53:17 +00:00
Zane Schepke c2725bf066 chore: changelog to releases 2024-11-13 21:48:12 -05:00
Zane Schepke 2993f1db22 fix: delete tunnel bug
closes #435
2024-11-12 22:47:03 -05:00
Zane Schepke e9ae48f96c add: donation selection to support 2024-11-12 22:46:06 -05:00
GitHub Actions 5cc2ae0d01 Automated build update 2024-11-08 04:14:02 +00:00
Zane Schepke 7fdec509e9 Merge branch 'main' of https://github.com/zaneschepke/wgtunnel 2024-11-07 23:03:02 -05:00
Zane Schepke cab2945930 fix: auto tunnel bugs
Fixes auto tunnel bug that can happen on startup
Fixes auto tunnel bug that didn't allow manual toggle override of tunnel while auto tunnel is active
Add basic foreground persistent notifications (optionally turned off via android settings)
2024-11-07 23:03:00 -05:00
GitHub Actions 6a90cd02b9 Automated build update 2024-11-08 03:30:59 +00:00
Zane Schepke 7d810c7c3d fix: auto tunnel crash 2024-11-07 20:02:35 -05:00
GitHub Actions dad34b9e24 Automated build update 2024-11-04 03:36:36 +00:00
16 changed files with 240 additions and 137 deletions
+29
View File
@@ -94,6 +94,32 @@ jobs:
fileDir: ${{ env.KEY_STORE_LOCATION }}
encodedString: ${{ secrets.KEYSTORE }}
# update latest tag
- name: Set latest tag
uses: rickstaa/action-create-tag@v1
id: tag_creation
with:
tag: "latest" # or any tag name you wish to use
message: "Automated tag for HEAD commit"
force_push_tag: true
tag_exists_error: false
- name: Get latest release
id: latest_release
uses: kaliber5/action-get-release@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
latest: true
- name: Generate Changelog
id: changelog
uses: requarks/changelog-action@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
toTag: ${{ github.event_name == 'schedule' && 'nightly' || steps.latest_release.outputs.tag_name }}
fromTag: "latest"
writeToFile: false # we won't write to file, just output
# create keystore path for gradle to read
- name: Create keystore path env var
run: |
@@ -209,6 +235,9 @@ jobs:
SHA256 fingerprint:
```${{ steps.checksum.outputs.checksum }}```
### Changelog
${{ steps.changelog.outputs.changes }}
tag_name: ${{ env.TAG_NAME }}
name: ${{ env.TAG_NAME }}
draft: false
@@ -9,3 +9,11 @@ annotation class Kernel
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class Userspace
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class TunnelShell
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AppShell
@@ -25,9 +25,18 @@ import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
class TunnelModule {
@Provides
@Singleton
fun provideRootShell(@ApplicationContext context: Context): RootShell {
@TunnelShell
fun provideTunnelRootShell(@ApplicationContext context: Context): RootShell {
return RootShell(context)
}
@Provides
@Singleton
@AppShell
fun provideAppRootShell(@ApplicationContext context: Context): RootShell {
return RootShell(context)
}
@@ -40,14 +49,14 @@ class TunnelModule {
@Provides
@Singleton
@Userspace
fun provideUserspaceBackend(@ApplicationContext context: Context, rootShell: RootShell): Backend {
fun provideUserspaceBackend(@ApplicationContext context: Context, @TunnelShell rootShell: RootShell): Backend {
return GoBackend(context, RootTunnelActionHandler(rootShell))
}
@Provides
@Singleton
@Kernel
fun provideKernelBackend(@ApplicationContext context: Context, rootShell: RootShell): Backend {
fun provideKernelBackend(@ApplicationContext context: Context, @TunnelShell rootShell: RootShell): Backend {
return WgQuickBackend(context, rootShell, ToolsInstaller(context, rootShell), RootTunnelActionHandler(rootShell))
}
@@ -82,6 +91,6 @@ class TunnelModule {
@Singleton
@Provides
fun provideServiceManager(@ApplicationContext context: Context): ServiceManager {
return ServiceManager(context)
return ServiceManager.getInstance(context)
}
}
@@ -1,6 +1,5 @@
package com.zaneschepke.wireguardautotunnel.service.foreground
import android.content.Context
import android.content.Intent
import android.net.NetworkCapabilities
import android.os.IBinder
@@ -13,6 +12,7 @@ import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.module.AppShell
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
import com.zaneschepke.wireguardautotunnel.module.MainImmediateDispatcher
import com.zaneschepke.wireguardautotunnel.service.network.EthernetService
@@ -26,7 +26,6 @@ import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.extensions.cancelWithMessage
import com.zaneschepke.wireguardautotunnel.util.extensions.getCurrentWifiName
import com.zaneschepke.wireguardautotunnel.util.extensions.isDown
import com.zaneschepke.wireguardautotunnel.util.extensions.isReachable
import com.zaneschepke.wireguardautotunnel.util.extensions.onNotRunning
import dagger.hilt.android.AndroidEntryPoint
@@ -35,7 +34,6 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.update
@@ -51,6 +49,7 @@ class AutoTunnelService : LifecycleService() {
private val foregroundId = 122
@Inject
@AppShell
lateinit var rootShell: Provider<RootShell>
@Inject
@@ -144,7 +143,7 @@ class AutoTunnelService : LifecycleService() {
super.onDestroy()
}
private fun launchWatcherNotification(description: String = getString(R.string.watcher_notification_text_active)) {
private fun launchWatcherNotification(description: String = getString(R.string.monitoring_state_changes)) {
val notification =
notificationService.createNotification(
channelId = getString(R.string.watcher_channel_id),
@@ -162,7 +161,7 @@ class AutoTunnelService : LifecycleService() {
private fun initWakeLock() {
wakeLock =
(getSystemService(Context.POWER_SERVICE) as PowerManager).run {
(getSystemService(POWER_SERVICE) as PowerManager).run {
val tag = this.javaClass.name
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "$tag::lock").apply {
try {
@@ -210,28 +209,16 @@ class AutoTunnelService : LifecycleService() {
when (status) {
is NetworkStatus.Available -> {
Timber.i("Gained Mobile data connection")
autoTunnelStateFlow.update {
it.copy(
isMobileDataConnected = true,
)
}
emitMobileDataConnected(true)
}
is NetworkStatus.CapabilitiesChanged -> {
autoTunnelStateFlow.update {
it.copy(
isMobileDataConnected = true,
)
}
emitMobileDataConnected(true)
Timber.i("Mobile data capabilities changed")
}
is NetworkStatus.Unavailable -> {
autoTunnelStateFlow.update {
it.copy(
isMobileDataConnected = false,
)
}
emitMobileDataConnected(false)
Timber.i("Lost mobile data connection")
}
}
@@ -284,6 +271,7 @@ class AutoTunnelService : LifecycleService() {
old.map { it.isActive } != new.map { it.isActive }
},
) { settings, tunnels ->
Timber.d("Tunnels or settings changed!")
autoTunnelStateFlow.value.copy(
settings = settings,
tunnels = tunnels,
@@ -300,7 +288,7 @@ class AutoTunnelService : LifecycleService() {
Timber.i("Starting vpn state watcher")
withContext(ioDispatcher) {
tunnelService.get().vpnState.distinctUntilChanged { old, new ->
old.tunnelConfig == new.tunnelConfig && old.status == new.status
old.tunnelConfig?.id == new.tunnelConfig?.id
}.collect { state ->
autoTunnelStateFlow.update {
it.copy(vpnState = state)
@@ -368,7 +356,7 @@ class AutoTunnelService : LifecycleService() {
mobileDataJob = null
}
private fun updateEthernet(connected: Boolean) {
private fun emitEthernetConnected(connected: Boolean) {
autoTunnelStateFlow.update {
it.copy(
isEthernetConnected = connected,
@@ -376,7 +364,7 @@ class AutoTunnelService : LifecycleService() {
}
}
private fun updateWifi(connected: Boolean) {
private fun emitWifiConnected(connected: Boolean) {
autoTunnelStateFlow.update {
it.copy(
isWifiConnected = connected,
@@ -384,6 +372,22 @@ class AutoTunnelService : LifecycleService() {
}
}
private fun emitWifiSSID(ssid: String) {
autoTunnelStateFlow.update {
it.copy(
currentNetworkSSID = ssid,
)
}
}
private fun emitMobileDataConnected(connected: Boolean) {
autoTunnelStateFlow.update {
it.copy(
isMobileDataConnected = connected,
)
}
}
private suspend fun watchForEthernetConnectivityChanges() {
withContext(ioDispatcher) {
Timber.i("Starting ethernet data watcher")
@@ -391,16 +395,16 @@ class AutoTunnelService : LifecycleService() {
when (status) {
is NetworkStatus.Available -> {
Timber.i("Gained Ethernet connection")
updateEthernet(true)
emitEthernetConnected(true)
}
is NetworkStatus.CapabilitiesChanged -> {
Timber.i("Ethernet capabilities changed")
updateEthernet(true)
emitEthernetConnected(true)
}
is NetworkStatus.Unavailable -> {
updateEthernet(false)
emitEthernetConnected(false)
Timber.i("Lost Ethernet connection")
}
}
@@ -415,12 +419,12 @@ class AutoTunnelService : LifecycleService() {
when (status) {
is NetworkStatus.Available -> {
Timber.i("Gained Wi-Fi connection")
updateWifi(true)
emitWifiConnected(true)
}
is NetworkStatus.CapabilitiesChanged -> {
Timber.i("Wifi capabilities changed")
updateWifi(true)
emitWifiConnected(true)
val ssid = getWifiSSID(status.networkCapabilities)
ssid?.let { name ->
if (name.contains(Constants.UNREADABLE_SSID)) {
@@ -429,16 +433,12 @@ class AutoTunnelService : LifecycleService() {
Timber.i("Detected valid SSID")
}
appDataRepository.appState.setCurrentSsid(name)
autoTunnelStateFlow.update {
it.copy(
currentNetworkSSID = name,
)
}
emitWifiSSID(name)
} ?: Timber.w("Failed to read ssid")
}
is NetworkStatus.Unavailable -> {
updateWifi(false)
emitWifiConnected(false)
Timber.i("Lost Wi-Fi connection")
}
}
@@ -462,17 +462,17 @@ class AutoTunnelService : LifecycleService() {
private suspend fun handleNetworkEventChanges() {
withContext(ioDispatcher) {
Timber.i("Starting network event watcher")
autoTunnelStateFlow.collectLatest { watcherState ->
autoTunnelStateFlow.collect { watcherState ->
val autoTunnel = "Auto-tunnel watcher"
Timber.d("New watcher state!")
// delay for rapid network state changes and then collect latest
delay(Constants.WATCHER_COLLECTION_DELAY)
val activeTunnel = watcherState.vpnState.tunnelConfig
val defaultTunnel = appDataRepository.getPrimaryOrFirstTunnel()
val isTunnelDown = tunnelService.get().getState() == TunnelState.DOWN
when {
watcherState.isEthernetConditionMet() -> {
Timber.i("$autoTunnel - tunnel on on ethernet condition met")
if (watcherState.vpnState.isDown()) {
if (isTunnelDown) {
defaultTunnel?.let {
tunnelService.get().startTunnel(it)
}
@@ -484,7 +484,7 @@ class AutoTunnelService : LifecycleService() {
val mobileDataTunnel = getMobileDataTunnel()
val tunnel =
mobileDataTunnel ?: defaultTunnel
if (watcherState.vpnState.isDown() || activeTunnel?.isMobileDataTunnel == false) {
if (isTunnelDown || activeTunnel?.isMobileDataTunnel == false) {
tunnel?.let {
tunnelService.get().startTunnel(it)
}
@@ -493,7 +493,7 @@ class AutoTunnelService : LifecycleService() {
watcherState.isTunnelOffOnMobileDataConditionMet() -> {
Timber.i("$autoTunnel - tunnel off on mobile data met, turning vpn off")
if (!watcherState.vpnState.isDown()) {
if (!isTunnelDown) {
activeTunnel?.let {
tunnelService.get().stopTunnel(it)
}
@@ -503,20 +503,20 @@ class AutoTunnelService : LifecycleService() {
watcherState.isUntrustedWifiConditionMet() -> {
Timber.i("Untrusted wifi condition met")
if (activeTunnel == null || watcherState.isCurrentSSIDActiveTunnelNetwork() == false ||
watcherState.vpnState.isDown()
isTunnelDown
) {
Timber.i(
"$autoTunnel - tunnel on ssid not associated with current tunnel condition met",
)
watcherState.getTunnelWithMatchingTunnelNetwork()?.let {
Timber.i("Found tunnel associated with this SSID, bringing tunnel up: ${it.name}")
if (watcherState.vpnState.isDown() || activeTunnel?.id != it.id) {
if (isTunnelDown || activeTunnel?.id != it.id) {
tunnelService.get().startTunnel(it)
}
} ?: suspend {
Timber.i("No tunnel associated with this SSID, using defaults")
val default = appDataRepository.getPrimaryOrFirstTunnel()
if (default?.name != tunnelService.get().name || watcherState.vpnState.isDown()) {
if (default?.name != tunnelService.get().name || isTunnelDown) {
default?.let {
tunnelService.get().startTunnel(it)
}
@@ -529,22 +529,22 @@ class AutoTunnelService : LifecycleService() {
Timber.i(
"$autoTunnel - tunnel off on trusted wifi condition met, turning vpn off",
)
if (!watcherState.vpnState.isDown()) activeTunnel?.let { tunnelService.get().stopTunnel(it) }
if (!isTunnelDown) activeTunnel?.let { tunnelService.get().stopTunnel(it) }
}
watcherState.isTunnelOffOnWifiConditionMet() -> {
Timber.i(
"$autoTunnel - tunnel off on wifi condition met, turning vpn off",
)
if (!watcherState.vpnState.isDown()) activeTunnel?.let { tunnelService.get().stopTunnel(it) }
}
watcherState.isTunnelOffOnNoConnectivityMet() -> {
Timber.i(
"$autoTunnel - tunnel off on no connectivity met, turning vpn off",
)
if (!watcherState.vpnState.isDown()) activeTunnel?.let { tunnelService.get().stopTunnel(it) }
if (!isTunnelDown) activeTunnel?.let { tunnelService.get().stopTunnel(it) }
}
// TODO disable for this now
// watcherState.isTunnelOffOnNoConnectivityMet() -> {
// Timber.i(
// "$autoTunnel - tunnel off on no connectivity met, turning vpn off",
// )
// if (!isTunnelDown) activeTunnel?.let { tunnelService.get().stopTunnel(it) }
// }
else -> {
Timber.i("$autoTunnel - no condition met")
@@ -27,12 +27,14 @@ class ServiceManager
companion object : SingletonHolder<ServiceManager, Context>(::ServiceManager)
private fun <T : Service> startService(cls: Class<T>, background: Boolean) {
val intent = Intent(context, cls)
if (background) {
context.startForegroundService(intent)
} else {
context.startService(intent)
}
runCatching {
val intent = Intent(context, cls)
if (background) {
context.startForegroundService(intent)
} else {
context.startService(intent)
}
}.onFailure { Timber.e(it) }
}
suspend fun startAutoTunnel(background: Boolean) {
@@ -62,7 +62,7 @@ class TunnelBackgroundService : LifecycleService() {
return notificationService.createNotification(
getString(R.string.vpn_channel_id),
getString(R.string.vpn_channel_name),
getString(R.string.tunnel_start_text),
getString(R.string.tunnel_running),
description = "",
)
}
@@ -25,6 +25,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.amnezia.awg.backend.Tunnel
import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
import javax.inject.Provider
@@ -52,6 +53,8 @@ constructor(
private var statsJob: Job? = null
private val runningHandle = AtomicBoolean(false)
private suspend fun backend(): Any {
val settings = appDataRepository.settings.getSettings()
if (settings.isKernelEnabled) return kernelBackend.get()
@@ -91,7 +94,14 @@ constructor(
override suspend fun startTunnel(tunnelConfig: TunnelConfig, background: Boolean): Result<TunnelState> {
return withContext(ioDispatcher) {
onBeforeStart(tunnelConfig, background)
if (runningHandle.get() == true && tunnelConfig == vpnState.value.tunnelConfig) {
Timber.w("Tunnel already running")
return@withContext Result.success(vpnState.value.status)
}
runningHandle.set(true)
onBeforeStart(tunnelConfig)
val settings = appDataRepository.settings.getSettings()
if (background || settings.isKernelEnabled) startBackgroundService()
setState(tunnelConfig, TunnelState.UP).onSuccess {
emitTunnelState(it)
}.onFailure {
@@ -109,6 +119,9 @@ constructor(
}.onFailure {
Timber.e(it)
onStopFailed()
}.also {
stopBackgroundService()
runningHandle.set(false)
}
}
}
@@ -143,30 +156,39 @@ constructor(
}
cancelStatsJob()
resetBackendStatistics()
runningHandle.set(false)
}
private suspend fun onBeforeStart(tunnelConfig: TunnelConfig, background: Boolean) {
if (_vpnState.value.status == TunnelState.UP &&
tunnelConfig != _vpnState.value.tunnelConfig
) {
vpnState.value.tunnelConfig?.let { stopTunnel(it) }
private suspend fun shutDownActiveTunnel(config: TunnelConfig) {
with(_vpnState.value) {
if (status == TunnelState.UP && tunnelConfig != config) {
tunnelConfig?.let { stopTunnel(it) }
}
}
if (background) serviceManager.startBackgroundService()
resetBackendStatistics()
}
private suspend fun startBackgroundService() {
serviceManager.startBackgroundService()
serviceManager.requestTunnelTileUpdate()
}
private fun stopBackgroundService() {
serviceManager.stopBackgroundService()
serviceManager.requestTunnelTileUpdate()
}
private suspend fun onBeforeStart(tunnelConfig: TunnelConfig) {
shutDownActiveTunnel(tunnelConfig)
appDataRepository.tunnels.save(tunnelConfig.copy(isActive = true))
emitVpnStateConfig(tunnelConfig)
resetBackendStatistics()
startStatsJob()
Timber.d("Updating start")
serviceManager.requestTunnelTileUpdate()
}
private suspend fun onBeforeStop(tunnelConfig: TunnelConfig) {
appDataRepository.tunnels.save(tunnelConfig.copy(isActive = false))
cancelStatsJob()
resetBackendStatistics()
appDataRepository.tunnels.save(tunnelConfig.copy(isActive = false))
serviceManager.stopBackgroundService()
Timber.d("UPdating stop")
serviceManager.requestTunnelTileUpdate()
}
private fun emitTunnelState(state: TunnelState) {
@@ -39,6 +39,7 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
@@ -60,6 +61,7 @@ import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.extensions.isBatteryOptimizationsDisabled
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
@OptIn(ExperimentalFoundationApi::class)
@Composable
@@ -114,7 +116,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
InfoDialog(
onDismiss = { showDeleteTunnelAlertDialog = false },
onAttest = {
selectedTunnel?.let { viewModel::onDelete }
selectedTunnel?.let { viewModel.onDelete(it) }
showDeleteTunnelAlertDialog = false
selectedTunnel = null
},
@@ -213,7 +215,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
)
LazyColumn(
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.Top,
verticalArrangement = Arrangement.spacedBy(5.dp.scaledHeight(), Alignment.Top),
modifier =
Modifier
.fillMaxSize().padding(it)
@@ -9,6 +9,7 @@ import com.wireguard.android.util.RootShell
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.module.AppShell
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
import com.zaneschepke.wireguardautotunnel.util.FileUtils
@@ -29,7 +30,7 @@ class SettingsViewModel
@Inject
constructor(
private val appDataRepository: AppDataRepository,
private val rootShell: Provider<RootShell>,
@AppShell private val rootShell: Provider<RootShell>,
private val fileUtils: FileUtils,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
) : ViewModel() {
@@ -6,6 +6,7 @@ import com.wireguard.android.util.RootShell
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.module.AppShell
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
import com.zaneschepke.wireguardautotunnel.util.StringValue
@@ -23,7 +24,7 @@ class AutoTunnelViewModel
@Inject
constructor(
private val appDataRepository: AppDataRepository,
private val rootShell: Provider<RootShell>,
@AppShell private val rootShell: Provider<RootShell>,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
) : ViewModel() {
@@ -1,10 +1,12 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.support
import android.os.Build
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AttachMoney
import androidx.compose.material.icons.filled.Book
import androidx.compose.material.icons.filled.LineStyle
import androidx.compose.material.icons.filled.Mail
@@ -19,6 +21,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.unit.dp
import com.zaneschepke.wireguardautotunnel.BuildConfig
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.Route
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
@@ -101,54 +104,83 @@ fun SupportScreen() {
),
)
SurfaceSelectionGroupButton(
listOf(
SelectionItem(
ImageVector.vectorResource(R.drawable.telegram),
title = {
Text(
stringResource(R.string.chat_description),
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
)
},
trailing = {
ForwardButton {
context.openWebUrl(context.getString(R.string.telegram_url))
}
},
onClick = {
context.openWebUrl(context.getString(R.string.telegram_url))
},
),
SelectionItem(
ImageVector.vectorResource(R.drawable.github),
title = { Text(stringResource(R.string.open_issue), style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface)) },
trailing = {
ForwardButton {
context.openWebUrl(context.getString(R.string.github_url))
}
},
onClick = {
context.openWebUrl(context.getString(R.string.github_url))
},
),
SelectionItem(
Icons.Filled.Mail,
title = {
Text(
stringResource(R.string.email_description),
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
)
},
trailing = {
ForwardButton {
context.launchSupportEmail()
}
},
onClick = {
context.launchSupportEmail()
},
),
),
buildList {
addAll(
listOf(
SelectionItem(
ImageVector.vectorResource(R.drawable.telegram),
title = {
Text(
stringResource(R.string.chat_description),
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
)
},
trailing = {
ForwardButton {
context.openWebUrl(context.getString(R.string.telegram_url))
}
},
onClick = {
context.openWebUrl(context.getString(R.string.telegram_url))
},
),
SelectionItem(
ImageVector.vectorResource(R.drawable.github),
title = {
Text(
stringResource(R.string.open_issue),
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface))
},
trailing = {
ForwardButton {
context.openWebUrl(context.getString(R.string.github_url))
}
},
onClick = {
context.openWebUrl(context.getString(R.string.github_url))
},
),
SelectionItem(
Icons.Filled.Mail,
title = {
Text(
stringResource(R.string.email_description),
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
)
},
trailing = {
ForwardButton {
context.launchSupportEmail()
}
},
onClick = {
context.launchSupportEmail()
},
),
)
)
if (BuildConfig.FLAVOR == "fdroid") {
add(
SelectionItem(
Icons.Filled.AttachMoney,
title = {
Text(
stringResource(R.string.donate),
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
)
},
trailing = {
ForwardButton {
context.openWebUrl(context.getString(R.string.donate_url))
}
},
onClick = {
context.openWebUrl(context.getString(R.string.donate_url))
},
),
)
}
}
)
VersionLabel()
}
@@ -21,7 +21,6 @@ object Constants {
const val SYSTEM_EXEMPT_SERVICE_TYPE_ID = 1024
const val SUBSCRIPTION_TIMEOUT = 5_000L
const val FOCUS_REQUEST_DELAY = 500L
const val TRANSITION_ANIMATION_TIME = 200
@@ -4,8 +4,6 @@ import androidx.compose.ui.graphics.Color
import com.wireguard.android.util.RootShell
import com.wireguard.config.Peer
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnState
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
import com.zaneschepke.wireguardautotunnel.ui.theme.SilverTree
import com.zaneschepke.wireguardautotunnel.ui.theme.Straw
@@ -23,10 +21,6 @@ fun TunnelStatistics.PeerStats.latestHandshakeSeconds(): Long? {
return NumberUtils.getSecondsBetweenTimestampAndNow(this.latestHandshakeEpochMillis)
}
fun VpnState.isDown(): Boolean {
return this.status == TunnelState.DOWN
}
fun TunnelStatistics.PeerStats.handshakeStatus(): HandshakeStatus {
// TODO add never connected status after duration
return this.latestHandshakeSeconds().let {
+4
View File
@@ -9,6 +9,7 @@
<string name="docs_features" translatable="false">https://zaneschepke.com/wgtunnel-docs/features.html</string>
<string name="privacy_policy_url" translatable="false">https://zaneschepke.com/wgtunnel-docs/privacypolicy.html</string>
<string name="docs_wildcards" translatable="false" >https://zaneschepke.com/wgtunnel-docs/features.html#wildcard-wi-fi-name-support</string>
<string name="donate_url" translatable="false">https://zaneschepke.com/donate/</string>
<string name="error_file_extension">File is not a .conf or .zip</string>
<string name="turn_off_tunnel">Action requires tunnel off</string>
<string name="no_tunnels">No tunnels added yet!</string>
@@ -225,4 +226,7 @@
<string name="kernel_not_supported">Kernel not supported</string>
<string name="start_auto">Start auto-tunnel</string>
<string name="stop_auto">Stop auto-tunnel</string>
<string name="tunnel_running">Tunnel running</string>
<string name="monitoring_state_changes">Monitoring state changes</string>
<string name="donate">Donate to project</string>
</resources>
+1 -1
View File
@@ -16,7 +16,7 @@ junit = "4.13.2"
kotlinx-serialization-json = "1.7.3"
lifecycle-runtime-compose = "2.8.7"
material3 = "1.3.1"
navigationCompose = "2.8.3"
navigationCompose = "2.8.4"
pinLockCompose = "1.0.4"
roomVersion = "2.6.1"
timber = "5.0.1"
+1 -1
View File
@@ -1 +1 @@
1
5