fix: navigation sync bug

This commit is contained in:
Zane Schepke
2025-09-03 18:39:28 -04:00
parent 7a0af2462b
commit 0ea84b3e31
72 changed files with 556 additions and 553 deletions
-3
View File
@@ -1,3 +0,0 @@
-keep class com.zaneschepke.wireguardautotunnel.ui.navigation.Route { *; }
-keep class com.zaneschepke.wireguardautotunnel.ui.navigation.Route$** { *; }
-keepclassmembers class com.zaneschepke.wireguardautotunnel.ui.navigation.Route$** { *; }
@@ -4,10 +4,10 @@ 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 java.io.IOException
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import java.io.IOException
@RunWith(AndroidJUnit4::class)
class MigrationTest {
@@ -50,6 +50,7 @@ import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.CustomSnackBar
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
import com.zaneschepke.wireguardautotunnel.ui.navigation.components.BottomNavbar
import com.zaneschepke.wireguardautotunnel.ui.navigation.components.DynamicTopAppBar
import com.zaneschepke.wireguardautotunnel.ui.navigation.components.currentBackStackEntryAsNavbarState
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.AutoTunnelScreen
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.advanced.AutoTunnelAdvancedScreen
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.detection.WifiDetectionMethodScreen
@@ -79,10 +80,11 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.*
import com.zaneschepke.wireguardautotunnel.viewmodel.*
import dagger.hilt.android.AndroidEntryPoint
import de.raphaelebner.roomdatabasebackup.core.RoomBackup
import kotlinx.coroutines.launch
import timber.log.Timber
import xyz.teamgravity.pin_lock_compose.PinManager
import java.util.*
import javax.inject.Inject
import kotlinx.coroutines.launch
import xyz.teamgravity.pin_lock_compose.PinManager
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@@ -132,6 +134,7 @@ class MainActivity : AppCompatActivity() {
}
}
val navState by navController.currentBackStackEntryAsNavbarState(viewModel)
val snackbar = remember { SnackbarHostState() }
var showVpnPermissionDialog by remember { mutableStateOf(false) }
var vpnPermissionDenied by remember { mutableStateOf(false) }
@@ -139,6 +142,8 @@ class MainActivity : AppCompatActivity() {
mutableStateOf<Pair<AppMode?, TunnelConf?>>(Pair(null, null))
}
LaunchedEffect(navState) { Timber.d("New navbar state $navState") }
val vpnActivity =
rememberLauncherForActivityResult(
ActivityResultContracts.StartActivityForResult(),
@@ -237,13 +242,9 @@ class MainActivity : AppCompatActivity() {
)
}
},
topBar = { DynamicTopAppBar(appState.navBarState) },
topBar = { DynamicTopAppBar(navState) },
bottomBar = {
BottomNavbar(
appState.isAutoTunnelActive,
appState.navBarState,
navController,
)
BottomNavbar(appState.isAutoTunnelActive, navState, navController)
},
) { padding ->
Box(
@@ -17,7 +17,6 @@ import com.zaneschepke.wireguardautotunnel.domain.enums.BackendMode
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.util.ReleaseTree
import dagger.hilt.android.HiltAndroidApp
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
@@ -26,6 +25,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@HiltAndroidApp
class WireGuardAutoTunnel : Application(), Configuration.Provider {
@@ -8,9 +8,9 @@ import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import javax.inject.Inject
@AndroidEntryPoint
class KernelReceiver : BroadcastReceiver() {
@@ -10,9 +10,9 @@ import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
import com.zaneschepke.wireguardautotunnel.domain.enums.NotificationAction
import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import javax.inject.Inject
@AndroidEntryPoint
class NotificationActionReceiver : BroadcastReceiver() {
@@ -9,10 +9,10 @@ import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.util.Constants
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@AndroidEntryPoint
class RemoteControlReceiver : BroadcastReceiver() {
@@ -10,11 +10,11 @@ import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@AndroidEntryPoint
class RestartReceiver : BroadcastReceiver() {
@@ -21,11 +21,11 @@ import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.extensions.distinctByKeys
import dagger.hilt.android.AndroidEntryPoint
import io.ktor.util.collections.*
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@AndroidEntryPoint
class TunnelForegroundService : LifecycleService() {
@@ -29,14 +29,14 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.Tunnels
import com.zaneschepke.wireguardautotunnel.util.extensions.to
import com.zaneschepke.wireguardautotunnel.util.extensions.toMillis
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import javax.inject.Provider
import kotlin.math.pow
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Provider
import kotlin.math.pow
@AndroidEntryPoint
class AutoTunnelService : LifecycleService() {
@@ -11,9 +11,9 @@ import androidx.lifecycle.lifecycleScope
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@AndroidEntryPoint
class AutoTunnelControlTile : TileService(), LifecycleOwner {
@@ -17,9 +17,9 @@ import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@AndroidEntryPoint
class TunnelControlTile : TileService(), LifecycleOwner {
@@ -9,10 +9,10 @@ import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelProvider
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@AndroidEntryPoint
class ShortcutsActivity : ComponentActivity() {
@@ -12,8 +12,6 @@ import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
import com.zaneschepke.wireguardautotunnel.ui.state.ConfigProxy
import com.zaneschepke.wireguardautotunnel.util.extensions.asTunnelState
import java.util.concurrent.ConcurrentHashMap
import kotlin.coroutines.cancellation.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
@@ -23,6 +21,8 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.amnezia.awg.crypto.Key
import timber.log.Timber
import java.util.concurrent.ConcurrentHashMap
import kotlin.coroutines.cancellation.CancellationException
abstract class BaseTunnel(
private val applicationScope: CoroutineScope,
@@ -14,9 +14,9 @@ import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
import com.zaneschepke.wireguardautotunnel.domain.state.WireGuardStatistics
import com.zaneschepke.wireguardautotunnel.util.extensions.toBackendCoreException
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import timber.log.Timber
import javax.inject.Inject
class KernelTunnel
@Inject
@@ -13,11 +13,6 @@ import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.domain.state.PingState
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
import kotlin.concurrent.atomics.AtomicBoolean
import kotlin.concurrent.atomics.AtomicReference
import kotlin.concurrent.atomics.ExperimentalAtomicApi
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -25,6 +20,11 @@ import kotlinx.coroutines.flow.*
import kotlinx.coroutines.plus
import org.amnezia.awg.crypto.Key
import timber.log.Timber
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
import kotlin.concurrent.atomics.AtomicBoolean
import kotlin.concurrent.atomics.AtomicReference
import kotlin.concurrent.atomics.ExperimentalAtomicApi
@OptIn(ExperimentalCoroutinesApi::class)
class TunnelManager
@@ -11,11 +11,11 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.toMillis
import com.zaneschepke.wireguardautotunnel.util.network.NetworkUtils
import dagger.hilt.android.scopes.ServiceScoped
import io.ktor.util.collections.*
import javax.inject.Inject
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import org.amnezia.awg.crypto.Key
import timber.log.Timber
import javax.inject.Inject
@ServiceScoped
class TunnelMonitor
@@ -8,10 +8,10 @@ import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.state.PingState
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
import java.util.concurrent.ConcurrentHashMap
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import org.amnezia.awg.crypto.Key
import java.util.concurrent.ConcurrentHashMap
interface TunnelProvider {
/** Starts the specified tunnel configuration. */
@@ -13,9 +13,6 @@ import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
import com.zaneschepke.wireguardautotunnel.util.extensions.asAmBackendMode
import com.zaneschepke.wireguardautotunnel.util.extensions.asBackendMode
import com.zaneschepke.wireguardautotunnel.util.extensions.toBackendCoreException
import java.io.IOException
import java.util.*
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import org.amnezia.awg.backend.Backend
import org.amnezia.awg.backend.BackendException
@@ -27,6 +24,9 @@ import org.amnezia.awg.config.proxy.HttpProxy
import org.amnezia.awg.config.proxy.Proxy
import org.amnezia.awg.config.proxy.Socks5Proxy
import timber.log.Timber
import java.io.IOException
import java.util.*
import javax.inject.Inject
class UserspaceTunnel
@Inject
@@ -8,10 +8,10 @@ import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.util.concurrent.TimeUnit
@HiltWorker
class ServiceWorker
@@ -7,7 +7,6 @@ import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
import java.io.IOException
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
@@ -15,6 +14,7 @@ import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.io.IOException
class DataStoreManager(
private val context: Context,
@@ -14,10 +14,10 @@ import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.utils.io.*
import java.io.File
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.io.File
class GitHubUpdateRepository(
private val gitHubApi: GitHubApi,
@@ -11,12 +11,12 @@ import com.zaneschepke.wireguardautotunnel.domain.model.InstalledPackage
import com.zaneschepke.wireguardautotunnel.domain.repository.InstalledPackageRepository
import com.zaneschepke.wireguardautotunnel.util.extensions.getAllInternetCapablePackages
import com.zaneschepke.wireguardautotunnel.util.extensions.getFriendlyAppName
import javax.inject.Singleton
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
import javax.inject.Singleton
@Singleton
class InstalledAndroidPackageRepository(
@@ -15,10 +15,10 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
@@ -20,9 +20,9 @@ import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import io.ktor.client.*
import javax.inject.Singleton
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
@@ -18,7 +18,6 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@@ -28,6 +27,7 @@ import org.amnezia.awg.backend.Backend
import org.amnezia.awg.backend.GoBackend
import org.amnezia.awg.backend.ProxyGoBackend
import org.amnezia.awg.backend.RootTunnelActionHandler
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
@@ -4,9 +4,9 @@ import android.os.Parcelable
import com.wireguard.android.backend.Tunnel
import com.wireguard.config.Config
import com.zaneschepke.wireguardautotunnel.util.extensions.*
import kotlinx.parcelize.Parcelize
import java.io.InputStream
import java.nio.charset.StandardCharsets
import kotlinx.parcelize.Parcelize
@Parcelize
data class TunnelConf(
@@ -1,9 +1,9 @@
package com.zaneschepke.wireguardautotunnel.domain.repository
import com.zaneschepke.wireguardautotunnel.domain.sideeffect.GlobalSideEffect
import javax.inject.Singleton
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import javax.inject.Singleton
@Singleton
class GlobalEffectRepository {
@@ -1,58 +1,61 @@
package com.zaneschepke.wireguardautotunnel.ui.navigation
import androidx.annotation.Keep
import kotlinx.serialization.Serializable
@Keep
@Serializable
sealed class Route {
@Serializable data object TunnelsGraph : Route()
@Serializable data object AutoTunnelGraph : Route()
@Keep @Serializable data object TunnelsGraph : Route()
@Serializable data object SettingsGraph : Route()
@Keep @Serializable data object AutoTunnelGraph : Route()
@Serializable data object SupportGraph : Route()
@Keep @Serializable data object SettingsGraph : Route()
@Serializable data object Support : Route()
@Keep @Serializable data object SupportGraph : Route()
@Serializable data object Lock : Route()
@Keep @Serializable data object Support : Route()
@Serializable data object License : Route()
@Keep @Serializable data object Lock : Route()
@Serializable data object Logs : Route()
@Keep @Serializable data object License : Route()
@Serializable data object Appearance : Route()
@Keep @Serializable data object Logs : Route()
@Serializable data object Language : Route()
@Keep @Serializable data object Appearance : Route()
@Serializable data object Display : Route()
@Keep @Serializable data object Language : Route()
@Serializable data object Tunnels : Route()
@Keep @Serializable data object Display : Route()
@Serializable data class TunnelOptions(val id: Int) : Route()
@Keep @Serializable data object Tunnels : Route()
@Serializable data class Config(val id: Int?) : Route()
@Keep @Serializable data class TunnelOptions(val id: Int) : Route()
@Serializable data class SplitTunnel(val id: Int) : Route()
@Keep @Serializable data class Config(val id: Int?) : Route()
@Serializable data class TunnelAutoTunnel(val id: Int) : Route()
@Keep @Serializable data class SplitTunnel(val id: Int) : Route()
@Serializable data object Sort : Route()
@Keep @Serializable data class TunnelAutoTunnel(val id: Int) : Route()
@Serializable data object Settings : Route()
@Keep @Serializable data object Sort : Route()
@Serializable data object TunnelMonitoring : Route()
@Keep @Serializable data object Settings : Route()
@Serializable data object SystemFeatures : Route()
@Keep @Serializable data object TunnelMonitoring : Route()
@Serializable data object Dns : Route()
@Keep @Serializable data object SystemFeatures : Route()
@Serializable data object ProxySettings : Route()
@Keep @Serializable data object Dns : Route()
@Serializable data object AutoTunnel : Route()
@Keep @Serializable data object ProxySettings : Route()
@Serializable data object AdvancedAutoTunnel : Route()
@Keep @Serializable data object AutoTunnel : Route()
@Serializable data object WifiDetectionMethod : Route()
@Keep @Serializable data object AdvancedAutoTunnel : Route()
@Serializable data object LocationDisclosure : Route()
@Keep @Serializable data object WifiDetectionMethod : Route()
@Keep @Serializable data object LocationDisclosure : Route()
}
@@ -17,7 +17,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController
import androidx.navigation.compose.currentBackStackEntryAsState
import com.zaneschepke.wireguardautotunnel.R
@@ -25,7 +24,6 @@ import com.zaneschepke.wireguardautotunnel.ui.navigation.BottomNavItem
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
import com.zaneschepke.wireguardautotunnel.ui.state.NavbarState
import com.zaneschepke.wireguardautotunnel.ui.theme.SilverTree
import com.zaneschepke.wireguardautotunnel.util.extensions.debounce
@Composable
fun NavHostController.getCurrentGraph(): State<Route?> {
@@ -54,44 +52,32 @@ fun BottomNavbar(
) {
val currentGraph by navController.getCurrentGraph()
val coroutineScope = rememberCoroutineScope()
val navigateToDebounced =
remember<(Route) -> Unit> {
debounce(scope = coroutineScope, 150L) { route ->
navController.navigate(route) {
popUpTo(navController.graph.findStartDestination().id) { saveState = true }
launchSingleTop = true
restoreState = true
}
}
}
val items =
listOf(
BottomNavItem(
name = stringResource(R.string.tunnels),
icon = Icons.Rounded.Home,
onClick = { navigateToDebounced(Route.TunnelsGraph) },
onClick = { navController.navigate(Route.TunnelsGraph) },
route = Route.TunnelsGraph,
),
BottomNavItem(
name = stringResource(R.string.auto_tunnel),
icon = Icons.Rounded.Bolt,
onClick = { navigateToDebounced(Route.AutoTunnelGraph) },
onClick = { navController.navigate(Route.AutoTunnelGraph) },
route = Route.AutoTunnelGraph,
active = isAutoTunnelActive,
),
BottomNavItem(
name = stringResource(R.string.settings),
icon = Icons.Rounded.Settings,
onClick = { navigateToDebounced(Route.SettingsGraph) },
onClick = { navController.navigate(Route.SettingsGraph) },
route = Route.SettingsGraph,
),
BottomNavItem(
name = stringResource(R.string.support),
icon = Icons.Rounded.QuestionMark,
onClick = { navigateToDebounced(Route.SupportGraph) },
onClick = { navController.navigate(Route.SupportGraph) },
route = Route.SupportGraph,
),
)
@@ -0,0 +1,259 @@
package com.zaneschepke.wireguardautotunnel.ui.navigation.components
import androidx.compose.foundation.layout.Row
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.Sort
import androidx.compose.material.icons.rounded.*
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavHostController
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.toRoute
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.common.button.ActionIconButton
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
import com.zaneschepke.wireguardautotunnel.ui.sideeffect.LocalSideEffect
import com.zaneschepke.wireguardautotunnel.ui.state.NavbarState
import com.zaneschepke.wireguardautotunnel.viewmodel.SharedAppViewModel
@Composable
fun NavHostController.currentBackStackEntryAsNavbarState(
sharedViewModel: SharedAppViewModel
): State<NavbarState> {
val sharedState by sharedViewModel.container.stateFlow.collectAsStateWithLifecycle()
val backStackEntry by currentBackStackEntryAsState()
val keyboardController = LocalSoftwareKeyboardController.current
val route =
remember(backStackEntry) {
backStackEntry?.destination?.route?.let {
when (it.substringBefore("?").substringBefore("/").substringAfterLast(".")) {
Route.Support::class.simpleName -> backStackEntry?.toRoute<Route.Support>()
Route.Lock::class.simpleName -> backStackEntry?.toRoute<Route.Lock>()
Route.License::class.simpleName -> backStackEntry?.toRoute<Route.License>()
Route.Logs::class.simpleName -> backStackEntry?.toRoute<Route.Logs>()
Route.Appearance::class.simpleName ->
backStackEntry?.toRoute<Route.Appearance>()
Route.Language::class.simpleName -> backStackEntry?.toRoute<Route.Language>()
Route.Display::class.simpleName -> backStackEntry?.toRoute<Route.Display>()
Route.Tunnels::class.simpleName -> backStackEntry?.toRoute<Route.Tunnels>()
Route.TunnelOptions::class.simpleName ->
backStackEntry?.toRoute<Route.TunnelOptions>()
Route.Config::class.simpleName -> backStackEntry?.toRoute<Route.Config>()
Route.SplitTunnel::class.simpleName ->
backStackEntry?.toRoute<Route.SplitTunnel>()
Route.TunnelAutoTunnel::class.simpleName ->
backStackEntry?.toRoute<Route.TunnelAutoTunnel>()
Route.Sort::class.simpleName -> backStackEntry?.toRoute<Route.Sort>()
Route.Settings::class.simpleName -> backStackEntry?.toRoute<Route.Settings>()
Route.TunnelMonitoring::class.simpleName ->
backStackEntry?.toRoute<Route.TunnelMonitoring>()
Route.SystemFeatures::class.simpleName ->
backStackEntry?.toRoute<Route.SystemFeatures>()
Route.Dns::class.simpleName -> backStackEntry?.toRoute<Route.Dns>()
Route.ProxySettings::class.simpleName ->
backStackEntry?.toRoute<Route.ProxySettings>()
Route.AutoTunnel::class.simpleName ->
backStackEntry?.toRoute<Route.AutoTunnel>()
Route.AdvancedAutoTunnel::class.simpleName ->
backStackEntry?.toRoute<Route.AdvancedAutoTunnel>()
Route.WifiDetectionMethod::class.simpleName ->
backStackEntry?.toRoute<Route.WifiDetectionMethod>()
Route.LocationDisclosure::class.simpleName ->
backStackEntry?.toRoute<Route.LocationDisclosure>()
else -> null
}
}
}
return produceState(initialValue = NavbarState(), route, sharedState.topNavActions) {
value =
when (route) {
Route.AdvancedAutoTunnel ->
NavbarState(
showBottomItems = true,
topTitle = { Text(stringResource(R.string.advanced_settings)) },
)
Route.Appearance ->
NavbarState(
showBottomItems = true,
topTitle = { Text(stringResource(R.string.appearance)) },
)
Route.AutoTunnel ->
NavbarState(
showBottomItems = true,
topTitle = { Text(stringResource(R.string.auto_tunnel)) },
)
is Route.Config -> {
val tunnel = sharedState.tunnels.find { it.id == route.id }
NavbarState(
showBottomItems = true,
topTitle = {
val title = tunnel?.name ?: stringResource(R.string.new_tunnel)
Text(title)
},
topTrailing = {
ActionIconButton(Icons.Rounded.Save, R.string.save) {
keyboardController?.hide()
sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges)
}
},
)
}
Route.Display ->
NavbarState(
showBottomItems = true,
topTitle = { Text(stringResource(R.string.display_theme)) },
)
Route.Dns ->
NavbarState(
showBottomItems = true,
topTitle = { Text(stringResource(R.string.dns_settings)) },
)
Route.Language ->
NavbarState(
showBottomItems = true,
topTitle = { Text(stringResource(R.string.language)) },
)
Route.License ->
NavbarState(
showBottomItems = true,
topTitle = { Text(stringResource(R.string.licenses)) },
)
Route.LocationDisclosure -> NavbarState(showBottomItems = true)
Route.Lock -> NavbarState(showBottomItems = false)
Route.Logs ->
NavbarState(
showBottomItems = false,
removeBottom = true,
topTitle = { Text(stringResource(R.string.logs)) },
topTrailing = {
ActionIconButton(Icons.Rounded.Menu, R.string.quick_actions) {
sharedViewModel.postSideEffect(LocalSideEffect.Sheet.LoggerActions)
}
},
)
Route.ProxySettings ->
NavbarState(
showBottomItems = true,
topTitle = { Text(stringResource(R.string.proxy_settings)) },
topTrailing = {
ActionIconButton(Icons.Rounded.Save, R.string.save) {
keyboardController?.hide()
sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges)
}
},
)
Route.Settings ->
NavbarState(
showBottomItems = true,
topTitle = { Text(stringResource(R.string.settings)) },
topTrailing = {
ActionIconButton(
Icons.Rounded.SettingsBackupRestore,
R.string.quick_actions,
) {
sharedViewModel.postSideEffect(LocalSideEffect.Sheet.BackupApp)
}
},
)
Route.Sort ->
NavbarState(
showBottomItems = true,
topTitle = { Text(stringResource(R.string.sort)) },
topTrailing = {
Row {
ActionIconButton(Icons.Rounded.SortByAlpha, R.string.sort) {
sharedViewModel.postSideEffect(LocalSideEffect.Sort)
}
ActionIconButton(Icons.Rounded.Save, R.string.save) {
sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges)
}
}
},
)
is Route.SplitTunnel -> {
val tunnel = sharedState.tunnels.find { it.id == route.id }
NavbarState(
topTitle = { Text(tunnel?.name ?: "") },
topTrailing = {
ActionIconButton(Icons.Rounded.Save, R.string.save) {
sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges)
}
},
showBottomItems = true,
)
}
Route.Support ->
NavbarState(
topTitle = { Text(stringResource(R.string.support)) },
showBottomItems = true,
)
Route.SystemFeatures ->
NavbarState(
topTitle = { Text(stringResource(R.string.android_integrations)) },
showBottomItems = true,
)
is Route.TunnelAutoTunnel -> {
val tunnel = sharedState.tunnels.find { it.id == route.id }
NavbarState(showBottomItems = true, topTitle = { Text(tunnel?.name ?: "") })
}
Route.TunnelMonitoring ->
NavbarState(
topTitle = { Text(stringResource(R.string.tunnel_monitoring)) },
showBottomItems = true,
)
is Route.TunnelOptions -> {
val tunnel = sharedState.tunnels.find { it.id == route.id }
NavbarState(
showBottomItems = true,
topTitle = { Text(tunnel?.name ?: "") },
topTrailing = {
Row {
ActionIconButton(Icons.Rounded.QrCode2, R.string.show_qr) {
sharedViewModel.postSideEffect(LocalSideEffect.Modal.QR)
}
ActionIconButton(Icons.Rounded.Edit, R.string.edit_tunnel) {
navigate(Route.Config(route.id))
}
}
},
)
}
Route.Tunnels ->
NavbarState(
topTitle = { Text(stringResource(R.string.tunnels)) },
topTrailing =
sharedState.topNavActions
?: {
Row {
ActionIconButton(
Icons.AutoMirrored.Rounded.Sort,
R.string.sort,
) {
navigate(Route.Sort)
}
ActionIconButton(Icons.Rounded.Add, R.string.add_tunnel) {
sharedViewModel.postSideEffect(
LocalSideEffect.Sheet.ImportTunnels
)
}
}
},
showBottomItems = true,
)
Route.WifiDetectionMethod ->
NavbarState(
topTitle = { Text(stringResource(R.string.wifi_detection_method)) },
showBottomItems = true,
)
Route.TunnelsGraph,
Route.SettingsGraph,
Route.AutoTunnelGraph,
Route.SupportGraph,
null -> NavbarState()
}
}
}
@@ -22,7 +22,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
import com.zaneschepke.wireguardautotunnel.ui.common.SectionDivider
import com.zaneschepke.wireguardautotunnel.ui.common.banner.WarningBanner
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
@@ -32,7 +31,6 @@ import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components.AdvancedSettingsItem
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components.networkTunnelingItems
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components.wifiTunnelingItems
import com.zaneschepke.wireguardautotunnel.ui.state.NavbarState
import com.zaneschepke.wireguardautotunnel.util.extensions.launchAppSettings
import com.zaneschepke.wireguardautotunnel.util.extensions.launchLocationServicesSettings
import com.zaneschepke.wireguardautotunnel.viewmodel.AutoTunnelViewModel
@@ -41,21 +39,10 @@ import com.zaneschepke.wireguardautotunnel.viewmodel.AutoTunnelViewModel
@Composable
fun AutoTunnelScreen(viewModel: AutoTunnelViewModel = hiltViewModel()) {
val context = LocalContext.current
val sharedViewModel = LocalSharedVm.current
val navController = LocalNavController.current
val autoTunnelState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
if (!autoTunnelState.stateInitialized) return
LaunchedEffect(Unit) {
sharedViewModel.updateNavbarState(
NavbarState(
showBottomItems = true,
topTitle = { Text(stringResource(R.string.auto_tunnel)) },
)
)
}
var showLocationDialog by remember { mutableStateOf(false) }
val showLocationServicesWarning by
@@ -12,7 +12,6 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -20,25 +19,12 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
import com.zaneschepke.wireguardautotunnel.ui.common.dropdown.LabelledDropdown
import com.zaneschepke.wireguardautotunnel.ui.state.NavbarState
import com.zaneschepke.wireguardautotunnel.viewmodel.AutoTunnelViewModel
@Composable
fun AutoTunnelAdvancedScreen(viewModel: AutoTunnelViewModel) {
val sharedViewModel = LocalSharedVm.current
val autoTunnelState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
LaunchedEffect(Unit) {
sharedViewModel.updateNavbarState(
NavbarState(
showBottomItems = true,
topTitle = { Text(stringResource(R.string.advanced_settings)) },
)
)
}
Column(
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
@@ -4,21 +4,16 @@ 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.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.data.model.WifiDetectionMethod
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
import com.zaneschepke.wireguardautotunnel.ui.common.button.IconSurfaceButton
import com.zaneschepke.wireguardautotunnel.ui.state.NavbarState
import com.zaneschepke.wireguardautotunnel.util.extensions.asDescriptionString
import com.zaneschepke.wireguardautotunnel.util.extensions.asTitleString
import com.zaneschepke.wireguardautotunnel.viewmodel.AutoTunnelViewModel
@@ -30,15 +25,6 @@ fun WifiDetectionMethodScreen(viewModel: AutoTunnelViewModel) {
val autoTunnelState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
LaunchedEffect(Unit) {
sharedViewModel.updateNavbarState(
NavbarState(
topTitle = { Text(stringResource(R.string.wifi_detection_method)) },
showBottomItems = true,
)
)
}
Column(
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(24.dp, Alignment.Top),
@@ -10,20 +10,15 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.disclosure.components.LocationDisclosureHeader
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.disclosure.components.appSettingsItem
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.disclosure.components.skipItem
import com.zaneschepke.wireguardautotunnel.ui.state.NavbarState
import com.zaneschepke.wireguardautotunnel.viewmodel.AutoTunnelViewModel
@Composable
fun LocationDisclosureScreen(viewModel: AutoTunnelViewModel) {
val navController = LocalNavController.current
val sharedViewModel = LocalSharedVm.current
LaunchedEffect(Unit) { sharedViewModel.updateNavbarState(NavbarState(showBottomItems = true)) }
LaunchedEffect(Unit) { viewModel.setLocationDisclosureShown() }
@@ -3,14 +3,16 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.pin
import androidx.activity.compose.BackHandler
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
import com.zaneschepke.wireguardautotunnel.ui.state.NavbarState
import com.zaneschepke.wireguardautotunnel.util.StringValue
import xyz.teamgravity.pin_lock_compose.PinLock
import xyz.teamgravity.pin_lock_compose.PinManager
@@ -22,8 +24,6 @@ fun PinLockScreen() {
val pinAlreadyExists by rememberSaveable { mutableStateOf(PinManager.pinExists()) }
var pinCreated by rememberSaveable { mutableStateOf(false) }
LaunchedEffect(Unit) { sharedViewModel.updateNavbarState(NavbarState(showBottomItems = false)) }
PinLock(
title = {
Text(
@@ -8,32 +8,27 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.SettingsBackupRestore
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.data.model.AppMode
import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
import com.zaneschepke.wireguardautotunnel.ui.common.SectionDivider
import com.zaneschepke.wireguardautotunnel.ui.common.button.ActionIconButton
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.*
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.proxy.compoents.AppModeBottomSheet
import com.zaneschepke.wireguardautotunnel.ui.state.NavbarState
import com.zaneschepke.wireguardautotunnel.ui.sideeffect.LocalSideEffect
import com.zaneschepke.wireguardautotunnel.viewmodel.SettingsViewModel
import org.orbitmvi.orbit.compose.collectSideEffect
import xyz.teamgravity.pin_lock_compose.PinManager
@Composable
@@ -47,11 +42,18 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
val settingsState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
if (!settingsState.stateInitialized) return
var showBackupSheet by rememberSaveable { mutableStateOf(false) }
var showAppModeSheet by rememberSaveable { mutableStateOf(false) }
if (!settingsState.stateInitialized) return
sharedViewModel.collectSideEffect { sideEffect ->
when (sideEffect) {
LocalSideEffect.Sheet.BackupApp -> showBackupSheet = true
else -> Unit
}
}
val showProxySettings by
remember(settingsState.settings.appMode) {
derivedStateOf {
@@ -62,20 +64,6 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
}
}
LaunchedEffect(Unit) {
sharedViewModel.updateNavbarState(
NavbarState(
showBottomItems = true,
topTitle = { Text(stringResource(R.string.settings)) },
topTrailing = {
ActionIconButton(Icons.Rounded.SettingsBackupRestore, R.string.quick_actions) {
showBackupSheet = true
}
},
)
)
}
if (showBackupSheet) BackupBottomSheet() { showBackupSheet = false }
if (showAppModeSheet)
AppModeBottomSheet(sharedViewModel::setAppMode, settingsState.settings.appMode) {
@@ -4,36 +4,20 @@ 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.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
import com.zaneschepke.wireguardautotunnel.ui.common.SectionDivider
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.components.DisplayThemeItem
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.components.LanguageItem
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.components.NotificationsItem
import com.zaneschepke.wireguardautotunnel.ui.state.NavbarState
@Composable
fun AppearanceScreen() {
val sharedViewModel = LocalSharedVm.current
val navController = LocalNavController.current
LaunchedEffect(Unit) {
sharedViewModel.updateNavbarState(
NavbarState(
showBottomItems = true,
topTitle = { Text(stringResource(R.string.appearance)) },
)
)
}
Column(
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
@@ -4,9 +4,7 @@ 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.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -16,7 +14,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
import com.zaneschepke.wireguardautotunnel.ui.common.button.IconSurfaceButton
import com.zaneschepke.wireguardautotunnel.ui.state.NavbarState
import com.zaneschepke.wireguardautotunnel.ui.theme.Theme
@Composable
@@ -26,15 +23,6 @@ fun DisplayScreen() {
val appState by sharedViewModel.container.stateFlow.collectAsStateWithLifecycle()
LaunchedEffect(Unit) {
sharedViewModel.updateNavbarState(
NavbarState(
showBottomItems = true,
topTitle = { Text(stringResource(R.string.display_theme)) },
)
)
}
Column(
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(24.dp, Alignment.Top),
@@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -18,7 +17,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
import com.zaneschepke.wireguardautotunnel.ui.common.button.IconSurfaceButton
import com.zaneschepke.wireguardautotunnel.ui.state.NavbarState
import com.zaneschepke.wireguardautotunnel.util.LocaleUtil
import java.text.Collator
import java.util.*
@@ -29,15 +27,6 @@ fun LanguageScreen() {
val sharedViewModel = LocalSharedVm.current
val appState by sharedViewModel.container.stateFlow.collectAsStateWithLifecycle()
LaunchedEffect(Unit) {
sharedViewModel.updateNavbarState(
NavbarState(
showBottomItems = true,
topTitle = { Text(stringResource(R.string.language)) },
)
)
}
val collator = Collator.getInstance(Locale.getDefault())
val locales =
LocaleUtil.supportedLocales.map {
@@ -14,7 +14,6 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -25,26 +24,14 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.data.model.DnsProtocol
import com.zaneschepke.wireguardautotunnel.data.model.DnsProvider
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
import com.zaneschepke.wireguardautotunnel.ui.common.dropdown.LabelledDropdown
import com.zaneschepke.wireguardautotunnel.ui.state.NavbarState
import com.zaneschepke.wireguardautotunnel.viewmodel.SettingsViewModel
@Composable
fun DnsSettingsScreen(viewModel: SettingsViewModel) {
val context = LocalContext.current
val sharedViewModel = LocalSharedVm.current
val settingsState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
LaunchedEffect(Unit) {
sharedViewModel.updateNavbarState(
NavbarState(
showBottomItems = true,
topTitle = { Text(stringResource(R.string.dns_settings)) },
)
)
}
Column(
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
@@ -4,8 +4,6 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Menu
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.*
@@ -19,15 +17,15 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
import com.zaneschepke.wireguardautotunnel.ui.common.button.ActionIconButton
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.logs.components.LogList
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.logs.components.LogsBottomSheet
import com.zaneschepke.wireguardautotunnel.ui.state.NavbarState
import com.zaneschepke.wireguardautotunnel.ui.sideeffect.LocalSideEffect
import com.zaneschepke.wireguardautotunnel.viewmodel.LoggerViewModel
import org.orbitmvi.orbit.compose.collectSideEffect
@Composable
fun LogsScreen(viewModel: LoggerViewModel = hiltViewModel()) {
val sharedViewModel = LocalSharedVm.current
val sharedAppViewModel = LocalSharedVm.current
val loggerState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
val lazyColumnListState = rememberLazyListState()
@@ -35,19 +33,8 @@ fun LogsScreen(viewModel: LoggerViewModel = hiltViewModel()) {
var lastScrollPosition by rememberSaveable() { mutableIntStateOf(0) }
var showLogsSheet by rememberSaveable { mutableStateOf(false) }
LaunchedEffect(Unit) {
sharedViewModel.updateNavbarState(
NavbarState(
showBottomItems = false,
removeBottom = true,
topTitle = { Text(stringResource(R.string.logs)) },
topTrailing = {
ActionIconButton(Icons.Rounded.Menu, R.string.quick_actions) {
showLogsSheet = true
}
},
)
)
sharedAppViewModel.collectSideEffect { sideEffect ->
if (sideEffect is LocalSideEffect.Sheet.LoggerActions) showLogsSheet = true
}
LaunchedEffect(isAutoScrolling) {
@@ -14,7 +14,6 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -22,28 +21,16 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
import com.zaneschepke.wireguardautotunnel.ui.common.dropdown.LabelledDropdown
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.monitoring.components.detailedPingStatsItem
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.monitoring.components.enablePingMonitoringItem
import com.zaneschepke.wireguardautotunnel.ui.state.NavbarState
import com.zaneschepke.wireguardautotunnel.viewmodel.SettingsViewModel
@Composable
fun TunnelMonitoringScreen(viewModel: SettingsViewModel) {
val sharedViewModel = LocalSharedVm.current
val settingsState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
LaunchedEffect(Unit) {
sharedViewModel.updateNavbarState(
NavbarState(
topTitle = { Text(stringResource(R.string.tunnel_monitoring)) },
showBottomItems = true,
)
)
}
Column(
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
@@ -5,10 +5,8 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Forward5
import androidx.compose.material.icons.outlined.Http
import androidx.compose.material.icons.outlined.RemoveRedEye
import androidx.compose.material.icons.rounded.Save
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -24,7 +22,6 @@ import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.domain.model.AppProxySettings
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
import com.zaneschepke.wireguardautotunnel.ui.common.SecureScreenFromRecording
import com.zaneschepke.wireguardautotunnel.ui.common.button.ActionIconButton
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel
@@ -32,14 +29,13 @@ import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLab
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
import com.zaneschepke.wireguardautotunnel.ui.common.textbox.ConfigurationTextBox
import com.zaneschepke.wireguardautotunnel.ui.state.NavbarState
import com.zaneschepke.wireguardautotunnel.ui.sideeffect.LocalSideEffect
import com.zaneschepke.wireguardautotunnel.viewmodel.ProxySettingsViewModel
import org.orbitmvi.orbit.compose.collectSideEffect
@Composable
fun ProxySettingsScreen(viewModel: ProxySettingsViewModel = hiltViewModel()) {
val sharedViewModel = LocalSharedVm.current
val proxySettingsState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
val proxySettings by remember { derivedStateOf { proxySettingsState.proxySettings } }
@@ -57,28 +53,22 @@ fun ProxySettingsScreen(viewModel: ProxySettingsViewModel = hiltViewModel()) {
if (!proxySettingsState.stateInitialized) return
LaunchedEffect(Unit) {
sharedViewModel.updateNavbarState(
NavbarState(
showBottomItems = true,
topTitle = { Text(stringResource(R.string.proxy_settings)) },
topTrailing = {
ActionIconButton(Icons.Rounded.Save, R.string.save) {
keyboardController?.hide()
viewModel.save(
AppProxySettings(
socks5ProxyEnabled = proxySettings.socks5ProxyEnabled,
socks5ProxyBindAddress = socksBindAddress,
httpProxyEnabled = proxySettings.httpProxyEnabled,
httpProxyBindAddress = httpBindAddress,
proxyUsername = proxyUsername,
proxyPassword = proxyPassword,
)
)
}
},
)
)
sharedViewModel.collectSideEffect { sideEffect ->
when (sideEffect) {
LocalSideEffect.SaveChanges -> {
viewModel.save(
AppProxySettings(
socks5ProxyEnabled = proxySettings.socks5ProxyEnabled,
socks5ProxyBindAddress = socksBindAddress,
httpProxyEnabled = proxySettings.httpProxyEnabled,
httpProxyBindAddress = httpBindAddress,
proxyUsername = proxyUsername,
proxyPassword = proxyPassword,
)
)
}
else -> Unit
}
}
SecureScreenFromRecording()
@@ -6,41 +6,25 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
import com.zaneschepke.wireguardautotunnel.ui.common.SectionDivider
import com.zaneschepke.wireguardautotunnel.ui.common.SecureScreenFromRecording
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.system.components.*
import com.zaneschepke.wireguardautotunnel.ui.state.NavbarState
import com.zaneschepke.wireguardautotunnel.viewmodel.SettingsViewModel
@Composable
fun SystemFeaturesScreen(viewModel: SettingsViewModel) {
val sharedViewModel = LocalSharedVm.current
val settingsState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
val isTv = LocalIsAndroidTV.current
LaunchedEffect(Unit) {
sharedViewModel.updateNavbarState(
NavbarState(
topTitle = { Text(stringResource(R.string.android_integrations)) },
showBottomItems = true,
)
)
}
SecureScreenFromRecording()
Column(
@@ -6,8 +6,11 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.StrokeCap
@@ -20,14 +23,12 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.zaneschepke.wireguardautotunnel.BuildConfig
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
import com.zaneschepke.wireguardautotunnel.ui.common.SectionDivider
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
import com.zaneschepke.wireguardautotunnel.ui.screens.support.components.ContactSupportOptions
import com.zaneschepke.wireguardautotunnel.ui.screens.support.components.GeneralSupportOptions
import com.zaneschepke.wireguardautotunnel.ui.screens.support.components.UpdateSection
import com.zaneschepke.wireguardautotunnel.ui.state.NavbarState
import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.extensions.canInstallPackages
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
@@ -37,21 +38,11 @@ import com.zaneschepke.wireguardautotunnel.viewmodel.SupportViewModel
@Composable
fun SupportScreen(viewModel: SupportViewModel) {
val context = LocalContext.current
val sharedViewModel = LocalSharedVm.current
val navController = LocalNavController.current
val supportState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
var showPermissionDialog by rememberSaveable { mutableStateOf(false) }
LaunchedEffect(Unit) {
sharedViewModel.updateNavbarState(
NavbarState(
topTitle = { Text(stringResource(R.string.support)) },
showBottomItems = true,
)
)
}
if (supportState.appUpdate != null) {
InfoDialog(
onDismiss = { viewModel.dismissUpdate() },
@@ -5,16 +5,11 @@ import android.content.Context
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
import com.zaneschepke.wireguardautotunnel.ui.screens.support.license.components.LicenseList
import com.zaneschepke.wireguardautotunnel.ui.state.NavbarState
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
@@ -22,18 +17,8 @@ import kotlinx.serialization.json.Json
@Composable
fun LicenseScreen() {
val context = LocalContext.current
val sharedViewModel = LocalSharedVm.current
var licenses by remember { mutableStateOf<List<LicenseFileEntry>>(emptyList()) }
LaunchedEffect(Unit) {
sharedViewModel.updateNavbarState(
NavbarState(
showBottomItems = true,
topTitle = { Text(stringResource(R.string.licenses)) },
)
)
}
LaunchedEffect(Unit) { licenses = loadLicenseeJson(context) }
if (licenses.isEmpty()) {
@@ -7,8 +7,10 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.Sort
import androidx.compose.material.icons.rounded.*
import androidx.compose.material.icons.rounded.CopyAll
import androidx.compose.material.icons.rounded.Delete
import androidx.compose.material.icons.rounded.Download
import androidx.compose.material.icons.rounded.SelectAll
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
@@ -30,10 +32,11 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.components.ExportT
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.components.TunnelImportSheet
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.components.TunnelList
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.components.UrlImportDialog
import com.zaneschepke.wireguardautotunnel.ui.state.NavbarState
import com.zaneschepke.wireguardautotunnel.ui.sideeffect.LocalSideEffect
import com.zaneschepke.wireguardautotunnel.util.FileUtils
import com.zaneschepke.wireguardautotunnel.util.StringValue
import com.zaneschepke.wireguardautotunnel.viewmodel.TunnelsViewModel
import org.orbitmvi.orbit.compose.collectSideEffect
@Composable
fun TunnelsScreen(viewModel: TunnelsViewModel) {
@@ -48,70 +51,57 @@ fun TunnelsScreen(viewModel: TunnelsViewModel) {
var showDeleteModal by rememberSaveable { mutableStateOf(false) }
var showUrlDialog by rememberSaveable { mutableStateOf(false) }
if (!tunnelsState.stateInitialized) return
@Composable
fun TunnelActionBar() {
val selectedCount by
remember(tunnelsState.selectedTunnels) {
derivedStateOf { tunnelsState.selectedTunnels.size }
}
val disableDelete by
remember(tunnelsState.activeTunnels, tunnelsState.selectedTunnels) {
derivedStateOf {
tunnelsState.activeTunnels.any { active ->
tunnelsState.selectedTunnels.any { it.id == active.key.id }
}
val disableDelete by
remember(tunnelsState.activeTunnels, tunnelsState.selectedTunnels) {
derivedStateOf {
tunnelsState.activeTunnels.any { active ->
tunnelsState.selectedTunnels.any { it.id == active.key.id }
}
}
}
Row {
if (selectedCount == 0) {
val showSort by
remember(tunnelsState.tunnels) {
derivedStateOf { tunnelsState.tunnels.size > 1 }
sharedViewModel.collectSideEffect { sideEffect ->
when (sideEffect) {
LocalSideEffect.Sheet.ImportTunnels -> showImportSheet = true
else -> Unit
}
}
LaunchedEffect(tunnelsState.selectedTunnels) {
when (val count = tunnelsState.selectedTunnels.count()) {
0 -> sharedViewModel.updateTopNavActions(null)
else -> {
sharedViewModel.updateTopNavActions {
Row {
ActionIconButton(Icons.Rounded.SelectAll, R.string.select_all) {
viewModel.toggleSelectAllTunnels()
}
// due to permissions, and SAF issues on TV, not support less than Android
// 10
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ActionIconButton(Icons.Rounded.Download, R.string.download) {
showExportSheet = true
}
}
if (count == 1) {
ActionIconButton(Icons.Rounded.CopyAll, R.string.copy) {
viewModel.copySelectedTunnel()
}
}
if (!disableDelete) {
ActionIconButton(Icons.Rounded.Delete, R.string.delete_tunnel) {
showDeleteModal = true
}
}
}
if (showSort)
ActionIconButton(Icons.AutoMirrored.Rounded.Sort, R.string.sort) {
navController.navigate(Route.Sort)
}
ActionIconButton(Icons.Rounded.Add, R.string.add_tunnel) { showImportSheet = true }
return@Row
}
ActionIconButton(Icons.Rounded.SelectAll, R.string.select_all) {
viewModel.toggleSelectAllTunnels()
}
// due to permissions, and SAF issues on TV, not support less than Android 10 on
// Android TV for file exports
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ActionIconButton(Icons.Rounded.Download, R.string.download) {
showExportSheet = true
}
}
if (selectedCount == 1) {
ActionIconButton(Icons.Rounded.CopyAll, R.string.copy) {
viewModel.copySelectedTunnel()
}
}
if (!disableDelete) {
ActionIconButton(Icons.Rounded.Delete, R.string.delete_tunnel) {
showDeleteModal = true
}
}
}
}
LaunchedEffect(Unit) {
sharedViewModel.updateNavbarState(
NavbarState(
topTitle = { Text(stringResource(R.string.tunnels)) },
topTrailing = { TunnelActionBar() },
showBottomItems = true,
)
)
}
if (!tunnelsState.stateInitialized) return
val tunnelFileImportResultLauncher =
rememberFileImportLauncherForResult(
@@ -203,3 +193,9 @@ fun TunnelsScreen(viewModel: TunnelsViewModel) {
navController,
)
}
@Composable
fun TunnelActionBar() {
Row {}
}
@@ -6,24 +6,23 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.autotunnel.components.MobileDataTunnelItem
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.autotunnel.components.PingRestartItem
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.autotunnel.components.WifiTunnelItem
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.autotunnel.components.ethernetTunnelItem
import com.zaneschepke.wireguardautotunnel.ui.state.NavbarState
import com.zaneschepke.wireguardautotunnel.viewmodel.TunnelsViewModel
@Composable
fun TunnelAutoTunnelScreen(tunnelId: Int, viewModel: TunnelsViewModel) {
val sharedViewModel = LocalSharedVm.current
val tunnelsState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
val tunnelConf by
@@ -31,12 +30,6 @@ fun TunnelAutoTunnelScreen(tunnelId: Int, viewModel: TunnelsViewModel) {
derivedStateOf { tunnelsState.tunnels.find { it.id == tunnelId }!! }
}
LaunchedEffect(Unit) {
sharedViewModel.updateNavbarState(
NavbarState(showBottomItems = true, topTitle = { Text(tunnelConf.name) })
)
}
Column(
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
@@ -6,37 +6,30 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Save
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
import com.zaneschepke.wireguardautotunnel.ui.common.SecureScreenFromRecording
import com.zaneschepke.wireguardautotunnel.ui.common.button.ActionIconButton
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.AuthorizationPromptWrapper
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.config.components.AddPeerButton
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.config.components.InterfaceSection
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.config.components.PeersSection
import com.zaneschepke.wireguardautotunnel.ui.sideeffect.LocalSideEffect
import com.zaneschepke.wireguardautotunnel.ui.state.ConfigProxy
import com.zaneschepke.wireguardautotunnel.ui.state.NavbarState
import com.zaneschepke.wireguardautotunnel.ui.state.PeerProxy
import com.zaneschepke.wireguardautotunnel.viewmodel.TunnelsViewModel
import org.orbitmvi.orbit.compose.collectSideEffect
@Composable
fun ConfigScreen(tunnelId: Int? = null, viewModel: TunnelsViewModel = hiltViewModel()) {
val sharedViewModel = LocalSharedVm.current
val isTv = LocalIsAndroidTV.current
val keyboardController = LocalSoftwareKeyboardController.current
val sharedViewModel = LocalSharedVm.current
val tunnelsState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
@@ -51,21 +44,6 @@ fun ConfigScreen(tunnelId: Int? = null, viewModel: TunnelsViewModel = hiltViewMo
var tunnelName by remember { mutableStateOf(tunnelConf?.name ?: "") }
LaunchedEffect(key1 = Unit) {
sharedViewModel.updateNavbarState(
NavbarState(
showBottomItems = true,
topTitle = { Text(tunnelConf?.name ?: stringResource(R.string.new_tunnel)) },
topTrailing = {
ActionIconButton(Icons.Rounded.Save, R.string.save) {
keyboardController?.hide()
viewModel.saveConfigProxy(tunnelId, configProxy, tunnelName)
}
},
)
)
}
val isTunnelNameTaken by
remember(tunnelName, tunnelsState.tunnels) {
derivedStateOf {
@@ -76,6 +54,11 @@ fun ConfigScreen(tunnelId: Int? = null, viewModel: TunnelsViewModel = hiltViewMo
var showAuthPrompt by rememberSaveable { mutableStateOf(false) }
var isAuthorized by rememberSaveable { mutableStateOf(isTv) }
sharedViewModel.collectSideEffect { sideEffect ->
if (sideEffect is LocalSideEffect.SaveChanges)
viewModel.saveConfigProxy(tunnelId, configProxy, tunnelName)
}
SecureScreenFromRecording()
if (showAuthPrompt) {
@@ -11,13 +11,13 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDownward
import androidx.compose.material.icons.filled.ArrowUpward
import androidx.compose.material.icons.filled.DragHandle
import androidx.compose.material.icons.rounded.Save
import androidx.compose.material.icons.rounded.SortByAlpha
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
@@ -30,10 +30,10 @@ import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
import com.zaneschepke.wireguardautotunnel.ui.common.ExpandingRowListItem
import com.zaneschepke.wireguardautotunnel.ui.common.button.ActionIconButton
import com.zaneschepke.wireguardautotunnel.ui.state.NavbarState
import com.zaneschepke.wireguardautotunnel.ui.sideeffect.LocalSideEffect
import com.zaneschepke.wireguardautotunnel.util.extensions.isSortedBy
import com.zaneschepke.wireguardautotunnel.viewmodel.TunnelsViewModel
import org.orbitmvi.orbit.compose.collectSideEffect
import sh.calvin.reorderable.DragGestureDetector
import sh.calvin.reorderable.ReorderableItem
import sh.calvin.reorderable.rememberReorderableLazyListState
@@ -48,34 +48,27 @@ fun SortScreen(viewModel: TunnelsViewModel) {
var sortAscending by rememberSaveable { mutableStateOf<Boolean?>(null) }
var editableTunnels by rememberSaveable { mutableStateOf(tunnelsState.tunnels) }
LaunchedEffect(Unit) {
sharedViewModel.updateNavbarState(
NavbarState(
showBottomItems = true,
topTitle = { Text(stringResource(R.string.sort)) },
topTrailing = {
Row {
ActionIconButton(Icons.Rounded.SortByAlpha, R.string.sort) {
sortAscending =
when (sortAscending) {
null -> !editableTunnels.isSortedBy { it.name }
true -> false
false -> null
}
editableTunnels =
when (sortAscending) {
true -> editableTunnels.sortedBy { it.name }
false -> editableTunnels.sortedByDescending { it.name }
null -> tunnelsState.tunnels
}
}
ActionIconButton(Icons.Rounded.Save, R.string.save) {
viewModel.saveSortChanges(editableTunnels)
}
sharedViewModel.collectSideEffect { sideEffect ->
when (sideEffect) {
LocalSideEffect.SaveChanges -> {
viewModel.saveSortChanges(editableTunnels)
}
LocalSideEffect.Sort -> {
sortAscending =
when (sortAscending) {
null -> !editableTunnels.isSortedBy { it.name }
true -> false
false -> null
}
},
)
)
editableTunnels =
when (sortAscending) {
true -> editableTunnels.sortedBy { it.name }
false -> editableTunnels.sortedByDescending { it.name }
null -> tunnelsState.tunnels
}
}
else -> Unit
}
}
val lazyListState = rememberLazyListState()
@@ -3,28 +3,24 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.splittunnel
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Save
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
import com.zaneschepke.wireguardautotunnel.ui.common.button.ActionIconButton
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.splittunnel.components.SplitTunnelContent
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.splittunnel.state.SplitOption
import com.zaneschepke.wireguardautotunnel.ui.state.NavbarState
import com.zaneschepke.wireguardautotunnel.ui.sideeffect.LocalSideEffect
import com.zaneschepke.wireguardautotunnel.viewmodel.SplitTunnelViewModel
import org.orbitmvi.orbit.compose.collectSideEffect
@Composable
fun SplitTunnelScreen(tunnelId: Int, viewModel: SplitTunnelViewModel = hiltViewModel()) {
val splitTunnelState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
val sharedViewModel = LocalSharedVm.current
val splitTunnelState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
if (!splitTunnelState.stateInitialized) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
@@ -51,18 +47,9 @@ fun SplitTunnelScreen(tunnelId: Int, viewModel: SplitTunnelViewModel = hiltViewM
)
}
LaunchedEffect(Unit) {
sharedViewModel.updateNavbarState(
NavbarState(
topTitle = { Text(tunnelConf.name) },
topTrailing = {
ActionIconButton(Icons.Rounded.Save, R.string.save) {
viewModel.saveSplitTunnelSelection(tunnelId, splitConfig)
}
},
showBottomItems = true,
)
)
sharedViewModel.collectSideEffect { sideEffect ->
if (sideEffect is LocalSideEffect.SaveChanges)
viewModel.saveSplitTunnelSelection(tunnelId, splitConfig)
}
SplitTunnelContent(
@@ -1,30 +1,27 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.tunneloptions
import androidx.compose.foundation.layout.*
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.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Edit
import androidx.compose.material.icons.rounded.QrCode2
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
import com.zaneschepke.wireguardautotunnel.ui.common.SectionDivider
import com.zaneschepke.wireguardautotunnel.ui.common.button.ActionIconButton
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route.Config
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.AuthorizationPromptWrapper
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.tunneloptions.components.*
import com.zaneschepke.wireguardautotunnel.ui.state.NavbarState
import com.zaneschepke.wireguardautotunnel.ui.sideeffect.LocalSideEffect
import com.zaneschepke.wireguardautotunnel.viewmodel.TunnelsViewModel
import org.orbitmvi.orbit.compose.collectSideEffect
@Composable
fun TunnelOptionsScreen(tunnelId: Int, viewModel: TunnelsViewModel) {
@@ -43,26 +40,8 @@ fun TunnelOptionsScreen(tunnelId: Int, viewModel: TunnelsViewModel) {
var isAuthorized by rememberSaveable { mutableStateOf(isTv) }
var showQrModal by rememberSaveable { mutableStateOf(false) }
LaunchedEffect(Unit) {
sharedViewModel.updateNavbarState(
NavbarState(
showBottomItems = true,
topTitle = { Text(tunnelConf.name) },
topTrailing = {
Row {
ActionIconButton(
Icons.Rounded.QrCode2,
com.zaneschepke.wireguardautotunnel.R.string.show_qr,
) {
showQrModal = true
}
ActionIconButton(Icons.Rounded.Edit, R.string.edit_tunnel) {
navController.navigate(Config(tunnelId))
}
}
},
)
)
sharedViewModel.collectSideEffect { sideEffect ->
if (sideEffect is LocalSideEffect.Modal.QR) showQrModal = true
}
if (showQrModal) {
@@ -0,0 +1,20 @@
package com.zaneschepke.wireguardautotunnel.ui.sideeffect
sealed class LocalSideEffect {
data object Sort : LocalSideEffect()
data object SaveChanges : LocalSideEffect()
sealed class Sheet : LocalSideEffect() {
data object ImportTunnels : Sheet()
data object BackupApp : Sheet()
data object LoggerActions : Sheet()
}
sealed class Modal : LocalSideEffect() {
data object QR : Modal()
}
}
@@ -1,6 +1,8 @@
package com.zaneschepke.wireguardautotunnel.ui.state
import androidx.compose.runtime.Composable
import com.zaneschepke.wireguardautotunnel.domain.model.GeneralSettings
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.ui.theme.Theme
import com.zaneschepke.wireguardautotunnel.util.LocaleUtil
@@ -12,6 +14,7 @@ data class SharedAppUiState(
val isAuthorized: Boolean = false,
val isAutoTunnelActive: Boolean = false,
val isLocationDisclosureShown: Boolean = false,
val tunnels: List<TunnelConf> = emptyList(),
val settings: GeneralSettings = GeneralSettings(),
val navBarState: NavbarState = NavbarState(),
val topNavActions: (@Composable () -> Unit)? = null,
)
@@ -11,13 +11,13 @@ import androidx.annotation.RequiresApi
import com.zaneschepke.wireguardautotunnel.util.extensions.QuickConfig
import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelName
import com.zaneschepke.wireguardautotunnel.util.extensions.getInputStreamFromUri
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.io.*
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
import timber.log.Timber
class FileUtils(private val context: Context, private val ioDispatcher: CoroutineDispatcher) {
@@ -1,11 +1,11 @@
package com.zaneschepke.wireguardautotunnel.util
import com.vdurmont.semver4j.Semver
import timber.log.Timber
import java.math.BigDecimal
import java.time.Duration
import java.time.Instant
import kotlin.math.pow
import timber.log.Timber
object NumberUtils {
private const val BYTES_IN_KB = 1024.0
@@ -24,11 +24,11 @@ import com.zaneschepke.wireguardautotunnel.core.service.tile.TunnelControlTile
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.splittunnel.state.TunnelApp
import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.FileUtils
import timber.log.Timber
import java.io.File
import java.io.InputStream
import java.util.*
import kotlin.system.exitProcess
import timber.log.Timber
fun Context.openWebUrl(url: String): Result<Unit> {
return kotlin
@@ -1,28 +1,8 @@
package com.zaneschepke.wireguardautotunnel.util.extensions
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
fun <K, V> Flow<Map<K, V>>.distinctByKeys(): Flow<Map<K, V>> {
return distinctUntilChanged { old, new -> old.keys == new.keys }
}
fun <T> debounce(
scope: CoroutineScope,
delayMillis: Long = 300L,
onDebounced: (T) -> Unit,
): (T) -> Unit {
var job: Job? = null
return { param: T ->
job?.cancel()
job =
scope.launch {
delay(delayMillis)
onDebounced(param)
}
}
}
@@ -3,14 +3,14 @@ package com.zaneschepke.wireguardautotunnel.util.network
import com.marsounjan.icmp4a.Icmp
import com.marsounjan.icmp4a.Icmp4a
import com.zaneschepke.wireguardautotunnel.util.extensions.round
import java.io.IOException
import java.time.Instant
import kotlin.math.sqrt
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onEach
import timber.log.Timber
import java.io.IOException
import java.time.Instant
import kotlin.math.sqrt
class NetworkUtils(private val ioDispatcher: CoroutineDispatcher) {
@@ -12,11 +12,11 @@ import com.zaneschepke.wireguardautotunnel.domain.sideeffect.GlobalSideEffect
import com.zaneschepke.wireguardautotunnel.ui.state.AutoTunnelUiState
import com.zaneschepke.wireguardautotunnel.util.StringValue
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import org.orbitmvi.orbit.ContainerHost
import org.orbitmvi.orbit.viewmodel.container
import javax.inject.Inject
@HiltViewModel
class AutoTunnelViewModel
@@ -11,14 +11,14 @@ import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.FileUtils
import com.zaneschepke.wireguardautotunnel.util.StringValue
import dagger.hilt.android.lifecycle.HiltViewModel
import java.time.Instant
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import org.orbitmvi.orbit.ContainerHost
import org.orbitmvi.orbit.viewmodel.container
import timber.log.Timber
import java.time.Instant
import javax.inject.Inject
@HiltViewModel
class LoggerViewModel
@@ -10,9 +10,9 @@ import com.zaneschepke.wireguardautotunnel.ui.state.ProxySettingsUiState
import com.zaneschepke.wireguardautotunnel.util.StringValue
import com.zaneschepke.wireguardautotunnel.util.extensions.isValidAndroidProxyBindAddress
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import org.orbitmvi.orbit.ContainerHost
import org.orbitmvi.orbit.viewmodel.container
import javax.inject.Inject
@HiltViewModel
class ProxySettingsViewModel
@@ -10,11 +10,11 @@ import com.zaneschepke.wireguardautotunnel.domain.repository.GlobalEffectReposit
import com.zaneschepke.wireguardautotunnel.domain.sideeffect.GlobalSideEffect
import com.zaneschepke.wireguardautotunnel.ui.state.SettingUiState
import dagger.hilt.android.lifecycle.HiltViewModel
import java.util.*
import javax.inject.Inject
import kotlinx.coroutines.flow.combine
import org.orbitmvi.orbit.ContainerHost
import org.orbitmvi.orbit.viewmodel.container
import java.util.*
import javax.inject.Inject
@HiltViewModel
class SettingsViewModel
@@ -1,5 +1,6 @@
package com.zaneschepke.wireguardautotunnel.viewmodel
import androidx.compose.runtime.Composable
import androidx.core.content.PermissionChecker.PERMISSION_GRANTED
import androidx.lifecycle.ViewModel
import com.wireguard.android.util.RootShell
@@ -14,15 +15,14 @@ import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.repository.AppStateRepository
import com.zaneschepke.wireguardautotunnel.domain.repository.GeneralSettingRepository
import com.zaneschepke.wireguardautotunnel.domain.repository.GlobalEffectRepository
import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository
import com.zaneschepke.wireguardautotunnel.domain.sideeffect.GlobalSideEffect
import com.zaneschepke.wireguardautotunnel.ui.state.NavbarState
import com.zaneschepke.wireguardautotunnel.ui.sideeffect.LocalSideEffect
import com.zaneschepke.wireguardautotunnel.ui.state.SharedAppUiState
import com.zaneschepke.wireguardautotunnel.ui.theme.Theme
import com.zaneschepke.wireguardautotunnel.util.LocaleUtil
import com.zaneschepke.wireguardautotunnel.util.StringValue
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import javax.inject.Provider
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
@@ -31,6 +31,8 @@ import org.orbitmvi.orbit.ContainerHost
import org.orbitmvi.orbit.viewmodel.container
import rikka.shizuku.Shizuku
import xyz.teamgravity.pin_lock_compose.PinManager
import javax.inject.Inject
import javax.inject.Provider
@HiltViewModel
class SharedAppViewModel
@@ -40,15 +42,16 @@ constructor(
private val serviceManager: ServiceManager,
private val tunnelManager: TunnelManager,
private val globalEffectRepository: GlobalEffectRepository,
private val tunnelRepository: TunnelRepository,
private val settingsRepository: GeneralSettingRepository,
@AppShell private val rootShell: Provider<RootShell>,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
) : ContainerHost<SharedAppUiState, Nothing>, ViewModel() {
) : ContainerHost<SharedAppUiState, LocalSideEffect>, ViewModel() {
val globalSideEffect = globalEffectRepository.flow
override val container =
container<SharedAppUiState, Nothing>(
container<SharedAppUiState, LocalSideEffect>(
SharedAppUiState(),
buildSettings = { repeatOnSubscribedStopTimeout = 5000L },
) {
@@ -57,12 +60,14 @@ constructor(
appStateRepository.flow,
serviceManager.autoTunnelService.map { it != null },
settingsRepository.flow,
) { appState, autoTunnelActive, settings ->
tunnelRepository.flow,
) { appState, autoTunnelActive, settings, tunnels ->
state.copy(
theme = appState.theme,
locale = appState.locale ?: LocaleUtil.OPTION_PHONE_LANGUAGE,
pinLockEnabled = appState.isPinLockEnabled,
isAutoTunnelActive = autoTunnelActive,
tunnels = tunnels,
settings = settings,
isLocationDisclosureShown = appState.isLocationDisclosureShown,
isAppLoaded = true,
@@ -95,6 +100,10 @@ constructor(
tunnelManager.startTunnel(tunnelConf)
}
fun postSideEffect(localSideEffect: LocalSideEffect) = intent {
postSideEffect(localSideEffect)
}
fun setTheme(theme: Theme) = intent { appStateRepository.setTheme(theme) }
fun setLocale(locale: String) = intent {
@@ -125,8 +134,8 @@ constructor(
settingsRepository.save(state.settings.copy(appMode = appMode))
}
fun updateNavbarState(newState: NavbarState) = intent {
reduce { state.copy(navBarState = newState) }
fun updateTopNavActions(actions: (@Composable () -> Unit)?) = intent {
reduce { state.copy(topNavActions = actions) }
}
suspend fun postSideEffect(globalSideEffect: GlobalSideEffect) {
@@ -12,11 +12,11 @@ import com.zaneschepke.wireguardautotunnel.ui.state.InterfaceProxy
import com.zaneschepke.wireguardautotunnel.ui.state.SplitTunnelUiState
import com.zaneschepke.wireguardautotunnel.util.StringValue
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flow
import org.orbitmvi.orbit.ContainerHost
import org.orbitmvi.orbit.viewmodel.container
import javax.inject.Inject
@HiltViewModel
class SplitTunnelViewModel
@@ -13,11 +13,11 @@ import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.StringValue
import dagger.hilt.android.lifecycle.HiltViewModel
import jakarta.inject.Inject
import javax.inject.Provider
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
import org.orbitmvi.orbit.ContainerHost
import org.orbitmvi.orbit.viewmodel.container
import javax.inject.Provider
@HiltViewModel
class SupportViewModel
@@ -23,11 +23,6 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelName
import com.zaneschepke.wireguardautotunnel.util.extensions.asStringValue
import com.zaneschepke.wireguardautotunnel.util.extensions.saveTunnelsUniquely
import dagger.hilt.android.lifecycle.HiltViewModel
import java.io.File
import java.io.IOException
import java.net.URL
import java.time.Instant
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.withContext
@@ -35,6 +30,11 @@ import org.amnezia.awg.config.BadConfigException
import org.orbitmvi.orbit.ContainerHost
import org.orbitmvi.orbit.viewmodel.container
import timber.log.Timber
import java.io.File
import java.io.IOException
import java.net.URL
import java.time.Instant
import javax.inject.Inject
@HiltViewModel
class TunnelsViewModel
@@ -1,12 +1,12 @@
package com.zaneschepke.logcatter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class LogFileManager(
private val logDir: String,
@@ -1,13 +1,13 @@
package com.zaneschepke.logcatter
import com.zaneschepke.logcatter.model.LogMessage
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
class LogcatStreamReader(private val pid: Int, private val fileManager: LogFileManager) {
private val bufferSize = 1024
@@ -17,13 +17,13 @@ import androidx.core.content.ContextCompat
import com.wireguard.android.util.RootShell
import com.zaneschepke.networkmonitor.shizuku.ShizukuShell
import com.zaneschepke.networkmonitor.util.*
import java.util.concurrent.ConcurrentHashMap
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.withTimeoutOrNull
import timber.log.Timber
import java.util.concurrent.ConcurrentHashMap
class AndroidNetworkMonitor(
private val appContext: Context,
@@ -1,10 +1,6 @@
package com.zaneschepke.networkmonitor.shizuku
import android.os.ParcelFileDescriptor
import java.io.BufferedReader
import java.io.FileInputStream
import java.io.InputStreamReader
import kotlin.coroutines.resumeWithException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
@@ -12,6 +8,10 @@ import moe.shizuku.server.IRemoteProcess
import moe.shizuku.server.IShizukuService
import rikka.shizuku.Shizuku
import timber.log.Timber
import java.io.BufferedReader
import java.io.FileInputStream
import java.io.InputStreamReader
import kotlin.coroutines.resumeWithException
class ShizukuShell(private val applicationScope: CoroutineScope) {
interface CommandResultListener {