diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/WireGuardAutoTunnel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/WireGuardAutoTunnel.kt index 7a6935b..e9f40c5 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/WireGuardAutoTunnel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/WireGuardAutoTunnel.kt @@ -6,6 +6,7 @@ import android.os.StrictMode import android.os.StrictMode.ThreadPolicy import com.zaneschepke.logcatter.LogReader import com.zaneschepke.wireguardautotunnel.data.datastore.LocaleStorage +import com.zaneschepke.wireguardautotunnel.data.repository.AppStateRepository import com.zaneschepke.wireguardautotunnel.module.ApplicationScope import com.zaneschepke.wireguardautotunnel.module.IoDispatcher import com.zaneschepke.wireguardautotunnel.util.LocaleUtil @@ -32,6 +33,9 @@ class WireGuardAutoTunnel : Application() { @Inject lateinit var logReader: LogReader + @Inject + lateinit var appStateRepository: AppStateRepository + @Inject @IoDispatcher lateinit var ioDispatcher: CoroutineDispatcher @@ -54,7 +58,10 @@ class WireGuardAutoTunnel : Application() { } if (!isRunningOnTv()) { applicationScope.launch(ioDispatcher) { - logReader.start() + if (appStateRepository.isLocalLogsEnabled()) { + Timber.d("Starting logger") + logReader.start() + } } } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/datastore/DataStoreManager.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/datastore/DataStoreManager.kt index 5ed880d..d6c1bfc 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/datastore/DataStoreManager.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/datastore/DataStoreManager.kt @@ -26,6 +26,7 @@ class DataStoreManager( val currentSSID = stringPreferencesKey("CURRENT_SSID") val pinLockEnabled = booleanPreferencesKey("PIN_LOCK_ENABLED") val tunnelStatsExpanded = booleanPreferencesKey("TUNNEL_STATS_EXPANDED") + val isLocalLogsEnabled = booleanPreferencesKey("LOCAL_LOGS_ENABLED") val theme = stringPreferencesKey("THEME") } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/domain/GeneralState.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/domain/GeneralState.kt index 59e4b15..2c2c6b2 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/domain/GeneralState.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/domain/GeneralState.kt @@ -7,6 +7,7 @@ data class GeneralState( val isBatteryOptimizationDisableShown: Boolean = BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT, val isPinLockEnabled: Boolean = PIN_LOCK_ENABLED_DEFAULT, val isTunnelStatsExpanded: Boolean = IS_TUNNEL_STATS_EXPANDED, + val isLocalLogsEnabled: Boolean = IS_LOGS_ENABLED_DEFAULT, val theme: Theme = Theme.AUTOMATIC, ) { companion object { @@ -14,5 +15,6 @@ data class GeneralState( const val BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT = false const val PIN_LOCK_ENABLED_DEFAULT = false const val IS_TUNNEL_STATS_EXPANDED = false + const val IS_LOGS_ENABLED_DEFAULT = false } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/AppStateRepository.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/AppStateRepository.kt index 7e5f007..2f9a610 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/AppStateRepository.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/AppStateRepository.kt @@ -29,5 +29,9 @@ interface AppStateRepository { suspend fun getTheme(): Theme + suspend fun isLocalLogsEnabled(): Boolean + + suspend fun setLocalLogsEnabled(enabled: Boolean) + val generalStateFlow: Flow } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/DataStoreAppStateRepository.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/DataStoreAppStateRepository.kt index 39dcfc8..796f4db 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/DataStoreAppStateRepository.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/DataStoreAppStateRepository.kt @@ -69,6 +69,14 @@ class DataStoreAppStateRepository( } ?: Theme.AUTOMATIC } + override suspend fun isLocalLogsEnabled(): Boolean { + return dataStoreManager.getFromStore(DataStoreManager.isLocalLogsEnabled) ?: GeneralState.IS_LOGS_ENABLED_DEFAULT + } + + override suspend fun setLocalLogsEnabled(enabled: Boolean) { + dataStoreManager.saveToDataStore(DataStoreManager.isLocalLogsEnabled, enabled) + } + override val generalStateFlow: Flow = dataStoreManager.preferencesFlow.map { prefs -> prefs?.let { pref -> @@ -84,6 +92,7 @@ class DataStoreAppStateRepository( pref[DataStoreManager.pinLockEnabled] ?: GeneralState.PIN_LOCK_ENABLED_DEFAULT, isTunnelStatsExpanded = pref[DataStoreManager.tunnelStatsExpanded] ?: GeneralState.IS_TUNNEL_STATS_EXPANDED, + isLocalLogsEnabled = pref[DataStoreManager.isLocalLogsEnabled] ?: GeneralState.IS_LOGS_ENABLED_DEFAULT, theme = getTheme(), ) } catch (e: IllegalArgumentException) { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt index 98982f6..66d51b7 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt @@ -49,7 +49,7 @@ constructor( vpnState.copy( tunnelConfig = tunnels.firstOrNull { it.id == vpnState.tunnelConfig?.id }, ) - }.stateIn(applicationScope, SharingStarted.Lazily, VpnState()) + }.stateIn(applicationScope, SharingStarted.Eagerly, VpnState()) private var statsJob: Job? = null diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/AppViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/AppViewModel.kt index 8ca0d21..ed458f4 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/AppViewModel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/AppViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.wireguard.android.backend.WgQuickBackend import com.wireguard.android.util.RootShell +import com.zaneschepke.logcatter.LogReader import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository @@ -25,6 +26,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.takeWhile +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.plus import kotlinx.coroutines.withContext @@ -41,6 +43,7 @@ constructor( @IoDispatcher private val ioDispatcher: CoroutineDispatcher, @AppShell private val rootShell: Provider, private val serviceManager: ServiceManager, + private val logReader: LogReader, ) : ViewModel() { val uiState = @@ -64,11 +67,14 @@ constructor( AppUiState(), ) - private val _isAppReady = MutableStateFlow(false) + private val _isAppReady = MutableStateFlow(false) val isAppReady = _isAppReady.asStateFlow() + private val _configurationChange = MutableStateFlow(false) + val configurationChange = _configurationChange.asStateFlow() + init { - viewModelScope.launch { + viewModelScope.launch(ioDispatcher) { initPin() initAutoTunnel() initTunnel() @@ -84,7 +90,6 @@ constructor( } private suspend fun initTunnel() { - if (tunnelService.get().getState() == TunnelState.UP) tunnelService.get().startStatsJob() val activeTunnels = appDataRepository.tunnels.getActive() if (activeTunnels.isNotEmpty() && tunnelService.get().getState() == TunnelState.DOWN @@ -116,6 +121,22 @@ constructor( appDataRepository.appState.setLocationDisclosureShown(true) } + fun onToggleLocalLogging() = viewModelScope.launch(ioDispatcher) { + with(uiState.value.generalState) { + val toggledOn = !isLocalLogsEnabled + appDataRepository.appState.setLocalLogsEnabled(toggledOn) + if (!toggledOn) onLoggerStop() + _configurationChange.update { + true + } + } + } + + private suspend fun onLoggerStop() { + logReader.stop() + logReader.deleteAndClearLogs() + } + fun onToggleAlwaysOnVPN() = viewModelScope.launch { with(uiState.value.settings) { appDataRepository.settings.save( diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt index 1e2cddb..599f48e 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt @@ -1,6 +1,7 @@ package com.zaneschepke.wireguardautotunnel.ui import android.content.Context +import android.content.Intent import android.os.Bundle import androidx.activity.compose.setContent import androidx.activity.viewModels @@ -24,9 +25,7 @@ import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen @@ -42,7 +41,6 @@ import com.zaneschepke.wireguardautotunnel.data.repository.AppStateRepository import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavBar import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavItem -import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalFocusRequester import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.CustomSnackBar import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarControllerProvider @@ -65,8 +63,8 @@ import com.zaneschepke.wireguardautotunnel.util.LocaleUtil import com.zaneschepke.wireguardautotunnel.util.extensions.requestAutoTunnelTileServiceUpdate import com.zaneschepke.wireguardautotunnel.util.extensions.requestTunnelTileServiceStateUpdate import dagger.hilt.android.AndroidEntryPoint -import timber.log.Timber import javax.inject.Inject +import kotlin.system.exitProcess @AndroidEntryPoint class MainActivity : AppCompatActivity() { @@ -83,11 +81,11 @@ class MainActivity : AppCompatActivity() { @Inject lateinit var tunnelService: TunnelService - private val viewModel by viewModels() - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + val viewModel by viewModels() + installSplashScreen().apply { setKeepOnScreenCondition { !viewModel.isAppReady.value @@ -95,15 +93,23 @@ class MainActivity : AppCompatActivity() { } setContent { - val appUiState by viewModel.uiState.collectAsStateWithLifecycle(lifecycle = this.lifecycle) + val appUiState by viewModel.uiState.collectAsStateWithLifecycle() + val configurationChange by viewModel.configurationChange.collectAsStateWithLifecycle() val navController = rememberNavController() - val rootItemFocusRequester = remember { FocusRequester() } LaunchedEffect(appUiState.tunnels) { - Timber.d("Updating launched") requestTunnelTileServiceStateUpdate() } + LaunchedEffect(configurationChange) { + if (configurationChange) { + Intent(this@MainActivity, MainActivity::class.java).also { + startActivity(it) + exitProcess(0) + } + } + } + LaunchedEffect(appUiState.autoTunnelActive) { requestAutoTunnelTileServiceUpdate() } @@ -114,109 +120,107 @@ class MainActivity : AppCompatActivity() { } } - CompositionLocalProvider(LocalFocusRequester provides rootItemFocusRequester) { - CompositionLocalProvider(LocalNavController provides navController) { - SnackbarControllerProvider { host -> - WireguardAutoTunnelTheme(theme = appUiState.generalState.theme) { - Scaffold( - contentWindowInsets = WindowInsets(0.dp), - snackbarHost = { - SnackbarHost(host) { snackbarData: SnackbarData -> - CustomSnackBar( - snackbarData.visuals.message, - isRtl = false, - containerColor = - MaterialTheme.colorScheme.surfaceColorAtElevation( - 2.dp, - ), - ) - } - }, - bottomBar = { - BottomNavBar( - navController, - listOf( - BottomNavItem( - name = stringResource(R.string.tunnels), - route = Route.Main, - icon = Icons.Rounded.Home, - ), - BottomNavItem( - name = stringResource(R.string.settings), - route = Route.Settings, - icon = Icons.Rounded.Settings, - ), - BottomNavItem( - name = stringResource(R.string.support), - route = Route.Support, - icon = Icons.Rounded.QuestionMark, - ), + CompositionLocalProvider(LocalNavController provides navController) { + SnackbarControllerProvider { host -> + WireguardAutoTunnelTheme(theme = appUiState.generalState.theme) { + Scaffold( + contentWindowInsets = WindowInsets(0.dp), + snackbarHost = { + SnackbarHost(host) { snackbarData: SnackbarData -> + CustomSnackBar( + snackbarData.visuals.message, + isRtl = false, + containerColor = + MaterialTheme.colorScheme.surfaceColorAtElevation( + 2.dp, ), ) - }, - ) { - Box(modifier = Modifier.fillMaxSize().padding(it)) { - NavHost( - navController, - enterTransition = { fadeIn(tween(Constants.TRANSITION_ANIMATION_TIME)) }, - exitTransition = { fadeOut(tween(Constants.TRANSITION_ANIMATION_TIME)) }, - startDestination = (if (appUiState.generalState.isPinLockEnabled == true) Route.Lock else Route.Main), - ) { - composable { - MainScreen( - uiState = appUiState, - ) - } - composable { - SettingsScreen( - appViewModel = viewModel, - uiState = appUiState, - ) - } - composable { - LocationDisclosureScreen(viewModel, appUiState) - } - composable { - AutoTunnelScreen( - appUiState, - ) - } - composable { - AppearanceScreen() - } - composable { - LanguageScreen(localeStorage) - } - composable { - DisplayScreen(appUiState) - } - composable { - SupportScreen() - } - composable { - LogsScreen() - } - composable { - val args = it.toRoute() - ConfigScreen( - tunnelId = args.id, - ) - } - composable { - val args = it.toRoute() - OptionsScreen( - tunnelId = args.id, - appUiState = appUiState, - ) - } - composable { - PinLockScreen( - appViewModel = viewModel, - ) - } - composable { - ScannerScreen() - } + } + }, + bottomBar = { + BottomNavBar( + navController, + listOf( + BottomNavItem( + name = stringResource(R.string.tunnels), + route = Route.Main, + icon = Icons.Rounded.Home, + ), + BottomNavItem( + name = stringResource(R.string.settings), + route = Route.Settings, + icon = Icons.Rounded.Settings, + ), + BottomNavItem( + name = stringResource(R.string.support), + route = Route.Support, + icon = Icons.Rounded.QuestionMark, + ), + ), + ) + }, + ) { + Box(modifier = Modifier.fillMaxSize().padding(it)) { + NavHost( + navController, + enterTransition = { fadeIn(tween(Constants.TRANSITION_ANIMATION_TIME)) }, + exitTransition = { fadeOut(tween(Constants.TRANSITION_ANIMATION_TIME)) }, + startDestination = (if (appUiState.generalState.isPinLockEnabled == true) Route.Lock else Route.Main), + ) { + composable { + MainScreen( + uiState = appUiState, + ) + } + composable { + SettingsScreen( + appViewModel = viewModel, + uiState = appUiState, + ) + } + composable { + LocationDisclosureScreen(viewModel, appUiState) + } + composable { + AutoTunnelScreen( + appUiState, + ) + } + composable { + AppearanceScreen() + } + composable { + LanguageScreen(localeStorage) + } + composable { + DisplayScreen(appUiState) + } + composable { + SupportScreen(appUiState, viewModel) + } + composable { + LogsScreen() + } + composable { + val args = it.toRoute() + ConfigScreen( + tunnelId = args.id, + ) + } + composable { + val args = it.toRoute() + OptionsScreen( + tunnelId = args.id, + appUiState = appUiState, + ) + } + composable { + PinLockScreen( + appViewModel = viewModel, + ) + } + composable { + ScannerScreen() } } } @@ -241,9 +245,4 @@ class MainActivity : AppCompatActivity() { } super.onResume() } - - override fun onDestroy() { - super.onDestroy() - tunnelService.cancelStatsJob() - } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/navigation/LocalNavController.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/navigation/LocalNavController.kt index 767b909..be5c806 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/navigation/LocalNavController.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/navigation/LocalNavController.kt @@ -1,11 +1,8 @@ package com.zaneschepke.wireguardautotunnel.ui.common.navigation import androidx.compose.runtime.compositionLocalOf -import androidx.compose.ui.focus.FocusRequester import androidx.navigation.NavHostController val LocalNavController = compositionLocalOf { error("NavController was not provided") } - -val LocalFocusRequester = compositionLocalOf { error("FocusRequester is not provided") } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/options/OptionsScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/options/OptionsScreen.kt index 5eb3715..480a811 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/options/OptionsScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/options/OptionsScreen.kt @@ -2,7 +2,6 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.options import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -53,7 +52,6 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.isValidIpv4orIpv6Addr import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth -@OptIn(ExperimentalLayoutApi::class) @Composable fun OptionsScreen(optionsViewModel: OptionsViewModel = hiltViewModel(), appUiState: AppUiState, tunnelId: Int) { val navController = LocalNavController.current 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 566b47d..1a29ac8 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 @@ -5,7 +5,6 @@ import androidx.compose.foundation.focusable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState @@ -30,13 +29,11 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.focusRequester 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 com.google.accompanist.permissions.ExperimentalPermissionsApi import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState import com.zaneschepke.wireguardautotunnel.ui.AppUiState @@ -45,7 +42,6 @@ import com.zaneschepke.wireguardautotunnel.ui.Route 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.SurfaceSelectionGroupButton -import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalFocusRequester import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController import com.zaneschepke.wireguardautotunnel.ui.common.prompt.AuthorizationPrompt import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController @@ -59,17 +55,12 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth import com.zaneschepke.wireguardautotunnel.util.extensions.showToast import xyz.teamgravity.pin_lock_compose.PinManager -@OptIn( - ExperimentalPermissionsApi::class, - ExperimentalLayoutApi::class, -) @Composable fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel: AppViewModel, uiState: AppUiState) { val context = LocalContext.current val navController = LocalNavController.current val focusManager = LocalFocusManager.current val snackbar = SnackbarController.current - val rootFocusRequester = LocalFocusRequester.current val isRunningOnTv = remember { context.isRunningOnTv() } val interactionSource = remember { MutableInteractionSource() } @@ -135,32 +126,34 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel: navController.navigate(Route.AutoTunnel) }, trailing = { - ForwardButton(Modifier.focusable().focusRequester(rootFocusRequester)) { navController.navigate(Route.AutoTunnel) } + ForwardButton(Modifier.focusable()) { navController.navigate(Route.AutoTunnel) } }, ), ), ) SurfaceSelectionGroupButton( buildList { + add( + SelectionItem( + Icons.Filled.AppShortcut, + { + ScaledSwitch( + uiState.settings.isShortcutsEnabled, + onClick = { appViewModel.onToggleShortcutsEnabled() }, + ) + }, + title = { + Text( + stringResource(R.string.enabled_app_shortcuts), + style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), + ) + }, + onClick = { appViewModel.onToggleShortcutsEnabled() }, + ), + ) if (!isRunningOnTv) { addAll( listOf( - SelectionItem( - Icons.Filled.AppShortcut, - { - ScaledSwitch( - uiState.settings.isShortcutsEnabled, - onClick = { appViewModel.onToggleShortcutsEnabled() }, - ) - }, - title = { - Text( - stringResource(R.string.enabled_app_shortcuts), - style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), - ) - }, - onClick = { appViewModel.onToggleShortcutsEnabled() }, - ), SelectionItem( Icons.Outlined.VpnLock, { 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 5bf50b2..2bbd4b6 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 @@ -9,12 +9,17 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AttachMoney import androidx.compose.material.icons.filled.Book -import androidx.compose.material.icons.filled.LineStyle import androidx.compose.material.icons.filled.Mail import androidx.compose.material.icons.filled.Policy +import androidx.compose.material.icons.filled.ViewTimeline +import androidx.compose.material.icons.outlined.ViewHeadline import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector @@ -24,24 +29,42 @@ import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp import com.zaneschepke.wireguardautotunnel.BuildConfig import com.zaneschepke.wireguardautotunnel.R +import com.zaneschepke.wireguardautotunnel.ui.AppUiState +import com.zaneschepke.wireguardautotunnel.ui.AppViewModel import com.zaneschepke.wireguardautotunnel.ui.Route +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.SurfaceSelectionGroupButton +import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel import com.zaneschepke.wireguardautotunnel.ui.common.label.VersionLabel import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.ForwardButton import com.zaneschepke.wireguardautotunnel.ui.theme.topPadding +import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv import com.zaneschepke.wireguardautotunnel.util.extensions.launchSupportEmail import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth @Composable -fun SupportScreen() { +fun SupportScreen(appUiState: AppUiState, appViewModel: AppViewModel) { val context = LocalContext.current val navController = LocalNavController.current + var showDialog by remember { mutableStateOf(false) } + + if (showDialog) { + InfoDialog(onAttest = { + showDialog = false + appViewModel.onToggleLocalLogging() + }, onDismiss = { + showDialog = false + }, title = { + Text(stringResource(R.string.configuration_change)) + }, body = { Text(stringResource(R.string.requires_app_relaunch)) }, confirmText = { Text(stringResource(R.string.yes)) }) + } + Column( horizontalAlignment = Alignment.Start, verticalArrangement = Arrangement.spacedBy(24.dp.scaledHeight(), Alignment.Top), @@ -54,56 +77,93 @@ fun SupportScreen() { ) { GroupLabel(stringResource(R.string.thank_you)) SurfaceSelectionGroupButton( - listOf( - SelectionItem( - Icons.Filled.Book, - title = { - Text( - stringResource(R.string.docs_description), - style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), + buildList { + add( + SelectionItem( + Icons.Filled.Book, + title = { + Text( + stringResource(R.string.docs_description), + style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), + ) + }, + trailing = { + ForwardButton { context.openWebUrl(context.getString(R.string.docs_url)) } + }, + onClick = { + context.openWebUrl(context.getString(R.string.docs_url)) + }, + ), + ) + if (!context.isRunningOnTv()) { + add( + SelectionItem( + Icons.Outlined.ViewHeadline, + title = { + Text( + stringResource(R.string.local_logging), + style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), + ) + }, + description = { + Text( + stringResource(R.string.enable_local_logging), + style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.outline), + ) + }, + trailing = { + ScaledSwitch( + appUiState.generalState.isLocalLogsEnabled, + onClick = { + showDialog = true + }, + ) + }, + onClick = { + showDialog = true + }, + ), + ) + if (appUiState.generalState.isLocalLogsEnabled) { + add( + SelectionItem( + Icons.Filled.ViewTimeline, + title = { + Text( + stringResource(R.string.read_logs), + style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), + ) + }, + trailing = { + ForwardButton { + navController.navigate(Route.Logs) + } + }, + onClick = { + navController.navigate(Route.Logs) + }, + ), ) - }, - trailing = { - ForwardButton { context.openWebUrl(context.getString(R.string.docs_url)) } - }, - onClick = { - context.openWebUrl(context.getString(R.string.docs_url)) - }, - ), - SelectionItem( - Icons.Filled.LineStyle, - title = { - Text( - stringResource(R.string.read_logs), - style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), - ) - }, - trailing = { - ForwardButton { - navController.navigate(Route.Logs) - } - }, - onClick = { - navController.navigate(Route.Logs) - }, - ), - SelectionItem( - Icons.Filled.Policy, - title = { - Text( - stringResource(R.string.privacy_policy), - style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), - ) - }, - trailing = { - ForwardButton { context.openWebUrl(context.getString(R.string.privacy_policy_url)) } - }, - onClick = { - context.openWebUrl(context.getString(R.string.privacy_policy_url)) - }, - ), - - ), + } + } + add( + SelectionItem( + Icons.Filled.Policy, + title = { + Text( + stringResource(R.string.privacy_policy), + style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), + ) + }, + trailing = { + ForwardButton { context.openWebUrl(context.getString(R.string.privacy_policy_url)) } + }, + onClick = { + context.openWebUrl(context.getString(R.string.privacy_policy_url)) + }, + ), + ) + }, ) SurfaceSelectionGroupButton( buildList { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index eb65e79..07fa15c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -229,4 +229,8 @@ Tunnel running Monitoring state changes Donate to project + Local logging + Enable local logging + Configuration change + This change requires an app relaunch. Would you like to proceed?