diff --git a/android/src/main/java/com/tailscale/ipn/ui/util/Lists.kt b/android/src/main/java/com/tailscale/ipn/ui/util/Lists.kt index 4c89bdc..a9f57d1 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/util/Lists.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/util/Lists.kt @@ -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 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 ef5fbf0..d3208e1 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 @@ -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 diff --git a/android/src/main/java/com/tailscale/ipn/ui/view/UserView.kt b/android/src/main/java/com/tailscale/ipn/ui/view/UserView.kt index 7ec3690..6911f00 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/view/UserView.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/view/UserView.kt @@ -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) + }) } } } 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 ed56836..6ee31ee 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 @@ -22,7 +22,6 @@ import kotlinx.coroutines.launch enum class SettingType { NAV, SWITCH, - NAV_WITH_TEXT, TEXT }