diff --git a/android/src/main/java/com/tailscale/ipn/ui/theme/Theme.kt b/android/src/main/java/com/tailscale/ipn/ui/theme/Theme.kt index 5676eb4..6206093 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/theme/Theme.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/theme/Theme.kt @@ -71,9 +71,11 @@ private val LightColors = surfaceContainer = Color(0xFFF7F5F4), // gray-100 surfaceContainerHigh = Color(0xFFF7F5F4), // gray-100 surfaceContainerHighest = Color(0xFFF7F5F4), // gray-100 + surfaceVariant = Color(0xFFF7F5F4), // gray-100, onSurface = Color(0xFF232222), // gray-800 onSurfaceVariant = Color(0xFF706E6D), // gray-500 - outline = Color(0xFFD9D6D5), // gray-300 + // outline = Color(0xFFD9D6D5), // gray-300 + outline = Color(0xFF706E6D), // gray-500 outlineVariant = Color(0xFFEDEBEA), // gray-200 inverseSurface = Color(0xFF232222), // gray-800 inverseOnSurface = Color(0xFFFFFFFF), // white diff --git a/android/src/main/java/com/tailscale/ipn/ui/util/ClipboardValueView.kt b/android/src/main/java/com/tailscale/ipn/ui/util/ClipboardValueView.kt index ef1b7ce..98e2ab3 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/util/ClipboardValueView.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/util/ClipboardValueView.kt @@ -17,26 +17,18 @@ import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp import com.tailscale.ipn.R import com.tailscale.ipn.ui.theme.titledListItem @Composable -fun ClipboardValueView( - value: String, - title: String? = null, - subtitle: String? = null, - fontFamily: FontFamily = FontFamily.Monospace -) { +fun ClipboardValueView(value: String, title: String? = null, subtitle: String? = null) { val localClipboardManager = LocalClipboardManager.current ListItem( colors = MaterialTheme.colorScheme.titledListItem, modifier = Modifier.clickable { localClipboardManager.setText(AnnotatedString(value)) }, overlineContent = { title?.let { Text(it, style = MaterialTheme.typography.titleMedium) } }, - headlineContent = { - Text(text = value, style = MaterialTheme.typography.bodyMedium, fontFamily = fontFamily) - }, + headlineContent = { Text(text = value, style = MaterialTheme.typography.bodyMedium) }, supportingContent = { subtitle?.let { subtitle -> Text( diff --git a/android/src/main/java/com/tailscale/ipn/ui/util/FeatureStateRepresentation.kt b/android/src/main/java/com/tailscale/ipn/ui/util/FeatureStateRepresentation.kt deleted file mode 100644 index c6d772f..0000000 --- a/android/src/main/java/com/tailscale/ipn/ui/util/FeatureStateRepresentation.kt +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package com.tailscale.ipn.ui.util - -import androidx.annotation.DrawableRes -import androidx.annotation.StringRes -import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.Color - -// FeatureStateRepresentation represents the status of a feature -// in the UI, by providing a symbol, a title, and a caption. -// It is typically implemented as an enumeration. -interface FeatureStateRepresentation { - @get:DrawableRes val symbolDrawable: Int - @get:Composable val tint: Color - @get:StringRes val title: Int - @get:StringRes val caption: Int -} diff --git a/android/src/main/java/com/tailscale/ipn/ui/util/LoadingIndicator.kt b/android/src/main/java/com/tailscale/ipn/ui/util/LoadingIndicator.kt index 581cefe..c18fd60 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/util/LoadingIndicator.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/util/LoadingIndicator.kt @@ -38,7 +38,7 @@ object LoadingIndicator { content() val isLoading = loading.collectAsState().value if (isLoading) { - Box(Modifier.clickable {}.matchParentSize().background(Color.Gray.copy(alpha = 0.5f))) + Box(Modifier.clickable {}.matchParentSize().background(Color.Gray.copy(alpha = 0.0f))) Column( modifier = Modifier.fillMaxWidth(), diff --git a/android/src/main/java/com/tailscale/ipn/ui/view/DNSSettingsView.kt b/android/src/main/java/com/tailscale/ipn/ui/view/DNSSettingsView.kt index b2b9217..763f1b7 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/view/DNSSettingsView.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/view/DNSSettingsView.kt @@ -5,12 +5,19 @@ package com.tailscale.ipn.ui.view import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.Icon +import androidx.compose.material3.ListItem +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import com.tailscale.ipn.R import com.tailscale.ipn.ui.model.DnsType @@ -41,10 +48,22 @@ fun DNSSettingsView( Scaffold(topBar = { Header(R.string.dns_settings, onBack = nav.onBack) }) { innerPadding -> LoadingIndicator.Wrap { LazyColumn(Modifier.padding(innerPadding)) { - item("state") { FeatureStateView(state) } + item("state") { + ListItem( + leadingContent = { + Icon( + painter = painterResource(state.symbolDrawable), + contentDescription = null, + tint = state.tint(), + modifier = Modifier.size(36.dp)) + }, + headlineContent = { + Text(stringResource(state.title), style = MaterialTheme.typography.titleMedium) + }, + supportingContent = { Text(stringResource(state.caption)) }) + + Lists.ItemDivider() - item("toggle") { - Lists.SectionDivider() SettingRow(model.useDNSSetting) } diff --git a/android/src/main/java/com/tailscale/ipn/ui/view/FeatureStateView.kt b/android/src/main/java/com/tailscale/ipn/ui/view/FeatureStateView.kt deleted file mode 100644 index 4eeeffd..0000000 --- a/android/src/main/java/com/tailscale/ipn/ui/view/FeatureStateView.kt +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package com.tailscale.ipn.ui.view - -import androidx.compose.foundation.layout.size -import androidx.compose.material3.Icon -import androidx.compose.material3.ListItem -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.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import com.tailscale.ipn.ui.util.FeatureStateRepresentation - -// FeatureStateView is a Composable that displays the contents of -// a FeatureStateRepresentation. -@Composable -fun FeatureStateView(state: FeatureStateRepresentation) { - ListItem( - leadingContent = { - Icon( - painter = painterResource(state.symbolDrawable), - contentDescription = null, - tint = state.tint, - modifier = Modifier.size(36.dp)) - }, - headlineContent = { - Text(stringResource(state.title), style = MaterialTheme.typography.titleMedium) - }, - supportingContent = { Text(stringResource(state.caption)) }) -} diff --git a/android/src/main/java/com/tailscale/ipn/ui/view/MainView.kt b/android/src/main/java/com/tailscale/ipn/ui/view/MainView.kt index e5c3e07..f289b1d 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/view/MainView.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/view/MainView.kt @@ -107,10 +107,7 @@ fun MainView(navigation: MainViewNavigation, viewModel: MainViewModel = viewMode colors = MaterialTheme.colorScheme.surfaceContainerListItem, leadingContent = { val isOn = viewModel.vpnToggleState.collectAsState(initial = false) - TintedSwitch( - onCheckedChange = { viewModel.toggleVpn() }, - checked = isOn.value, - enabled = state != Ipn.State.NoState) + TintedSwitch(onCheckedChange = { viewModel.toggleVpn() }, checked = isOn.value) }, headlineContent = { user?.NetworkProfile?.DomainName?.let { domain -> diff --git a/android/src/main/java/com/tailscale/ipn/ui/view/SettingsView.kt b/android/src/main/java/com/tailscale/ipn/ui/view/SettingsView.kt index fa9b610..d667f2d 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/view/SettingsView.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/view/SettingsView.kt @@ -104,8 +104,12 @@ fun SettingRow(setting: Setting) { @Composable private fun TextRow(setting: Setting) { val enabled = setting.enabled.collectAsState().value + var modifier: Modifier = Modifier + if (enabled) { + setting.onClick?.let { modifier = modifier.clickable(onClick = it) } + } ListItem( - modifier = Modifier.clickable { if (enabled) setting.onClick() }, + modifier = modifier, colors = MaterialTheme.colorScheme.listItem, headlineContent = { Text( @@ -120,8 +124,12 @@ private fun TextRow(setting: Setting) { private fun SwitchRow(setting: Setting) { val enabled = setting.enabled.collectAsState().value val swVal = setting.isOn?.collectAsState()?.value ?: false + var modifier: Modifier = Modifier + if (enabled) { + setting.onClick?.let { modifier = modifier.clickable(onClick = it) } + } ListItem( - modifier = Modifier.clickable { if (enabled) setting.onClick() }, + modifier = modifier, colors = MaterialTheme.colorScheme.listItem, headlineContent = { Text( @@ -136,8 +144,10 @@ private fun SwitchRow(setting: Setting) { @Composable private fun NavRow(setting: Setting) { + var modifier: Modifier = Modifier + setting.onClick?.let { modifier = modifier.clickable(onClick = it) } ListItem( - modifier = Modifier.clickable { setting.onClick() }, + modifier = modifier, colors = MaterialTheme.colorScheme.listItem, headlineContent = { Text( diff --git a/android/src/main/java/com/tailscale/ipn/ui/viewModel/DNSSettingsViewModel.kt b/android/src/main/java/com/tailscale/ipn/ui/viewModel/DNSSettingsViewModel.kt index 1def574..db15d8a 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/viewModel/DNSSettingsViewModel.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/viewModel/DNSSettingsViewModel.kt @@ -18,7 +18,6 @@ import com.tailscale.ipn.ui.model.Tailcfg import com.tailscale.ipn.ui.notifier.Notifier import com.tailscale.ipn.ui.theme.off import com.tailscale.ipn.ui.theme.success -import com.tailscale.ipn.ui.util.FeatureStateRepresentation import com.tailscale.ipn.ui.util.set import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -84,45 +83,25 @@ class DNSSettingsViewModel() : IpnViewModel() { } } -enum class DNSEnablementState : FeatureStateRepresentation { - NOT_RUNNING { - override val title: Int - @StringRes get() = R.string.not_running - - override val caption: Int - @StringRes - get() = R.string.tailscale_is_not_running_this_device_is_using_the_system_dns_resolver - - override val tint: Color - @Composable get() = MaterialTheme.colorScheme.off - - override val symbolDrawable: Int - get() = R.drawable.xmark_circle - }, - ENABLED { - override val title: Int - @StringRes get() = R.string.using_tailscale_dns - - override val caption: Int - @StringRes get() = R.string.this_device_is_using_tailscale_to_resolve_dns_names - - override val tint: Color - @Composable get() = MaterialTheme.colorScheme.success - - override val symbolDrawable: Int - get() = R.drawable.check_circle - }, - DISABLED { - override val title: Int - @StringRes get() = R.string.not_using_tailscale_dns - - override val caption: Int - @StringRes get() = R.string.this_device_is_using_the_system_dns_resolver - - override val tint: Color - @Composable get() = MaterialTheme.colorScheme.error - - override val symbolDrawable: Int - get() = R.drawable.xmark_circle - } +enum class DNSEnablementState( + @StringRes val title: Int, + @StringRes val caption: Int, + val symbolDrawable: Int, + val tint: @Composable () -> Color +) { + NOT_RUNNING( + R.string.not_running, + R.string.tailscale_is_not_running_this_device_is_using_the_system_dns_resolver, + R.drawable.xmark_circle, + { MaterialTheme.colorScheme.off }), + ENABLED( + R.string.using_tailscale_dns, + R.string.this_device_is_using_tailscale_to_resolve_dns_names, + R.drawable.check_circle, + { MaterialTheme.colorScheme.success }), + DISABLED( + R.string.not_using_tailscale_dns, + R.string.this_device_is_using_the_system_dns_resolver, + R.drawable.xmark_circle, + { MaterialTheme.colorScheme.error }) } diff --git a/android/src/main/java/com/tailscale/ipn/ui/viewModel/SettingsViewModel.kt b/android/src/main/java/com/tailscale/ipn/ui/viewModel/SettingsViewModel.kt index d5a668d..5b3974b 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/viewModel/SettingsViewModel.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/viewModel/SettingsViewModel.kt @@ -40,8 +40,8 @@ data class Setting( val destructive: Boolean = false, val enabled: StateFlow = MutableStateFlow(true), val isOn: StateFlow? = null, - val onClick: () -> Unit = {}, - val onToggle: (Boolean) -> Unit = {}, + val onClick: (() -> Unit)? = null, + val onToggle: (Boolean) -> Unit = {} ) data class SettingsNav(