diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b47383b2..6ce5264a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -17,7 +17,7 @@ android { val versionMajor = 2 val versionMinor = 3 - val versionPatch = 5 + val versionPatch = 6 val versionBuild = 0 defaultConfig { @@ -89,7 +89,7 @@ dependencies { implementation("com.jakewharton.timber:timber:5.0.1") // compose navigation - implementation("androidx.navigation:navigation-compose:2.7.0") + implementation("androidx.navigation:navigation-compose:2.7.1") implementation("androidx.hilt:hilt-navigation-compose:1.0.0") // hilt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 951bb88f..716f9878 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -64,6 +64,20 @@ android:foregroundServiceType="remoteMessaging" android:exported="false"> + + + + + + + + + @Inject + lateinit var configRepo : Repository + + @Inject + lateinit var vpnService : VpnService + + private val scope = CoroutineScope(Dispatchers.Main); + + private lateinit var job : Job + + override fun onStartListening() { + if (!this::job.isInitialized) { + job = scope.launch { + updateTileState() + } + } + Timber.d("On start listening") + super.onStartListening() + } + + override fun onTileAdded() { + super.onTileAdded() + qsTile.contentDescription = "Toggle VPN" + scope.launch { + updateTileState(); + } + } + + override fun onTileRemoved() { + super.onTileRemoved() + cancelJob() + } + + override fun onClick() { + unlockAndRun { + scope.launch { + try { + if(vpnService.getState() == Tunnel.State.UP) { + stopTunnel(); + return@launch + } + val settings = settingsRepo.getAll() + if (!settings.isNullOrEmpty()) { + val setting = settings.first() + if (setting.defaultTunnel != null) { + startTunnel(setting.defaultTunnel!!) + } else { + val config = configRepo.getAll()?.first(); + if(config != null) { + startTunnel(config.toString()); + } + } + } + } finally { + cancel() + } + } + super.onClick() + } + } + + private fun stopTunnel() { + ServiceTracker.actionOnService( + Action.STOP, this@TunnelControlTile, + WireGuardConnectivityWatcherService::class.java) + ServiceTracker.actionOnService( + Action.STOP, this@TunnelControlTile, + WireGuardTunnelService::class.java) + } + + private fun startTunnel(tunnelConfig : String) { + ServiceTracker.actionOnService( + Action.START, this.applicationContext, + WireGuardTunnelService::class.java, + mapOf(this.applicationContext.resources. + getString(R.string.tunnel_extras_key) to + tunnelConfig)) + } + + private suspend fun updateTileState() { + vpnService.state.collect { + when(it) { + Tunnel.State.UP -> { + setTileOn() + } + Tunnel.State.DOWN -> { + setTileOff() + } + else -> { + qsTile.state = Tile.STATE_UNAVAILABLE + } + } + qsTile.updateTile() + } + } + + private fun setTileOff() { + qsTile.state = Tile.STATE_INACTIVE; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + qsTile.subtitle = "Off" + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + qsTile.stateDescription = "VPN Off"; + } + } + + private fun setTileOn() { + qsTile.state = Tile.STATE_ACTIVE; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + qsTile.subtitle = "On" + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + qsTile.stateDescription = "VPN On"; + } + } + private fun cancelJob() { + if(this::job.isInitialized) { + job.cancel(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt index c25dc78f..4e5758af 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt @@ -152,7 +152,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 = { when (initialState.destination.route) { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt index 2cb3e0ad..162c4975 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt @@ -18,6 +18,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.FileOpen @@ -85,7 +86,6 @@ import kotlinx.coroutines.launch @Composable fun MainScreen( viewModel: MainViewModel = hiltViewModel(), padding: PaddingValues, - focusRequester: FocusRequester, snackbarHostState: SnackbarHostState, navController: NavController ) { @@ -149,7 +149,7 @@ fun MainScreen( }) }, floatingActionButtonPosition = FabPosition.End, - floatingActionButton = { + floatingActionButton = { AnimatedVisibility( visible = isVisible.value, enter = slideInVertically(initialOffsetY = { it * 2 }), @@ -241,9 +241,12 @@ fun MainScreen( .padding(padding) ) { - LazyColumn(modifier = Modifier.fillMaxSize() - .nestedScroll(nestedScrollConnection),) { - items(tunnels.toList()) { tunnel -> + LazyColumn( + modifier = Modifier.fillMaxSize() + .nestedScroll(nestedScrollConnection), + ) { + itemsIndexed(tunnels.toList()) { index, tunnel -> + val focusRequester = FocusRequester(); RowListItem(leadingIcon = Icons.Rounded.Circle, leadingIconColor = if (tunnelName == tunnel.name) when (handshakeStatus) { HandshakeStatus.HEALTHY -> mint @@ -263,15 +266,15 @@ fun MainScreen( selectedTunnel = tunnel; }, onClick = { - if(!WireGuardAutoTunnel.isRunningOnAndroidTv(context)){ + if (!WireGuardAutoTunnel.isRunningOnAndroidTv(context)) { navController.navigate("${Routes.Detail.name}/${tunnel.id}") } else { focusRequester.requestFocus() } - }, + }, rowButton = { if (tunnel.id == selectedTunnel?.id) { - Row() { + Row { IconButton(onClick = { navController.navigate("${Routes.Config.name}/${selectedTunnel?.id}") }) { @@ -287,30 +290,43 @@ fun MainScreen( } } } else { - if(WireGuardAutoTunnel.isRunningOnAndroidTv(context)){ - Row() { - IconButton(modifier = Modifier.focusRequester(focusRequester),onClick = { - navController.navigate("${Routes.Detail.name}/${tunnel.id}") - }) { + if (WireGuardAutoTunnel.isRunningOnAndroidTv(context)) { + Row { + IconButton( + modifier = Modifier.focusRequester(focusRequester), + onClick = { + navController.navigate("${Routes.Detail.name}/${tunnel.id}") + }) { Icon(Icons.Rounded.Info, "Info") } IconButton(onClick = { if (state == Tunnel.State.UP && tunnel.name == tunnelName) scope.launch { - viewModel.showSnackBarMessage(context.resources.getString(R.string.turn_off_tunnel)) + viewModel.showSnackBarMessage( + context.resources.getString( + R.string.turn_off_tunnel + ) + ) } else { - navController.navigate("${Routes.Config.name}/${tunnel.id}") - } + navController.navigate("${Routes.Config.name}/${tunnel.id}") + } }) { - Icon(Icons.Rounded.Edit, stringResource(id = R.string.edit)) + 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)) + viewModel.showSnackBarMessage( + context.resources.getString( + R.string.turn_off_tunnel + ) + ) } else { - viewModel.onDelete(tunnel) - } + viewModel.onDelete(tunnel) + } }) { Icon( Icons.Rounded.Delete, diff --git a/app/src/main/res/drawable/shield.xml b/app/src/main/res/drawable/shield.xml new file mode 100644 index 00000000..e49a1f26 --- /dev/null +++ b/app/src/main/res/drawable/shield.xml @@ -0,0 +1,5 @@ + + + diff --git a/build.gradle.kts b/build.gradle.kts index 1acbd182..f9d7e05a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,7 +13,7 @@ buildscript { } plugins { - id("com.android.application") version "8.2.0-alpha15" apply false + id("com.android.application") version "8.2.0-beta01" apply false id("org.jetbrains.kotlin.android") version "1.8.22" apply false id("com.google.dagger.hilt.android") version "2.44" apply false kotlin("plugin.serialization") version "1.8.22" apply false