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:
Zane Schepke 2024-11-22 21:06:58 -05:00
parent b2938c3ac8
commit f9ef308f8a
13 changed files with 297 additions and 202 deletions

View File

@ -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()
}
}
}
}

View File

@ -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")
}

View File

@ -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
}
}

View File

@ -29,5 +29,9 @@ interface AppStateRepository {
suspend fun getTheme(): Theme
suspend fun isLocalLogsEnabled(): Boolean
suspend fun setLocalLogsEnabled(enabled: Boolean)
val generalStateFlow: Flow<GeneralState>
}

View File

@ -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<GeneralState> =
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) {

View File

@ -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

View File

@ -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<RootShell>,
private val serviceManager: ServiceManager,
private val logReader: LogReader,
) : ViewModel() {
val uiState =
@ -64,11 +67,14 @@ constructor(
AppUiState(),
)
private val _isAppReady = MutableStateFlow<Boolean>(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(

View File

@ -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<AppViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val viewModel by viewModels<AppViewModel>()
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<Route.Main> {
MainScreen(
uiState = appUiState,
)
}
composable<Route.Settings> {
SettingsScreen(
appViewModel = viewModel,
uiState = appUiState,
)
}
composable<Route.LocationDisclosure> {
LocationDisclosureScreen(viewModel, appUiState)
}
composable<Route.AutoTunnel> {
AutoTunnelScreen(
appUiState,
)
}
composable<Route.Appearance> {
AppearanceScreen()
}
composable<Route.Language> {
LanguageScreen(localeStorage)
}
composable<Route.Display> {
DisplayScreen(appUiState)
}
composable<Route.Support> {
SupportScreen()
}
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()
}
}
},
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<Route.Main> {
MainScreen(
uiState = appUiState,
)
}
composable<Route.Settings> {
SettingsScreen(
appViewModel = viewModel,
uiState = appUiState,
)
}
composable<Route.LocationDisclosure> {
LocationDisclosureScreen(viewModel, appUiState)
}
composable<Route.AutoTunnel> {
AutoTunnelScreen(
appUiState,
)
}
composable<Route.Appearance> {
AppearanceScreen()
}
composable<Route.Language> {
LanguageScreen(localeStorage)
}
composable<Route.Display> {
DisplayScreen(appUiState)
}
composable<Route.Support> {
SupportScreen(appUiState, viewModel)
}
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()
}
override fun onDestroy() {
super.onDestroy()
tunnelService.cancelStatsJob()
}
}

View File

@ -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<NavHostController> {
error("NavController was not provided")
}
val LocalFocusRequester = compositionLocalOf<FocusRequester> { error("FocusRequester is not provided") }

View File

@ -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

View File

@ -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,
{

View File

@ -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 {

View File

@ -229,4 +229,8 @@
<string name="tunnel_running">Tunnel running</string>
<string name="monitoring_state_changes">Monitoring state changes</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>