android/ui: updated main settings screen to material design

Updates #cleanup

Signed-off-by: Percy Wegmann <percy@tailscale.com>
pull/251/head
Percy Wegmann 2 months ago committed by Percy Wegmann
parent 9f3e871637
commit 7392c7086e

@ -16,7 +16,7 @@ import androidx.compose.ui.unit.dp
object Lists {
@Composable
fun SectionDivider() {
Box(Modifier.size(0.dp, 24.dp))
Box(Modifier.size(0.dp, 8.dp))
}
@Composable

@ -4,26 +4,23 @@
package com.tailscale.ipn.ui.view
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.text.ClickableText
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.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle
@ -31,10 +28,9 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.tailscale.ipn.R
import com.tailscale.ipn.ui.Links
import com.tailscale.ipn.ui.model.IpnLocal
import com.tailscale.ipn.ui.theme.ts_color_dark_desctrutive_text
import com.tailscale.ipn.ui.util.defaultPaddingModifier
import com.tailscale.ipn.ui.util.settingsRowModifier
import com.tailscale.ipn.ui.util.Lists
import com.tailscale.ipn.ui.util.itemsWithDividers
import com.tailscale.ipn.ui.viewModel.Setting
import com.tailscale.ipn.ui.viewModel.SettingType
import com.tailscale.ipn.ui.viewModel.SettingsNav
@ -49,116 +45,79 @@ fun Settings(
val handler = LocalUriHandler.current
val user = viewModel.loggedInUser.collectAsState().value
val isAdmin = viewModel.isAdmin.collectAsState().value
val settingsBundles = viewModel.settings.collectAsState().value
Scaffold(
topBar = { Header(title = R.string.settings_title, onBack = settingsNav.onBackPressed) }) {
innerPadding ->
Column(modifier = Modifier.padding(innerPadding).fillMaxHeight().padding(16.dp)) {
UserView(
profile = user,
actionState = UserActionState.NAV,
onClick = viewModel.navigation.onNavigateToUserSwitcher)
if (isAdmin) {
Spacer(modifier = Modifier.height(4.dp))
AdminTextView { handler.openUri(Links.ADMIN_URL) }
LazyColumn(modifier = Modifier.padding(innerPadding)) {
item {
UserView(
profile = user,
actionState = UserActionState.NAV,
onClick = viewModel.navigation.onNavigateToUserSwitcher)
}
Spacer(modifier = Modifier.height(8.dp))
val settings = viewModel.settings.collectAsState().value
settings.forEach { settingBundle ->
Column(modifier = settingsRowModifier()) {
settingBundle.title?.let { SettingTitle(it) }
settingBundle.settings.forEach { SettingRow(it) }
if (isAdmin) {
item {
Spacer(modifier = Modifier.height(4.dp))
AdminTextView { handler.openUri(Links.ADMIN_URL) }
}
Spacer(modifier = Modifier.height(8.dp))
}
}
}
}
@Composable
fun UserView(
profile: IpnLocal.LoginProfile?,
isAdmin: Boolean,
adminText: AnnotatedString,
onClick: () -> Unit
) {
Column {
Row(modifier = settingsRowModifier().padding(8.dp)) {
Box(modifier = defaultPaddingModifier()) { Avatar(profile = profile, size = 36) }
Column(verticalArrangement = Arrangement.Center) {
Text(
text = profile?.UserProfile?.DisplayName ?: "",
style = MaterialTheme.typography.titleMedium)
Text(text = profile?.Name ?: "", style = MaterialTheme.typography.bodyMedium)
}
}
settingsBundles.forEach { bundle ->
item { Lists.SectionDivider() }
if (isAdmin) {
Column(modifier = Modifier.padding(horizontal = 12.dp)) {
ClickableText(
text = adminText, style = MaterialTheme.typography.bodySmall, onClick = { onClick() })
itemsWithDividers(bundle.settings) { setting -> SettingRow(setting) }
}
}
}
}
}
}
@Composable
fun SettingTitle(title: String) {
Text(
text = title, style = MaterialTheme.typography.titleMedium, modifier = Modifier.padding(8.dp))
}
@Composable
fun SettingRow(setting: Setting) {
val enabled = setting.enabled.collectAsState().value
val swVal = setting.isOn?.collectAsState()?.value ?: false
val txtVal = setting.value?.collectAsState()?.value ?: ""
val txtVal = setting.value?.collectAsState()?.value
Row(
modifier = defaultPaddingModifier().clickable { if (enabled) setting.onClick() },
verticalAlignment = Alignment.CenterVertically) {
when (setting.type) {
SettingType.NAV_WITH_TEXT -> {
Text(
setting.title.getString(),
style = MaterialTheme.typography.bodyMedium,
color =
if (setting.destructive) ts_color_dark_desctrutive_text
else MaterialTheme.colorScheme.primary)
Box(modifier = Modifier.weight(1f), contentAlignment = Alignment.CenterEnd) {
Text(text = txtVal, style = MaterialTheme.typography.bodyMedium)
}
}
SettingType.TEXT -> {
Text(
setting.title.getString(),
style = MaterialTheme.typography.bodyMedium,
color =
if (setting.destructive) ts_color_dark_desctrutive_text
else MaterialTheme.colorScheme.primary)
}
SettingType.SWITCH -> {
Text(setting.title.getString())
Box(modifier = Modifier.weight(1f), contentAlignment = Alignment.CenterEnd) {
TintedSwitch(checked = swVal, onCheckedChange = setting.onToggle, enabled = enabled)
}
}
SettingType.NAV -> {
Text(
setting.title.getString(),
style = MaterialTheme.typography.bodyMedium,
color =
if (setting.destructive) ts_color_dark_desctrutive_text
else MaterialTheme.colorScheme.primary)
Box(modifier = Modifier.weight(1f), contentAlignment = Alignment.CenterEnd) {
Text(text = txtVal, style = MaterialTheme.typography.bodyMedium)
}
}
}
Box {
when (setting.type) {
SettingType.TEXT ->
ListItem(
modifier = Modifier.clickable { if (enabled) setting.onClick() },
headlineContent = {
Text(
setting.title.getString(),
style = MaterialTheme.typography.bodyMedium,
color =
if (setting.destructive) ts_color_dark_desctrutive_text
else MaterialTheme.colorScheme.primary)
},
)
SettingType.SWITCH ->
ListItem(
modifier = Modifier.clickable { if (enabled) setting.onClick() },
headlineContent = { Text(setting.title.getString()) },
trailingContent = {
TintedSwitch(checked = swVal, onCheckedChange = setting.onToggle, enabled = enabled)
})
SettingType.NAV -> {
ListItem(
modifier = Modifier.clickable { if (enabled) setting.onClick() },
headlineContent = {
Text(
setting.title.getString(),
style = MaterialTheme.typography.bodyMedium,
color =
if (setting.destructive) ts_color_dark_desctrutive_text
else MaterialTheme.colorScheme.primary)
},
supportingContent = {
txtVal?.let { Text(text = it, style = MaterialTheme.typography.bodyMedium) }
})
}
}
}
}
@Composable

@ -4,20 +4,15 @@
package com.tailscale.ipn.ui.view
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.ListItem
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 com.tailscale.ipn.R
import com.tailscale.ipn.ui.model.IpnLocal
import com.tailscale.ipn.ui.util.defaultPaddingModifier
import com.tailscale.ipn.ui.util.settingsRowModifier
// Used to decorate UserViews.
// NONE indicates no decoration
@ -37,34 +32,29 @@ fun UserView(
onClick: () -> Unit = {},
actionState: UserActionState = UserActionState.NONE
) {
Column {
Row(
modifier = settingsRowModifier().clickable { onClick() },
verticalAlignment = Alignment.CenterVertically) {
profile?.let {
Box(modifier = defaultPaddingModifier()) { Avatar(profile = profile, size = 36) }
Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.Center) {
Text(
text = profile.UserProfile.DisplayName,
style = MaterialTheme.typography.titleMedium)
Text(text = profile.Name, style = MaterialTheme.typography.bodyMedium)
}
}
?: run {
Box(modifier = Modifier.weight(1f)) {
Text(
text = stringResource(id = R.string.accounts),
style = MaterialTheme.typography.titleMedium)
}
}
when (actionState) {
UserActionState.CURRENT -> CheckedIndicator()
UserActionState.SWITCHING -> SimpleActivityIndicator(size = 26)
UserActionState.NAV -> Unit
UserActionState.NONE -> Unit
}
Box {
profile?.let {
ListItem(
modifier = Modifier.clickable { onClick() },
leadingContent = { Avatar(profile = profile, size = 36) },
headlineContent = {
Text(
text = profile.UserProfile.DisplayName,
style = MaterialTheme.typography.titleMedium)
},
supportingContent = {
Text(text = profile.Name, style = MaterialTheme.typography.bodyMedium)
},
)
}
?: run {
ListItem(
modifier = Modifier.clickable { onClick() },
headlineContent = {
Text(
text = stringResource(id = R.string.accounts),
style = MaterialTheme.typography.titleMedium)
})
}
}
}

@ -22,7 +22,6 @@ import kotlinx.coroutines.launch
enum class SettingType {
NAV,
SWITCH,
NAV_WITH_TEXT,
TEXT
}

Loading…
Cancel
Save