From 0ea84b3e3143a4adf7e29dad59bf0a9def2d7e73 Mon Sep 17 00:00:00 2001 From: Zane Schepke Date: Wed, 3 Sep 2025 18:39:28 -0400 Subject: [PATCH] fix: navigation sync bug --- app/proguard-rules.pro | 3 - .../wireguardautotunnel/MigrationTest.kt | 2 +- .../wireguardautotunnel/MainActivity.kt | 17 +- .../WireGuardAutoTunnel.kt | 2 +- .../core/broadcast/KernelReceiver.kt | 2 +- .../broadcast/NotificationActionReceiver.kt | 2 +- .../core/broadcast/RemoteControlReceiver.kt | 2 +- .../core/broadcast/RestartReceiver.kt | 2 +- .../core/service/TunnelForegroundService.kt | 2 +- .../service/autotunnel/AutoTunnelService.kt | 6 +- .../service/tile/AutoTunnelControlTile.kt | 2 +- .../core/service/tile/TunnelControlTile.kt | 2 +- .../core/shortcut/ShortcutsActivity.kt | 2 +- .../core/tunnel/BaseTunnel.kt | 4 +- .../core/tunnel/KernelTunnel.kt | 2 +- .../core/tunnel/TunnelManager.kt | 10 +- .../core/tunnel/TunnelMonitor.kt | 2 +- .../core/tunnel/TunnelProvider.kt | 2 +- .../core/tunnel/UserspaceTunnel.kt | 6 +- .../core/worker/ServiceWorker.kt | 2 +- .../data/DataStoreManager.kt | 2 +- .../data/repository/GitHubUpdateRepository.kt | 2 +- .../InstalledAndroidPackageRepository.kt | 2 +- .../wireguardautotunnel/di/AppModule.kt | 2 +- .../di/RepositoryModule.kt | 2 +- .../wireguardautotunnel/di/TunnelModule.kt | 2 +- .../domain/model/TunnelConf.kt | 2 +- .../domain/repository/EffectRepository.kt | 2 +- .../ui/navigation/Route.kt | 55 ++-- .../ui/navigation/components/BottomNavbar.kt | 22 +- .../currentBackStackEntryAsNavbarState.kt | 259 ++++++++++++++++++ .../ui/screens/autotunnel/AutoTunnelScreen.kt | 13 - .../advanced/AutoTunnelAdvancedScreen.kt | 14 - .../detection/WifiDetectionMethodScreen.kt | 14 - .../disclosure/LocationDisclosureScreen.kt | 5 - .../ui/screens/pin/PinLockScreen.kt | 8 +- .../ui/screens/settings/SettingsScreen.kt | 34 +-- .../settings/appearance/AppearanceScreen.kt | 16 -- .../appearance/display/DisplayScreen.kt | 12 - .../appearance/language/LanguageScreen.kt | 11 - .../screens/settings/dns/DnsSettingsScreen.kt | 13 - .../ui/screens/settings/logs/LogsScreen.kt | 23 +- .../monitoring/TunnelMonitoringScreen.kt | 13 - .../settings/proxy/ProxySettingsScreen.kt | 46 ++-- .../settings/system/SystemFeaturesScreen.kt | 16 -- .../ui/screens/support/SupportScreen.kt | 17 +- .../screens/support/license/LicenseScreen.kt | 15 - .../ui/screens/tunnels/TunnelsScreen.kt | 112 ++++---- .../autotunnel/TunnelAutoTunnelScreen.kt | 15 +- .../ui/screens/tunnels/config/ConfigScreen.kt | 33 +-- .../ui/screens/tunnels/sort/SortScreen.kt | 59 ++-- .../tunnels/splittunnel/SplitTunnelScreen.kt | 25 +- .../tunneloptions/TunnelOptionsScreen.kt | 37 +-- .../ui/sideeffect/LocalSideEffect.kt | 20 ++ .../ui/state/SharedAppUiState.kt | 5 +- .../wireguardautotunnel/util/FileUtils.kt | 6 +- .../wireguardautotunnel/util/NumberUtils.kt | 2 +- .../util/extensions/ContextExtensions.kt | 2 +- .../util/extensions/CoroutineExtensions.kt | 20 -- .../util/network/NetworkUtils.kt | 6 +- .../viewmodel/AutoTunnelViewModel.kt | 2 +- .../viewmodel/LoggerViewModel.kt | 4 +- .../viewmodel/ProxySettingsViewModel.kt | 2 +- .../viewmodel/SettingsViewModel.kt | 4 +- .../viewmodel/SharedAppViewModel.kt | 25 +- .../viewmodel/SplitTunnelViewModel.kt | 2 +- .../viewmodel/SupportViewModel.kt | 2 +- .../viewmodel/TunnelsViewModel.kt | 10 +- .../zaneschepke/logcatter/LogFileManager.kt | 4 +- .../logcatter/LogcatStreamReader.kt | 6 +- .../networkmonitor/AndroidNetworkMonitor.kt | 2 +- .../networkmonitor/shizuku/ShizukuShell.kt | 8 +- 72 files changed, 556 insertions(+), 553 deletions(-) create mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/components/currentBackStackEntryAsNavbarState.kt create mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/sideeffect/LocalSideEffect.kt diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index d06e655d..e69de29b 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -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$** { *; } \ No newline at end of file diff --git a/app/src/androidTest/java/com/zaneschepke/wireguardautotunnel/MigrationTest.kt b/app/src/androidTest/java/com/zaneschepke/wireguardautotunnel/MigrationTest.kt index 2e0c1fed..d218f232 100644 --- a/app/src/androidTest/java/com/zaneschepke/wireguardautotunnel/MigrationTest.kt +++ b/app/src/androidTest/java/com/zaneschepke/wireguardautotunnel/MigrationTest.kt @@ -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 { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/MainActivity.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/MainActivity.kt index ce8c2058..fa11628d 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/MainActivity.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/MainActivity.kt @@ -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(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( diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/WireGuardAutoTunnel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/WireGuardAutoTunnel.kt index 096e7135..7c2f839d 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/WireGuardAutoTunnel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/WireGuardAutoTunnel.kt @@ -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 { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/broadcast/KernelReceiver.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/broadcast/KernelReceiver.kt index 96e6cc5c..32bfc9e1 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/broadcast/KernelReceiver.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/broadcast/KernelReceiver.kt @@ -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() { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/broadcast/NotificationActionReceiver.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/broadcast/NotificationActionReceiver.kt index 6cfb0673..c9c89054 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/broadcast/NotificationActionReceiver.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/broadcast/NotificationActionReceiver.kt @@ -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() { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/broadcast/RemoteControlReceiver.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/broadcast/RemoteControlReceiver.kt index 617ad340..bae408a9 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/broadcast/RemoteControlReceiver.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/broadcast/RemoteControlReceiver.kt @@ -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() { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/broadcast/RestartReceiver.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/broadcast/RestartReceiver.kt index f208a59d..3c1fdd1a 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/broadcast/RestartReceiver.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/broadcast/RestartReceiver.kt @@ -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() { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/service/TunnelForegroundService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/service/TunnelForegroundService.kt index 5c8083f4..880e9723 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/service/TunnelForegroundService.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/service/TunnelForegroundService.kt @@ -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() { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/service/autotunnel/AutoTunnelService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/service/autotunnel/AutoTunnelService.kt index 159098ec..a5ef0270 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/service/autotunnel/AutoTunnelService.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/service/autotunnel/AutoTunnelService.kt @@ -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() { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/service/tile/AutoTunnelControlTile.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/service/tile/AutoTunnelControlTile.kt index 846b6992..0efaff9c 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/service/tile/AutoTunnelControlTile.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/service/tile/AutoTunnelControlTile.kt @@ -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 { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/service/tile/TunnelControlTile.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/service/tile/TunnelControlTile.kt index c5b259e3..cfa89561 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/service/tile/TunnelControlTile.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/service/tile/TunnelControlTile.kt @@ -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 { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/shortcut/ShortcutsActivity.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/shortcut/ShortcutsActivity.kt index 71c2c6bc..f8723a1b 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/shortcut/ShortcutsActivity.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/shortcut/ShortcutsActivity.kt @@ -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() { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/tunnel/BaseTunnel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/tunnel/BaseTunnel.kt index de7eecfe..996382cc 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/tunnel/BaseTunnel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/tunnel/BaseTunnel.kt @@ -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, diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/tunnel/KernelTunnel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/tunnel/KernelTunnel.kt index 75905752..2f065527 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/tunnel/KernelTunnel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/tunnel/KernelTunnel.kt @@ -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 diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/tunnel/TunnelManager.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/tunnel/TunnelManager.kt index b0e815cc..297bb958 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/tunnel/TunnelManager.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/tunnel/TunnelManager.kt @@ -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 diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/tunnel/TunnelMonitor.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/tunnel/TunnelMonitor.kt index e548447c..234ef688 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/tunnel/TunnelMonitor.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/tunnel/TunnelMonitor.kt @@ -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 diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/tunnel/TunnelProvider.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/tunnel/TunnelProvider.kt index c1a58d44..e3c938c9 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/tunnel/TunnelProvider.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/tunnel/TunnelProvider.kt @@ -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. */ diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/tunnel/UserspaceTunnel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/tunnel/UserspaceTunnel.kt index 7856b1ca..80f34541 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/tunnel/UserspaceTunnel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/tunnel/UserspaceTunnel.kt @@ -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 diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/worker/ServiceWorker.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/worker/ServiceWorker.kt index 7c8b7bd3..e1af0b98 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/worker/ServiceWorker.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/worker/ServiceWorker.kt @@ -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 diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/DataStoreManager.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/DataStoreManager.kt index afe347db..2b68d4c7 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/DataStoreManager.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/DataStoreManager.kt @@ -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, diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/GitHubUpdateRepository.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/GitHubUpdateRepository.kt index b8681b8e..09c33b5c 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/GitHubUpdateRepository.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/GitHubUpdateRepository.kt @@ -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, diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/InstalledAndroidPackageRepository.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/InstalledAndroidPackageRepository.kt index 0d3281e7..273694a2 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/InstalledAndroidPackageRepository.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/InstalledAndroidPackageRepository.kt @@ -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( diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/di/AppModule.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/di/AppModule.kt index 3dddb594..9124a266 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/di/AppModule.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/di/AppModule.kt @@ -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) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/di/RepositoryModule.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/di/RepositoryModule.kt index 47f44f36..ad0030a4 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/di/RepositoryModule.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/di/RepositoryModule.kt @@ -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) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/di/TunnelModule.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/di/TunnelModule.kt index 038ffb85..c9bf8b90 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/di/TunnelModule.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/di/TunnelModule.kt @@ -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) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/domain/model/TunnelConf.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/domain/model/TunnelConf.kt index 989ac075..9ec3d46d 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/domain/model/TunnelConf.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/domain/model/TunnelConf.kt @@ -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( diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/domain/repository/EffectRepository.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/domain/repository/EffectRepository.kt index 4da14c9e..188eb4c1 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/domain/repository/EffectRepository.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/domain/repository/EffectRepository.kt @@ -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 { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/Route.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/Route.kt index 509fb3a2..0960fc71 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/Route.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/Route.kt @@ -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() } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/components/BottomNavbar.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/components/BottomNavbar.kt index 00b732c0..5f0bbe64 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/components/BottomNavbar.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/components/BottomNavbar.kt @@ -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 { @@ -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, ), ) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/components/currentBackStackEntryAsNavbarState.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/components/currentBackStackEntryAsNavbarState.kt new file mode 100644 index 00000000..35f14bbc --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/components/currentBackStackEntryAsNavbarState.kt @@ -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 { + 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.Lock::class.simpleName -> backStackEntry?.toRoute() + Route.License::class.simpleName -> backStackEntry?.toRoute() + Route.Logs::class.simpleName -> backStackEntry?.toRoute() + Route.Appearance::class.simpleName -> + backStackEntry?.toRoute() + Route.Language::class.simpleName -> backStackEntry?.toRoute() + Route.Display::class.simpleName -> backStackEntry?.toRoute() + Route.Tunnels::class.simpleName -> backStackEntry?.toRoute() + Route.TunnelOptions::class.simpleName -> + backStackEntry?.toRoute() + Route.Config::class.simpleName -> backStackEntry?.toRoute() + Route.SplitTunnel::class.simpleName -> + backStackEntry?.toRoute() + Route.TunnelAutoTunnel::class.simpleName -> + backStackEntry?.toRoute() + Route.Sort::class.simpleName -> backStackEntry?.toRoute() + Route.Settings::class.simpleName -> backStackEntry?.toRoute() + Route.TunnelMonitoring::class.simpleName -> + backStackEntry?.toRoute() + Route.SystemFeatures::class.simpleName -> + backStackEntry?.toRoute() + Route.Dns::class.simpleName -> backStackEntry?.toRoute() + Route.ProxySettings::class.simpleName -> + backStackEntry?.toRoute() + Route.AutoTunnel::class.simpleName -> + backStackEntry?.toRoute() + Route.AdvancedAutoTunnel::class.simpleName -> + backStackEntry?.toRoute() + Route.WifiDetectionMethod::class.simpleName -> + backStackEntry?.toRoute() + Route.LocationDisclosure::class.simpleName -> + backStackEntry?.toRoute() + 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() + } + } +} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/AutoTunnelScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/AutoTunnelScreen.kt index 07236a5c..60190912 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/AutoTunnelScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/AutoTunnelScreen.kt @@ -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 diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/advanced/AutoTunnelAdvancedScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/advanced/AutoTunnelAdvancedScreen.kt index 329ca8e9..04c5117d 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/advanced/AutoTunnelAdvancedScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/advanced/AutoTunnelAdvancedScreen.kt @@ -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), diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/detection/WifiDetectionMethodScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/detection/WifiDetectionMethodScreen.kt index 8d9aa02a..4a746ae0 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/detection/WifiDetectionMethodScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/detection/WifiDetectionMethodScreen.kt @@ -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), diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/disclosure/LocationDisclosureScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/disclosure/LocationDisclosureScreen.kt index 1fc14533..9ff9d677 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/disclosure/LocationDisclosureScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/disclosure/LocationDisclosureScreen.kt @@ -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() } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/pin/PinLockScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/pin/PinLockScreen.kt index 6e3faa46..cc53d425 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/pin/PinLockScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/pin/PinLockScreen.kt @@ -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( diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsScreen.kt index bb4c5a34..bae640a2 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsScreen.kt @@ -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) { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/AppearanceScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/AppearanceScreen.kt index 9f8c5f5b..ded533c7 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/AppearanceScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/AppearanceScreen.kt @@ -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), diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/display/DisplayScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/display/DisplayScreen.kt index 4643c592..0cf25370 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/display/DisplayScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/display/DisplayScreen.kt @@ -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), diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/language/LanguageScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/language/LanguageScreen.kt index 423c0686..4a7dee3f 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/language/LanguageScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/language/LanguageScreen.kt @@ -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 { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/dns/DnsSettingsScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/dns/DnsSettingsScreen.kt index 28aa8ba8..b677f69c 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/dns/DnsSettingsScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/dns/DnsSettingsScreen.kt @@ -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), diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/logs/LogsScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/logs/LogsScreen.kt index d854709a..0c2856cf 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/logs/LogsScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/logs/LogsScreen.kt @@ -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) { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/monitoring/TunnelMonitoringScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/monitoring/TunnelMonitoringScreen.kt index fe639a2e..e38bfbea 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/monitoring/TunnelMonitoringScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/monitoring/TunnelMonitoringScreen.kt @@ -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), diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/proxy/ProxySettingsScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/proxy/ProxySettingsScreen.kt index 99cc35c0..abf5bacb 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/proxy/ProxySettingsScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/proxy/ProxySettingsScreen.kt @@ -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() diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/SystemFeaturesScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/SystemFeaturesScreen.kt index 8aab2252..8228537f 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/SystemFeaturesScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/SystemFeaturesScreen.kt @@ -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( diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/SupportScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/SupportScreen.kt index 4187f04b..5b474ce0 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/SupportScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/SupportScreen.kt @@ -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() }, diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/license/LicenseScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/license/LicenseScreen.kt index 71d112b2..53794720 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/license/LicenseScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/license/LicenseScreen.kt @@ -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>(emptyList()) } - LaunchedEffect(Unit) { - sharedViewModel.updateNavbarState( - NavbarState( - showBottomItems = true, - topTitle = { Text(stringResource(R.string.licenses)) }, - ) - ) - } - LaunchedEffect(Unit) { licenses = loadLicenseeJson(context) } if (licenses.isEmpty()) { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/TunnelsScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/TunnelsScreen.kt index a79b4501..e89379f3 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/TunnelsScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/TunnelsScreen.kt @@ -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 {} +} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/autotunnel/TunnelAutoTunnelScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/autotunnel/TunnelAutoTunnelScreen.kt index ec94bb38..510bfedd 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/autotunnel/TunnelAutoTunnelScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/autotunnel/TunnelAutoTunnelScreen.kt @@ -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), diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/config/ConfigScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/config/ConfigScreen.kt index 3183dd46..998396c2 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/config/ConfigScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/config/ConfigScreen.kt @@ -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) { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/sort/SortScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/sort/SortScreen.kt index 3f710997..db449829 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/sort/SortScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/sort/SortScreen.kt @@ -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(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() diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/splittunnel/SplitTunnelScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/splittunnel/SplitTunnelScreen.kt index 2bff4d1d..ad06f240 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/splittunnel/SplitTunnelScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/splittunnel/SplitTunnelScreen.kt @@ -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( diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/tunneloptions/TunnelOptionsScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/tunneloptions/TunnelOptionsScreen.kt index ca02c7b2..dfd7b6f7 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/tunneloptions/TunnelOptionsScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/tunneloptions/TunnelOptionsScreen.kt @@ -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) { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/sideeffect/LocalSideEffect.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/sideeffect/LocalSideEffect.kt new file mode 100644 index 00000000..9b235dfc --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/sideeffect/LocalSideEffect.kt @@ -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() + } +} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/state/SharedAppUiState.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/state/SharedAppUiState.kt index d0821945..224ce5ec 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/state/SharedAppUiState.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/state/SharedAppUiState.kt @@ -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 = emptyList(), val settings: GeneralSettings = GeneralSettings(), - val navBarState: NavbarState = NavbarState(), + val topNavActions: (@Composable () -> Unit)? = null, ) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/FileUtils.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/FileUtils.kt index 92d46519..44bf45cc 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/FileUtils.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/FileUtils.kt @@ -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) { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/NumberUtils.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/NumberUtils.kt index f3afaf19..dd5b76db 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/NumberUtils.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/NumberUtils.kt @@ -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 diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/extensions/ContextExtensions.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/extensions/ContextExtensions.kt index d5488b49..b67da3a2 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/extensions/ContextExtensions.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/extensions/ContextExtensions.kt @@ -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 { return kotlin diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/extensions/CoroutineExtensions.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/extensions/CoroutineExtensions.kt index 6ad3e668..e649ae8c 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/extensions/CoroutineExtensions.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/extensions/CoroutineExtensions.kt @@ -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 Flow>.distinctByKeys(): Flow> { return distinctUntilChanged { old, new -> old.keys == new.keys } } - -fun 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) - } - } -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/network/NetworkUtils.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/network/NetworkUtils.kt index 9164fee7..def8498f 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/network/NetworkUtils.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/network/NetworkUtils.kt @@ -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) { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/AutoTunnelViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/AutoTunnelViewModel.kt index a6b4f63a..51a1659e 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/AutoTunnelViewModel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/AutoTunnelViewModel.kt @@ -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 diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/LoggerViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/LoggerViewModel.kt index 408790eb..877c5d6a 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/LoggerViewModel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/LoggerViewModel.kt @@ -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 diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/ProxySettingsViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/ProxySettingsViewModel.kt index 22957511..16ba7a64 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/ProxySettingsViewModel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/ProxySettingsViewModel.kt @@ -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 diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/SettingsViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/SettingsViewModel.kt index 73dee9d6..70f8ad5c 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/SettingsViewModel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/SettingsViewModel.kt @@ -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 diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/SharedAppViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/SharedAppViewModel.kt index cbab062b..0e298828 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/SharedAppViewModel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/SharedAppViewModel.kt @@ -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, @IoDispatcher private val ioDispatcher: CoroutineDispatcher, -) : ContainerHost, ViewModel() { +) : ContainerHost, ViewModel() { val globalSideEffect = globalEffectRepository.flow override val container = - container( + container( 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) { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/SplitTunnelViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/SplitTunnelViewModel.kt index 95788ae8..42a11b0e 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/SplitTunnelViewModel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/SplitTunnelViewModel.kt @@ -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 diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/SupportViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/SupportViewModel.kt index 5d0adbb7..243e0c42 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/SupportViewModel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/SupportViewModel.kt @@ -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 diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/TunnelsViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/TunnelsViewModel.kt index 8d6b59da..c667f0cf 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/TunnelsViewModel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/TunnelsViewModel.kt @@ -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 diff --git a/logcatter/src/main/java/com/zaneschepke/logcatter/LogFileManager.kt b/logcatter/src/main/java/com/zaneschepke/logcatter/LogFileManager.kt index 02b055d8..f7456819 100644 --- a/logcatter/src/main/java/com/zaneschepke/logcatter/LogFileManager.kt +++ b/logcatter/src/main/java/com/zaneschepke/logcatter/LogFileManager.kt @@ -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, diff --git a/logcatter/src/main/java/com/zaneschepke/logcatter/LogcatStreamReader.kt b/logcatter/src/main/java/com/zaneschepke/logcatter/LogcatStreamReader.kt index 9324376f..d06d1a0c 100644 --- a/logcatter/src/main/java/com/zaneschepke/logcatter/LogcatStreamReader.kt +++ b/logcatter/src/main/java/com/zaneschepke/logcatter/LogcatStreamReader.kt @@ -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 diff --git a/networkmonitor/src/main/java/com/zaneschepke/networkmonitor/AndroidNetworkMonitor.kt b/networkmonitor/src/main/java/com/zaneschepke/networkmonitor/AndroidNetworkMonitor.kt index ce8a5978..8905042e 100644 --- a/networkmonitor/src/main/java/com/zaneschepke/networkmonitor/AndroidNetworkMonitor.kt +++ b/networkmonitor/src/main/java/com/zaneschepke/networkmonitor/AndroidNetworkMonitor.kt @@ -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, diff --git a/networkmonitor/src/main/java/com/zaneschepke/networkmonitor/shizuku/ShizukuShell.kt b/networkmonitor/src/main/java/com/zaneschepke/networkmonitor/shizuku/ShizukuShell.kt index 1d777ff3..8ad0cd77 100644 --- a/networkmonitor/src/main/java/com/zaneschepke/networkmonitor/shizuku/ShizukuShell.kt +++ b/networkmonitor/src/main/java/com/zaneschepke/networkmonitor/shizuku/ShizukuShell.kt @@ -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 {