mirror of
https://github.com/wgtunnel/android.git
synced 2026-07-03 14:07:49 +02:00
refactor: ui improvements
This commit is contained in:
@@ -61,6 +61,8 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.AutoTunnelScree
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.advanced.AutoTunnelAdvancedScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.detection.WifiDetectionMethodScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.disclosure.LocationDisclosureScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.preferences.PreferredTunnelScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.preferences.wifi.WifiSettingsScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.pin.PinLockScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.SettingsScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.AppearanceScreen
|
||||
@@ -70,13 +72,12 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.settings.dns.DnsSettingsSc
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.globals.TunnelGlobalsScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.logs.LogsScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.monitoring.TunnelMonitoringScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.system.SystemFeaturesScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.system.AndroidIntegrationsScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.SupportScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.donate.DonateScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.donate.crypto.AddressesScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.license.LicenseScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.TunnelsScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.autotunnel.TunnelAutoTunnelScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.config.ConfigScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.sort.SortScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.splittunnel.SplitTunnelScreen
|
||||
@@ -149,13 +150,15 @@ class MainActivity : AppCompatActivity() {
|
||||
val startingStack = buildList {
|
||||
add(Route.Tunnels)
|
||||
if (intent?.action == Intent.ACTION_APPLICATION_PREFERENCES) add(Route.Settings)
|
||||
if (appState.pinLockEnabled) add(Route.Lock)
|
||||
}
|
||||
|
||||
val backStack = rememberNavBackStack(*startingStack.toTypedArray())
|
||||
var previousRoute by remember { mutableStateOf<Route?>(null) }
|
||||
|
||||
val navController =
|
||||
rememberNavController<NavKey>(backStack) { previousKey ->
|
||||
rememberNavController<NavKey>(backStack, appState.isLocationDisclosureShown) {
|
||||
previousKey ->
|
||||
previousRoute = previousKey as? Route
|
||||
}
|
||||
|
||||
@@ -376,18 +379,6 @@ class MainActivity : AppCompatActivity() {
|
||||
)
|
||||
SplitTunnelScreen(viewModel)
|
||||
}
|
||||
entry<Route.TunnelAutoTunnel> { key ->
|
||||
val viewModel =
|
||||
hiltViewModel<
|
||||
TunnelViewModel,
|
||||
TunnelViewModel.Factory,
|
||||
>(
|
||||
creationCallback = { factory ->
|
||||
factory.create(key.id)
|
||||
}
|
||||
)
|
||||
TunnelAutoTunnelScreen(viewModel)
|
||||
}
|
||||
entry<Route.Config> { key ->
|
||||
val viewModel =
|
||||
hiltViewModel<
|
||||
@@ -404,6 +395,9 @@ class MainActivity : AppCompatActivity() {
|
||||
LocationDisclosureScreen()
|
||||
}
|
||||
entry<Route.AutoTunnel> { AutoTunnelScreen() }
|
||||
entry<Route.WifiPreferences> {
|
||||
WifiSettingsScreen()
|
||||
}
|
||||
entry<Route.AdvancedAutoTunnel> {
|
||||
AutoTunnelAdvancedScreen()
|
||||
}
|
||||
@@ -414,8 +408,8 @@ class MainActivity : AppCompatActivity() {
|
||||
entry<Route.TunnelMonitoring> {
|
||||
TunnelMonitoringScreen()
|
||||
}
|
||||
entry<Route.SystemFeatures> {
|
||||
SystemFeaturesScreen()
|
||||
entry<Route.AndroidIntegrations> {
|
||||
AndroidIntegrationsScreen()
|
||||
}
|
||||
entry<Route.Dns> { DnsSettingsScreen() }
|
||||
entry<Route.TunnelGlobals> { key ->
|
||||
@@ -454,6 +448,9 @@ class MainActivity : AppCompatActivity() {
|
||||
entry<Route.License> { LicenseScreen() }
|
||||
entry<Route.Donate> { DonateScreen() }
|
||||
entry<Route.Addresses> { AddressesScreen() }
|
||||
entry<Route.PreferredTunnel> { key ->
|
||||
PreferredTunnelScreen(key.tunnelNetwork)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.common.button
|
||||
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize
|
||||
|
||||
@Composable
|
||||
fun ActionIconButton(icon: ImageVector, labelRes: Int, onClick: () -> Unit) {
|
||||
IconButton(onClick = onClick) {
|
||||
Icon(
|
||||
icon,
|
||||
contentDescription = stringResource(labelRes),
|
||||
modifier = Modifier.size(iconSize),
|
||||
)
|
||||
}
|
||||
}
|
||||
+1
-8
@@ -1,6 +1,5 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.common.button
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.*
|
||||
@@ -11,7 +10,6 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
||||
@Composable
|
||||
fun ClickableIconButton(
|
||||
onClick: () -> Unit,
|
||||
onIconClick: () -> Unit,
|
||||
text: String,
|
||||
icon: ImageVector,
|
||||
enabled: Boolean = true,
|
||||
@@ -27,12 +25,7 @@ fun ClickableIconButton(
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = icon.name,
|
||||
modifier =
|
||||
Modifier.size(ButtonDefaults.IconSize).weight(1f, false).clickable {
|
||||
if (enabled) {
|
||||
onIconClick()
|
||||
}
|
||||
},
|
||||
modifier = Modifier.size(ButtonDefaults.IconSize).weight(1f, false),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.common.button
|
||||
|
||||
import androidx.compose.foundation.focusable
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.ArrowForward
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize
|
||||
|
||||
@Composable
|
||||
fun ForwardButton(modifier: Modifier = Modifier.focusable(), onClick: () -> Unit) {
|
||||
IconButton(modifier = modifier, onClick = onClick) {
|
||||
val icon = Icons.AutoMirrored.Outlined.ArrowForward
|
||||
Icon(icon, icon.name, Modifier.size(iconSize))
|
||||
}
|
||||
}
|
||||
+5
-3
@@ -12,8 +12,6 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType
|
||||
|
||||
@Composable
|
||||
fun IconSurfaceButton(
|
||||
@@ -59,7 +57,11 @@ fun IconSurfaceButton(
|
||||
) {
|
||||
leading?.invoke()
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
SelectionItemLabel(title, SelectionLabelType.TITLE)
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
description?.let {
|
||||
Text(
|
||||
description,
|
||||
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.common.button
|
||||
|
||||
import androidx.compose.foundation.focusable
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.Launch
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize
|
||||
|
||||
@Composable
|
||||
fun LaunchButton(modifier: Modifier = Modifier.focusable(), onClick: () -> Unit) {
|
||||
IconButton(modifier = modifier, onClick = onClick) {
|
||||
val icon = Icons.AutoMirrored.Outlined.Launch
|
||||
Icon(icon, icon.name, Modifier.size(iconSize))
|
||||
}
|
||||
}
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.common.button
|
||||
|
||||
import androidx.compose.foundation.focusable
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.OpenInNew
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize
|
||||
|
||||
@Composable
|
||||
fun LinkIconButton(modifier: Modifier = Modifier.focusable(), onClick: () -> Unit) {
|
||||
IconButton(modifier = modifier, onClick = onClick) {
|
||||
val icon = Icons.AutoMirrored.Outlined.OpenInNew
|
||||
Icon(icon, icon.name, Modifier.size(iconSize))
|
||||
}
|
||||
}
|
||||
+1
-3
@@ -5,8 +5,6 @@ import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.SwitchDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun ScaledSwitch(
|
||||
@@ -18,7 +16,7 @@ fun ScaledSwitch(
|
||||
Switch(
|
||||
checked,
|
||||
{ onClick(it) },
|
||||
modifier.scale((52.dp / 52.dp)),
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
colors =
|
||||
SwitchDefaults.colors()
|
||||
|
||||
-73
@@ -1,73 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.common.button
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.ripple
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType
|
||||
|
||||
@Composable
|
||||
fun SelectionItemButton(
|
||||
buttonText: String,
|
||||
description: String? = null,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
leading: (@Composable () -> Unit)? = null,
|
||||
trailing: (@Composable () -> Unit)? = null,
|
||||
ripple: Boolean = true,
|
||||
) {
|
||||
Card(
|
||||
modifier =
|
||||
modifier
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.clickable(
|
||||
indication = if (ripple) ripple() else null,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
onClick = { onClick() },
|
||||
)
|
||||
.height(IntrinsicSize.Min),
|
||||
colors = CardDefaults.cardColors(containerColor = Color.Transparent),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxSize().padding(horizontal = 12.dp),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(24.dp),
|
||||
) {
|
||||
leading?.let { it() }
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
modifier = Modifier.weight(1f).padding(end = 12.dp),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
SelectionItemLabel(
|
||||
buttonText,
|
||||
SelectionLabelType.TITLE,
|
||||
modifier = Modifier.weight(1f).padding(end = 24.dp),
|
||||
)
|
||||
description?.let {
|
||||
SelectionItemLabel(
|
||||
it,
|
||||
SelectionLabelType.DESCRIPTION,
|
||||
modifier = Modifier.weight(1f).padding(end = 24.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
trailing?.let { it() }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.common.button
|
||||
|
||||
import android.R.attr.description
|
||||
import android.R.attr.enabled
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.focusGroup
|
||||
import androidx.compose.foundation.focusable
|
||||
import androidx.compose.foundation.indication
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.ripple
|
||||
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.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusProperties
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun SurfaceRow(
|
||||
title: AnnotatedString,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: (() -> Unit)? = null,
|
||||
description: @Composable (() -> Unit)? = null,
|
||||
expandedContent: @Composable (() -> Unit)? = null,
|
||||
onLongClick: () -> Unit = {},
|
||||
enabled: Boolean = true,
|
||||
selected: Boolean = false,
|
||||
leading: @Composable (() -> Unit)? = null,
|
||||
trailing: @Composable ((Modifier) -> Unit)? = null,
|
||||
) {
|
||||
val density = LocalDensity.current
|
||||
var leadingPadding by remember { mutableStateOf(0.dp) }
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val mainFocusRequester = remember { FocusRequester() }
|
||||
val trailingFocusRequester = remember { FocusRequester() }
|
||||
|
||||
Column(
|
||||
modifier =
|
||||
modifier
|
||||
.fillMaxWidth()
|
||||
.focusGroup()
|
||||
.indication(interactionSource, ripple())
|
||||
.background(
|
||||
if (!selected) MaterialTheme.colorScheme.surface
|
||||
else MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)
|
||||
)
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
.defaultMinSize(minHeight = 42.dp)
|
||||
.animateContentSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
) {
|
||||
Row(
|
||||
modifier =
|
||||
Modifier.weight(1f)
|
||||
.focusRequester(mainFocusRequester)
|
||||
.focusProperties { right = trailingFocusRequester }
|
||||
.run {
|
||||
if (onClick != null) {
|
||||
combinedClickable(
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick,
|
||||
enabled = enabled,
|
||||
interactionSource = interactionSource,
|
||||
indication = null,
|
||||
)
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
.focusable(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
) {
|
||||
if (leading != null) {
|
||||
Row(
|
||||
modifier =
|
||||
Modifier.onSizeChanged {
|
||||
leadingPadding = with(density) { it.width.toDp() }
|
||||
}
|
||||
) {
|
||||
leading()
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
}
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier.padding(end = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color =
|
||||
if (enabled) MaterialTheme.colorScheme.onSurface
|
||||
else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f),
|
||||
)
|
||||
if (description != null) {
|
||||
description()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (trailing != null) {
|
||||
trailing(
|
||||
Modifier.focusRequester(trailingFocusRequester).focusProperties {
|
||||
left = mainFocusRequester
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
if (expandedContent != null) {
|
||||
Row(modifier = Modifier.fillMaxWidth().padding(start = leadingPadding, top = 4.dp)) {
|
||||
expandedContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SurfaceRow(
|
||||
title: String,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: (() -> Unit)? = null,
|
||||
description: @Composable (() -> Unit)? = null,
|
||||
expandedContent: @Composable (() -> Unit)? = null,
|
||||
onLongClick: () -> Unit = {},
|
||||
enabled: Boolean = true,
|
||||
selected: Boolean = false,
|
||||
leading: @Composable (() -> Unit)? = null,
|
||||
trailing: @Composable ((Modifier) -> Unit)? = null,
|
||||
) {
|
||||
SurfaceRow(
|
||||
title = AnnotatedString(title),
|
||||
onClick = onClick,
|
||||
description = description,
|
||||
expandedContent = expandedContent,
|
||||
onLongClick = onLongClick,
|
||||
enabled = enabled,
|
||||
selected = selected,
|
||||
leading = leading,
|
||||
trailing = trailing,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.common.button
|
||||
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.VerticalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun SwitchWithDivider(
|
||||
checked: Boolean,
|
||||
onClick: (Boolean) -> Unit,
|
||||
enabled: Boolean = true,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.height(IntrinsicSize.Min),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
VerticalDivider(
|
||||
modifier = Modifier.fillMaxHeight().padding(horizontal = 8.dp, vertical = 8.dp),
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
)
|
||||
Box(modifier = Modifier.pointerInput(Unit) { detectTapGestures {} }) {
|
||||
ScaledSwitch(
|
||||
checked = checked,
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.common.button.surface
|
||||
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
data class SelectionItem(
|
||||
val leading: (@Composable () -> Unit)? = null,
|
||||
val trailing: (@Composable () -> Unit)? = null,
|
||||
val title: (@Composable () -> Unit),
|
||||
val description: (@Composable () -> Unit)? = null,
|
||||
val onClick: (() -> Unit)? = null,
|
||||
val isEnabled: Boolean = true,
|
||||
val disabledReason: String? = null,
|
||||
val modifier: Modifier = Modifier.height(48.dp),
|
||||
val padding: Dp = if (description == null && disabledReason == null) 16.dp else 6.dp,
|
||||
val onLongPress: (() -> Unit)? = null,
|
||||
)
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.common.button.surface
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
@Composable
|
||||
fun SelectionItemLabel(text: String, labelType: SelectionLabelType, modifier: Modifier = Modifier) {
|
||||
|
||||
val style =
|
||||
when (labelType) {
|
||||
SelectionLabelType.DESCRIPTION ->
|
||||
MaterialTheme.typography.bodySmall.copy(color = MaterialTheme.colorScheme.outline)
|
||||
SelectionLabelType.TITLE ->
|
||||
MaterialTheme.typography.bodyMedium.copy(
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
|
||||
Text(text = text, style = style, modifier = modifier)
|
||||
}
|
||||
|
||||
enum class SelectionLabelType {
|
||||
|
||||
DESCRIPTION,
|
||||
TITLE,
|
||||
}
|
||||
-117
@@ -1,117 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.common.button.surface
|
||||
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.semantics.stateDescription
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
|
||||
@Composable
|
||||
fun SurfaceSelectionGroupButton(items: List<SelectionItem>, modifier: Modifier = Modifier) {
|
||||
if (items.isEmpty()) return
|
||||
val context = LocalContext.current
|
||||
Card(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
items.forEach { item ->
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier =
|
||||
Modifier.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.alpha(if (item.isEnabled) 1f else 0.6f)
|
||||
.semantics {
|
||||
if (!item.isEnabled) {
|
||||
stateDescription =
|
||||
item.disabledReason ?: context.getString(R.string.disabled)
|
||||
}
|
||||
}
|
||||
.combinedClickable(
|
||||
onClick = { item.onClick?.invoke() },
|
||||
onLongClick = { item.onLongPress?.invoke() },
|
||||
enabled = true,
|
||||
),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier =
|
||||
Modifier.weight(4f, false)
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = item.padding),
|
||||
) {
|
||||
item.leading?.let {
|
||||
Box(modifier = Modifier.alpha(if (item.isEnabled) 1f else 0.6f)) {
|
||||
it()
|
||||
}
|
||||
}
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement =
|
||||
Arrangement.spacedBy(2.dp, Alignment.CenterVertically),
|
||||
modifier =
|
||||
Modifier.fillMaxWidth()
|
||||
.padding(start = if (item.leading != null) 16.dp else 0.dp)
|
||||
.weight(1f),
|
||||
) {
|
||||
Box(modifier = Modifier.alpha(if (item.isEnabled) 1f else 0.6f)) {
|
||||
item.title()
|
||||
}
|
||||
item.description?.invoke()
|
||||
if (!item.isEnabled && item.disabledReason != null) {
|
||||
Text(
|
||||
text = item.disabledReason,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color =
|
||||
MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
item.trailing?.let {
|
||||
Box(
|
||||
contentAlignment = Alignment.CenterEnd,
|
||||
modifier =
|
||||
Modifier.padding(start = 16.dp)
|
||||
.alpha(if (item.isEnabled) 1f else 0.6f)
|
||||
.run {
|
||||
if (!item.isEnabled) {
|
||||
semantics {
|
||||
stateDescription =
|
||||
context.getString(R.string.disabled)
|
||||
}
|
||||
} else {
|
||||
this
|
||||
}
|
||||
},
|
||||
) {
|
||||
it()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
-23
@@ -1,6 +1,7 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.common.dropdown
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
@@ -27,30 +28,35 @@ fun <T> DropdownSelector(
|
||||
it?.toString() ?: stringResource(R.string._default)
|
||||
},
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(5.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
if (label != null) label()
|
||||
Text(text = optionToString(currentValue), style = MaterialTheme.typography.bodyMedium)
|
||||
Icon(Icons.Default.ArrowDropDown, contentDescription = stringResource(R.string.dropdown))
|
||||
}
|
||||
|
||||
DropdownMenu(
|
||||
modifier = modifier.heightIn(max = 250.dp),
|
||||
scrollState = rememberScrollState(),
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
expanded = isExpanded,
|
||||
onDismissRequest = onDismiss,
|
||||
) {
|
||||
options.forEach { option ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(optionToString(option)) },
|
||||
onClick = {
|
||||
onValueSelected(option)
|
||||
onDismiss()
|
||||
},
|
||||
Box(modifier = modifier) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(5.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
if (label != null) label()
|
||||
Text(text = optionToString(currentValue), style = MaterialTheme.typography.bodyMedium)
|
||||
Icon(
|
||||
Icons.Default.ArrowDropDown,
|
||||
contentDescription = stringResource(R.string.dropdown),
|
||||
)
|
||||
}
|
||||
|
||||
DropdownMenu(
|
||||
modifier = modifier.heightIn(max = 250.dp),
|
||||
scrollState = rememberScrollState(),
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
expanded = isExpanded,
|
||||
onDismissRequest = onDismiss,
|
||||
) {
|
||||
options.forEach { option ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(optionToString(option)) },
|
||||
onClick = {
|
||||
onValueSelected(option)
|
||||
onDismiss()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+16
-21
@@ -1,12 +1,11 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.common.dropdown
|
||||
|
||||
import androidx.compose.runtime.*
|
||||
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.SurfaceRow
|
||||
|
||||
@Composable
|
||||
fun <T> LabelledDropdown(
|
||||
title: @Composable () -> Unit,
|
||||
title: String,
|
||||
description: (@Composable () -> Unit)? = null,
|
||||
leading: @Composable () -> Unit,
|
||||
onSelected: (T?) -> Unit,
|
||||
@@ -16,24 +15,20 @@ fun <T> LabelledDropdown(
|
||||
) {
|
||||
var isDropDownExpanded by remember { mutableStateOf(false) }
|
||||
|
||||
SurfaceSelectionGroupButton(
|
||||
listOf(
|
||||
SelectionItem(
|
||||
leading = leading,
|
||||
title = title,
|
||||
description = description,
|
||||
onClick = { isDropDownExpanded = true },
|
||||
trailing = {
|
||||
DropdownSelector(
|
||||
currentValue = currentValue,
|
||||
options = options,
|
||||
onValueSelected = { selected -> onSelected(selected) },
|
||||
isExpanded = isDropDownExpanded,
|
||||
onDismiss = { isDropDownExpanded = false },
|
||||
optionToString = optionToString,
|
||||
)
|
||||
},
|
||||
SurfaceRow(
|
||||
leading = leading,
|
||||
title = title,
|
||||
description = description,
|
||||
onClick = { isDropDownExpanded = true },
|
||||
trailing = {
|
||||
DropdownSelector(
|
||||
currentValue = currentValue,
|
||||
options = options,
|
||||
onValueSelected = { selected -> onSelected(selected) },
|
||||
isExpanded = isDropDownExpanded,
|
||||
onDismiss = { isDropDownExpanded = false },
|
||||
optionToString = optionToString,
|
||||
)
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,10 +7,15 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
|
||||
@Composable
|
||||
fun GroupLabel(title: String, modifier: Modifier = Modifier) {
|
||||
fun GroupLabel(
|
||||
title: String,
|
||||
modifier: Modifier = Modifier,
|
||||
color: Color = MaterialTheme.colorScheme.primary,
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
@@ -20,7 +25,7 @@ fun GroupLabel(title: String, modifier: Modifier = Modifier) {
|
||||
title,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
color = color,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.common.text
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
@Composable
|
||||
fun DescriptionText(text: String, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.bodySmall.copy(color = MaterialTheme.colorScheme.outline),
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
+10
-1
@@ -3,12 +3,18 @@ package com.zaneschepke.wireguardautotunnel.ui.common.textbox
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
@@ -34,11 +40,14 @@ fun ConfigurationTextBox(
|
||||
readOnly: Boolean = false,
|
||||
singleLine: Boolean = true,
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
||||
CustomTextField(
|
||||
isError = isError,
|
||||
textStyle =
|
||||
MaterialTheme.typography.bodySmall.copy(color = MaterialTheme.colorScheme.onSurface),
|
||||
modifier = modifier.fillMaxWidth().height(48.dp),
|
||||
modifier =
|
||||
modifier.fillMaxWidth().height(48.dp).focusRequester(focusRequester).imePadding(),
|
||||
value = value,
|
||||
visualTransformation = visualTransformation,
|
||||
singleLine = singleLine,
|
||||
|
||||
@@ -5,6 +5,7 @@ import androidx.navigation3.runtime.NavKey
|
||||
|
||||
class NavController(
|
||||
private val backStack: NavBackStack<NavKey>,
|
||||
private val isDisclosureShown: Boolean,
|
||||
private val onChange: (previous: NavKey?) -> Unit = {},
|
||||
) {
|
||||
fun push(route: NavKey) {
|
||||
@@ -27,6 +28,9 @@ class NavController(
|
||||
backStack.removeLast()
|
||||
}
|
||||
if (backStack.last() != route) {
|
||||
val route =
|
||||
if (route is Route.AutoTunnel && !isDisclosureShown) Route.LocationDisclosure
|
||||
else route
|
||||
backStack.add(route)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,15 +46,13 @@ sealed class Route : NavKey {
|
||||
|
||||
@Keep @Serializable data class SplitTunnelGlobal(val id: Int) : Route()
|
||||
|
||||
@Keep @Serializable data class TunnelAutoTunnel(val id: Int) : Route()
|
||||
|
||||
@Keep @Serializable data object Sort : Route()
|
||||
|
||||
@Keep @Serializable data object Settings : Route()
|
||||
|
||||
@Keep @Serializable data object TunnelMonitoring : Route()
|
||||
|
||||
@Keep @Serializable data object SystemFeatures : Route()
|
||||
@Keep @Serializable data object AndroidIntegrations : Route()
|
||||
|
||||
@Keep @Serializable data object Dns : Route()
|
||||
|
||||
@@ -66,11 +64,22 @@ sealed class Route : NavKey {
|
||||
|
||||
@Keep @Serializable data object WifiDetectionMethod : Route()
|
||||
|
||||
@Keep @Serializable data object WifiPreferences : Route()
|
||||
|
||||
@Keep @Serializable data object LocationDisclosure : Route()
|
||||
|
||||
@Keep @Serializable data object Donate : Route()
|
||||
|
||||
@Keep @Serializable data object Addresses : Route()
|
||||
|
||||
@Keep @Serializable data class PreferredTunnel(val tunnelNetwork: TunnelNetwork) : Route()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class TunnelNetwork {
|
||||
MOBILE_DATA,
|
||||
ETHERNET,
|
||||
WIFI,
|
||||
}
|
||||
|
||||
enum class Tab(
|
||||
@@ -98,15 +107,17 @@ enum class Tab(
|
||||
Route.Sort,
|
||||
is Route.TunnelOptions,
|
||||
is Route.Config,
|
||||
is Route.SplitTunnel,
|
||||
is Route.TunnelAutoTunnel -> TUNNELS
|
||||
is Route.Lock,
|
||||
is Route.SplitTunnel -> TUNNELS
|
||||
is Route.AutoTunnel,
|
||||
Route.AdvancedAutoTunnel,
|
||||
Route.WifiDetectionMethod,
|
||||
Route.WifiPreferences,
|
||||
is Route.PreferredTunnel,
|
||||
Route.LocationDisclosure -> AUTOTUNNEL
|
||||
is Route.Settings,
|
||||
Route.TunnelMonitoring,
|
||||
Route.SystemFeatures,
|
||||
Route.AndroidIntegrations,
|
||||
Route.Dns,
|
||||
is Route.TunnelGlobals,
|
||||
is Route.ConfigGlobal,
|
||||
|
||||
+286
-133
@@ -6,14 +6,18 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.rounded.Sort
|
||||
import androidx.compose.material.icons.rounded.*
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ActionIconButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.NavController
|
||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
|
||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route.*
|
||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.TunnelNetwork
|
||||
import com.zaneschepke.wireguardautotunnel.ui.sideeffect.LocalSideEffect
|
||||
import com.zaneschepke.wireguardautotunnel.ui.state.NavbarState
|
||||
import com.zaneschepke.wireguardautotunnel.ui.state.SharedAppUiState
|
||||
@@ -32,27 +36,33 @@ fun currentRouteAsNavbarState(
|
||||
return remember(route, sharedState) {
|
||||
derivedStateOf {
|
||||
when (route) {
|
||||
Route.AdvancedAutoTunnel ->
|
||||
AdvancedAutoTunnel ->
|
||||
NavbarState(
|
||||
topLeading = {
|
||||
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
|
||||
navController.pop()
|
||||
IconButton(onClick = { navController.pop() }) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
stringResource(R.string.back),
|
||||
)
|
||||
}
|
||||
},
|
||||
showBottomItems = true,
|
||||
topTitle = context.getString(R.string.advanced_settings),
|
||||
)
|
||||
Route.Appearance ->
|
||||
Appearance ->
|
||||
NavbarState(
|
||||
topLeading = {
|
||||
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
|
||||
navController.pop()
|
||||
IconButton(onClick = { navController.pop() }) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
stringResource(R.string.back),
|
||||
)
|
||||
}
|
||||
},
|
||||
showBottomItems = true,
|
||||
topTitle = context.getString(R.string.appearance),
|
||||
)
|
||||
Route.AutoTunnel ->
|
||||
AutoTunnel ->
|
||||
NavbarState(
|
||||
showBottomItems = true,
|
||||
topTitle =
|
||||
@@ -61,295 +71,392 @@ fun currentRouteAsNavbarState(
|
||||
context.getString(R.string.auto_tunnel)
|
||||
},
|
||||
)
|
||||
Route.Display ->
|
||||
Display ->
|
||||
NavbarState(
|
||||
topLeading = {
|
||||
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
|
||||
navController.pop()
|
||||
IconButton(onClick = { navController.pop() }) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
stringResource(R.string.back),
|
||||
)
|
||||
}
|
||||
},
|
||||
showBottomItems = true,
|
||||
topTitle = context.getString(R.string.display_theme),
|
||||
)
|
||||
Route.Dns ->
|
||||
Dns ->
|
||||
NavbarState(
|
||||
topLeading = {
|
||||
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
|
||||
navController.pop()
|
||||
IconButton(onClick = { navController.pop() }) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
stringResource(R.string.back),
|
||||
)
|
||||
}
|
||||
},
|
||||
showBottomItems = true,
|
||||
topTitle = context.getString(R.string.dns_settings),
|
||||
)
|
||||
Route.Language ->
|
||||
Language ->
|
||||
NavbarState(
|
||||
topLeading = {
|
||||
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
|
||||
navController.pop()
|
||||
IconButton(onClick = { navController.pop() }) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
stringResource(R.string.back),
|
||||
)
|
||||
}
|
||||
},
|
||||
showBottomItems = true,
|
||||
topTitle = context.getString(R.string.language),
|
||||
)
|
||||
Route.License ->
|
||||
License ->
|
||||
NavbarState(
|
||||
topLeading = {
|
||||
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
|
||||
navController.pop()
|
||||
IconButton(onClick = { navController.pop() }) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
stringResource(R.string.back),
|
||||
)
|
||||
}
|
||||
},
|
||||
showBottomItems = true,
|
||||
topTitle = context.getString(R.string.licenses),
|
||||
)
|
||||
Route.LocationDisclosure -> NavbarState(showBottomItems = true)
|
||||
Route.Lock -> NavbarState(showBottomItems = false)
|
||||
Route.Logs ->
|
||||
LocationDisclosure -> NavbarState(showBottomItems = true)
|
||||
Lock -> NavbarState(showBottomItems = false)
|
||||
Logs ->
|
||||
NavbarState(
|
||||
topLeading = {
|
||||
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
|
||||
navController.pop()
|
||||
IconButton(onClick = { navController.pop() }) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
stringResource(R.string.back),
|
||||
)
|
||||
}
|
||||
},
|
||||
showBottomItems = true,
|
||||
topTitle = context.getString(R.string.logs),
|
||||
topTrailing = {
|
||||
ActionIconButton(Icons.Rounded.Menu, R.string.quick_actions) {
|
||||
sharedViewModel.postSideEffect(LocalSideEffect.Sheet.LoggerActions)
|
||||
IconButton(
|
||||
onClick = {
|
||||
sharedViewModel.postSideEffect(
|
||||
LocalSideEffect.Sheet.LoggerActions
|
||||
)
|
||||
}
|
||||
) {
|
||||
Icon(Icons.Rounded.Menu, stringResource(R.string.quick_actions))
|
||||
}
|
||||
},
|
||||
)
|
||||
Route.ProxySettings ->
|
||||
ProxySettings ->
|
||||
NavbarState(
|
||||
topLeading = {
|
||||
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
|
||||
navController.pop()
|
||||
IconButton(onClick = { navController.pop() }) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
stringResource(R.string.back),
|
||||
)
|
||||
}
|
||||
},
|
||||
showBottomItems = true,
|
||||
topTitle = context.getString(R.string.proxy_settings),
|
||||
topTrailing = {
|
||||
ActionIconButton(Icons.Rounded.Save, R.string.save) {
|
||||
keyboardController?.hide()
|
||||
sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges)
|
||||
IconButton(
|
||||
onClick = {
|
||||
keyboardController?.hide()
|
||||
sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges)
|
||||
}
|
||||
) {
|
||||
Icon(Icons.Rounded.Save, stringResource(R.string.save))
|
||||
}
|
||||
},
|
||||
)
|
||||
Route.Settings ->
|
||||
Settings ->
|
||||
NavbarState(
|
||||
showBottomItems = true,
|
||||
topTitle = context.getString(R.string.settings),
|
||||
topTrailing = {
|
||||
ActionIconButton(
|
||||
Icons.Rounded.SettingsBackupRestore,
|
||||
R.string.quick_actions,
|
||||
IconButton(
|
||||
onClick = {
|
||||
sharedViewModel.postSideEffect(LocalSideEffect.Sheet.BackupApp)
|
||||
}
|
||||
) {
|
||||
sharedViewModel.postSideEffect(LocalSideEffect.Sheet.BackupApp)
|
||||
Icon(
|
||||
Icons.Rounded.SettingsBackupRestore,
|
||||
stringResource(R.string.quick_actions),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
Route.Sort ->
|
||||
Sort ->
|
||||
NavbarState(
|
||||
topLeading = {
|
||||
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
|
||||
navController.pop()
|
||||
IconButton(onClick = { navController.pop() }) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
stringResource(R.string.back),
|
||||
)
|
||||
}
|
||||
},
|
||||
showBottomItems = true,
|
||||
topTitle = context.getString(R.string.sort),
|
||||
topTrailing = {
|
||||
Row {
|
||||
ActionIconButton(Icons.Rounded.SortByAlpha, R.string.sort) {
|
||||
sharedViewModel.postSideEffect(LocalSideEffect.Sort)
|
||||
IconButton(
|
||||
onClick = {
|
||||
sharedViewModel.postSideEffect(LocalSideEffect.Sort)
|
||||
}
|
||||
) {
|
||||
Icon(Icons.Rounded.SortByAlpha, stringResource(R.string.sort))
|
||||
}
|
||||
ActionIconButton(Icons.Rounded.Save, R.string.save) {
|
||||
sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges)
|
||||
IconButton(
|
||||
onClick = {
|
||||
sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges)
|
||||
}
|
||||
) {
|
||||
Icon(Icons.Rounded.Save, stringResource(R.string.save))
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
is Route.Config -> {
|
||||
is Config -> {
|
||||
val tunnelName = sharedState.tunnelNames[route.id]
|
||||
NavbarState(
|
||||
topLeading = {
|
||||
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
|
||||
navController.pop()
|
||||
IconButton(onClick = { navController.pop() }) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
stringResource(R.string.back),
|
||||
)
|
||||
}
|
||||
},
|
||||
showBottomItems = true,
|
||||
topTitle = tunnelName ?: context.getString(R.string.new_tunnel),
|
||||
topTrailing = {
|
||||
ActionIconButton(Icons.Rounded.Save, R.string.save) {
|
||||
keyboardController?.hide()
|
||||
sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges)
|
||||
IconButton(
|
||||
onClick = {
|
||||
keyboardController?.hide()
|
||||
sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges)
|
||||
}
|
||||
) {
|
||||
Icon(Icons.Rounded.Save, stringResource(R.string.save))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
is Route.SplitTunnel -> {
|
||||
is SplitTunnel -> {
|
||||
val tunnelName = sharedState.tunnelNames[route.id]
|
||||
NavbarState(
|
||||
topLeading = {
|
||||
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
|
||||
navController.pop()
|
||||
IconButton(onClick = { navController.pop() }) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
stringResource(R.string.back),
|
||||
)
|
||||
}
|
||||
},
|
||||
topTitle = tunnelName ?: "",
|
||||
topTrailing = {
|
||||
ActionIconButton(Icons.Rounded.Save, R.string.save) {
|
||||
sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges)
|
||||
IconButton(
|
||||
onClick = {
|
||||
sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges)
|
||||
}
|
||||
) {
|
||||
Icon(Icons.Rounded.Save, stringResource(R.string.save))
|
||||
}
|
||||
},
|
||||
showBottomItems = true,
|
||||
)
|
||||
}
|
||||
is Route.SplitTunnelGlobal -> {
|
||||
is SplitTunnelGlobal -> {
|
||||
NavbarState(
|
||||
topLeading = {
|
||||
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
|
||||
navController.pop()
|
||||
IconButton(onClick = { navController.pop() }) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
stringResource(R.string.back),
|
||||
)
|
||||
}
|
||||
},
|
||||
topTitle = context.getString(R.string.splt_tunneling),
|
||||
topTrailing = {
|
||||
ActionIconButton(Icons.Rounded.Save, R.string.save) {
|
||||
sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges)
|
||||
IconButton(
|
||||
onClick = {
|
||||
sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges)
|
||||
}
|
||||
) {
|
||||
Icon(Icons.Rounded.Save, stringResource(R.string.save))
|
||||
}
|
||||
},
|
||||
showBottomItems = true,
|
||||
)
|
||||
}
|
||||
is Route.ConfigGlobal -> {
|
||||
is ConfigGlobal -> {
|
||||
NavbarState(
|
||||
topLeading = {
|
||||
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
|
||||
navController.pop()
|
||||
IconButton(onClick = { navController.pop() }) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
stringResource(R.string.back),
|
||||
)
|
||||
}
|
||||
},
|
||||
showBottomItems = true,
|
||||
topTitle = context.getString(R.string.configuration),
|
||||
topTrailing = {
|
||||
ActionIconButton(Icons.Rounded.Save, R.string.save) {
|
||||
keyboardController?.hide()
|
||||
sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges)
|
||||
IconButton(
|
||||
onClick = {
|
||||
sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges)
|
||||
}
|
||||
) {
|
||||
Icon(Icons.Rounded.Save, stringResource(R.string.save))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
Route.Support ->
|
||||
Support ->
|
||||
NavbarState(
|
||||
topTitle = context.getString(R.string.support),
|
||||
showBottomItems = true,
|
||||
)
|
||||
Route.SystemFeatures ->
|
||||
AndroidIntegrations ->
|
||||
NavbarState(
|
||||
topLeading = {
|
||||
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
|
||||
navController.pop()
|
||||
IconButton(onClick = { navController.pop() }) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
stringResource(R.string.back),
|
||||
)
|
||||
}
|
||||
},
|
||||
topTitle = context.getString(R.string.android_integrations),
|
||||
showBottomItems = true,
|
||||
)
|
||||
is Route.TunnelAutoTunnel -> {
|
||||
val tunnelName = sharedState.tunnelNames[route.id]
|
||||
TunnelMonitoring ->
|
||||
NavbarState(
|
||||
topLeading = {
|
||||
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
|
||||
navController.pop()
|
||||
}
|
||||
},
|
||||
showBottomItems = true,
|
||||
topTitle = tunnelName ?: "",
|
||||
)
|
||||
}
|
||||
Route.TunnelMonitoring ->
|
||||
NavbarState(
|
||||
topLeading = {
|
||||
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
|
||||
navController.pop()
|
||||
IconButton(onClick = { navController.pop() }) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
stringResource(R.string.back),
|
||||
)
|
||||
}
|
||||
},
|
||||
topTitle = context.getString(R.string.tunnel_monitoring),
|
||||
showBottomItems = true,
|
||||
)
|
||||
is Route.TunnelOptions -> {
|
||||
is TunnelOptions -> {
|
||||
val tunnelName = sharedState.tunnelNames[route.id]
|
||||
NavbarState(
|
||||
topLeading = {
|
||||
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
|
||||
navController.pop()
|
||||
IconButton(onClick = { navController.pop() }) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
stringResource(R.string.back),
|
||||
)
|
||||
}
|
||||
},
|
||||
showBottomItems = true,
|
||||
topTitle = tunnelName ?: "",
|
||||
topTrailing = {
|
||||
Row {
|
||||
ActionIconButton(Icons.Rounded.QrCode2, R.string.show_qr) {
|
||||
sharedViewModel.postSideEffect(LocalSideEffect.Modal.QR)
|
||||
IconButton(
|
||||
onClick = {
|
||||
sharedViewModel.postSideEffect(LocalSideEffect.Modal.QR)
|
||||
}
|
||||
) {
|
||||
Icon(Icons.Rounded.QrCode2, stringResource(R.string.show_qr))
|
||||
}
|
||||
ActionIconButton(Icons.Rounded.Edit, R.string.edit_tunnel) {
|
||||
navController.push(Route.Config(route.id))
|
||||
IconButton(onClick = { navController.push(Config(route.id)) }) {
|
||||
Icon(Icons.Rounded.Edit, stringResource(R.string.edit_tunnel))
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
Route.Tunnels -> {
|
||||
Tunnels -> {
|
||||
NavbarState(
|
||||
topTitle = context.getString(R.string.tunnels),
|
||||
topTrailing = {
|
||||
when (sharedState.selectedTunnelCount) {
|
||||
0 ->
|
||||
Row {
|
||||
ActionIconButton(
|
||||
Icons.AutoMirrored.Rounded.Sort,
|
||||
R.string.sort,
|
||||
) {
|
||||
navController.push(Route.Sort)
|
||||
IconButton(onClick = { navController.push(Sort) }) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Rounded.Sort,
|
||||
stringResource(R.string.sort),
|
||||
)
|
||||
}
|
||||
ActionIconButton(Icons.Rounded.Add, R.string.add_tunnel) {
|
||||
sharedViewModel.postSideEffect(
|
||||
LocalSideEffect.Sheet.ImportTunnels
|
||||
IconButton(
|
||||
onClick = {
|
||||
sharedViewModel.postSideEffect(
|
||||
LocalSideEffect.Sheet.ImportTunnels
|
||||
)
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
Icons.Rounded.Add,
|
||||
stringResource(R.string.add_tunnel),
|
||||
)
|
||||
}
|
||||
}
|
||||
else ->
|
||||
Row {
|
||||
ActionIconButton(
|
||||
Icons.Rounded.SelectAll,
|
||||
R.string.select_all,
|
||||
IconButton(
|
||||
onClick = {
|
||||
sharedViewModel.postSideEffect(
|
||||
LocalSideEffect.SelectedTunnels.SelectAll
|
||||
)
|
||||
}
|
||||
) {
|
||||
sharedViewModel.postSideEffect(
|
||||
LocalSideEffect.SelectedTunnels.SelectAll
|
||||
Icon(
|
||||
Icons.Rounded.SelectAll,
|
||||
stringResource(R.string.select_all),
|
||||
)
|
||||
}
|
||||
// due to permissions, and SAF issues on TV, not support
|
||||
// less than Android
|
||||
// 10
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
ActionIconButton(
|
||||
Icons.Rounded.Download,
|
||||
R.string.download,
|
||||
IconButton(
|
||||
onClick = {
|
||||
sharedViewModel.postSideEffect(
|
||||
LocalSideEffect.Sheet.ExportTunnels
|
||||
)
|
||||
}
|
||||
) {
|
||||
sharedViewModel.postSideEffect(
|
||||
LocalSideEffect.Sheet.ExportTunnels
|
||||
Icon(
|
||||
Icons.Rounded.Download,
|
||||
stringResource(R.string.download),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (sharedState.selectedTunnelCount == 1) {
|
||||
ActionIconButton(Icons.Rounded.CopyAll, R.string.copy) {
|
||||
sharedViewModel.postSideEffect(
|
||||
LocalSideEffect.SelectedTunnels.Copy
|
||||
IconButton(
|
||||
onClick = {
|
||||
sharedViewModel.postSideEffect(
|
||||
LocalSideEffect.SelectedTunnels.Copy
|
||||
)
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
Icons.Rounded.CopyAll,
|
||||
stringResource(R.string.copy),
|
||||
)
|
||||
}
|
||||
}
|
||||
ActionIconButton(
|
||||
Icons.Rounded.Delete,
|
||||
R.string.delete_tunnel,
|
||||
IconButton(
|
||||
onClick = {
|
||||
sharedViewModel.postSideEffect(
|
||||
LocalSideEffect.Modal.DeleteTunnels
|
||||
)
|
||||
}
|
||||
) {
|
||||
sharedViewModel.postSideEffect(
|
||||
LocalSideEffect.Modal.DeleteTunnels
|
||||
Icon(
|
||||
Icons.Rounded.Delete,
|
||||
stringResource(R.string.delete_tunnel),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -358,46 +465,92 @@ fun currentRouteAsNavbarState(
|
||||
showBottomItems = true,
|
||||
)
|
||||
}
|
||||
Route.WifiDetectionMethod ->
|
||||
WifiDetectionMethod ->
|
||||
NavbarState(
|
||||
topLeading = {
|
||||
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
|
||||
navController.pop()
|
||||
IconButton(onClick = { navController.pop() }) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
stringResource(R.string.back),
|
||||
)
|
||||
}
|
||||
},
|
||||
topTitle = context.getString(R.string.wifi_detection_method),
|
||||
showBottomItems = true,
|
||||
)
|
||||
Route.Donate -> {
|
||||
Donate -> {
|
||||
NavbarState(
|
||||
topLeading = {
|
||||
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
|
||||
navController.pop()
|
||||
IconButton(onClick = { navController.pop() }) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
stringResource(R.string.back),
|
||||
)
|
||||
}
|
||||
},
|
||||
topTitle = context.getString(R.string.donate_title),
|
||||
showBottomItems = true,
|
||||
)
|
||||
}
|
||||
Route.Addresses -> {
|
||||
Addresses -> {
|
||||
NavbarState(
|
||||
topLeading = {
|
||||
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
|
||||
navController.pop()
|
||||
IconButton(onClick = { navController.pop() }) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
stringResource(R.string.back),
|
||||
)
|
||||
}
|
||||
},
|
||||
topTitle = context.getString(R.string.addresses),
|
||||
showBottomItems = true,
|
||||
)
|
||||
}
|
||||
is Route.TunnelGlobals -> {
|
||||
is TunnelGlobals -> {
|
||||
NavbarState(
|
||||
topLeading = {
|
||||
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
|
||||
navController.pop()
|
||||
IconButton(onClick = { navController.pop() }) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
stringResource(R.string.back),
|
||||
)
|
||||
}
|
||||
},
|
||||
topTitle = context.getString(R.string.tunnel_global_overrides),
|
||||
topTitle = context.getString(R.string.global_overrides),
|
||||
showBottomItems = true,
|
||||
)
|
||||
}
|
||||
is WifiPreferences -> {
|
||||
NavbarState(
|
||||
topLeading = {
|
||||
IconButton(onClick = { navController.pop() }) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
stringResource(R.string.back),
|
||||
)
|
||||
}
|
||||
},
|
||||
topTitle = context.getString(R.string.wifi_settings),
|
||||
showBottomItems = true,
|
||||
)
|
||||
}
|
||||
is PreferredTunnel -> {
|
||||
val title =
|
||||
when (route.tunnelNetwork) {
|
||||
TunnelNetwork.MOBILE_DATA,
|
||||
TunnelNetwork.ETHERNET -> context.getString(R.string.preferred_tunnel)
|
||||
TunnelNetwork.WIFI -> context.getString(R.string.tunnel_mapping)
|
||||
}
|
||||
NavbarState(
|
||||
topLeading = {
|
||||
IconButton(onClick = { navController.pop() }) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
stringResource(R.string.back),
|
||||
)
|
||||
}
|
||||
},
|
||||
topTitle = title,
|
||||
showBottomItems = true,
|
||||
)
|
||||
}
|
||||
|
||||
+4
-1
@@ -9,7 +9,10 @@ import com.zaneschepke.wireguardautotunnel.ui.navigation.NavController
|
||||
@Composable
|
||||
fun <T : NavKey> rememberNavController(
|
||||
backStack: NavBackStack<NavKey>,
|
||||
isDisclosureShown: Boolean,
|
||||
onChange: (NavKey?) -> Unit = {},
|
||||
): NavController {
|
||||
return remember(backStack, onChange) { NavController(backStack, onChange) }
|
||||
return remember(backStack, onChange, isDisclosureShown) {
|
||||
NavController(backStack, isDisclosureShown, onChange)
|
||||
}
|
||||
}
|
||||
|
||||
+200
-85
@@ -7,30 +7,31 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.CheckCircle
|
||||
import androidx.compose.material.icons.outlined.Info
|
||||
import androidx.compose.material.icons.outlined.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.SectionDivider
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.banner.WarningBanner
|
||||
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.ScaledSwitch
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SwitchWithDivider
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components.AdvancedSettingsItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components.networkTunnelingItems
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components.wifiTunnelingItems
|
||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.TunnelNetwork
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.launchAppSettings
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.launchLocationServicesSettings
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.AutoTunnelViewModel
|
||||
@@ -42,15 +43,18 @@ fun AutoTunnelScreen(viewModel: AutoTunnelViewModel = hiltViewModel()) {
|
||||
val navController = LocalNavController.current
|
||||
val autoTunnelState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
||||
|
||||
LaunchedEffect(autoTunnelState.stateInitialized) {
|
||||
if (!autoTunnelState.isLocationDisclosureShown && autoTunnelState.stateInitialized) {
|
||||
navController.push(Route.LocationDisclosure)
|
||||
}
|
||||
}
|
||||
|
||||
if (!autoTunnelState.stateInitialized) return
|
||||
if (autoTunnelState.isLoading) return
|
||||
var showLocationDialog by remember { mutableStateOf(false) }
|
||||
|
||||
val (ethernetTunnel, mobileDataTunnel, mappedTunnels) =
|
||||
remember(autoTunnelState.tunnels) {
|
||||
Triple(
|
||||
autoTunnelState.tunnels.firstOrNull { it.isEthernetTunnel },
|
||||
autoTunnelState.tunnels.firstOrNull { it.isMobileDataTunnel },
|
||||
autoTunnelState.tunnels.any { it.tunnelNetworks.isNotEmpty() },
|
||||
)
|
||||
}
|
||||
|
||||
val showLocationServicesWarning by
|
||||
remember(
|
||||
autoTunnelState.connectivityState?.wifiState,
|
||||
@@ -92,83 +96,194 @@ fun AutoTunnelScreen(viewModel: AutoTunnelViewModel = hiltViewModel()) {
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
||||
modifier =
|
||||
Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top),
|
||||
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
WarningBanner(
|
||||
stringResource(R.string.location_services_not_detected),
|
||||
showLocationServicesWarning,
|
||||
trailing = {
|
||||
TextButton({ context.launchLocationServicesSettings() }) {
|
||||
Text(
|
||||
stringResource(R.string.fix),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
WarningBanner(
|
||||
stringResource(R.string.location_permissions_missing),
|
||||
showLocationPermissionsWarning,
|
||||
trailing = {
|
||||
TextButton({ showLocationDialog = true }) {
|
||||
Text(
|
||||
stringResource(R.string.fix),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
val (title, buttonText, icon) =
|
||||
remember(autoTunnelState.autoTunnelActive) {
|
||||
when (autoTunnelState.autoTunnelActive) {
|
||||
true ->
|
||||
Triple(
|
||||
context.getString(R.string.auto_tunnel_running),
|
||||
context.getString(R.string.stop),
|
||||
Icons.Outlined.CheckCircle,
|
||||
Column {
|
||||
WarningBanner(
|
||||
stringResource(R.string.location_services_not_detected),
|
||||
showLocationServicesWarning,
|
||||
trailing = {
|
||||
TextButton({ context.launchLocationServicesSettings() }) {
|
||||
Text(
|
||||
stringResource(R.string.fix),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
false ->
|
||||
Triple(
|
||||
context.getString(R.string.auto_tunnel_not_running),
|
||||
context.getString(R.string.start),
|
||||
Icons.Outlined.Info,
|
||||
}
|
||||
},
|
||||
)
|
||||
WarningBanner(
|
||||
stringResource(R.string.location_permissions_missing),
|
||||
showLocationPermissionsWarning,
|
||||
trailing = {
|
||||
TextButton({ showLocationDialog = true }) {
|
||||
Text(
|
||||
stringResource(R.string.fix),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
val (title, buttonText, icon) =
|
||||
remember(autoTunnelState.autoTunnelActive) {
|
||||
when (autoTunnelState.autoTunnelActive) {
|
||||
true ->
|
||||
Triple(
|
||||
context.getString(R.string.auto_tunnel_running),
|
||||
context.getString(R.string.stop),
|
||||
Icons.Outlined.CheckCircle,
|
||||
)
|
||||
false ->
|
||||
Triple(
|
||||
context.getString(R.string.auto_tunnel_not_running),
|
||||
context.getString(R.string.start),
|
||||
Icons.Outlined.Info,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
SurfaceSelectionGroupButton(
|
||||
items =
|
||||
listOf(
|
||||
SelectionItem(
|
||||
leading = { Icon(icon, null) },
|
||||
title = { Text(title) },
|
||||
trailing = {
|
||||
Button({ viewModel.toggleAutoTunnel() }) {
|
||||
Text(
|
||||
buttonText,
|
||||
fontWeight = FontWeight.Bold,
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(
|
||||
color = MaterialTheme.colorScheme.surface
|
||||
),
|
||||
SurfaceRow(
|
||||
leading = { Icon(icon, null) },
|
||||
title = title,
|
||||
trailing = {
|
||||
Button({ viewModel.toggleAutoTunnel() }) {
|
||||
Text(
|
||||
buttonText,
|
||||
fontWeight = FontWeight.Bold,
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(
|
||||
color = MaterialTheme.colorScheme.surface
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
onClick = { viewModel.toggleAutoTunnel() },
|
||||
)
|
||||
}
|
||||
Column {
|
||||
GroupLabel(
|
||||
stringResource(R.string.networks),
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.Wifi, contentDescription = null) },
|
||||
title = stringResource(R.string.tunnel_on_wifi),
|
||||
trailing = { modifier ->
|
||||
SwitchWithDivider(
|
||||
checked = autoTunnelState.settings.isTunnelOnWifiEnabled,
|
||||
onClick = { viewModel.setAutoTunnelOnWifiEnabled(it) },
|
||||
modifier = modifier,
|
||||
)
|
||||
},
|
||||
description = {
|
||||
Text(
|
||||
buildAnnotatedString {
|
||||
append(stringResource(R.string.preferred_tunnel_label))
|
||||
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
|
||||
append(
|
||||
if (mappedTunnels) stringResource(R.string.mapped)
|
||||
else stringResource(R.string._default)
|
||||
)
|
||||
}
|
||||
},
|
||||
style =
|
||||
MaterialTheme.typography.bodySmall.copy(
|
||||
color = MaterialTheme.colorScheme.outline
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
SurfaceSelectionGroupButton(items = wifiTunnelingItems(autoTunnelState, viewModel))
|
||||
SectionDivider()
|
||||
SurfaceSelectionGroupButton(items = networkTunnelingItems(autoTunnelState, viewModel))
|
||||
SectionDivider()
|
||||
SurfaceSelectionGroupButton(
|
||||
items =
|
||||
listOf(
|
||||
AdvancedSettingsItem(onClick = { navController.push(Route.AdvancedAutoTunnel) })
|
||||
)
|
||||
)
|
||||
},
|
||||
onClick = { navController.push(Route.WifiPreferences) },
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.SignalCellular4Bar, contentDescription = null) },
|
||||
title = stringResource(R.string.tunnel_mobile_data),
|
||||
trailing = { modifier ->
|
||||
SwitchWithDivider(
|
||||
checked = autoTunnelState.settings.isTunnelOnMobileDataEnabled,
|
||||
onClick = { viewModel.setTunnelOnCellular(it) },
|
||||
modifier = modifier,
|
||||
)
|
||||
},
|
||||
description = {
|
||||
Text(
|
||||
buildAnnotatedString {
|
||||
append(stringResource(R.string.preferred_tunnel_label))
|
||||
mobileDataTunnel?.tunName?.let { append(it) }
|
||||
?: withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
|
||||
append(stringResource(R.string._default))
|
||||
}
|
||||
},
|
||||
style =
|
||||
MaterialTheme.typography.bodySmall.copy(
|
||||
color = MaterialTheme.colorScheme.outline
|
||||
),
|
||||
)
|
||||
},
|
||||
onClick = { navController.push(Route.PreferredTunnel(TunnelNetwork.MOBILE_DATA)) },
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.SettingsEthernet, contentDescription = null) },
|
||||
title = stringResource(R.string.tunnel_on_ethernet),
|
||||
trailing = { modifier ->
|
||||
SwitchWithDivider(
|
||||
checked = autoTunnelState.settings.isTunnelOnEthernetEnabled,
|
||||
onClick = { viewModel.setTunnelOnEthernet(it) },
|
||||
modifier = modifier,
|
||||
)
|
||||
},
|
||||
description = {
|
||||
Text(
|
||||
buildAnnotatedString {
|
||||
append(stringResource(R.string.preferred_tunnel_label))
|
||||
ethernetTunnel?.tunName?.let { append(it) }
|
||||
?: withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
|
||||
append(stringResource(R.string._default))
|
||||
}
|
||||
},
|
||||
style =
|
||||
MaterialTheme.typography.bodySmall.copy(
|
||||
color = MaterialTheme.colorScheme.outline
|
||||
),
|
||||
)
|
||||
},
|
||||
onClick = { navController.push(Route.PreferredTunnel(TunnelNetwork.ETHERNET)) },
|
||||
)
|
||||
}
|
||||
Column {
|
||||
GroupLabel(
|
||||
stringResource(R.string.other),
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.PublicOff, contentDescription = null) },
|
||||
title = stringResource(R.string.stop_on_no_internet),
|
||||
description = {
|
||||
Text(
|
||||
stringResource(R.string.stop_on_internet_loss),
|
||||
style =
|
||||
MaterialTheme.typography.bodySmall.copy(
|
||||
MaterialTheme.colorScheme.outline
|
||||
),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
checked = autoTunnelState.settings.isStopOnNoInternetEnabled,
|
||||
onClick = { viewModel.setStopOnNoInternetEnabled(it) },
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
viewModel.setStopOnNoInternetEnabled(
|
||||
!autoTunnelState.settings.isStopOnNoInternetEnabled
|
||||
)
|
||||
},
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.Settings, contentDescription = null) },
|
||||
title = stringResource(R.string.advanced_settings),
|
||||
onClick = { navController.push(Route.AdvancedAutoTunnel) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-14
@@ -3,14 +3,11 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.advanced
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.PauseCircle
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -29,19 +26,10 @@ fun AutoTunnelAdvancedScreen(viewModel: AutoTunnelViewModel = hiltViewModel()) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
||||
modifier =
|
||||
Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 16.dp),
|
||||
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
LabelledDropdown(
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.debounce_delay),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
),
|
||||
)
|
||||
},
|
||||
title = stringResource(R.string.debounce_delay),
|
||||
leading = { Icon(Icons.Outlined.PauseCircle, null) },
|
||||
onSelected = { selected -> viewModel.setDebounceDelay(selected!!) },
|
||||
options = (0..10).toList(),
|
||||
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Settings
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
|
||||
@Composable
|
||||
fun AdvancedSettingsItem(onClick: () -> Unit): SelectionItem {
|
||||
return SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.Settings, contentDescription = null) },
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.advanced_settings),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
trailing = { ForwardButton { onClick() } },
|
||||
onClick = { onClick() },
|
||||
)
|
||||
}
|
||||
-133
@@ -1,133 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.PublicOff
|
||||
import androidx.compose.material.icons.outlined.SettingsEthernet
|
||||
import androidx.compose.material.icons.outlined.SignalCellular4Bar
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.state.AutoTunnelUiState
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.AutoTunnelViewModel
|
||||
|
||||
@Composable
|
||||
fun networkTunnelingItems(
|
||||
autoTunnelState: AutoTunnelUiState,
|
||||
viewModel: AutoTunnelViewModel,
|
||||
): List<SelectionItem> {
|
||||
return listOf(
|
||||
SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.SignalCellular4Bar, contentDescription = null) },
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.tunnel_mobile_data),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(
|
||||
MaterialTheme.colorScheme.onSurface
|
||||
),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
checked = autoTunnelState.settings.isTunnelOnMobileDataEnabled,
|
||||
onClick = { viewModel.setTunnelOnCellular(it) },
|
||||
)
|
||||
},
|
||||
description = {
|
||||
val cellularActive =
|
||||
remember(autoTunnelState.connectivityState) {
|
||||
autoTunnelState.connectivityState?.cellularConnected ?: false
|
||||
}
|
||||
Text(
|
||||
text =
|
||||
if (cellularActive) stringResource(R.string.active)
|
||||
else stringResource(R.string.inactive),
|
||||
style =
|
||||
MaterialTheme.typography.bodySmall.copy(
|
||||
color = MaterialTheme.colorScheme.outline
|
||||
),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
viewModel.setTunnelOnCellular(!autoTunnelState.settings.isTunnelOnMobileDataEnabled)
|
||||
},
|
||||
),
|
||||
SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.SettingsEthernet, contentDescription = null) },
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.tunnel_on_ethernet),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(
|
||||
MaterialTheme.colorScheme.onSurface
|
||||
),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
checked = autoTunnelState.settings.isTunnelOnEthernetEnabled,
|
||||
onClick = { viewModel.setTunnelOnEthernet(it) },
|
||||
)
|
||||
},
|
||||
description = {
|
||||
val ethernetActive =
|
||||
remember(autoTunnelState.connectivityState) {
|
||||
autoTunnelState.connectivityState?.ethernetConnected ?: false
|
||||
}
|
||||
Text(
|
||||
text =
|
||||
if (ethernetActive) stringResource(R.string.active)
|
||||
else stringResource(R.string.inactive),
|
||||
style =
|
||||
MaterialTheme.typography.bodySmall.copy(
|
||||
color = MaterialTheme.colorScheme.outline
|
||||
),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
viewModel.setTunnelOnEthernet(!autoTunnelState.settings.isTunnelOnEthernetEnabled)
|
||||
},
|
||||
),
|
||||
SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.PublicOff, contentDescription = null) },
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.stop_on_no_internet),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(
|
||||
MaterialTheme.colorScheme.onSurface
|
||||
),
|
||||
)
|
||||
},
|
||||
description = {
|
||||
Text(
|
||||
stringResource(R.string.stop_on_internet_loss),
|
||||
style =
|
||||
MaterialTheme.typography.bodySmall.copy(MaterialTheme.colorScheme.outline),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
checked = autoTunnelState.settings.isStopOnNoInternetEnabled,
|
||||
onClick = { viewModel.setStopOnNoInternetEnabled(it) },
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
viewModel.setStopOnNoInternetEnabled(
|
||||
!autoTunnelState.settings.isStopOnNoInternetEnabled
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
+17
-12
@@ -1,7 +1,11 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components
|
||||
|
||||
import android.R.attr.text
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.relocation.BringIntoViewRequester
|
||||
import androidx.compose.foundation.relocation.bringIntoViewRequester
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
@@ -12,6 +16,7 @@ import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -20,7 +25,6 @@ import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ClickableIconButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.textbox.CustomTextField
|
||||
|
||||
@@ -33,21 +37,23 @@ fun TrustedNetworkTextBox(
|
||||
onSave: (ssid: String) -> Unit,
|
||||
onValueChange: (network: String) -> Unit,
|
||||
supporting: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val isTv = LocalIsAndroidTV.current
|
||||
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
val bringIntoViewRequester = remember { BringIntoViewRequester() }
|
||||
|
||||
LaunchedEffect(trustedNetworks.size) { bringIntoViewRequester.bringIntoView() }
|
||||
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
modifier = modifier.animateContentSize().imePadding(),
|
||||
) {
|
||||
FlowRow(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier.fillMaxWidth().animateContentSize(),
|
||||
horizontalArrangement = Arrangement.spacedBy(5.dp, Alignment.CenterHorizontally),
|
||||
) {
|
||||
trustedNetworks.forEach { ssid ->
|
||||
ClickableIconButton(
|
||||
onClick = {
|
||||
if (isTv) {
|
||||
onDelete(ssid)
|
||||
}
|
||||
},
|
||||
onIconClick = { onDelete(ssid) },
|
||||
onClick = { onDelete(ssid) },
|
||||
text = ssid,
|
||||
icon = Icons.Filled.Close,
|
||||
)
|
||||
@@ -70,8 +76,7 @@ fun TrustedNetworkTextBox(
|
||||
},
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
supportingText = supporting,
|
||||
modifier =
|
||||
Modifier.padding(top = 5.dp, bottom = 10.dp).fillMaxWidth().padding(end = 16.dp),
|
||||
modifier = Modifier.fillMaxWidth().bringIntoViewRequester(bringIntoViewRequester),
|
||||
singleLine = true,
|
||||
keyboardOptions =
|
||||
KeyboardOptions(
|
||||
|
||||
-218
@@ -1,218 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Filter1
|
||||
import androidx.compose.material.icons.outlined.Security
|
||||
import androidx.compose.material.icons.outlined.Wifi
|
||||
import androidx.compose.material.icons.outlined.WifiFind
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.functions.rememberClipboardHelper
|
||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.LearnMoreLinkLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.state.AutoTunnelUiState
|
||||
import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asTitleString
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.AutoTunnelViewModel
|
||||
|
||||
@Composable
|
||||
fun wifiTunnelingItems(
|
||||
autoTunnelState: AutoTunnelUiState,
|
||||
viewModel: AutoTunnelViewModel,
|
||||
): List<SelectionItem> {
|
||||
val context = LocalContext.current
|
||||
val navController = LocalNavController.current
|
||||
val clipboardHelper = rememberClipboardHelper()
|
||||
|
||||
var currentText by rememberSaveable { mutableStateOf("") }
|
||||
|
||||
LaunchedEffect(autoTunnelState.settings.trustedNetworkSSIDs) { currentText = "" }
|
||||
|
||||
val baseItems =
|
||||
listOf(
|
||||
SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.Wifi, contentDescription = null) },
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.tunnel_on_wifi),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(
|
||||
MaterialTheme.colorScheme.onSurface
|
||||
),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
checked = autoTunnelState.settings.isTunnelOnWifiEnabled,
|
||||
onClick = { viewModel.setAutoTunnelOnWifiEnabled(it) },
|
||||
)
|
||||
},
|
||||
description = {
|
||||
val wifiInfo by
|
||||
remember(autoTunnelState.connectivityState) {
|
||||
derivedStateOf {
|
||||
autoTunnelState.connectivityState
|
||||
?.wifiState
|
||||
?.takeIf { it.connected }
|
||||
.let { Pair(it?.ssid, it?.securityType) }
|
||||
}
|
||||
}
|
||||
val (wifiName, securityType) = wifiInfo
|
||||
Column {
|
||||
Text(
|
||||
text =
|
||||
wifiName?.let { stringResource(R.string.wifi_name_template, it) }
|
||||
?: stringResource(R.string.inactive),
|
||||
style =
|
||||
MaterialTheme.typography.bodySmall.copy(
|
||||
color = MaterialTheme.colorScheme.outline
|
||||
),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier =
|
||||
Modifier.clickable { wifiName?.let { clipboardHelper.copy(it) } },
|
||||
)
|
||||
securityType?.let {
|
||||
Text(
|
||||
text = stringResource(R.string.security_template, it.name),
|
||||
style =
|
||||
MaterialTheme.typography.bodySmall.copy(
|
||||
color = MaterialTheme.colorScheme.outline
|
||||
),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
viewModel.setAutoTunnelOnWifiEnabled(
|
||||
!autoTunnelState.settings.isTunnelOnWifiEnabled
|
||||
)
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
return if (autoTunnelState.settings.isTunnelOnWifiEnabled) {
|
||||
baseItems +
|
||||
listOf(
|
||||
SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.WifiFind, contentDescription = null) },
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.wifi_detection_method),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(
|
||||
MaterialTheme.colorScheme.onSurface
|
||||
),
|
||||
)
|
||||
},
|
||||
description = {
|
||||
Text(
|
||||
stringResource(
|
||||
R.string.current_template,
|
||||
autoTunnelState.settings.wifiDetectionMethod.asTitleString(context),
|
||||
),
|
||||
style =
|
||||
MaterialTheme.typography.bodySmall.copy(
|
||||
MaterialTheme.colorScheme.outline
|
||||
),
|
||||
)
|
||||
},
|
||||
trailing = { ForwardButton { navController.push(Route.WifiDetectionMethod) } },
|
||||
onClick = { navController.push(Route.WifiDetectionMethod) },
|
||||
),
|
||||
SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.Filter1, contentDescription = null) },
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.use_wildcards),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(
|
||||
MaterialTheme.colorScheme.onSurface
|
||||
),
|
||||
)
|
||||
},
|
||||
description = {
|
||||
LearnMoreLinkLabel(
|
||||
{ context.openWebUrl(it) },
|
||||
stringResource(R.string.docs_wildcards),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
checked = autoTunnelState.settings.isWildcardsEnabled,
|
||||
onClick = { viewModel.setWildcardsEnabled(it) },
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
viewModel.setWildcardsEnabled(!autoTunnelState.settings.isWildcardsEnabled)
|
||||
},
|
||||
),
|
||||
SelectionItem(
|
||||
title = {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.weight(4f, false).fillMaxWidth(),
|
||||
) {
|
||||
val icon = Icons.Outlined.Security
|
||||
Icon(icon, icon.name, modifier = Modifier.size(iconSize))
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement =
|
||||
Arrangement.spacedBy(2.dp, Alignment.CenterVertically),
|
||||
modifier =
|
||||
Modifier.fillMaxWidth()
|
||||
.padding(start = 16.dp)
|
||||
.padding(vertical = 6.dp),
|
||||
) {
|
||||
Text(
|
||||
stringResource(R.string.trusted_wifi_names),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(
|
||||
MaterialTheme.colorScheme.onSurface
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
description = {
|
||||
TrustedNetworkTextBox(
|
||||
autoTunnelState.settings.trustedNetworkSSIDs,
|
||||
onDelete = { viewModel.removeTrustedNetworkName(it) },
|
||||
currentText = currentText,
|
||||
onSave = { ssid -> viewModel.saveTrustedNetworkName(ssid) },
|
||||
onValueChange = { currentText = it },
|
||||
supporting = {
|
||||
if (autoTunnelState.settings.isWildcardsEnabled) WildcardsLabel()
|
||||
},
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
} else {
|
||||
baseItems
|
||||
}
|
||||
}
|
||||
+31
-18
@@ -9,19 +9,24 @@ import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.DirectionsWalk
|
||||
import androidx.compose.material.icons.automirrored.outlined.Launch
|
||||
import androidx.compose.material.icons.outlined.Settings
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.disclosure.components.LocationDisclosureHeader
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.disclosure.components.appSettingsItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.disclosure.components.skipItem
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.AutoTunnelViewModel
|
||||
|
||||
@Composable
|
||||
@@ -43,21 +48,29 @@ fun LocationDisclosureScreen(viewModel: AutoTunnelViewModel = hiltViewModel()) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(18.dp, Alignment.Top),
|
||||
modifier = Modifier.fillMaxSize().padding(top = 18.dp).padding(horizontal = 16.dp),
|
||||
modifier = Modifier.fillMaxSize().padding(top = 18.dp),
|
||||
) {
|
||||
LocationDisclosureHeader()
|
||||
SurfaceSelectionGroupButton(
|
||||
items =
|
||||
listOf(
|
||||
appSettingsItem {
|
||||
val intent =
|
||||
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
||||
data = Uri.fromParts("package", context.packageName, null)
|
||||
}
|
||||
settingsLauncher.launch(intent)
|
||||
}
|
||||
)
|
||||
)
|
||||
SurfaceSelectionGroupButton(items = listOf(skipItem { goToAutoTunnel() }))
|
||||
LocationDisclosureHeader(Modifier.padding(horizontal = 16.dp))
|
||||
Column {
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.Settings, contentDescription = null) },
|
||||
title = stringResource(R.string.launch_app_settings),
|
||||
trailing = { Icon(Icons.AutoMirrored.Outlined.Launch, null) },
|
||||
onClick = {
|
||||
val intent =
|
||||
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
||||
data = Uri.fromParts("package", context.packageName, null)
|
||||
}
|
||||
settingsLauncher.launch(intent)
|
||||
},
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = {
|
||||
Icon(Icons.AutoMirrored.Outlined.DirectionsWalk, contentDescription = null)
|
||||
},
|
||||
title = stringResource(R.string.skip),
|
||||
onClick = { goToAutoTunnel() },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.disclosure.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.LocationOn
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.LaunchButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType
|
||||
|
||||
@Composable
|
||||
fun appSettingsItem(onClick: () -> Unit): SelectionItem {
|
||||
return SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.LocationOn, contentDescription = null) },
|
||||
title = {
|
||||
SelectionItemLabel(
|
||||
stringResource(R.string.launch_app_settings),
|
||||
labelType = SelectionLabelType.TITLE,
|
||||
)
|
||||
},
|
||||
trailing = { LaunchButton { onClick() } },
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
+29
-16
@@ -1,6 +1,8 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.disclosure.components
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.PermScanWifi
|
||||
@@ -8,26 +10,37 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
|
||||
@Composable
|
||||
fun LocationDisclosureHeader() {
|
||||
val icon = Icons.Rounded.PermScanWifi
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = icon.name,
|
||||
modifier = Modifier.padding(24.dp).size(100.dp),
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.prominent_background_location_title),
|
||||
style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold),
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.prominent_background_location_message),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
fun LocationDisclosureHeader(modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
modifier = modifier,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.PermScanWifi,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(100.dp),
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.prominent_background_location_title),
|
||||
style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold),
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.prominent_background_location_message),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
textAlign = TextAlign.Start,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.disclosure.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType
|
||||
|
||||
@Composable
|
||||
fun skipItem(onClick: () -> Unit): SelectionItem {
|
||||
return SelectionItem(
|
||||
title = {
|
||||
SelectionItemLabel(stringResource(R.string.skip), labelType = SelectionLabelType.TITLE)
|
||||
},
|
||||
trailing = { ForwardButton { onClick() } },
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
+271
@@ -0,0 +1,271 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.preferences
|
||||
|
||||
import androidx.compose.foundation.gestures.ScrollableDefaults
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.overscroll
|
||||
import androidx.compose.foundation.relocation.BringIntoViewRequester
|
||||
import androidx.compose.foundation.relocation.bringIntoViewRequester
|
||||
import androidx.compose.foundation.rememberOverscrollEffect
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.outlined.Add
|
||||
import androidx.compose.material.icons.outlined.Check
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.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.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ClickableIconButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.textbox.CustomTextField
|
||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.TunnelNetwork
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components.WildcardsLabel
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.AutoTunnelViewModel
|
||||
|
||||
@Composable
|
||||
fun PreferredTunnelScreen(
|
||||
tunnelNetwork: TunnelNetwork,
|
||||
viewModel: AutoTunnelViewModel = hiltViewModel(),
|
||||
) {
|
||||
val navController = LocalNavController.current
|
||||
|
||||
val autoTunnelState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
||||
|
||||
if (autoTunnelState.isLoading) return
|
||||
|
||||
var selectedTunnel by remember { mutableStateOf<TunnelConf?>(null) }
|
||||
|
||||
val currentSelection =
|
||||
remember(autoTunnelState.tunnels) {
|
||||
when (tunnelNetwork) {
|
||||
TunnelNetwork.MOBILE_DATA ->
|
||||
autoTunnelState.tunnels.firstOrNull { it.isMobileDataTunnel }
|
||||
TunnelNetwork.ETHERNET ->
|
||||
autoTunnelState.tunnels.firstOrNull { it.isEthernetTunnel }
|
||||
TunnelNetwork.WIFI -> null
|
||||
}
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.Top,
|
||||
modifier =
|
||||
Modifier.pointerInput(Unit) {
|
||||
if (autoTunnelState.tunnels.isEmpty()) return@pointerInput
|
||||
}
|
||||
.overscroll(rememberOverscrollEffect()),
|
||||
state = rememberLazyListState(),
|
||||
userScrollEnabled = true,
|
||||
reverseLayout = false,
|
||||
flingBehavior = ScrollableDefaults.flingBehavior(),
|
||||
) {
|
||||
item { GroupLabel(stringResource(R.string.tunnels), Modifier.padding(horizontal = 16.dp)) }
|
||||
if (tunnelNetwork != TunnelNetwork.WIFI) {
|
||||
item {
|
||||
SurfaceRow(
|
||||
title =
|
||||
buildAnnotatedString {
|
||||
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
|
||||
append(stringResource(R.string._default))
|
||||
}
|
||||
},
|
||||
trailing =
|
||||
if (currentSelection == null) {
|
||||
{
|
||||
Icon(
|
||||
Icons.Outlined.Check,
|
||||
stringResource(id = R.string.selected),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
} else null,
|
||||
onClick = {
|
||||
when (tunnelNetwork) {
|
||||
TunnelNetwork.MOBILE_DATA -> {
|
||||
viewModel.setPreferredMobileDataTunnel(null)
|
||||
navController.pop()
|
||||
}
|
||||
TunnelNetwork.ETHERNET -> {
|
||||
viewModel.setPreferredEthernetTunnel(null)
|
||||
navController.pop()
|
||||
}
|
||||
TunnelNetwork.WIFI -> Unit
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
items(autoTunnelState.tunnels, key = { it.id }) { tunnel ->
|
||||
SurfaceRow(
|
||||
title = tunnel.tunName,
|
||||
trailing =
|
||||
if (currentSelection?.id == tunnel.id) {
|
||||
{
|
||||
Icon(
|
||||
Icons.Outlined.Check,
|
||||
stringResource(id = R.string.selected),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
} else null,
|
||||
onClick = {
|
||||
when (tunnelNetwork) {
|
||||
TunnelNetwork.MOBILE_DATA -> {
|
||||
viewModel.setPreferredMobileDataTunnel(tunnel)
|
||||
navController.pop()
|
||||
}
|
||||
|
||||
TunnelNetwork.ETHERNET -> {
|
||||
viewModel.setPreferredEthernetTunnel(tunnel)
|
||||
navController.pop()
|
||||
}
|
||||
|
||||
TunnelNetwork.WIFI -> {
|
||||
selectedTunnel = tunnel
|
||||
}
|
||||
}
|
||||
},
|
||||
expandedContent =
|
||||
if (tunnelNetwork == TunnelNetwork.WIFI) {
|
||||
{
|
||||
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
FlowRow(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement =
|
||||
Arrangement.spacedBy(5.dp, Alignment.CenterHorizontally),
|
||||
) {
|
||||
tunnel.tunnelNetworks.forEach { ssid ->
|
||||
ClickableIconButton(
|
||||
onClick = {
|
||||
viewModel.removeTunnelNetwork(tunnel, ssid)
|
||||
},
|
||||
text = ssid,
|
||||
icon = Icons.Filled.Close,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (selectedTunnel?.id == tunnel.id) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val bringIntoViewRequester = remember {
|
||||
BringIntoViewRequester()
|
||||
}
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
|
||||
var currentText by remember { mutableStateOf("") }
|
||||
|
||||
LaunchedEffect(tunnel.tunnelNetworks.size) {
|
||||
bringIntoViewRequester.bringIntoView()
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
focusRequester.requestFocus()
|
||||
keyboardController?.show()
|
||||
}
|
||||
|
||||
CustomTextField(
|
||||
textStyle =
|
||||
MaterialTheme.typography.bodySmall.copy(
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
),
|
||||
value = currentText,
|
||||
onValueChange = { currentText = it },
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
label = {
|
||||
Text(
|
||||
stringResource(R.string.add_wifi_name),
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
)
|
||||
},
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
supportingText =
|
||||
if (autoTunnelState.settings.isWildcardsEnabled) {
|
||||
{ WildcardsLabel() }
|
||||
} else null,
|
||||
modifier =
|
||||
Modifier.fillMaxWidth()
|
||||
.focusRequester(focusRequester)
|
||||
.bringIntoViewRequester(bringIntoViewRequester)
|
||||
.imePadding(),
|
||||
singleLine = true,
|
||||
keyboardOptions =
|
||||
KeyboardOptions(
|
||||
capitalization = KeyboardCapitalization.None,
|
||||
imeAction = ImeAction.Done,
|
||||
),
|
||||
keyboardActions =
|
||||
KeyboardActions(
|
||||
onDone = {
|
||||
viewModel.addTunnelNetwork(tunnel, currentText)
|
||||
currentText = ""
|
||||
}
|
||||
),
|
||||
trailing = {
|
||||
if (currentText != "") {
|
||||
IconButton(
|
||||
onClick = {
|
||||
viewModel.addTunnelNetwork(
|
||||
tunnel,
|
||||
currentText,
|
||||
)
|
||||
currentText = ""
|
||||
}
|
||||
) {
|
||||
val icon = Icons.Outlined.Add
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription =
|
||||
stringResource(
|
||||
R.string
|
||||
.trusted_ssid_value_description
|
||||
),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.preferences.wifi
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Filter1
|
||||
import androidx.compose.material.icons.outlined.Map
|
||||
import androidx.compose.material.icons.outlined.WifiFind
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.text.DescriptionText
|
||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
|
||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.TunnelNetwork
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components.TrustedNetworkTextBox
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components.WildcardsLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.LearnMoreLinkLabel
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asTitleString
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.AutoTunnelViewModel
|
||||
|
||||
@Composable
|
||||
fun WifiSettingsScreen(viewModel: AutoTunnelViewModel = hiltViewModel()) {
|
||||
val context = LocalContext.current
|
||||
val navController = LocalNavController.current
|
||||
|
||||
val autoTunnelState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
||||
|
||||
var currentText by rememberSaveable { mutableStateOf("") }
|
||||
|
||||
LaunchedEffect(autoTunnelState.settings.trustedNetworkSSIDs) { currentText = "" }
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top),
|
||||
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
Column {
|
||||
GroupLabel(stringResource(R.string.general), Modifier.padding(horizontal = 16.dp))
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.WifiFind, contentDescription = null) },
|
||||
title = stringResource(R.string.wifi_detection_method),
|
||||
description = {
|
||||
DescriptionText(
|
||||
stringResource(
|
||||
R.string.current_template,
|
||||
autoTunnelState.settings.wifiDetectionMethod.asTitleString(context),
|
||||
)
|
||||
)
|
||||
},
|
||||
onClick = { navController.push(Route.WifiDetectionMethod) },
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.Filter1, contentDescription = null) },
|
||||
title = stringResource(R.string.use_wildcards),
|
||||
description = {
|
||||
LearnMoreLinkLabel(
|
||||
{ context.openWebUrl(it) },
|
||||
stringResource(R.string.docs_wildcards),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
checked = autoTunnelState.settings.isWildcardsEnabled,
|
||||
onClick = { viewModel.setWildcardsEnabled(it) },
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
viewModel.setWildcardsEnabled(!autoTunnelState.settings.isWildcardsEnabled)
|
||||
},
|
||||
)
|
||||
}
|
||||
Column {
|
||||
GroupLabel(stringResource(R.string.networks), Modifier.padding(horizontal = 16.dp))
|
||||
SurfaceRow(
|
||||
title = stringResource(R.string.trusted_wifi_names),
|
||||
expandedContent = {
|
||||
TrustedNetworkTextBox(
|
||||
autoTunnelState.settings.trustedNetworkSSIDs,
|
||||
onDelete = { viewModel.removeTrustedNetworkName(it) },
|
||||
currentText = currentText,
|
||||
onSave = { ssid -> viewModel.saveTrustedNetworkName(ssid) },
|
||||
onValueChange = { currentText = it },
|
||||
supporting = {
|
||||
if (autoTunnelState.settings.isWildcardsEnabled) WildcardsLabel()
|
||||
},
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
Column {
|
||||
GroupLabel(stringResource(R.string.tunnels), Modifier.padding(horizontal = 16.dp))
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.Map, contentDescription = null) },
|
||||
title = stringResource(R.string.tunnel_mapping),
|
||||
onClick = { navController.push(Route.PreferredTunnel(TunnelNetwork.WIFI)) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+164
-101
@@ -1,51 +1,66 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.ViewQuilt
|
||||
import androidx.compose.material.icons.outlined.Android
|
||||
import androidx.compose.material.icons.outlined.Dns
|
||||
import androidx.compose.material.icons.outlined.ExpandMore
|
||||
import androidx.compose.material.icons.outlined.Lan
|
||||
import androidx.compose.material.icons.outlined.MonitorHeart
|
||||
import androidx.compose.material.icons.outlined.Pin
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.data.model.AppMode
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.SectionDivider
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SwitchWithDivider
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.text.DescriptionText
|
||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.*
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.proxy.compoents.AppModeBottomSheet
|
||||
import com.zaneschepke.wireguardautotunnel.ui.sideeffect.LocalSideEffect
|
||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asString
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asTitleString
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.capitalize
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.SettingsViewModel
|
||||
import java.util.Locale
|
||||
import org.orbitmvi.orbit.compose.collectSideEffect
|
||||
|
||||
@Composable
|
||||
fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
|
||||
val isTv = LocalIsAndroidTV.current
|
||||
val context = LocalContext.current
|
||||
val navController = LocalNavController.current
|
||||
val focusManager = LocalFocusManager.current
|
||||
val sharedViewModel = LocalSharedVm.current
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
|
||||
val locale = remember { Locale.getDefault() }
|
||||
|
||||
val settingsState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
||||
|
||||
if (settingsState.isLoading) return
|
||||
|
||||
var showBackupSheet by rememberSaveable { mutableStateOf(false) }
|
||||
var showAppModeSheet by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
@@ -53,8 +68,11 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
|
||||
rememberSaveable(settingsState.settings.appMode) {
|
||||
mutableStateOf(settingsState.settings.appMode)
|
||||
}
|
||||
|
||||
if (!settingsState.stateInitialized) return
|
||||
val monitoringEnabled by
|
||||
rememberSaveable(appMode) {
|
||||
mutableStateOf(appMode == AppMode.VPN || appMode == AppMode.KERNEL)
|
||||
}
|
||||
val dnsEnabled by rememberSaveable(appMode) { mutableStateOf(appMode != AppMode.KERNEL) }
|
||||
|
||||
sharedViewModel.collectSideEffect { sideEffect ->
|
||||
when (sideEffect) {
|
||||
@@ -81,101 +99,146 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
||||
modifier =
|
||||
Modifier.verticalScroll(rememberScrollState())
|
||||
.fillMaxSize()
|
||||
.then(
|
||||
if (!isTv) {
|
||||
Modifier.clickable(
|
||||
indication = null,
|
||||
interactionSource = interactionSource,
|
||||
onClick = { focusManager.clearFocus() },
|
||||
)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
)
|
||||
.padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top),
|
||||
modifier = Modifier.verticalScroll(rememberScrollState()).fillMaxSize(),
|
||||
) {
|
||||
SurfaceSelectionGroupButton(
|
||||
listOf(appModeItem(settingsState.settings.appMode) { showAppModeSheet = true })
|
||||
)
|
||||
SectionDivider()
|
||||
SurfaceSelectionGroupButton(
|
||||
items =
|
||||
buildList {
|
||||
if (appMode == AppMode.LOCK_DOWN) {
|
||||
add(
|
||||
lanTrafficItem(settingsState.settings.isLanOnKillSwitchEnabled) {
|
||||
viewModel.setLanKillSwitchEnabled(it)
|
||||
}
|
||||
Column {
|
||||
GroupLabel(
|
||||
stringResource(R.string.tunnel).capitalize(locale),
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = {
|
||||
Icon(ImageVector.vectorResource(R.drawable.sdk), contentDescription = null)
|
||||
},
|
||||
trailing = {
|
||||
Icon(
|
||||
Icons.Outlined.ExpandMore,
|
||||
contentDescription = stringResource(R.string.select),
|
||||
)
|
||||
},
|
||||
title = stringResource(R.string.backend_mode),
|
||||
description = {
|
||||
DescriptionText(
|
||||
stringResource(R.string.current_template, appMode.asTitleString(context))
|
||||
)
|
||||
},
|
||||
onClick = { showAppModeSheet = true },
|
||||
)
|
||||
if (appMode == AppMode.LOCK_DOWN) {
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.Lan, contentDescription = null) },
|
||||
title = stringResource(R.string.allow_lan_traffic),
|
||||
description = {
|
||||
Text(
|
||||
text = stringResource(R.string.bypass_lan_for_kill_switch),
|
||||
style =
|
||||
MaterialTheme.typography.bodySmall.copy(
|
||||
MaterialTheme.colorScheme.outline
|
||||
),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
checked = settingsState.settings.isLanOnKillSwitchEnabled,
|
||||
onClick = { viewModel.setLanKillSwitchEnabled(it) },
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
viewModel.setLanKillSwitchEnabled(
|
||||
!settingsState.settings.isLanOnKillSwitchEnabled
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.MonitorHeart, null) },
|
||||
title = stringResource(R.string.tunnel_monitoring),
|
||||
enabled = monitoringEnabled,
|
||||
onClick = { navController.push(Route.TunnelMonitoring) },
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.Dns, null) },
|
||||
title = stringResource(R.string.dns_settings),
|
||||
enabled = dnsEnabled,
|
||||
onClick = {
|
||||
if (dnsEnabled) navController.push(Route.Dns)
|
||||
else
|
||||
sharedViewModel.showSnackMessage(
|
||||
StringValue.StringResource(
|
||||
R.string.mode_disabled_template,
|
||||
appMode.asString(context),
|
||||
)
|
||||
)
|
||||
},
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = {
|
||||
Icon(ImageVector.vectorResource(R.drawable.globe), contentDescription = null)
|
||||
},
|
||||
title = stringResource(R.string.global_overrides),
|
||||
trailing = { modifier ->
|
||||
SwitchWithDivider(
|
||||
checked = settingsState.settings.isTunnelGlobalsEnabled,
|
||||
onClick = { viewModel.setTunnelGlobals(it) },
|
||||
modifier = modifier,
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
settingsState.globalTunnelConf?.let {
|
||||
navController.push(Route.TunnelGlobals(it.id))
|
||||
}
|
||||
add(
|
||||
tunnelMonitoringItem(
|
||||
appMode,
|
||||
onClick = { navController.push(Route.TunnelMonitoring) },
|
||||
) {
|
||||
sharedViewModel.showSnackMessage(
|
||||
StringValue.StringResource(
|
||||
R.string.mode_disabled_template,
|
||||
appMode.asString(context),
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
add(
|
||||
dnsSettingsItem(appMode, onClick = { navController.push(Route.Dns) }) {
|
||||
sharedViewModel.showSnackMessage(
|
||||
StringValue.StringResource(
|
||||
R.string.mode_disabled_template,
|
||||
appMode.asString(context),
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
add(
|
||||
tunnelGlobalsSettingItem(
|
||||
settingsState.settings.isTunnelGlobalsEnabled,
|
||||
onClick = viewModel::setTunnelGlobals,
|
||||
) {
|
||||
settingsState.globalTunnelConf?.let {
|
||||
navController.push(Route.TunnelGlobals(it.id))
|
||||
}
|
||||
}
|
||||
)
|
||||
if (showProxySettings)
|
||||
add(proxYSettingsItem { navController.push(Route.ProxySettings) })
|
||||
}
|
||||
)
|
||||
SectionDivider()
|
||||
SurfaceSelectionGroupButton(
|
||||
listOf(systemFeaturesItem { navController.push(Route.SystemFeatures) })
|
||||
)
|
||||
SectionDivider()
|
||||
SurfaceSelectionGroupButton(
|
||||
items =
|
||||
buildList {
|
||||
add(appearanceItem { navController.push(Route.Appearance) })
|
||||
add(
|
||||
localLoggingItem(settingsState.isLocalLoggingEnabled) {
|
||||
viewModel.setLocalLogging(it)
|
||||
}
|
||||
)
|
||||
if (settingsState.isLocalLoggingEnabled)
|
||||
add(readLogsItem { navController.push(Route.Logs) })
|
||||
add(
|
||||
pinLockItem(settingsState.isPinLockEnabled) { enabled ->
|
||||
if (enabled) {
|
||||
},
|
||||
)
|
||||
if (showProxySettings) {
|
||||
SurfaceRow(
|
||||
leading = { Icon(ImageVector.vectorResource(R.drawable.proxy), null) },
|
||||
title = stringResource(R.string.proxy_settings),
|
||||
onClick = { navController.push(Route.ProxySettings) },
|
||||
)
|
||||
}
|
||||
}
|
||||
Column {
|
||||
GroupLabel(
|
||||
stringResource(R.string.general),
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.Android, null) },
|
||||
title = stringResource(R.string.android_integrations),
|
||||
onClick = { navController.push(Route.AndroidIntegrations) },
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = {
|
||||
Icon(Icons.AutoMirrored.Outlined.ViewQuilt, contentDescription = null)
|
||||
},
|
||||
title = stringResource(R.string.appearance),
|
||||
onClick = { navController.push(Route.Appearance) },
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.Pin, contentDescription = null) },
|
||||
title = stringResource(R.string.enable_app_lock),
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
checked = settingsState.isPinLockEnabled,
|
||||
onClick = {
|
||||
if (it) {
|
||||
navController.push(Route.Lock)
|
||||
} else {
|
||||
sharedViewModel.setPinLockEnabled(false)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
)
|
||||
Spacer(Modifier.height(80.dp))
|
||||
},
|
||||
onClick = {
|
||||
if (!settingsState.isPinLockEnabled) {
|
||||
navController.push(Route.Lock)
|
||||
} else {
|
||||
sharedViewModel.setPinLockEnabled(false)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+35
-13
@@ -3,28 +3,50 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.Launch
|
||||
import androidx.compose.material.icons.outlined.Contrast
|
||||
import androidx.compose.material.icons.outlined.Notifications
|
||||
import androidx.compose.material.icons.outlined.Translate
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.SectionDivider
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.components.DisplayThemeItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.components.LanguageItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.components.NotificationsItem
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.launchNotificationSettings
|
||||
|
||||
@Composable
|
||||
fun AppearanceScreen() {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
||||
modifier = Modifier.fillMaxSize().padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
SurfaceSelectionGroupButton(items = listOf(LanguageItem()))
|
||||
SectionDivider()
|
||||
SurfaceSelectionGroupButton(items = listOf(NotificationsItem()))
|
||||
SectionDivider()
|
||||
SurfaceSelectionGroupButton(items = listOf(DisplayThemeItem()))
|
||||
val navController = LocalNavController.current
|
||||
val context = LocalContext.current
|
||||
Column {
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.Translate, contentDescription = null) },
|
||||
title = stringResource(R.string.language),
|
||||
onClick = { navController.push(Route.Language) },
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.Notifications, contentDescription = null) },
|
||||
title = stringResource(R.string.notifications),
|
||||
trailing = { Icon(Icons.AutoMirrored.Outlined.Launch, null) },
|
||||
onClick = { context.launchNotificationSettings() },
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.Contrast, contentDescription = null) },
|
||||
title = stringResource(R.string.display_theme),
|
||||
onClick = { navController.push(Route.Display) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Contrast
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
|
||||
|
||||
@Composable
|
||||
fun DisplayThemeItem(): SelectionItem {
|
||||
val navController = LocalNavController.current
|
||||
return SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.Contrast, contentDescription = null) },
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.display_theme),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
trailing = { ForwardButton { navController.push(Route.Display) } },
|
||||
onClick = { navController.push(Route.Display) },
|
||||
)
|
||||
}
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Translate
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
|
||||
|
||||
@Composable
|
||||
fun LanguageItem(): SelectionItem {
|
||||
val navController = LocalNavController.current
|
||||
return SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.Translate, contentDescription = null) },
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.language),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
trailing = { ForwardButton { navController.push(Route.Language) } },
|
||||
onClick = { navController.push(Route.Language) },
|
||||
)
|
||||
}
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Notifications
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.LaunchButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.launchNotificationSettings
|
||||
|
||||
@Composable
|
||||
fun NotificationsItem(): SelectionItem {
|
||||
val context = LocalContext.current
|
||||
return SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.Notifications, contentDescription = null) },
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.notifications),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
trailing = { LaunchButton { context.launchNotificationSettings() } },
|
||||
onClick = { context.launchNotificationSettings() },
|
||||
)
|
||||
}
|
||||
+18
-7
@@ -3,17 +3,19 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.displ
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Check
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.IconSurfaceButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
||||
import com.zaneschepke.wireguardautotunnel.ui.theme.Theme
|
||||
|
||||
@Composable
|
||||
@@ -25,8 +27,8 @@ fun DisplayScreen() {
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
||||
modifier = Modifier.fillMaxSize().padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.Top,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
enumValues<Theme>().forEach {
|
||||
val title =
|
||||
@@ -38,10 +40,19 @@ fun DisplayScreen() {
|
||||
Theme.DARKER -> stringResource(R.string.darker)
|
||||
Theme.AMOLED -> stringResource(R.string.amoled)
|
||||
}
|
||||
IconSurfaceButton(
|
||||
SurfaceRow(
|
||||
title = title,
|
||||
trailing =
|
||||
if (appState.theme == it) {
|
||||
{
|
||||
Icon(
|
||||
Icons.Outlined.Check,
|
||||
stringResource(id = R.string.selected),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
} else null,
|
||||
onClick = { sharedViewModel.setTheme(it) },
|
||||
selected = appState.theme == it,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+30
-12
@@ -1,22 +1,23 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.language
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Check
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.IconSurfaceButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
||||
import com.zaneschepke.wireguardautotunnel.util.LocaleUtil
|
||||
import java.text.Collator
|
||||
import java.util.*
|
||||
@@ -62,13 +63,21 @@ fun LanguageScreen() {
|
||||
state = lazyListState,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Top,
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
) {
|
||||
item {
|
||||
IconSurfaceButton(
|
||||
stringResource(R.string.automatic),
|
||||
{ sharedViewModel.setLocale(LocaleUtil.OPTION_PHONE_LANGUAGE) },
|
||||
selected = appState.locale == LocaleUtil.OPTION_PHONE_LANGUAGE,
|
||||
SurfaceRow(
|
||||
title = stringResource(R.string.automatic),
|
||||
trailing =
|
||||
if (appState.locale == LocaleUtil.OPTION_PHONE_LANGUAGE) {
|
||||
{
|
||||
Icon(
|
||||
Icons.Outlined.Check,
|
||||
stringResource(id = R.string.selected),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
} else null,
|
||||
onClick = { sharedViewModel.setLocale(LocaleUtil.OPTION_PHONE_LANGUAGE) },
|
||||
)
|
||||
}
|
||||
items(sortedLocales, key = { it }) { locale ->
|
||||
@@ -76,10 +85,19 @@ fun LanguageScreen() {
|
||||
locale.getDisplayName(locale).replaceFirstChar {
|
||||
if (it.isLowerCase()) it.titlecase(locale) else it.toString()
|
||||
}
|
||||
IconSurfaceButton(
|
||||
buttonText,
|
||||
SurfaceRow(
|
||||
title = buttonText,
|
||||
trailing =
|
||||
if (appState.locale == locale.toLanguageTag()) {
|
||||
{
|
||||
Icon(
|
||||
Icons.Outlined.Check,
|
||||
stringResource(id = R.string.selected),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
} else null,
|
||||
onClick = { sharedViewModel.setLocale(locale.toLanguageTag()) },
|
||||
selected = appState.locale == locale.toLanguageTag(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.language.components
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SelectionItemButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.label.SelectedLabel
|
||||
import com.zaneschepke.wireguardautotunnel.util.LocaleUtil
|
||||
|
||||
@Composable
|
||||
fun automaticLanguageItem(locale: String, onChange: (locale: String) -> Unit) {
|
||||
val isTv = LocalIsAndroidTV.current
|
||||
Box(modifier = Modifier.padding(top = 24.dp)) {
|
||||
SelectionItemButton(
|
||||
buttonText = stringResource(R.string.automatic),
|
||||
onClick = { onChange(LocaleUtil.OPTION_PHONE_LANGUAGE) },
|
||||
trailing = {
|
||||
if (locale == LocaleUtil.OPTION_PHONE_LANGUAGE) {
|
||||
SelectedLabel()
|
||||
}
|
||||
},
|
||||
ripple = isTv,
|
||||
)
|
||||
}
|
||||
}
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.language.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SelectionItemButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.label.SelectedLabel
|
||||
import java.util.*
|
||||
|
||||
@Composable
|
||||
fun languageItem(currentLocale: String, locale: Locale, onChange: (String) -> Unit) {
|
||||
val isTv = LocalIsAndroidTV.current
|
||||
SelectionItemButton(
|
||||
buttonText =
|
||||
locale.getDisplayLanguage(locale).replaceFirstChar {
|
||||
if (it.isLowerCase()) it.titlecase(locale) else it.toString()
|
||||
} +
|
||||
if (locale.toLanguageTag().contains("-")) {
|
||||
" (${locale.getDisplayCountry(locale).replaceFirstChar {
|
||||
if (it.isLowerCase()) it.titlecase(locale) else it.toString()
|
||||
}})"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
onClick = { onChange(locale.toLanguageTag()) },
|
||||
trailing = {
|
||||
if (locale.toLanguageTag() == currentLocale) {
|
||||
SelectedLabel()
|
||||
}
|
||||
},
|
||||
ripple = isTv,
|
||||
)
|
||||
}
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Lan
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
|
||||
@Composable
|
||||
fun lanTrafficItem(
|
||||
isLanKillSwitchEnabled: Boolean,
|
||||
onClick: (enable: Boolean) -> Unit,
|
||||
): SelectionItem {
|
||||
return SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.Lan, contentDescription = null) },
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.allow_lan_traffic),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
description = {
|
||||
Text(
|
||||
text = stringResource(R.string.bypass_lan_for_kill_switch),
|
||||
style = MaterialTheme.typography.bodySmall.copy(MaterialTheme.colorScheme.outline),
|
||||
)
|
||||
},
|
||||
trailing = { ScaledSwitch(checked = isLanKillSwitchEnabled, onClick = onClick) },
|
||||
onClick = { onClick(!isLanKillSwitchEnabled) },
|
||||
)
|
||||
}
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.ViewHeadline
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType
|
||||
|
||||
@Composable
|
||||
fun localLoggingItem(
|
||||
isLocalLoggingEnabled: Boolean,
|
||||
onClick: (checked: Boolean) -> Unit,
|
||||
): SelectionItem {
|
||||
return SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.ViewHeadline, contentDescription = null) },
|
||||
title = {
|
||||
SelectionItemLabel(stringResource(R.string.local_logging), SelectionLabelType.TITLE)
|
||||
},
|
||||
description = {
|
||||
SelectionItemLabel(
|
||||
stringResource(R.string.enable_local_logging),
|
||||
SelectionLabelType.DESCRIPTION,
|
||||
)
|
||||
},
|
||||
trailing = { ScaledSwitch(checked = isLocalLoggingEnabled, onClick = onClick) },
|
||||
onClick = { onClick(!isLocalLoggingEnabled) },
|
||||
)
|
||||
}
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.components
|
||||
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
|
||||
@Composable
|
||||
fun LocationServicesDialog(show: Boolean, onDismiss: () -> Unit, onAttest: () -> Unit) {
|
||||
if (show) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { onDismiss() },
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
onDismiss()
|
||||
onAttest()
|
||||
}
|
||||
) {
|
||||
Text(text = stringResource(R.string.okay))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { onDismiss() }) {
|
||||
Text(text = stringResource(R.string.cancel))
|
||||
}
|
||||
},
|
||||
title = { Text(text = stringResource(R.string.location_services_not_detected)) },
|
||||
text = { Text(text = stringResource(R.string.location_services_missing_message)) },
|
||||
)
|
||||
}
|
||||
}
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Pin
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
|
||||
@Composable
|
||||
fun pinLockItem(isPinLockEnabled: Boolean, onClick: (checked: Boolean) -> Unit): SelectionItem {
|
||||
|
||||
return SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.Pin, contentDescription = null) },
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.enable_app_lock),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
trailing = { ScaledSwitch(checked = isPinLockEnabled, onClick = onClick) },
|
||||
onClick = { onClick(!isPinLockEnabled) },
|
||||
)
|
||||
}
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.components
|
||||
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.ExpandMore
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.data.model.AppMode
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType
|
||||
import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asTitleString
|
||||
|
||||
@Composable
|
||||
fun appModeItem(appMode: AppMode, onClick: () -> Unit): SelectionItem {
|
||||
val context = LocalContext.current
|
||||
return SelectionItem(
|
||||
leading = { Icon(ImageVector.vectorResource(R.drawable.sdk), contentDescription = null) },
|
||||
trailing = {
|
||||
IconButton(onClick = onClick) {
|
||||
Icon(
|
||||
Icons.Outlined.ExpandMore,
|
||||
contentDescription = stringResource(R.string.select),
|
||||
modifier = Modifier.size(iconSize),
|
||||
)
|
||||
}
|
||||
},
|
||||
title = {
|
||||
SelectionItemLabel(stringResource(R.string.backend_mode), SelectionLabelType.TITLE)
|
||||
},
|
||||
description = {
|
||||
SelectionItemLabel(
|
||||
stringResource(R.string.current_template, appMode.asTitleString(context)),
|
||||
SelectionLabelType.DESCRIPTION,
|
||||
)
|
||||
},
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.ViewQuilt
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
|
||||
@Composable
|
||||
fun appearanceItem(onClick: () -> Unit): SelectionItem {
|
||||
return SelectionItem(
|
||||
leading = { Icon(Icons.AutoMirrored.Outlined.ViewQuilt, contentDescription = null) },
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.appearance),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
trailing = { ForwardButton { onClick() } },
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Dns
|
||||
import androidx.compose.material3.Icon
|
||||
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.saveable.rememberSaveable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.data.model.AppMode
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asString
|
||||
|
||||
@Composable
|
||||
fun dnsSettingsItem(
|
||||
appMode: AppMode,
|
||||
onClick: () -> Unit,
|
||||
onDisabledClick: () -> Unit,
|
||||
): SelectionItem {
|
||||
val context = LocalContext.current
|
||||
val enabled by rememberSaveable(appMode) { mutableStateOf(appMode != AppMode.KERNEL) }
|
||||
val click = if (enabled) onClick else onDisabledClick
|
||||
return SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.Dns, null) },
|
||||
trailing = { ForwardButton { click() } },
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.dns_settings),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
onClick = click,
|
||||
disabledReason =
|
||||
context.getString(R.string.mode_disabled_template, appMode.asString(context)),
|
||||
)
|
||||
}
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.components
|
||||
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
|
||||
@Composable
|
||||
fun proxYSettingsItem(onClick: () -> Unit): SelectionItem {
|
||||
return SelectionItem(
|
||||
leading = { Icon(ImageVector.vectorResource(R.drawable.proxy), null) },
|
||||
trailing = { ForwardButton { onClick() } },
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.proxy_settings),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ViewTimeline
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType
|
||||
|
||||
@Composable
|
||||
fun readLogsItem(onClick: () -> Unit): SelectionItem {
|
||||
return SelectionItem(
|
||||
leading = { Icon(Icons.Filled.ViewTimeline, contentDescription = null) },
|
||||
title = {
|
||||
SelectionItemLabel(stringResource(R.string.read_logs), SelectionLabelType.TITLE)
|
||||
},
|
||||
trailing = { ForwardButton { onClick() } },
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Android
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
|
||||
@Composable
|
||||
fun systemFeaturesItem(onClick: () -> Unit): SelectionItem {
|
||||
return SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.Android, null) },
|
||||
trailing = { ForwardButton { onClick.invoke() } },
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.system_features),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.components
|
||||
|
||||
import android.R.attr.enabled
|
||||
import android.R.attr.onClick
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType
|
||||
|
||||
@Composable
|
||||
fun tunnelGlobalsSettingItem(
|
||||
enabled: Boolean,
|
||||
onClick: (Boolean) -> Unit,
|
||||
onItemClick: () -> Unit,
|
||||
): SelectionItem {
|
||||
return SelectionItem(
|
||||
leading = { Icon(ImageVector.vectorResource(R.drawable.globe), contentDescription = null) },
|
||||
title = {
|
||||
SelectionItemLabel(
|
||||
stringResource(R.string.tunnel_global_overrides),
|
||||
SelectionLabelType.TITLE,
|
||||
)
|
||||
},
|
||||
trailing = { ScaledSwitch(checked = enabled, onClick = onClick) },
|
||||
onClick = onItemClick,
|
||||
)
|
||||
}
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.MonitorHeart
|
||||
import androidx.compose.material3.Icon
|
||||
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.saveable.rememberSaveable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.data.model.AppMode
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asString
|
||||
|
||||
@Composable
|
||||
fun tunnelMonitoringItem(
|
||||
appMode: AppMode,
|
||||
onClick: () -> Unit,
|
||||
onDisabledClick: () -> Unit,
|
||||
): SelectionItem {
|
||||
val context = LocalContext.current
|
||||
val enabled by
|
||||
rememberSaveable(appMode) {
|
||||
mutableStateOf(appMode == AppMode.VPN || appMode == AppMode.KERNEL)
|
||||
}
|
||||
val click = if (enabled) onClick else onDisabledClick
|
||||
return SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.MonitorHeart, null) },
|
||||
trailing = { ForwardButton { click() } },
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.tunnel_monitoring),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
isEnabled = enabled,
|
||||
onClick = click,
|
||||
disabledReason =
|
||||
context.getString(R.string.mode_disabled_template, appMode.asString(context)),
|
||||
)
|
||||
}
|
||||
+22
-38
@@ -4,15 +4,12 @@ import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Cloud
|
||||
import androidx.compose.material.icons.outlined.Dns
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -33,47 +30,34 @@ fun DnsSettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
|
||||
val context = LocalContext.current
|
||||
val settingsState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
||||
|
||||
if (settingsState.isLoading) return
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
||||
modifier =
|
||||
Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 16.dp),
|
||||
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
LabelledDropdown(
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.dns_protocol),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
),
|
||||
)
|
||||
},
|
||||
leading = { Icon(Icons.Outlined.Dns, contentDescription = null) },
|
||||
currentValue = settingsState.settings.dnsProtocol,
|
||||
onSelected = { selected -> selected?.let { viewModel.setDnsProtocol(it) } },
|
||||
options = DnsProtocol.entries,
|
||||
optionToString = { (it ?: DnsProtocol.SYSTEM).asString(context) },
|
||||
)
|
||||
AnimatedVisibility(settingsState.settings.dnsProtocol != DnsProtocol.SYSTEM) {
|
||||
Column {
|
||||
LabelledDropdown(
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.dns_provider),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
),
|
||||
)
|
||||
},
|
||||
leading = { Icon(Icons.Outlined.Cloud, contentDescription = null) },
|
||||
currentValue =
|
||||
settingsState.settings.dnsEndpoint?.let { DnsProvider.fromAddress(it) }
|
||||
?: DnsProvider.CLOUDFLARE,
|
||||
onSelected = { selected -> selected?.let { viewModel.setDnsProvider(it) } },
|
||||
options = DnsProvider.entries,
|
||||
optionToString = { it?.name ?: DnsProvider.CLOUDFLARE.name },
|
||||
title = stringResource(R.string.dns_protocol),
|
||||
leading = { Icon(Icons.Outlined.Dns, contentDescription = null) },
|
||||
currentValue = settingsState.settings.dnsProtocol,
|
||||
onSelected = { selected -> selected?.let { viewModel.setDnsProtocol(it) } },
|
||||
options = DnsProtocol.entries,
|
||||
optionToString = { (it ?: DnsProtocol.SYSTEM).asString(context) },
|
||||
)
|
||||
AnimatedVisibility(settingsState.settings.dnsProtocol != DnsProtocol.SYSTEM) {
|
||||
LabelledDropdown(
|
||||
title = stringResource(R.string.dns_provider),
|
||||
leading = { Icon(Icons.Outlined.Cloud, contentDescription = null) },
|
||||
currentValue =
|
||||
settingsState.settings.dnsEndpoint?.let { DnsProvider.fromAddress(it) }
|
||||
?: DnsProvider.CLOUDFLARE,
|
||||
onSelected = { selected -> selected?.let { viewModel.setDnsProvider(it) } },
|
||||
options = DnsProvider.entries,
|
||||
optionToString = { it?.name ?: DnsProvider.CLOUDFLARE.name },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+21
-14
@@ -1,11 +1,15 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.globals
|
||||
|
||||
import android.R.attr.onClick
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.CallSplit
|
||||
import androidx.compose.material.icons.outlined.Settings
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -13,10 +17,8 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.globals.components.globalConfigItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.tunneloptions.components.splitTunnelingItem
|
||||
|
||||
@Composable
|
||||
fun TunnelGlobalsScreen(globalTunnelId: Int) {
|
||||
@@ -24,17 +26,22 @@ fun TunnelGlobalsScreen(globalTunnelId: Int) {
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
||||
modifier =
|
||||
Modifier.verticalScroll(rememberScrollState()).fillMaxSize().padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top),
|
||||
modifier = Modifier.verticalScroll(rememberScrollState()).fillMaxSize(),
|
||||
) {
|
||||
SurfaceSelectionGroupButton(
|
||||
listOf(
|
||||
globalConfigItem { navController.push(Route.ConfigGlobal(globalTunnelId)) },
|
||||
splitTunnelingItem(stringResource(R.string.splt_tunneling)) {
|
||||
navController.push(Route.SplitTunnelGlobal(id = globalTunnelId))
|
||||
},
|
||||
Column {
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.Settings, contentDescription = null) },
|
||||
title = stringResource(R.string.configuration),
|
||||
onClick = { navController.push(Route.ConfigGlobal(globalTunnelId)) },
|
||||
)
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = {
|
||||
Icon(Icons.AutoMirrored.Outlined.CallSplit, contentDescription = null)
|
||||
},
|
||||
title = stringResource(R.string.splt_tunneling),
|
||||
onClick = { navController.push(Route.SplitTunnelGlobal(id = globalTunnelId)) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.globals.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Settings
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType
|
||||
|
||||
@Composable
|
||||
fun globalConfigItem(onClick: () -> Unit): SelectionItem {
|
||||
return SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.Settings, contentDescription = null) },
|
||||
title = {
|
||||
SelectionItemLabel(stringResource(R.string.configuration), SelectionLabelType.TITLE)
|
||||
},
|
||||
trailing = { ForwardButton { onClick() } },
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
+60
-44
@@ -7,9 +7,12 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.NetworkPing
|
||||
import androidx.compose.material.icons.outlined.QueryStats
|
||||
import androidx.compose.material.icons.outlined.Replay
|
||||
import androidx.compose.material.icons.outlined.Timer
|
||||
import androidx.compose.material.icons.outlined.TimerOff
|
||||
import androidx.compose.material.icons.outlined.ViewHeadline
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
@@ -22,40 +25,46 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SwitchWithDivider
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.dropdown.LabelledDropdown
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.monitoring.components.detailedPingStatsItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.monitoring.components.enablePingMonitoringItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.text.DescriptionText
|
||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.SettingsViewModel
|
||||
|
||||
@Composable
|
||||
fun TunnelMonitoringScreen(viewModel: SettingsViewModel = hiltViewModel()) {
|
||||
val navController = LocalNavController.current
|
||||
val settingsState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
||||
|
||||
if (settingsState.isLoading) return
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
||||
modifier =
|
||||
Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 16.dp),
|
||||
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
SurfaceSelectionGroupButton(
|
||||
listOf(
|
||||
enablePingMonitoringItem(settingsState.settings.isPingEnabled) {
|
||||
viewModel.setPingEnabled(it)
|
||||
}
|
||||
Column {
|
||||
GroupLabel(
|
||||
stringResource(R.string.ping),
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
)
|
||||
if (settingsState.settings.isPingEnabled) {
|
||||
LabelledDropdown(
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.tunnel_ping_interval),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
),
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.NetworkPing, contentDescription = null) },
|
||||
title = stringResource(R.string.ping_monitor),
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
checked = settingsState.showDetailedPingStats,
|
||||
onClick = { viewModel.setPingEnabled(it) },
|
||||
)
|
||||
},
|
||||
onClick = { viewModel.setPingEnabled(!settingsState.settings.isPingEnabled) },
|
||||
)
|
||||
LabelledDropdown(
|
||||
title = stringResource(R.string.tunnel_ping_interval),
|
||||
leading = { Icon(Icons.Outlined.Timer, contentDescription = null) },
|
||||
currentValue = settingsState.settings.tunnelPingIntervalSeconds,
|
||||
onSelected = { selected ->
|
||||
@@ -65,15 +74,7 @@ fun TunnelMonitoringScreen(viewModel: SettingsViewModel = hiltViewModel()) {
|
||||
optionToString = { it?.toString() ?: stringResource(R.string._default) },
|
||||
)
|
||||
LabelledDropdown(
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.attempts_per_interval),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
),
|
||||
)
|
||||
},
|
||||
title = stringResource(R.string.attempts_per_interval),
|
||||
leading = { Icon(Icons.Outlined.Replay, contentDescription = null) },
|
||||
currentValue = settingsState.settings.tunnelPingAttempts,
|
||||
onSelected = { selected -> selected?.let { viewModel.setTunnelPingAttempts(it) } },
|
||||
@@ -81,15 +82,7 @@ fun TunnelMonitoringScreen(viewModel: SettingsViewModel = hiltViewModel()) {
|
||||
optionToString = { it?.toString() ?: stringResource(R.string._default) },
|
||||
)
|
||||
LabelledDropdown(
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.ping_timeout),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
),
|
||||
)
|
||||
},
|
||||
title = stringResource(R.string.ping_timeout),
|
||||
leading = { Icon(Icons.Outlined.TimerOff, contentDescription = null) },
|
||||
currentValue = settingsState.settings.tunnelPingTimeoutSeconds,
|
||||
description = {
|
||||
@@ -107,12 +100,35 @@ fun TunnelMonitoringScreen(viewModel: SettingsViewModel = hiltViewModel()) {
|
||||
options = (10..20).toList() + null,
|
||||
optionToString = { it?.toString() ?: stringResource(R.string._default) },
|
||||
)
|
||||
SurfaceSelectionGroupButton(
|
||||
listOf(
|
||||
detailedPingStatsItem(settingsState.showDetailedPingStats) {
|
||||
viewModel.setDetailedPingStats(it)
|
||||
}
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.QueryStats, contentDescription = null) },
|
||||
title = stringResource(R.string.display_detailed_ping_stats),
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
checked = settingsState.showDetailedPingStats,
|
||||
onClick = { viewModel.setDetailedPingStats(it) },
|
||||
)
|
||||
},
|
||||
onClick = { viewModel.setDetailedPingStats(!settingsState.showDetailedPingStats) },
|
||||
)
|
||||
}
|
||||
Column {
|
||||
GroupLabel(
|
||||
stringResource(R.string.other),
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.ViewHeadline, contentDescription = null) },
|
||||
title = stringResource(R.string.local_logging),
|
||||
description = { DescriptionText(stringResource(R.string.enable_local_logging)) },
|
||||
trailing = { modifier ->
|
||||
SwitchWithDivider(
|
||||
checked = settingsState.isLocalLoggingEnabled,
|
||||
onClick = { viewModel.setLocalLogging(it) },
|
||||
modifier = modifier,
|
||||
)
|
||||
},
|
||||
onClick = { navController.push(Route.Logs) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.monitoring.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.QueryStats
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
|
||||
@Composable
|
||||
fun detailedPingStatsItem(
|
||||
detailedPingStatsEnabled: Boolean,
|
||||
onChange: (checked: Boolean) -> Unit,
|
||||
): SelectionItem {
|
||||
return SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.QueryStats, contentDescription = null) },
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.display_detailed_ping_stats),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
trailing = { ScaledSwitch(checked = detailedPingStatsEnabled, onClick = onChange) },
|
||||
onClick = { onChange(!detailedPingStatsEnabled) },
|
||||
)
|
||||
}
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.monitoring.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.NetworkPing
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
|
||||
@Composable
|
||||
fun enablePingMonitoringItem(
|
||||
isPingEnabled: Boolean,
|
||||
onChange: (checked: Boolean) -> Unit,
|
||||
): SelectionItem {
|
||||
return SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.NetworkPing, contentDescription = null) },
|
||||
trailing = { ScaledSwitch(checked = isPingEnabled, onClick = onChange) },
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.monitoring_ping),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
onClick = { onChange(!isPingEnabled) },
|
||||
)
|
||||
}
|
||||
+52
-71
@@ -26,10 +26,7 @@ import com.zaneschepke.wireguardautotunnel.domain.model.AppProxySettings
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.SecureScreenFromRecording
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.textbox.ConfigurationTextBox
|
||||
import com.zaneschepke.wireguardautotunnel.ui.sideeffect.LocalSideEffect
|
||||
@@ -100,82 +97,68 @@ fun ProxySettingsScreen(viewModel: ProxySettingsViewModel = hiltViewModel()) {
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp, Alignment.Top),
|
||||
modifier = Modifier.fillMaxSize().padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
SurfaceSelectionGroupButton(
|
||||
listOf(
|
||||
SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.Forward5, contentDescription = null) },
|
||||
title = {
|
||||
SelectionItemLabel(
|
||||
stringResource(R.string.socks_5_proxy),
|
||||
SelectionLabelType.TITLE,
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ScaledSwitch(checked = socks5Enabled, onClick = { socks5Enabled = it })
|
||||
},
|
||||
onClick = { socks5Enabled = !socks5Enabled },
|
||||
)
|
||||
)
|
||||
)
|
||||
if (socks5Enabled) {
|
||||
ConfigurationTextBox(
|
||||
modifier = Modifier.padding(horizontal = 12.dp),
|
||||
hint =
|
||||
stringResource(
|
||||
R.string.defaults_to_template,
|
||||
AppProxySettings.DEFAULT_SOCKS_BIND_ADDRESS,
|
||||
),
|
||||
label = stringResource(R.string.socks_5_bind_address),
|
||||
value = socksBindAddress,
|
||||
isError = proxySettingsState.isSocks5BindAddressError,
|
||||
onValueChange = {
|
||||
if (proxySettingsState.isSocks5BindAddressError)
|
||||
viewModel.clearSocks5BindError()
|
||||
socksBindAddress = it
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.Forward5, contentDescription = null) },
|
||||
title = stringResource(R.string.socks_5_proxy),
|
||||
trailing = {
|
||||
ScaledSwitch(checked = socks5Enabled, onClick = { socks5Enabled = it })
|
||||
},
|
||||
onClick = { socks5Enabled = !socks5Enabled },
|
||||
)
|
||||
if (socks5Enabled) {
|
||||
ConfigurationTextBox(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
hint =
|
||||
stringResource(
|
||||
R.string.defaults_to_template,
|
||||
AppProxySettings.DEFAULT_SOCKS_BIND_ADDRESS,
|
||||
),
|
||||
label = stringResource(R.string.socks_5_bind_address),
|
||||
value = socksBindAddress,
|
||||
isError = proxySettingsState.isSocks5BindAddressError,
|
||||
onValueChange = {
|
||||
if (proxySettingsState.isSocks5BindAddressError)
|
||||
viewModel.clearSocks5BindError()
|
||||
socksBindAddress = it
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
SurfaceSelectionGroupButton(
|
||||
listOf(
|
||||
SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.Http, contentDescription = null) },
|
||||
title = {
|
||||
SelectionItemLabel(
|
||||
stringResource(R.string.http_proxy),
|
||||
SelectionLabelType.TITLE,
|
||||
)
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.Http, contentDescription = null) },
|
||||
title = stringResource(R.string.http_proxy),
|
||||
trailing = { ScaledSwitch(checked = httpEnabled, onClick = { httpEnabled = it }) },
|
||||
onClick = { httpEnabled = !httpEnabled },
|
||||
)
|
||||
if (httpEnabled) {
|
||||
ConfigurationTextBox(
|
||||
hint =
|
||||
stringResource(
|
||||
R.string.defaults_to_template,
|
||||
AppProxySettings.DEFAULT_HTTP_BIND_ADDRESS,
|
||||
),
|
||||
label = stringResource(R.string.http_bind_address),
|
||||
value = httpBindAddress,
|
||||
isError = proxySettingsState.isHttpBindAddressError,
|
||||
onValueChange = {
|
||||
if (proxySettingsState.isSocks5BindAddressError)
|
||||
viewModel.clearHttpBindError()
|
||||
httpBindAddress = it
|
||||
},
|
||||
trailing = {
|
||||
ScaledSwitch(checked = httpEnabled, onClick = { httpEnabled = it })
|
||||
},
|
||||
onClick = { httpEnabled = !httpEnabled },
|
||||
modifier = Modifier.padding(horizontal = 12.dp),
|
||||
)
|
||||
)
|
||||
)
|
||||
if (httpEnabled) {
|
||||
ConfigurationTextBox(
|
||||
hint =
|
||||
stringResource(
|
||||
R.string.defaults_to_template,
|
||||
AppProxySettings.DEFAULT_HTTP_BIND_ADDRESS,
|
||||
),
|
||||
label = stringResource(R.string.http_bind_address),
|
||||
value = httpBindAddress,
|
||||
isError = proxySettingsState.isHttpBindAddressError,
|
||||
onValueChange = {
|
||||
if (proxySettingsState.isSocks5BindAddressError) viewModel.clearHttpBindError()
|
||||
httpBindAddress = it
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 12.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
if (socks5Enabled || httpEnabled) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp, Alignment.Top),
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
) {
|
||||
GroupLabel(
|
||||
stringResource(
|
||||
@@ -194,7 +177,6 @@ fun ProxySettingsScreen(viewModel: ProxySettingsViewModel = hiltViewModel()) {
|
||||
hint = "",
|
||||
keyboardActions = keyboardActions,
|
||||
keyboardOptions = keyboardOptions,
|
||||
modifier = Modifier.padding(horizontal = 12.dp),
|
||||
)
|
||||
ConfigurationTextBox(
|
||||
value = proxyPassword,
|
||||
@@ -207,7 +189,6 @@ fun ProxySettingsScreen(viewModel: ProxySettingsViewModel = hiltViewModel()) {
|
||||
hint = "",
|
||||
keyboardActions = keyboardActions,
|
||||
keyboardOptions = keyboardOptions,
|
||||
modifier = Modifier.padding(horizontal = 12.dp),
|
||||
trailing = {
|
||||
IconButton(onClick = { passwordVisible = !passwordVisible }) {
|
||||
Icon(
|
||||
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.system
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.Launch
|
||||
import androidx.compose.material.icons.filled.AppShortcut
|
||||
import androidx.compose.material.icons.filled.SmartToy
|
||||
import androidx.compose.material.icons.outlined.AdminPanelSettings
|
||||
import androidx.compose.material.icons.outlined.Restore
|
||||
import androidx.compose.material.icons.outlined.VpnLock
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.SecureScreenFromRecording
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.functions.rememberClipboardHelper
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.launchVpnSettings
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.SettingsViewModel
|
||||
|
||||
@Composable
|
||||
fun AndroidIntegrationsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val settingsState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
||||
|
||||
val clipboard = rememberClipboardHelper()
|
||||
|
||||
SecureScreenFromRecording()
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
||||
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.AdminPanelSettings, contentDescription = null) },
|
||||
title = stringResource(R.string.native_kill_switch),
|
||||
trailing = { Icon(Icons.AutoMirrored.Outlined.Launch, null) },
|
||||
onClick = { context.launchVpnSettings() },
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.Restore, contentDescription = null) },
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
checked = settingsState.settings.isRestoreOnBootEnabled,
|
||||
onClick = { viewModel.setRestoreOnBootEnabled(it) },
|
||||
)
|
||||
},
|
||||
title = stringResource(R.string.restart_at_boot),
|
||||
onClick = {
|
||||
viewModel.setRestoreOnBootEnabled(!settingsState.settings.isRestoreOnBootEnabled)
|
||||
},
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.VpnLock, contentDescription = null) },
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
checked = settingsState.settings.isAlwaysOnVpnEnabled,
|
||||
onClick = { viewModel.setAlwaysOnVpnEnabled(it) },
|
||||
)
|
||||
},
|
||||
title = stringResource(R.string.always_on_vpn_support),
|
||||
onClick = {
|
||||
viewModel.setAlwaysOnVpnEnabled(!settingsState.settings.isAlwaysOnVpnEnabled)
|
||||
},
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Filled.AppShortcut, contentDescription = null) },
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
checked = settingsState.settings.isShortcutsEnabled,
|
||||
onClick = { viewModel.setShortcutsEnabled(it) },
|
||||
)
|
||||
},
|
||||
title = stringResource(R.string.enabled_app_shortcuts),
|
||||
onClick = { viewModel.setShortcutsEnabled(!settingsState.settings.isShortcutsEnabled) },
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Filled.SmartToy, contentDescription = null) },
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
checked = settingsState.isRemoteEnabled,
|
||||
onClick = { viewModel.setRemoteEnabled(it) },
|
||||
)
|
||||
},
|
||||
description = {
|
||||
settingsState.remoteKey?.let { key ->
|
||||
AnimatedVisibility(visible = settingsState.isRemoteEnabled) {
|
||||
Text(
|
||||
text = stringResource(R.string.remote_key_template, key),
|
||||
style =
|
||||
MaterialTheme.typography.bodySmall.copy(
|
||||
color = MaterialTheme.colorScheme.outline
|
||||
),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.clickable { clipboard.copy(key) },
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
title = stringResource(R.string.enable_remote_app_control),
|
||||
onClick = { viewModel.setRemoteEnabled(!settingsState.isRemoteEnabled) },
|
||||
)
|
||||
}
|
||||
}
|
||||
-70
@@ -1,70 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.system
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.SectionDivider
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.SecureScreenFromRecording
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.system.components.*
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.SettingsViewModel
|
||||
|
||||
@Composable
|
||||
fun SystemFeaturesScreen(viewModel: SettingsViewModel = hiltViewModel()) {
|
||||
val settingsState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
||||
|
||||
val isTv = LocalIsAndroidTV.current
|
||||
|
||||
SecureScreenFromRecording()
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
||||
modifier =
|
||||
Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 16.dp),
|
||||
) {
|
||||
SurfaceSelectionGroupButton(buildList { if (!isTv) add(nativeKillSwitchItem()) })
|
||||
SectionDivider()
|
||||
SurfaceSelectionGroupButton(
|
||||
buildList {
|
||||
add(
|
||||
restartAtBootItem(settingsState.settings.isRestoreOnBootEnabled) {
|
||||
viewModel.setRestoreOnBootEnabled(it)
|
||||
}
|
||||
)
|
||||
if (!isTv)
|
||||
add(
|
||||
alwaysOnVpnItem(settingsState.settings.isAlwaysOnVpnEnabled) {
|
||||
viewModel.setAlwaysOnVpnEnabled(it)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
SectionDivider()
|
||||
SurfaceSelectionGroupButton(
|
||||
buildList {
|
||||
add(
|
||||
appShortcutsItem(settingsState.settings.isShortcutsEnabled) {
|
||||
viewModel.setShortcutsEnabled(it)
|
||||
}
|
||||
)
|
||||
add(
|
||||
remoteControlItem(settingsState.isRemoteEnabled, settingsState.remoteKey) {
|
||||
viewModel.setRemoteEnabled(it)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.system.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.VpnLock
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
|
||||
@Composable
|
||||
fun alwaysOnVpnItem(checked: Boolean, onChange: (checked: Boolean) -> Unit): SelectionItem {
|
||||
return SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.VpnLock, contentDescription = null) },
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
// enabled =
|
||||
// !((uiState.generalSettings.isTunnelOnWifiEnabled ||
|
||||
// uiState.generalSettings.isTunnelOnEthernetEnabled ||
|
||||
// uiState.generalSettings.isTunnelOnMobileDataEnabled) &&
|
||||
// uiState.generalSettings.isAutoTunnelEnabled),
|
||||
checked = checked,
|
||||
onClick = onChange,
|
||||
)
|
||||
},
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.always_on_vpn_support),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
onClick = { onChange(!checked) },
|
||||
)
|
||||
}
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.system.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AppShortcut
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
|
||||
@Composable
|
||||
fun appShortcutsItem(checked: Boolean, onChange: (checked: Boolean) -> Unit): SelectionItem {
|
||||
return SelectionItem(
|
||||
leading = { Icon(Icons.Filled.AppShortcut, contentDescription = null) },
|
||||
trailing = { ScaledSwitch(checked = checked, onClick = onChange) },
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.enabled_app_shortcuts),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
onClick = { onChange(!checked) },
|
||||
)
|
||||
}
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.system.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.AdminPanelSettings
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.LaunchButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.launchVpnSettings
|
||||
|
||||
@Composable
|
||||
fun nativeKillSwitchItem(): SelectionItem {
|
||||
val context = LocalContext.current
|
||||
return SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.AdminPanelSettings, contentDescription = null) },
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.native_kill_switch),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
trailing = { LaunchButton { context.launchVpnSettings() } },
|
||||
onClick = { context.launchVpnSettings() },
|
||||
)
|
||||
}
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.system.components
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.SmartToy
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.functions.rememberClipboardHelper
|
||||
|
||||
@Composable
|
||||
fun remoteControlItem(
|
||||
checked: Boolean,
|
||||
key: String?,
|
||||
onChange: (checked: Boolean) -> Unit,
|
||||
): SelectionItem {
|
||||
val clipboardManager = rememberClipboardHelper()
|
||||
|
||||
return SelectionItem(
|
||||
leading = { Icon(Icons.Filled.SmartToy, contentDescription = null) },
|
||||
trailing = { ScaledSwitch(checked = checked, onClick = onChange) },
|
||||
description = {
|
||||
key?.let { key ->
|
||||
AnimatedVisibility(visible = checked) {
|
||||
Text(
|
||||
text = stringResource(R.string.remote_key_template, key),
|
||||
style =
|
||||
MaterialTheme.typography.bodySmall.copy(
|
||||
color = MaterialTheme.colorScheme.outline
|
||||
),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.clickable { clipboardManager.copy(key) },
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.enable_remote_app_control),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
onClick = { onChange(!checked) },
|
||||
)
|
||||
}
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.system.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Restore
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
|
||||
@Composable
|
||||
fun restartAtBootItem(enabled: Boolean, onChange: (checked: Boolean) -> Unit): SelectionItem {
|
||||
return SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.Restore, contentDescription = null) },
|
||||
trailing = { ScaledSwitch(checked = enabled, onClick = onChange) },
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.restart_at_boot),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
onClick = { onChange(!enabled) },
|
||||
)
|
||||
}
|
||||
+162
-103
@@ -1,144 +1,203 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.support
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.Launch
|
||||
import androidx.compose.material.icons.outlined.*
|
||||
import androidx.compose.material3.Icon
|
||||
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.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.*
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.zaneschepke.wireguardautotunnel.BuildConfig
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.SectionDivider
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.functions.rememberClipboardHelper
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.text.DescriptionText
|
||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.components.ContactSupportOptions
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.components.DonateSection
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.components.GeneralSupportOptions
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.components.UpdateSection
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.components.PermissionDialog
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.components.UpdateDialog
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.canInstallPackages
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.requestInstallPackagesPermission
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.*
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.SupportViewModel
|
||||
|
||||
@Composable
|
||||
fun SupportScreen(viewModel: SupportViewModel = hiltViewModel()) {
|
||||
val context = LocalContext.current
|
||||
val navController = LocalNavController.current
|
||||
|
||||
val supportState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
||||
|
||||
val clipboardManager = rememberClipboardHelper()
|
||||
|
||||
val version = remember {
|
||||
"v${BuildConfig.VERSION_NAME +
|
||||
if(BuildConfig.DEBUG) "-debug" else "" }"
|
||||
}
|
||||
|
||||
var showPermissionDialog by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
if (supportState.appUpdate != null) {
|
||||
InfoDialog(
|
||||
onDismiss = { viewModel.dismissUpdate() },
|
||||
onAttest = {
|
||||
if (BuildConfig.FLAVOR != Constants.STANDALONE_FLAVOR) {
|
||||
supportState.appUpdate?.apkUrl?.let { context.openWebUrl(it) }
|
||||
return@InfoDialog
|
||||
}
|
||||
if (context.canInstallPackages()) {
|
||||
viewModel.downloadAndInstall()
|
||||
} else {
|
||||
showPermissionDialog = true
|
||||
}
|
||||
},
|
||||
title = { Text(stringResource(R.string.update_available)) },
|
||||
body = {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
val annotatedString = buildAnnotatedString {
|
||||
append("${supportState.appUpdate?.version ?: ""}\n")
|
||||
// Add clickable text for second line
|
||||
withLink(
|
||||
link =
|
||||
LinkAnnotation.Clickable(
|
||||
tag = stringResource(id = R.string.release_notes),
|
||||
linkInteractionListener = { viewModel.viewReleaseNotes() },
|
||||
styles =
|
||||
TextLinkStyles(
|
||||
style =
|
||||
SpanStyle(
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
textDecoration = TextDecoration.Underline,
|
||||
)
|
||||
),
|
||||
)
|
||||
) {
|
||||
append(stringResource(R.string.release_notes))
|
||||
}
|
||||
}
|
||||
|
||||
Text(text = annotatedString)
|
||||
if (supportState.isLoading) {
|
||||
LinearProgressIndicator(
|
||||
progress = { supportState.downloadProgress },
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 16.dp),
|
||||
trackColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
strokeCap = StrokeCap.Round,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmText = {
|
||||
Text(
|
||||
if (BuildConfig.FLAVOR != Constants.STANDALONE_FLAVOR)
|
||||
stringResource(R.string.download)
|
||||
else stringResource(R.string.download_and_install)
|
||||
)
|
||||
},
|
||||
UpdateDialog(
|
||||
viewModel = viewModel,
|
||||
context = context,
|
||||
onPermissionNeeded = { showPermissionDialog = true },
|
||||
)
|
||||
}
|
||||
|
||||
if (showPermissionDialog) {
|
||||
InfoDialog(
|
||||
onDismiss = { showPermissionDialog = false },
|
||||
onAttest = {
|
||||
context.requestInstallPackagesPermission()
|
||||
showPermissionDialog = false
|
||||
},
|
||||
title = { Text(stringResource(R.string.permission_required)) },
|
||||
body = { Text(stringResource(R.string.install_updated_permission)) },
|
||||
confirmText = { Text(stringResource(R.string.allow)) },
|
||||
)
|
||||
PermissionDialog(context = context, onDismiss = { showPermissionDialog = false })
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier =
|
||||
Modifier.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(horizontal = 16.dp),
|
||||
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top),
|
||||
) {
|
||||
GroupLabel(stringResource(R.string.thank_you), modifier = Modifier.padding(bottom = 12.dp))
|
||||
UpdateSection { viewModel.checkForUpdate() }
|
||||
SectionDivider()
|
||||
DonateSection { navController.push(Route.Donate) }
|
||||
SectionDivider()
|
||||
GeneralSupportOptions()
|
||||
SectionDivider()
|
||||
ContactSupportOptions(context)
|
||||
GroupLabel(
|
||||
stringResource(R.string.thank_you),
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
Column {
|
||||
GroupLabel(stringResource(R.string.resources), Modifier.padding(horizontal = 16.dp))
|
||||
SurfaceRow(
|
||||
stringResource(R.string.docs_description),
|
||||
onClick = { context.openWebUrl(context.getString(R.string.docs_url)) },
|
||||
leading = { Icon(Icons.Outlined.Book, contentDescription = null) },
|
||||
trailing = { Icon(Icons.AutoMirrored.Outlined.Launch, null) },
|
||||
)
|
||||
SurfaceRow(
|
||||
stringResource(R.string.website),
|
||||
onClick = { context.openWebUrl(context.getString(R.string.website_url)) },
|
||||
leading = { Icon(Icons.Outlined.Web, contentDescription = null) },
|
||||
trailing = { Icon(Icons.AutoMirrored.Outlined.Launch, null) },
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.Favorite, contentDescription = null) },
|
||||
title = stringResource(R.string.donate),
|
||||
onClick = { navController.push(Route.Donate) },
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.Balance, contentDescription = null) },
|
||||
title = stringResource(R.string.licenses),
|
||||
onClick = { navController.push(Route.License) },
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.Policy, contentDescription = null) },
|
||||
title = stringResource(R.string.privacy_policy),
|
||||
trailing = { Icon(Icons.AutoMirrored.Outlined.Launch, null) },
|
||||
onClick = { context.openWebUrl(context.getString(R.string.privacy_policy_url)) },
|
||||
)
|
||||
if (BuildConfig.FLAVOR == Constants.GOOGLE_PLAY_FLAVOR) {
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.Reviews, contentDescription = null) },
|
||||
title = stringResource(R.string.review),
|
||||
trailing = { Icon(Icons.AutoMirrored.Outlined.Launch, null) },
|
||||
onClick = { context.launchPlayStoreReview() },
|
||||
)
|
||||
}
|
||||
}
|
||||
Column {
|
||||
GroupLabel(
|
||||
stringResource(R.string.contact),
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = {
|
||||
Icon(
|
||||
ImageVector.vectorResource(R.drawable.matrix),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp),
|
||||
)
|
||||
},
|
||||
title = stringResource(R.string.join_matrix),
|
||||
trailing = { Icon(Icons.AutoMirrored.Outlined.Launch, null) },
|
||||
onClick = { context.openWebUrl(context.getString(R.string.matrix_url)) },
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = {
|
||||
Icon(
|
||||
ImageVector.vectorResource(R.drawable.telegram),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp),
|
||||
)
|
||||
},
|
||||
title = stringResource(R.string.join_telegram),
|
||||
trailing = { Icon(Icons.AutoMirrored.Outlined.Launch, null) },
|
||||
onClick = { context.openWebUrl(context.getString(R.string.telegram_url)) },
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = {
|
||||
Icon(
|
||||
ImageVector.vectorResource(R.drawable.github),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp),
|
||||
)
|
||||
},
|
||||
title = stringResource(R.string.open_issue),
|
||||
trailing = { Icon(Icons.AutoMirrored.Outlined.Launch, null) },
|
||||
onClick = { context.openWebUrl(context.getString(R.string.github_url)) },
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.Mail, contentDescription = null) },
|
||||
title = stringResource(R.string.email_description),
|
||||
trailing = { Icon(Icons.AutoMirrored.Outlined.Launch, null) },
|
||||
onClick = { context.launchSupportEmail() },
|
||||
)
|
||||
}
|
||||
Column {
|
||||
GroupLabel(
|
||||
stringResource(R.string.other),
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.Memory, contentDescription = null) },
|
||||
title = stringResource(R.string.about),
|
||||
description = {
|
||||
Column {
|
||||
DescriptionText(stringResource(R.string.version_template, version))
|
||||
DescriptionText(
|
||||
stringResource(R.string.flavor_template, BuildConfig.FLAVOR)
|
||||
)
|
||||
}
|
||||
},
|
||||
onClick = { clipboardManager.copy(version) },
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.InstallMobile, contentDescription = null) },
|
||||
title = stringResource(R.string.check_for_update),
|
||||
onClick = {
|
||||
if (BuildConfig.DEBUG)
|
||||
return@SurfaceRow context.showToast(R.string.update_check_unsupported)
|
||||
when (BuildConfig.FLAVOR) {
|
||||
Constants.GOOGLE_PLAY_FLAVOR -> context.launchPlayStoreListing()
|
||||
Constants.FDROID_FLAVOR -> context.launchFDroidListing()
|
||||
else -> viewModel.checkForStandaloneUpdate()
|
||||
}
|
||||
},
|
||||
trailing =
|
||||
if (BuildConfig.FLAVOR == Constants.STANDALONE_FLAVOR) null
|
||||
else {
|
||||
{ Icon(Icons.AutoMirrored.Outlined.Launch, null) }
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-99
@@ -1,99 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.support.components
|
||||
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Mail
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.LaunchButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.launchSupportEmail
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
||||
|
||||
@Composable
|
||||
fun ContactSupportOptions(context: android.content.Context) {
|
||||
SurfaceSelectionGroupButton(
|
||||
items =
|
||||
listOf(
|
||||
SelectionItem(
|
||||
leading = {
|
||||
Icon(
|
||||
ImageVector.vectorResource(R.drawable.matrix),
|
||||
contentDescription = null,
|
||||
Modifier.size(iconSize),
|
||||
)
|
||||
},
|
||||
title = {
|
||||
SelectionItemLabel(
|
||||
stringResource(R.string.join_matrix),
|
||||
SelectionLabelType.TITLE,
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
LaunchButton { context.openWebUrl(context.getString(R.string.matrix_url)) }
|
||||
},
|
||||
onClick = { context.openWebUrl(context.getString(R.string.matrix_url)) },
|
||||
),
|
||||
SelectionItem(
|
||||
leading = {
|
||||
Icon(
|
||||
ImageVector.vectorResource(R.drawable.telegram),
|
||||
contentDescription = null,
|
||||
Modifier.size(iconSize),
|
||||
)
|
||||
},
|
||||
title = {
|
||||
SelectionItemLabel(
|
||||
stringResource(R.string.join_telegram),
|
||||
SelectionLabelType.TITLE,
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
LaunchButton {
|
||||
context.openWebUrl(context.getString(R.string.telegram_url))
|
||||
}
|
||||
},
|
||||
onClick = { context.openWebUrl(context.getString(R.string.telegram_url)) },
|
||||
),
|
||||
SelectionItem(
|
||||
leading = {
|
||||
Icon(
|
||||
ImageVector.vectorResource(R.drawable.github),
|
||||
contentDescription = null,
|
||||
Modifier.size(iconSize),
|
||||
)
|
||||
},
|
||||
title = {
|
||||
SelectionItemLabel(
|
||||
stringResource(R.string.open_issue),
|
||||
SelectionLabelType.TITLE,
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
LaunchButton { context.openWebUrl(context.getString(R.string.github_url)) }
|
||||
},
|
||||
onClick = { context.openWebUrl(context.getString(R.string.github_url)) },
|
||||
),
|
||||
SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.Mail, contentDescription = null) },
|
||||
title = {
|
||||
SelectionItemLabel(
|
||||
stringResource(R.string.email_description),
|
||||
SelectionLabelType.TITLE,
|
||||
)
|
||||
},
|
||||
trailing = { LaunchButton { context.launchSupportEmail() } },
|
||||
onClick = { context.launchSupportEmail() },
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.support.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Favorite
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
||||
|
||||
@Composable
|
||||
fun DonateSection(onClick: () -> Unit) {
|
||||
SurfaceSelectionGroupButton(
|
||||
listOf(
|
||||
SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.Favorite, contentDescription = null) },
|
||||
title = {
|
||||
SelectionItemLabel(stringResource(R.string.donate), SelectionLabelType.TITLE)
|
||||
},
|
||||
trailing = { ForwardButton { onClick() } },
|
||||
onClick = onClick,
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
-80
@@ -1,80 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.support.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Balance
|
||||
import androidx.compose.material.icons.outlined.Book
|
||||
import androidx.compose.material.icons.outlined.Policy
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.LaunchButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
||||
|
||||
@Composable
|
||||
fun GeneralSupportOptions() {
|
||||
val context = LocalContext.current
|
||||
val navController = LocalNavController.current
|
||||
SurfaceSelectionGroupButton(
|
||||
items =
|
||||
buildList {
|
||||
add(
|
||||
SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.Book, contentDescription = null) },
|
||||
title = {
|
||||
SelectionItemLabel(
|
||||
stringResource(R.string.docs_description),
|
||||
SelectionLabelType.TITLE,
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
LaunchButton {
|
||||
context.openWebUrl(context.getString(R.string.docs_url))
|
||||
}
|
||||
},
|
||||
onClick = { context.openWebUrl(context.getString(R.string.docs_url)) },
|
||||
)
|
||||
)
|
||||
add(
|
||||
SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.Policy, contentDescription = null) },
|
||||
title = {
|
||||
SelectionItemLabel(
|
||||
stringResource(R.string.privacy_policy),
|
||||
SelectionLabelType.TITLE,
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
LaunchButton {
|
||||
context.openWebUrl(context.getString(R.string.privacy_policy_url))
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
context.openWebUrl(context.getString(R.string.privacy_policy_url))
|
||||
},
|
||||
)
|
||||
)
|
||||
add(
|
||||
SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.Balance, contentDescription = null) },
|
||||
title = {
|
||||
SelectionItemLabel(
|
||||
stringResource(R.string.licenses),
|
||||
SelectionLabelType.TITLE,
|
||||
)
|
||||
},
|
||||
trailing = { ForwardButton { navController.push(Route.License) } },
|
||||
onClick = { navController.push(Route.License) },
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.support.components
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.requestInstallPackagesPermission
|
||||
|
||||
@Composable
|
||||
fun PermissionDialog(context: Context, onDismiss: () -> Unit) {
|
||||
InfoDialog(
|
||||
onDismiss = onDismiss,
|
||||
onAttest = {
|
||||
context.requestInstallPackagesPermission()
|
||||
onDismiss()
|
||||
},
|
||||
title = { Text(stringResource(R.string.permission_required)) },
|
||||
body = { Text(stringResource(R.string.install_updated_permission)) },
|
||||
confirmText = { Text(stringResource(R.string.allow)) },
|
||||
)
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.support.components
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.LinearWavyProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.*
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.zaneschepke.wireguardautotunnel.BuildConfig
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.canInstallPackages
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.SupportViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun UpdateDialog(viewModel: SupportViewModel, context: Context, onPermissionNeeded: () -> Unit) {
|
||||
val supportState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
||||
|
||||
InfoDialog(
|
||||
onDismiss = { viewModel.dismissUpdate() },
|
||||
onAttest = {
|
||||
if (BuildConfig.FLAVOR != Constants.STANDALONE_FLAVOR) {
|
||||
supportState.appUpdate?.apkUrl?.let { context.openWebUrl(it) }
|
||||
return@InfoDialog
|
||||
}
|
||||
if (context.canInstallPackages()) {
|
||||
viewModel.downloadAndInstall()
|
||||
} else {
|
||||
onPermissionNeeded()
|
||||
}
|
||||
},
|
||||
title = { Text(stringResource(R.string.update_available)) },
|
||||
body = {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
val annotatedString = buildAnnotatedString {
|
||||
append("${supportState.appUpdate?.version ?: ""}\n")
|
||||
// Add clickable text for second line
|
||||
withLink(
|
||||
link =
|
||||
LinkAnnotation.Clickable(
|
||||
tag = stringResource(id = R.string.release_notes),
|
||||
linkInteractionListener = { viewModel.viewReleaseNotes() },
|
||||
styles =
|
||||
TextLinkStyles(
|
||||
style =
|
||||
SpanStyle(
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
textDecoration = TextDecoration.Underline,
|
||||
)
|
||||
),
|
||||
)
|
||||
) {
|
||||
append(stringResource(R.string.release_notes))
|
||||
}
|
||||
}
|
||||
|
||||
Text(text = annotatedString)
|
||||
if (supportState.isLoading) {
|
||||
val stroke = Stroke(cap = StrokeCap.Round)
|
||||
LinearWavyProgressIndicator(
|
||||
progress = { supportState.downloadProgress },
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 16.dp),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
trackColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f),
|
||||
stroke = stroke,
|
||||
trackStroke = stroke,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmText = {
|
||||
Text(
|
||||
if (BuildConfig.FLAVOR != Constants.STANDALONE_FLAVOR)
|
||||
stringResource(R.string.download)
|
||||
else stringResource(R.string.download_and_install)
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.support.components
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.CloudDownload
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.zaneschepke.wireguardautotunnel.BuildConfig
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.functions.rememberClipboardHelper
|
||||
|
||||
@Composable
|
||||
fun UpdateSection(onUpdateCheck: () -> Unit) {
|
||||
val clipboardManager = rememberClipboardHelper()
|
||||
val version = remember {
|
||||
"v${BuildConfig.VERSION_NAME +
|
||||
if(BuildConfig.DEBUG) "-debug" else "" }"
|
||||
}
|
||||
SurfaceSelectionGroupButton(
|
||||
listOf(
|
||||
SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.CloudDownload, contentDescription = null) },
|
||||
title = {
|
||||
SelectionItemLabel(
|
||||
stringResource(R.string.check_for_update),
|
||||
SelectionLabelType.TITLE,
|
||||
)
|
||||
},
|
||||
description = {
|
||||
Column {
|
||||
SelectionItemLabel(
|
||||
stringResource(R.string.version_template, version),
|
||||
SelectionLabelType.DESCRIPTION,
|
||||
)
|
||||
SelectionItemLabel(
|
||||
stringResource(R.string.flavor_template, BuildConfig.FLAVOR),
|
||||
SelectionLabelType.DESCRIPTION,
|
||||
)
|
||||
}
|
||||
},
|
||||
onClick = onUpdateCheck,
|
||||
onLongPress = { clipboardManager.copy(version) },
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
+71
-10
@@ -4,36 +4,97 @@ import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.Launch
|
||||
import androidx.compose.material.icons.outlined.CurrencyBitcoin
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
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.LocalNavController
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.SectionDivider
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.donate.components.DonationHeroSection
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.donate.components.DonationOptions
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.donate.components.GoogleDonationMessage
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
||||
|
||||
@Composable
|
||||
fun DonateScreen() {
|
||||
val context = LocalContext.current
|
||||
val navController = LocalNavController.current
|
||||
val isGoogleFlavor = remember { BuildConfig.FLAVOR == Constants.GOOGLE_PLAY_FLAVOR }
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
||||
modifier =
|
||||
Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top),
|
||||
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
DonationHeroSection()
|
||||
SectionDivider()
|
||||
if (BuildConfig.FLAVOR != Constants.GOOGLE_PLAY_FLAVOR) {
|
||||
DonationOptions { navController.push(Route.Addresses) }
|
||||
} else {
|
||||
GoogleDonationMessage()
|
||||
Column {
|
||||
GroupLabel(
|
||||
stringResource(R.string.options),
|
||||
Modifier.padding(bottom = if (isGoogleFlavor) 8.dp else 0.dp)
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
if (!isGoogleFlavor) {
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.CurrencyBitcoin, contentDescription = null) },
|
||||
title = stringResource(R.string.crypto),
|
||||
onClick = { navController.push(Route.Addresses) },
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = {
|
||||
Icon(
|
||||
ImageVector.vectorResource(R.drawable.github),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp),
|
||||
)
|
||||
},
|
||||
title = stringResource(R.string.github_sponsors),
|
||||
trailing = { Icon(Icons.AutoMirrored.Outlined.Launch, null) },
|
||||
onClick = {
|
||||
context.openWebUrl(context.getString(R.string.github_sponsors_url))
|
||||
},
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = {
|
||||
Icon(
|
||||
ImageVector.vectorResource(R.drawable.liberapay),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp),
|
||||
)
|
||||
},
|
||||
title = stringResource(R.string.liberapay),
|
||||
trailing = { Icon(Icons.AutoMirrored.Outlined.Launch, null) },
|
||||
onClick = { context.openWebUrl(context.getString(R.string.liberapay_url)) },
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = {
|
||||
Icon(
|
||||
ImageVector.vectorResource(R.drawable.kofi),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp),
|
||||
)
|
||||
},
|
||||
title = stringResource(R.string.kofi),
|
||||
trailing = { Icon(Icons.AutoMirrored.Outlined.Launch, null) },
|
||||
onClick = { context.openWebUrl(context.getString(R.string.kofi_url)) },
|
||||
)
|
||||
} else {
|
||||
GoogleDonationMessage()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -3,6 +3,7 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.support.donate.components
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
@@ -19,7 +20,7 @@ import com.zaneschepke.wireguardautotunnel.R
|
||||
fun DonationHeroSection() {
|
||||
Surface(modifier = Modifier.fillMaxWidth()) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
|
||||
-97
@@ -1,97 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.support.donate.components
|
||||
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.CurrencyBitcoin
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.LaunchButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
||||
|
||||
@Composable
|
||||
fun DonationOptions(onAddressesClick: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
SurfaceSelectionGroupButton(
|
||||
listOf(
|
||||
SelectionItem(
|
||||
leading = {
|
||||
Icon(
|
||||
Icons.Outlined.CurrencyBitcoin,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(iconSize),
|
||||
)
|
||||
},
|
||||
title = {
|
||||
SelectionItemLabel(stringResource(R.string.crypto), SelectionLabelType.TITLE)
|
||||
},
|
||||
trailing = { ForwardButton { onAddressesClick() } },
|
||||
onClick = onAddressesClick,
|
||||
),
|
||||
SelectionItem(
|
||||
leading = {
|
||||
Icon(
|
||||
ImageVector.vectorResource(R.drawable.github),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(iconSize),
|
||||
)
|
||||
},
|
||||
title = {
|
||||
SelectionItemLabel(
|
||||
stringResource(R.string.github_sponsors),
|
||||
SelectionLabelType.TITLE,
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
LaunchButton {
|
||||
context.openWebUrl(context.getString(R.string.github_sponsors_url))
|
||||
}
|
||||
},
|
||||
onClick = { context.openWebUrl(context.getString(R.string.github_sponsors_url)) },
|
||||
),
|
||||
SelectionItem(
|
||||
leading = {
|
||||
Icon(
|
||||
ImageVector.vectorResource(R.drawable.liberapay),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(iconSize),
|
||||
)
|
||||
},
|
||||
title = {
|
||||
SelectionItemLabel(stringResource(R.string.liberapay), SelectionLabelType.TITLE)
|
||||
},
|
||||
trailing = {
|
||||
LaunchButton { context.openWebUrl(context.getString(R.string.liberapay_url)) }
|
||||
},
|
||||
onClick = { context.openWebUrl(context.getString(R.string.liberapay_url)) },
|
||||
),
|
||||
SelectionItem(
|
||||
leading = {
|
||||
Icon(
|
||||
ImageVector.vectorResource(R.drawable.kofi),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(iconSize),
|
||||
)
|
||||
},
|
||||
title = {
|
||||
SelectionItemLabel(stringResource(R.string.kofi), SelectionLabelType.TITLE)
|
||||
},
|
||||
trailing = {
|
||||
LaunchButton { context.openWebUrl(context.getString(R.string.kofi_url)) }
|
||||
},
|
||||
onClick = { context.openWebUrl(context.getString(R.string.kofi_url)) },
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
+12
-21
@@ -4,9 +4,6 @@ import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -19,24 +16,18 @@ import com.zaneschepke.wireguardautotunnel.R
|
||||
|
||||
@Composable
|
||||
fun GoogleDonationMessage() {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.google_donation_message),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
),
|
||||
textAlign = TextAlign.Start,
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = stringResource(R.string.google_donation_message),
|
||||
style =
|
||||
MaterialTheme.typography.bodySmall.copy(
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
),
|
||||
textAlign = TextAlign.Start,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+4
-10
@@ -3,28 +3,22 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.support.donate.crypto
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.functions.rememberClipboardHelper
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.donate.crypto.components.addressItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.donate.crypto.components.AddressItem
|
||||
|
||||
@Composable
|
||||
fun AddressesScreen() {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
||||
modifier =
|
||||
Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.Top,
|
||||
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
val clipboard = rememberClipboardHelper()
|
||||
SurfaceSelectionGroupButton(
|
||||
Address.allAddresses.map { addressItem(it) { address -> clipboard.copy(address) } }
|
||||
)
|
||||
Address.allAddresses.forEach { AddressItem(it) { address -> clipboard.copy(address) } }
|
||||
}
|
||||
}
|
||||
|
||||
+6
-8
@@ -19,23 +19,21 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.donate.crypto.Address
|
||||
import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize
|
||||
|
||||
@Composable
|
||||
fun addressItem(address: Address, onClick: (address: String) -> Unit): SelectionItem {
|
||||
fun AddressItem(address: Address, onClick: (address: String) -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val walletAddress = context.getString(address.address)
|
||||
var expand by rememberSaveable { mutableStateOf(false) }
|
||||
return SelectionItem(
|
||||
SurfaceRow(
|
||||
leading = {
|
||||
Image(
|
||||
painter = painterResource(id = address.icon),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(iconSize),
|
||||
modifier = Modifier.size(24.dp),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
@@ -43,7 +41,7 @@ fun addressItem(address: Address, onClick: (address: String) -> Unit): Selection
|
||||
Icon(Icons.Outlined.CopyAll, contentDescription = null)
|
||||
}
|
||||
},
|
||||
title = { SelectionItemLabel(stringResource(address.name), SelectionLabelType.TITLE) },
|
||||
title = stringResource(address.name),
|
||||
description = {
|
||||
Text(
|
||||
text = walletAddress,
|
||||
|
||||
+9
-23
@@ -1,44 +1,30 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.support.license
|
||||
|
||||
import LicenseFileEntry
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.CircularWavyProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.license.components.LicenseList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.json.Json
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.LicenseViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun LicenseScreen() {
|
||||
val context = LocalContext.current
|
||||
var licenses by remember { mutableStateOf<List<LicenseFileEntry>>(emptyList()) }
|
||||
fun LicenseScreen(viewModel: LicenseViewModel = hiltViewModel()) {
|
||||
val licenseUiState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
||||
|
||||
LaunchedEffect(Unit) { licenses = loadLicenseeJson(context) }
|
||||
|
||||
if (licenses.isEmpty()) {
|
||||
if (licenseUiState.isLoading) {
|
||||
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
CircularWavyProgressIndicator(waveSpeed = 60.dp, modifier = Modifier.size(48.dp))
|
||||
}
|
||||
} else {
|
||||
LicenseList(licenses)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun loadLicenseeJson(context: Context): List<LicenseFileEntry> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val json = Json { ignoreUnknownKeys = true }
|
||||
|
||||
val jsonResult = context.assets.open("licenses.json").bufferedReader().use { it.readText() }
|
||||
json.decodeFromString(jsonResult)
|
||||
LicenseList(licenseUiState.licenses)
|
||||
}
|
||||
}
|
||||
|
||||
+11
-11
@@ -1,10 +1,13 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.support.license.components
|
||||
|
||||
import LicenseFileEntry
|
||||
import androidx.compose.foundation.focusable
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.Launch
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -12,21 +15,22 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.LinkIconButton
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
||||
|
||||
@Composable
|
||||
fun LicenseList(licenses: List<LicenseFileEntry>) {
|
||||
val context = LocalContext.current
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize().padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
items(licenses) { entry ->
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
modifier =
|
||||
Modifier.clickable(enabled = entry.scm?.url != null) {
|
||||
entry.scm?.url?.let { context.openWebUrl(it) }
|
||||
}
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
@@ -42,11 +46,7 @@ fun LicenseList(licenses: List<LicenseFileEntry>) {
|
||||
)
|
||||
}
|
||||
}
|
||||
entry.scm?.url?.let { scmUrl ->
|
||||
LinkIconButton(modifier = Modifier.size(20.dp).focusable()) {
|
||||
context.openWebUrl(scmUrl)
|
||||
}
|
||||
}
|
||||
entry.scm?.url?.let { Icon(Icons.AutoMirrored.Outlined.Launch, null) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.autotunnel
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.autotunnel.components.MobileDataTunnelItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.autotunnel.components.WifiTunnelItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.autotunnel.components.ethernetTunnelItem
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.TunnelViewModel
|
||||
|
||||
@Composable
|
||||
fun TunnelAutoTunnelScreen(viewModel: TunnelViewModel) {
|
||||
val tunnelState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
||||
|
||||
if (tunnelState.isLoading) return
|
||||
val tunnel = tunnelState.tunnel ?: return
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
||||
modifier =
|
||||
Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 16.dp),
|
||||
) {
|
||||
SurfaceSelectionGroupButton(
|
||||
items =
|
||||
buildList {
|
||||
add(
|
||||
MobileDataTunnelItem(tunnel.isMobileDataTunnel) {
|
||||
viewModel.setMobileDataTunnel(it)
|
||||
}
|
||||
)
|
||||
add(
|
||||
ethernetTunnelItem(tunnel.isEthernetTunnel) {
|
||||
viewModel.setEthernetTunnel(it)
|
||||
}
|
||||
)
|
||||
add(
|
||||
WifiTunnelItem(
|
||||
tunnel.tunnelNetworks,
|
||||
isWildcardsEnabled = tunnelState.isWildcardsEnabled,
|
||||
onSaveTunnelNetwork = { viewModel.addTunnelNetwork(it) },
|
||||
onDeleteTunnelNetwork = { viewModel.removeTunnelNetwork(it) },
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.autotunnel.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.PhoneAndroid
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
|
||||
@Composable
|
||||
fun MobileDataTunnelItem(enabled: Boolean, onClick: (Boolean) -> Unit): SelectionItem {
|
||||
return SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.PhoneAndroid, contentDescription = null) },
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.mobile_tunnel),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
description = {
|
||||
Text(
|
||||
text = stringResource(R.string.mobile_data_tunnel),
|
||||
style = MaterialTheme.typography.bodySmall.copy(MaterialTheme.colorScheme.outline),
|
||||
)
|
||||
},
|
||||
trailing = { ScaledSwitch(checked = enabled, onClick = onClick) },
|
||||
onClick = { onClick(!enabled) },
|
||||
)
|
||||
}
|
||||
-70
@@ -1,70 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.autotunnel.components
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Security
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components.TrustedNetworkTextBox
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components.WildcardsLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize
|
||||
|
||||
@Composable
|
||||
fun WifiTunnelItem(
|
||||
tunnelNetworks: Set<String>,
|
||||
isWildcardsEnabled: Boolean,
|
||||
onSaveTunnelNetwork: (String) -> Unit,
|
||||
onDeleteTunnelNetwork: (String) -> Unit,
|
||||
): SelectionItem {
|
||||
|
||||
var currentText by rememberSaveable { mutableStateOf("") }
|
||||
|
||||
LaunchedEffect(tunnelNetworks) { currentText = "" }
|
||||
|
||||
return SelectionItem(
|
||||
title = {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Security,
|
||||
contentDescription = stringResource(R.string.use_tunnel_on_wifi_name),
|
||||
modifier = Modifier.size(iconSize),
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.use_tunnel_on_wifi_name),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(
|
||||
MaterialTheme.colorScheme.onSurface
|
||||
),
|
||||
modifier = Modifier.padding(start = 16.dp),
|
||||
)
|
||||
}
|
||||
},
|
||||
description = {
|
||||
TrustedNetworkTextBox(
|
||||
trustedNetworks = tunnelNetworks,
|
||||
onDelete = onDeleteTunnelNetwork,
|
||||
currentText = currentText,
|
||||
onSave = onSaveTunnelNetwork,
|
||||
onValueChange = { currentText = it },
|
||||
supporting = {
|
||||
if (isWildcardsEnabled) {
|
||||
WildcardsLabel()
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.autotunnel.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.SettingsEthernet
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
|
||||
@Composable
|
||||
fun ethernetTunnelItem(enabled: Boolean, onClick: (Boolean) -> Unit): SelectionItem {
|
||||
return SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.SettingsEthernet, contentDescription = null) },
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.ethernet_tunnel),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
description = {
|
||||
Text(
|
||||
text = stringResource(R.string.set_ethernet_tunnel),
|
||||
style = MaterialTheme.typography.bodySmall.copy(MaterialTheme.colorScheme.outline),
|
||||
)
|
||||
},
|
||||
trailing = { ScaledSwitch(checked = enabled, onClick = onClick) },
|
||||
onClick = { onClick(!enabled) },
|
||||
)
|
||||
}
|
||||
+61
-35
@@ -1,26 +1,32 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.components
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.gestures.ScrollableDefaults
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.overscroll
|
||||
import androidx.compose.foundation.rememberOverscrollEffect
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Circle
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SwitchWithDivider
|
||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
|
||||
import com.zaneschepke.wireguardautotunnel.ui.state.TunnelsUiState
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asColor
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.SharedAppViewModel
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.TunnelsViewModel
|
||||
@@ -34,7 +40,6 @@ fun TunnelList(
|
||||
sharedViewModel: SharedAppViewModel,
|
||||
) {
|
||||
val navController = LocalNavController.current
|
||||
val isTv = LocalIsAndroidTV.current
|
||||
val context = LocalContext.current
|
||||
|
||||
val lazyListState = rememberLazyListState()
|
||||
@@ -58,7 +63,7 @@ fun TunnelList(
|
||||
if (tunnelsState.tunnels.isEmpty()) {
|
||||
item { GettingStartedLabel(onClick = { context.openWebUrl(it) }) }
|
||||
}
|
||||
items(tunnelsState.tunnels.toList(), key = { it.id }) { tunnel ->
|
||||
items(tunnelsState.tunnels, key = { it.id }) { tunnel ->
|
||||
val tunnelState =
|
||||
remember(tunnelsState.activeTunnels) {
|
||||
tunnelsState.activeTunnels[tunnel.id] ?: TunnelState()
|
||||
@@ -67,35 +72,56 @@ fun TunnelList(
|
||||
remember(tunnelsState.selectedTunnels) {
|
||||
tunnelsState.selectedTunnels.any { it.id == tunnel.id }
|
||||
}
|
||||
TunnelRowItem(
|
||||
state = tunnelState,
|
||||
isSelected = selected,
|
||||
tunnel = tunnel,
|
||||
onTvClick = { navController.push(Route.TunnelOptions(tunnel.id)) },
|
||||
onToggleSelectedTunnel = { tunnel -> viewModel.toggleSelectedTunnel(tunnel.id) },
|
||||
onSwitchClick = { checked ->
|
||||
if (checked) sharedViewModel.startTunnel(tunnel)
|
||||
else sharedViewModel.stopTunnel(tunnel)
|
||||
var leadingIconColor by
|
||||
remember(
|
||||
tunnelState.status,
|
||||
tunnelState.logHealthState,
|
||||
tunnelState.pingStates,
|
||||
tunnelState.statistics,
|
||||
) {
|
||||
mutableStateOf(tunnelState.health().asColor())
|
||||
}
|
||||
|
||||
SurfaceRow(
|
||||
leading = {
|
||||
Icon(
|
||||
Icons.Rounded.Circle,
|
||||
contentDescription = stringResource(R.string.tunnel_monitoring),
|
||||
tint = leadingIconColor,
|
||||
modifier = Modifier.size(14.dp),
|
||||
)
|
||||
},
|
||||
title = tunnel.tunName,
|
||||
onClick = {
|
||||
if (tunnelsState.selectedTunnels.isNotEmpty()) {
|
||||
viewModel.toggleSelectedTunnel(tunnel.id)
|
||||
} else {
|
||||
navController.push(Route.TunnelOptions(tunnel.id))
|
||||
viewModel.clearSelectedTunnels()
|
||||
}
|
||||
},
|
||||
selected = selected,
|
||||
expandedContent =
|
||||
if (!tunnelState.status.isDown()) {
|
||||
{
|
||||
TunnelStatisticsRow(
|
||||
tunnelState,
|
||||
tunnelsState.isPingEnabled,
|
||||
tunnelsState.showPingStats,
|
||||
)
|
||||
}
|
||||
} else null,
|
||||
onLongClick = { viewModel.toggleSelectedTunnel(tunnel.id) },
|
||||
trailing = { modifier ->
|
||||
SwitchWithDivider(
|
||||
checked = tunnelState.status.isUpOrStarting(),
|
||||
onClick = { checked ->
|
||||
if (checked) sharedViewModel.startTunnel(tunnel)
|
||||
else sharedViewModel.stopTunnel(tunnel)
|
||||
},
|
||||
modifier = modifier,
|
||||
)
|
||||
},
|
||||
isTv = isTv,
|
||||
isPingEnabled = tunnelsState.isPingEnabled,
|
||||
showDetailedStats = tunnelsState.showPingStats,
|
||||
modifier =
|
||||
(if (!isTv)
|
||||
Modifier.combinedClickable(
|
||||
onClick = {
|
||||
if (tunnelsState.selectedTunnels.isNotEmpty()) {
|
||||
viewModel.toggleSelectedTunnel(tunnel.id)
|
||||
} else {
|
||||
navController.push(Route.TunnelOptions(tunnel.id))
|
||||
viewModel.clearSelectedTunnels()
|
||||
}
|
||||
},
|
||||
onLongClick = { viewModel.toggleSelectedTunnel(tunnel.id) },
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null,
|
||||
)
|
||||
else Modifier),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-5
@@ -48,7 +48,7 @@ fun TunnelStatisticsRow(
|
||||
|
||||
statistics?.let { stats ->
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().padding(start = 32.dp, bottom = 10.dp, end = 10.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
) {
|
||||
@@ -99,7 +99,6 @@ fun TunnelStatisticsRow(
|
||||
}
|
||||
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
// Peer ID row
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
@@ -110,7 +109,6 @@ fun TunnelStatisticsRow(
|
||||
color = textColor,
|
||||
)
|
||||
}
|
||||
// RX/TX row
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
@@ -140,7 +138,6 @@ fun TunnelStatisticsRow(
|
||||
Text(formattedTx, style = textStyle, color = textColor)
|
||||
}
|
||||
}
|
||||
// Handshake row
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
@@ -163,7 +160,6 @@ fun TunnelStatisticsRow(
|
||||
)
|
||||
}
|
||||
}
|
||||
// Ping block (per unique peer key)
|
||||
AnimatedVisibility(visible = pingState != null && pingEnabled) {
|
||||
pingState?.let {
|
||||
val reachableText =
|
||||
|
||||
-1
@@ -12,7 +12,6 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.zaneschepke.wireguardautotunnel.data.entity.TunnelConfig
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.SecureScreenFromRecording
|
||||
|
||||
+1
-3
@@ -2,7 +2,6 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.config.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.MoreVert
|
||||
import androidx.compose.material3.*
|
||||
@@ -12,7 +11,6 @@ import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize
|
||||
|
||||
@Composable
|
||||
fun InterfaceDropdown(
|
||||
@@ -29,7 +27,7 @@ fun InterfaceDropdown(
|
||||
onMimicSip: () -> Unit,
|
||||
) {
|
||||
Column {
|
||||
IconButton(modifier = Modifier.size(iconSize), onClick = { onExpandedChange(true) }) {
|
||||
IconButton(onClick = { onExpandedChange(true) }) {
|
||||
Icon(
|
||||
Icons.Rounded.MoreVert,
|
||||
contentDescription = stringResource(R.string.quick_actions),
|
||||
|
||||
+2
-9
@@ -17,7 +17,6 @@ import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.state.ConfigProxy
|
||||
import com.zaneschepke.wireguardautotunnel.ui.state.PeerProxy
|
||||
import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize
|
||||
|
||||
@Composable
|
||||
fun PeersSection(
|
||||
@@ -44,20 +43,14 @@ fun PeersSection(
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
IconButton(
|
||||
modifier = Modifier.size(iconSize),
|
||||
onClick = { onRemove(index) },
|
||||
) {
|
||||
IconButton(onClick = { onRemove(index) }) {
|
||||
Icon(
|
||||
Icons.Rounded.Delete,
|
||||
contentDescription = stringResource(R.string.delete),
|
||||
)
|
||||
}
|
||||
Column {
|
||||
IconButton(
|
||||
modifier = Modifier.size(iconSize),
|
||||
onClick = { isDropDownExpanded = true },
|
||||
) {
|
||||
IconButton(onClick = { isDropDownExpanded = true }) {
|
||||
Icon(
|
||||
Icons.Rounded.MoreVert,
|
||||
contentDescription = stringResource(R.string.quick_actions),
|
||||
|
||||
+14
-7
@@ -7,19 +7,24 @@ import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.LocalMinimumInteractiveComponentSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.domain.model.InstalledPackage
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SelectionItemButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.text.DescriptionText
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AppListItem(
|
||||
installedPackage: InstalledPackage,
|
||||
@@ -37,23 +42,25 @@ fun AppListItem(
|
||||
}
|
||||
}
|
||||
|
||||
SelectionItemButton(
|
||||
SurfaceRow(
|
||||
leading = {
|
||||
Image(
|
||||
painter = rememberDrawablePainter(icon),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(iconSize),
|
||||
modifier = Modifier.size(24.dp),
|
||||
)
|
||||
},
|
||||
buttonText = installedPackage.name,
|
||||
description = installedPackage.packageName,
|
||||
title = installedPackage.name,
|
||||
description = { DescriptionText(installedPackage.packageName) },
|
||||
onClick = { onToggle(!isSelected) },
|
||||
trailing = {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.End,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Checkbox(checked = isSelected, onCheckedChange = { onToggle(it) })
|
||||
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
|
||||
Checkbox(checked = isSelected, onCheckedChange = { onToggle(it) })
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ fun AppListSection(
|
||||
label = {},
|
||||
leading = { Icon(Icons.Outlined.Search, stringResource(R.string.search)) },
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
modifier = Modifier.fillMaxWidth().height(inputHeight),
|
||||
modifier = Modifier.fillMaxWidth().height(inputHeight).padding(horizontal = 16.dp),
|
||||
singleLine = true,
|
||||
keyboardOptions =
|
||||
KeyboardOptions(
|
||||
|
||||
+2
@@ -3,6 +3,7 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.splittunnel.compo
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
@@ -27,6 +28,7 @@ fun SplitOptionSelector(selectedOption: SplitOption, onOptionChange: (SplitOptio
|
||||
Modifier.background(color = MaterialTheme.colorScheme.surface)
|
||||
.fillMaxWidth()
|
||||
.height(inputHeight)
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
SplitOption.entries.forEachIndexed { index, entry ->
|
||||
val active = selectedOption == entry
|
||||
|
||||
+2
-3
@@ -2,8 +2,7 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.splittunnel.compo
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -21,7 +20,7 @@ fun SplitTunnelContent(
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp, Alignment.Top),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
SplitOptionSelector(
|
||||
selectedOption = splitConfig.first,
|
||||
|
||||
+155
-34
@@ -1,27 +1,49 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.tunneloptions
|
||||
|
||||
import android.util.Patterns
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.CallSplit
|
||||
import androidx.compose.material.icons.outlined.Dns
|
||||
import androidx.compose.material.icons.outlined.Save
|
||||
import androidx.compose.material.icons.outlined.Star
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.data.model.AppMode
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.SectionDivider
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.text.DescriptionText
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.textbox.CustomTextField
|
||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.tunneloptions.components.*
|
||||
import com.zaneschepke.wireguardautotunnel.ui.sideeffect.LocalSideEffect
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isValidIpv4orIpv6Address
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.TunnelViewModel
|
||||
import org.orbitmvi.orbit.compose.collectSideEffect
|
||||
|
||||
@@ -48,42 +70,141 @@ fun TunnelOptionsScreen(viewModel: TunnelViewModel) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
||||
modifier =
|
||||
Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 16.dp),
|
||||
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
SurfaceSelectionGroupButton(
|
||||
items =
|
||||
buildList {
|
||||
add(primaryTunnelItem(tunnel) { viewModel.togglePrimaryTunnel() })
|
||||
add(autoTunnelingItem(tunnel))
|
||||
add(
|
||||
splitTunnelingItem(stringResource(R.string.splt_tunneling)) {
|
||||
navController.push(Route.SplitTunnel(id = tunnel.id))
|
||||
}
|
||||
Column {
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.Star, contentDescription = null) },
|
||||
title = stringResource(R.string.primary_tunnel),
|
||||
description = {
|
||||
Text(
|
||||
text = stringResource(R.string.set_primary_tunnel),
|
||||
style =
|
||||
MaterialTheme.typography.bodySmall.copy(
|
||||
MaterialTheme.colorScheme.outline
|
||||
),
|
||||
)
|
||||
}
|
||||
)
|
||||
SectionDivider()
|
||||
SurfaceSelectionGroupButton(
|
||||
items =
|
||||
buildList {
|
||||
add(
|
||||
dynamicDnsItem(tunnel.restartOnPingFailure) {
|
||||
viewModel.setRestartOnPing(it)
|
||||
}
|
||||
},
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
checked = tunnel.isPrimaryTunnel,
|
||||
onClick = { viewModel.togglePrimaryTunnel() },
|
||||
)
|
||||
if (tunnelState.appMode != AppMode.KERNEL)
|
||||
add(
|
||||
preferIpv6Item(!tunnel.isIpv4Preferred) {
|
||||
viewModel.toggleIpv4Preferred()
|
||||
},
|
||||
onClick = { viewModel.togglePrimaryTunnel() },
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = {
|
||||
Icon(Icons.AutoMirrored.Outlined.CallSplit, contentDescription = null)
|
||||
},
|
||||
title = stringResource(R.string.splt_tunneling),
|
||||
onClick = { navController.push(Route.SplitTunnel(id = tunnel.id)) },
|
||||
)
|
||||
}
|
||||
Column {
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.Dns, contentDescription = null) },
|
||||
title = stringResource(R.string.ddns_auto_update),
|
||||
description = {
|
||||
DescriptionText(stringResource(R.string.ddns_auto_update_description))
|
||||
},
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
checked = tunnel.restartOnPingFailure,
|
||||
onClick = { viewModel.setRestartOnPing(it) },
|
||||
)
|
||||
},
|
||||
onClick = { viewModel.setRestartOnPing(!tunnel.restartOnPingFailure) },
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = {
|
||||
Icon(ImageVector.vectorResource(R.drawable.host), contentDescription = null)
|
||||
},
|
||||
title = stringResource(R.string.prefer_ipv6_resolution),
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
checked = !tunnel.isIpv4Preferred,
|
||||
onClick = { viewModel.toggleIpv4Preferred() },
|
||||
)
|
||||
},
|
||||
onClick = { viewModel.toggleIpv4Preferred() },
|
||||
)
|
||||
SurfaceRow(
|
||||
title = "",
|
||||
description = {
|
||||
val focusManager = LocalFocusManager.current
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
|
||||
var stateValue by remember { mutableStateOf(tunnel.pingTarget ?: "") }
|
||||
val isError by
|
||||
remember(stateValue) {
|
||||
derivedStateOf {
|
||||
stateValue.isNotBlank() &&
|
||||
!stateValue.isValidIpv4orIpv6Address() &&
|
||||
!Patterns.DOMAIN_NAME.matcher(stateValue).matches()
|
||||
}
|
||||
}
|
||||
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
CustomTextField(
|
||||
isError = isError,
|
||||
textStyle =
|
||||
MaterialTheme.typography.bodySmall.copy(
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
),
|
||||
value = stateValue,
|
||||
onValueChange = { stateValue = it },
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
label = {
|
||||
Text(
|
||||
stringResource(R.string.set_custom_ping_target),
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
)
|
||||
},
|
||||
placeholder = {
|
||||
Text(
|
||||
stringResource(R.string.ip_or_hostname),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
)
|
||||
},
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
supportingText = {
|
||||
Text(stringResource(R.string.ping_target_description))
|
||||
},
|
||||
modifier =
|
||||
Modifier.padding(top = 5.dp, bottom = 10.dp)
|
||||
.fillMaxWidth()
|
||||
.padding(end = 16.dp),
|
||||
singleLine = true,
|
||||
keyboardOptions =
|
||||
KeyboardOptions(
|
||||
capitalization = KeyboardCapitalization.None,
|
||||
imeAction = ImeAction.Done,
|
||||
),
|
||||
keyboardActions =
|
||||
KeyboardActions(onDone = { viewModel.setTunnelPingIp(stateValue) }),
|
||||
trailing = {
|
||||
if (!isError) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
viewModel.setTunnelPingIp(stateValue)
|
||||
keyboardController?.hide()
|
||||
focusManager.clearFocus()
|
||||
}
|
||||
) {
|
||||
val icon = Icons.Outlined.Save
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = icon.name,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
)
|
||||
if (tunnelState.isPingEnabled) {
|
||||
SectionDivider()
|
||||
SurfaceSelectionGroupButton(
|
||||
items = listOf(pingConfigItem(tunnel) { ip -> viewModel.setTunnelPingIp(ip) })
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
-40
@@ -1,40 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.tunneloptions.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Bolt
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
|
||||
|
||||
@Composable
|
||||
fun autoTunnelingItem(tunnelConf: TunnelConf): SelectionItem {
|
||||
val navController = LocalNavController.current
|
||||
return SelectionItem(
|
||||
leading = { Icon(Icons.Outlined.Bolt, contentDescription = null) },
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.auto_tunneling),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
description = {
|
||||
Text(
|
||||
text = stringResource(R.string.tunnel_specific_settings),
|
||||
style = MaterialTheme.typography.bodySmall.copy(MaterialTheme.colorScheme.outline),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ForwardButton { navController.push(Route.TunnelAutoTunnel(id = tunnelConf.id)) }
|
||||
},
|
||||
onClick = { navController.push(Route.TunnelAutoTunnel(id = tunnelConf.id)) },
|
||||
)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user