feat: user logger toggle control
Allow users to toggle on and off local logger. This will hopefully help address #437 Allow shortcuts activation on AndroidTV closes #451
This commit is contained in:
parent
b2938c3ac8
commit
f9ef308f8a
|
@ -6,6 +6,7 @@ import android.os.StrictMode
|
||||||
import android.os.StrictMode.ThreadPolicy
|
import android.os.StrictMode.ThreadPolicy
|
||||||
import com.zaneschepke.logcatter.LogReader
|
import com.zaneschepke.logcatter.LogReader
|
||||||
import com.zaneschepke.wireguardautotunnel.data.datastore.LocaleStorage
|
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.ApplicationScope
|
||||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.util.LocaleUtil
|
import com.zaneschepke.wireguardautotunnel.util.LocaleUtil
|
||||||
|
@ -32,6 +33,9 @@ class WireGuardAutoTunnel : Application() {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var logReader: LogReader
|
lateinit var logReader: LogReader
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var appStateRepository: AppStateRepository
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@IoDispatcher
|
@IoDispatcher
|
||||||
lateinit var ioDispatcher: CoroutineDispatcher
|
lateinit var ioDispatcher: CoroutineDispatcher
|
||||||
|
@ -54,7 +58,10 @@ class WireGuardAutoTunnel : Application() {
|
||||||
}
|
}
|
||||||
if (!isRunningOnTv()) {
|
if (!isRunningOnTv()) {
|
||||||
applicationScope.launch(ioDispatcher) {
|
applicationScope.launch(ioDispatcher) {
|
||||||
logReader.start()
|
if (appStateRepository.isLocalLogsEnabled()) {
|
||||||
|
Timber.d("Starting logger")
|
||||||
|
logReader.start()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ class DataStoreManager(
|
||||||
val currentSSID = stringPreferencesKey("CURRENT_SSID")
|
val currentSSID = stringPreferencesKey("CURRENT_SSID")
|
||||||
val pinLockEnabled = booleanPreferencesKey("PIN_LOCK_ENABLED")
|
val pinLockEnabled = booleanPreferencesKey("PIN_LOCK_ENABLED")
|
||||||
val tunnelStatsExpanded = booleanPreferencesKey("TUNNEL_STATS_EXPANDED")
|
val tunnelStatsExpanded = booleanPreferencesKey("TUNNEL_STATS_EXPANDED")
|
||||||
|
val isLocalLogsEnabled = booleanPreferencesKey("LOCAL_LOGS_ENABLED")
|
||||||
val theme = stringPreferencesKey("THEME")
|
val theme = stringPreferencesKey("THEME")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ data class GeneralState(
|
||||||
val isBatteryOptimizationDisableShown: Boolean = BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT,
|
val isBatteryOptimizationDisableShown: Boolean = BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT,
|
||||||
val isPinLockEnabled: Boolean = PIN_LOCK_ENABLED_DEFAULT,
|
val isPinLockEnabled: Boolean = PIN_LOCK_ENABLED_DEFAULT,
|
||||||
val isTunnelStatsExpanded: Boolean = IS_TUNNEL_STATS_EXPANDED,
|
val isTunnelStatsExpanded: Boolean = IS_TUNNEL_STATS_EXPANDED,
|
||||||
|
val isLocalLogsEnabled: Boolean = IS_LOGS_ENABLED_DEFAULT,
|
||||||
val theme: Theme = Theme.AUTOMATIC,
|
val theme: Theme = Theme.AUTOMATIC,
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -14,5 +15,6 @@ data class GeneralState(
|
||||||
const val BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT = false
|
const val BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT = false
|
||||||
const val PIN_LOCK_ENABLED_DEFAULT = false
|
const val PIN_LOCK_ENABLED_DEFAULT = false
|
||||||
const val IS_TUNNEL_STATS_EXPANDED = false
|
const val IS_TUNNEL_STATS_EXPANDED = false
|
||||||
|
const val IS_LOGS_ENABLED_DEFAULT = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,5 +29,9 @@ interface AppStateRepository {
|
||||||
|
|
||||||
suspend fun getTheme(): Theme
|
suspend fun getTheme(): Theme
|
||||||
|
|
||||||
|
suspend fun isLocalLogsEnabled(): Boolean
|
||||||
|
|
||||||
|
suspend fun setLocalLogsEnabled(enabled: Boolean)
|
||||||
|
|
||||||
val generalStateFlow: Flow<GeneralState>
|
val generalStateFlow: Flow<GeneralState>
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,6 +69,14 @@ class DataStoreAppStateRepository(
|
||||||
} ?: Theme.AUTOMATIC
|
} ?: 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<GeneralState> =
|
override val generalStateFlow: Flow<GeneralState> =
|
||||||
dataStoreManager.preferencesFlow.map { prefs ->
|
dataStoreManager.preferencesFlow.map { prefs ->
|
||||||
prefs?.let { pref ->
|
prefs?.let { pref ->
|
||||||
|
@ -84,6 +92,7 @@ class DataStoreAppStateRepository(
|
||||||
pref[DataStoreManager.pinLockEnabled]
|
pref[DataStoreManager.pinLockEnabled]
|
||||||
?: GeneralState.PIN_LOCK_ENABLED_DEFAULT,
|
?: GeneralState.PIN_LOCK_ENABLED_DEFAULT,
|
||||||
isTunnelStatsExpanded = pref[DataStoreManager.tunnelStatsExpanded] ?: GeneralState.IS_TUNNEL_STATS_EXPANDED,
|
isTunnelStatsExpanded = pref[DataStoreManager.tunnelStatsExpanded] ?: GeneralState.IS_TUNNEL_STATS_EXPANDED,
|
||||||
|
isLocalLogsEnabled = pref[DataStoreManager.isLocalLogsEnabled] ?: GeneralState.IS_LOGS_ENABLED_DEFAULT,
|
||||||
theme = getTheme(),
|
theme = getTheme(),
|
||||||
)
|
)
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
|
|
|
@ -49,7 +49,7 @@ constructor(
|
||||||
vpnState.copy(
|
vpnState.copy(
|
||||||
tunnelConfig = tunnels.firstOrNull { it.id == vpnState.tunnelConfig?.id },
|
tunnelConfig = tunnels.firstOrNull { it.id == vpnState.tunnelConfig?.id },
|
||||||
)
|
)
|
||||||
}.stateIn(applicationScope, SharingStarted.Lazily, VpnState())
|
}.stateIn(applicationScope, SharingStarted.Eagerly, VpnState())
|
||||||
|
|
||||||
private var statsJob: Job? = null
|
private var statsJob: Job? = null
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.wireguard.android.backend.WgQuickBackend
|
import com.wireguard.android.backend.WgQuickBackend
|
||||||
import com.wireguard.android.util.RootShell
|
import com.wireguard.android.util.RootShell
|
||||||
|
import com.zaneschepke.logcatter.LogReader
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
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.onCompletion
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.flow.takeWhile
|
import kotlinx.coroutines.flow.takeWhile
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.plus
|
import kotlinx.coroutines.plus
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
@ -41,6 +43,7 @@ constructor(
|
||||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||||
@AppShell private val rootShell: Provider<RootShell>,
|
@AppShell private val rootShell: Provider<RootShell>,
|
||||||
private val serviceManager: ServiceManager,
|
private val serviceManager: ServiceManager,
|
||||||
|
private val logReader: LogReader,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
val uiState =
|
val uiState =
|
||||||
|
@ -64,11 +67,14 @@ constructor(
|
||||||
AppUiState(),
|
AppUiState(),
|
||||||
)
|
)
|
||||||
|
|
||||||
private val _isAppReady = MutableStateFlow<Boolean>(false)
|
private val _isAppReady = MutableStateFlow(false)
|
||||||
val isAppReady = _isAppReady.asStateFlow()
|
val isAppReady = _isAppReady.asStateFlow()
|
||||||
|
|
||||||
|
private val _configurationChange = MutableStateFlow(false)
|
||||||
|
val configurationChange = _configurationChange.asStateFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch(ioDispatcher) {
|
||||||
initPin()
|
initPin()
|
||||||
initAutoTunnel()
|
initAutoTunnel()
|
||||||
initTunnel()
|
initTunnel()
|
||||||
|
@ -84,7 +90,6 @@ constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun initTunnel() {
|
private suspend fun initTunnel() {
|
||||||
if (tunnelService.get().getState() == TunnelState.UP) tunnelService.get().startStatsJob()
|
|
||||||
val activeTunnels = appDataRepository.tunnels.getActive()
|
val activeTunnels = appDataRepository.tunnels.getActive()
|
||||||
if (activeTunnels.isNotEmpty() &&
|
if (activeTunnels.isNotEmpty() &&
|
||||||
tunnelService.get().getState() == TunnelState.DOWN
|
tunnelService.get().getState() == TunnelState.DOWN
|
||||||
|
@ -116,6 +121,22 @@ constructor(
|
||||||
appDataRepository.appState.setLocationDisclosureShown(true)
|
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 {
|
fun onToggleAlwaysOnVPN() = viewModelScope.launch {
|
||||||
with(uiState.value.settings) {
|
with(uiState.value.settings) {
|
||||||
appDataRepository.settings.save(
|
appDataRepository.settings.save(
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.ui
|
package com.zaneschepke.wireguardautotunnel.ui
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
|
@ -24,9 +25,7 @@ import androidx.compose.material3.surfaceColorAtElevation
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
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.service.tunnel.TunnelService
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavBar
|
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavBar
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavItem
|
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.navigation.LocalNavController
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.CustomSnackBar
|
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.CustomSnackBar
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarControllerProvider
|
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.requestAutoTunnelTileServiceUpdate
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.requestTunnelTileServiceStateUpdate
|
import com.zaneschepke.wireguardautotunnel.util.extensions.requestTunnelTileServiceStateUpdate
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import timber.log.Timber
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
@ -83,11 +81,11 @@ class MainActivity : AppCompatActivity() {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var tunnelService: TunnelService
|
lateinit var tunnelService: TunnelService
|
||||||
|
|
||||||
private val viewModel by viewModels<AppViewModel>()
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
val viewModel by viewModels<AppViewModel>()
|
||||||
|
|
||||||
installSplashScreen().apply {
|
installSplashScreen().apply {
|
||||||
setKeepOnScreenCondition {
|
setKeepOnScreenCondition {
|
||||||
!viewModel.isAppReady.value
|
!viewModel.isAppReady.value
|
||||||
|
@ -95,15 +93,23 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
setContent {
|
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 navController = rememberNavController()
|
||||||
val rootItemFocusRequester = remember { FocusRequester() }
|
|
||||||
|
|
||||||
LaunchedEffect(appUiState.tunnels) {
|
LaunchedEffect(appUiState.tunnels) {
|
||||||
Timber.d("Updating launched")
|
|
||||||
requestTunnelTileServiceStateUpdate()
|
requestTunnelTileServiceStateUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(configurationChange) {
|
||||||
|
if (configurationChange) {
|
||||||
|
Intent(this@MainActivity, MainActivity::class.java).also {
|
||||||
|
startActivity(it)
|
||||||
|
exitProcess(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(appUiState.autoTunnelActive) {
|
LaunchedEffect(appUiState.autoTunnelActive) {
|
||||||
requestAutoTunnelTileServiceUpdate()
|
requestAutoTunnelTileServiceUpdate()
|
||||||
}
|
}
|
||||||
|
@ -114,109 +120,107 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CompositionLocalProvider(LocalFocusRequester provides rootItemFocusRequester) {
|
CompositionLocalProvider(LocalNavController provides navController) {
|
||||||
CompositionLocalProvider(LocalNavController provides navController) {
|
SnackbarControllerProvider { host ->
|
||||||
SnackbarControllerProvider { host ->
|
WireguardAutoTunnelTheme(theme = appUiState.generalState.theme) {
|
||||||
WireguardAutoTunnelTheme(theme = appUiState.generalState.theme) {
|
Scaffold(
|
||||||
Scaffold(
|
contentWindowInsets = WindowInsets(0.dp),
|
||||||
contentWindowInsets = WindowInsets(0.dp),
|
snackbarHost = {
|
||||||
snackbarHost = {
|
SnackbarHost(host) { snackbarData: SnackbarData ->
|
||||||
SnackbarHost(host) { snackbarData: SnackbarData ->
|
CustomSnackBar(
|
||||||
CustomSnackBar(
|
snackbarData.visuals.message,
|
||||||
snackbarData.visuals.message,
|
isRtl = false,
|
||||||
isRtl = false,
|
containerColor =
|
||||||
containerColor =
|
MaterialTheme.colorScheme.surfaceColorAtElevation(
|
||||||
MaterialTheme.colorScheme.surfaceColorAtElevation(
|
2.dp,
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
) {
|
},
|
||||||
Box(modifier = Modifier.fillMaxSize().padding(it)) {
|
bottomBar = {
|
||||||
NavHost(
|
BottomNavBar(
|
||||||
navController,
|
navController,
|
||||||
enterTransition = { fadeIn(tween(Constants.TRANSITION_ANIMATION_TIME)) },
|
listOf(
|
||||||
exitTransition = { fadeOut(tween(Constants.TRANSITION_ANIMATION_TIME)) },
|
BottomNavItem(
|
||||||
startDestination = (if (appUiState.generalState.isPinLockEnabled == true) Route.Lock else Route.Main),
|
name = stringResource(R.string.tunnels),
|
||||||
) {
|
route = Route.Main,
|
||||||
composable<Route.Main> {
|
icon = Icons.Rounded.Home,
|
||||||
MainScreen(
|
),
|
||||||
uiState = appUiState,
|
BottomNavItem(
|
||||||
)
|
name = stringResource(R.string.settings),
|
||||||
}
|
route = Route.Settings,
|
||||||
composable<Route.Settings> {
|
icon = Icons.Rounded.Settings,
|
||||||
SettingsScreen(
|
),
|
||||||
appViewModel = viewModel,
|
BottomNavItem(
|
||||||
uiState = appUiState,
|
name = stringResource(R.string.support),
|
||||||
)
|
route = Route.Support,
|
||||||
}
|
icon = Icons.Rounded.QuestionMark,
|
||||||
composable<Route.LocationDisclosure> {
|
),
|
||||||
LocationDisclosureScreen(viewModel, appUiState)
|
),
|
||||||
}
|
)
|
||||||
composable<Route.AutoTunnel> {
|
},
|
||||||
AutoTunnelScreen(
|
) {
|
||||||
appUiState,
|
Box(modifier = Modifier.fillMaxSize().padding(it)) {
|
||||||
)
|
NavHost(
|
||||||
}
|
navController,
|
||||||
composable<Route.Appearance> {
|
enterTransition = { fadeIn(tween(Constants.TRANSITION_ANIMATION_TIME)) },
|
||||||
AppearanceScreen()
|
exitTransition = { fadeOut(tween(Constants.TRANSITION_ANIMATION_TIME)) },
|
||||||
}
|
startDestination = (if (appUiState.generalState.isPinLockEnabled == true) Route.Lock else Route.Main),
|
||||||
composable<Route.Language> {
|
) {
|
||||||
LanguageScreen(localeStorage)
|
composable<Route.Main> {
|
||||||
}
|
MainScreen(
|
||||||
composable<Route.Display> {
|
uiState = appUiState,
|
||||||
DisplayScreen(appUiState)
|
)
|
||||||
}
|
}
|
||||||
composable<Route.Support> {
|
composable<Route.Settings> {
|
||||||
SupportScreen()
|
SettingsScreen(
|
||||||
}
|
appViewModel = viewModel,
|
||||||
composable<Route.Logs> {
|
uiState = appUiState,
|
||||||
LogsScreen()
|
)
|
||||||
}
|
}
|
||||||
composable<Route.Config> {
|
composable<Route.LocationDisclosure> {
|
||||||
val args = it.toRoute<Route.Config>()
|
LocationDisclosureScreen(viewModel, appUiState)
|
||||||
ConfigScreen(
|
}
|
||||||
tunnelId = args.id,
|
composable<Route.AutoTunnel> {
|
||||||
)
|
AutoTunnelScreen(
|
||||||
}
|
appUiState,
|
||||||
composable<Route.Option> {
|
)
|
||||||
val args = it.toRoute<Route.Option>()
|
}
|
||||||
OptionsScreen(
|
composable<Route.Appearance> {
|
||||||
tunnelId = args.id,
|
AppearanceScreen()
|
||||||
appUiState = appUiState,
|
}
|
||||||
)
|
composable<Route.Language> {
|
||||||
}
|
LanguageScreen(localeStorage)
|
||||||
composable<Route.Lock> {
|
}
|
||||||
PinLockScreen(
|
composable<Route.Display> {
|
||||||
appViewModel = viewModel,
|
DisplayScreen(appUiState)
|
||||||
)
|
}
|
||||||
}
|
composable<Route.Support> {
|
||||||
composable<Route.Scanner> {
|
SupportScreen(appUiState, viewModel)
|
||||||
ScannerScreen()
|
}
|
||||||
}
|
composable<Route.Logs> {
|
||||||
|
LogsScreen()
|
||||||
|
}
|
||||||
|
composable<Route.Config> {
|
||||||
|
val args = it.toRoute<Route.Config>()
|
||||||
|
ConfigScreen(
|
||||||
|
tunnelId = args.id,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
composable<Route.Option> {
|
||||||
|
val args = it.toRoute<Route.Option>()
|
||||||
|
OptionsScreen(
|
||||||
|
tunnelId = args.id,
|
||||||
|
appUiState = appUiState,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
composable<Route.Lock> {
|
||||||
|
PinLockScreen(
|
||||||
|
appViewModel = viewModel,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
composable<Route.Scanner> {
|
||||||
|
ScannerScreen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -241,9 +245,4 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
super.onResume()
|
super.onResume()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
super.onDestroy()
|
|
||||||
tunnelService.cancelStatsJob()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.ui.common.navigation
|
package com.zaneschepke.wireguardautotunnel.ui.common.navigation
|
||||||
|
|
||||||
import androidx.compose.runtime.compositionLocalOf
|
import androidx.compose.runtime.compositionLocalOf
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
|
|
||||||
val LocalNavController = compositionLocalOf<NavHostController> {
|
val LocalNavController = compositionLocalOf<NavHostController> {
|
||||||
error("NavController was not provided")
|
error("NavController was not provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
val LocalFocusRequester = compositionLocalOf<FocusRequester> { error("FocusRequester is not provided") }
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.options
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
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.scaledHeight
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
|
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
|
||||||
|
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun OptionsScreen(optionsViewModel: OptionsViewModel = hiltViewModel(), appUiState: AppUiState, tunnelId: Int) {
|
fun OptionsScreen(optionsViewModel: OptionsViewModel = hiltViewModel(), appUiState: AppUiState, tunnelId: Int) {
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
|
|
|
@ -5,7 +5,6 @@ import androidx.compose.foundation.focusable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
@ -30,13 +29,11 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.focusRequester
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.AppUiState
|
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.ScaledSwitch
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
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.navigation.LocalNavController
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.AuthorizationPrompt
|
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.AuthorizationPrompt
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
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 com.zaneschepke.wireguardautotunnel.util.extensions.showToast
|
||||||
import xyz.teamgravity.pin_lock_compose.PinManager
|
import xyz.teamgravity.pin_lock_compose.PinManager
|
||||||
|
|
||||||
@OptIn(
|
|
||||||
ExperimentalPermissionsApi::class,
|
|
||||||
ExperimentalLayoutApi::class,
|
|
||||||
)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel: AppViewModel, uiState: AppUiState) {
|
fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel: AppViewModel, uiState: AppUiState) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
val focusManager = LocalFocusManager.current
|
val focusManager = LocalFocusManager.current
|
||||||
val snackbar = SnackbarController.current
|
val snackbar = SnackbarController.current
|
||||||
val rootFocusRequester = LocalFocusRequester.current
|
|
||||||
val isRunningOnTv = remember { context.isRunningOnTv() }
|
val isRunningOnTv = remember { context.isRunningOnTv() }
|
||||||
|
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
@ -135,32 +126,34 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||||
navController.navigate(Route.AutoTunnel)
|
navController.navigate(Route.AutoTunnel)
|
||||||
},
|
},
|
||||||
trailing = {
|
trailing = {
|
||||||
ForwardButton(Modifier.focusable().focusRequester(rootFocusRequester)) { navController.navigate(Route.AutoTunnel) }
|
ForwardButton(Modifier.focusable()) { navController.navigate(Route.AutoTunnel) }
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
SurfaceSelectionGroupButton(
|
SurfaceSelectionGroupButton(
|
||||||
buildList {
|
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) {
|
if (!isRunningOnTv) {
|
||||||
addAll(
|
addAll(
|
||||||
listOf(
|
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(
|
SelectionItem(
|
||||||
Icons.Outlined.VpnLock,
|
Icons.Outlined.VpnLock,
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,12 +9,17 @@ import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.AttachMoney
|
import androidx.compose.material.icons.filled.AttachMoney
|
||||||
import androidx.compose.material.icons.filled.Book
|
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.Mail
|
||||||
import androidx.compose.material.icons.filled.Policy
|
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.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
@ -24,24 +29,42 @@ import androidx.compose.ui.res.vectorResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.zaneschepke.wireguardautotunnel.BuildConfig
|
import com.zaneschepke.wireguardautotunnel.BuildConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
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.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.SelectionItem
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
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.GroupLabel
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.label.VersionLabel
|
import com.zaneschepke.wireguardautotunnel.ui.common.label.VersionLabel
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
|
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.ForwardButton
|
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.ForwardButton
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.topPadding
|
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.launchSupportEmail
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
|
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
|
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SupportScreen() {
|
fun SupportScreen(appUiState: AppUiState, appViewModel: AppViewModel) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val navController = LocalNavController.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(
|
Column(
|
||||||
horizontalAlignment = Alignment.Start,
|
horizontalAlignment = Alignment.Start,
|
||||||
verticalArrangement = Arrangement.spacedBy(24.dp.scaledHeight(), Alignment.Top),
|
verticalArrangement = Arrangement.spacedBy(24.dp.scaledHeight(), Alignment.Top),
|
||||||
|
@ -54,56 +77,93 @@ fun SupportScreen() {
|
||||||
) {
|
) {
|
||||||
GroupLabel(stringResource(R.string.thank_you))
|
GroupLabel(stringResource(R.string.thank_you))
|
||||||
SurfaceSelectionGroupButton(
|
SurfaceSelectionGroupButton(
|
||||||
listOf(
|
buildList {
|
||||||
SelectionItem(
|
add(
|
||||||
Icons.Filled.Book,
|
SelectionItem(
|
||||||
title = {
|
Icons.Filled.Book,
|
||||||
Text(
|
title = {
|
||||||
stringResource(R.string.docs_description),
|
Text(
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
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)) }
|
add(
|
||||||
},
|
SelectionItem(
|
||||||
onClick = {
|
Icons.Filled.Policy,
|
||||||
context.openWebUrl(context.getString(R.string.docs_url))
|
title = {
|
||||||
},
|
Text(
|
||||||
),
|
stringResource(R.string.privacy_policy),
|
||||||
SelectionItem(
|
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||||
Icons.Filled.LineStyle,
|
)
|
||||||
title = {
|
},
|
||||||
Text(
|
trailing = {
|
||||||
stringResource(R.string.read_logs),
|
ForwardButton { context.openWebUrl(context.getString(R.string.privacy_policy_url)) }
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
},
|
||||||
)
|
onClick = {
|
||||||
},
|
context.openWebUrl(context.getString(R.string.privacy_policy_url))
|
||||||
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))
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
SurfaceSelectionGroupButton(
|
SurfaceSelectionGroupButton(
|
||||||
buildList {
|
buildList {
|
||||||
|
|
|
@ -229,4 +229,8 @@
|
||||||
<string name="tunnel_running">Tunnel running</string>
|
<string name="tunnel_running">Tunnel running</string>
|
||||||
<string name="monitoring_state_changes">Monitoring state changes</string>
|
<string name="monitoring_state_changes">Monitoring state changes</string>
|
||||||
<string name="donate">Donate to project</string>
|
<string name="donate">Donate to project</string>
|
||||||
|
<string name="local_logging">Local logging</string>
|
||||||
|
<string name="enable_local_logging">Enable local logging</string>
|
||||||
|
<string name="configuration_change">Configuration change</string>
|
||||||
|
<string name="requires_app_relaunch">This change requires an app relaunch. Would you like to proceed?</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in New Issue