From 5454b34dd1468d64acd35694cf2660324c6930d8 Mon Sep 17 00:00:00 2001 From: James Tucker Date: Fri, 22 Mar 2024 11:28:54 -0700 Subject: [PATCH] Revert "[568eb59] android/ui: address preliminary design feedback (#227)" This reverts commit 910511d83859900ac23da317ee3a22b6fa947e78. Signed-off-by: James Tucker --- android/src/main/AndroidManifest.xml | 1 - .../java/com/tailscale/ipn/MainActivity.kt | 16 +- .../com/tailscale/ipn/ui/view/AboutView.kt | 7 +- .../tailscale/ipn/ui/view/BugReportView.kt | 51 ++-- .../tailscale/ipn/ui/view/ExitNodePicker.kt | 3 +- .../ipn/ui/view/MDMSettingsDebugView.kt | 5 +- .../com/tailscale/ipn/ui/view/MainView.kt | 232 ++++++++---------- .../tailscale/ipn/ui/view/ManagedByView.kt | 7 +- .../com/tailscale/ipn/ui/view/PeerDetails.kt | 95 ++++--- .../com/tailscale/ipn/ui/view/SettingsView.kt | 44 ++-- .../com/tailscale/ipn/ui/view/SharedViews.kt | 21 +- .../com/tailscale/ipn/ui/view/TintedSwitch.kt | 24 -- .../tailscale/ipn/ui/view/UserSwitcherView.kt | 4 +- .../com/tailscale/ipn/ui/view/UserView.kt | 2 +- .../ipn/ui/viewModel/SettingsViewModel.kt | 3 +- android/src/main/res/values/strings.xml | 12 +- 16 files changed, 226 insertions(+), 301 deletions(-) delete mode 100644 android/src/main/java/com/tailscale/ipn/ui/view/TintedSwitch.kt diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index aa30b6d..e87b8bb 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -29,7 +29,6 @@ android:allowBackup="false" android:banner="@drawable/tv_banner" android:icon="@mipmap/ic_launcher" - android:theme="@style/Theme.AppCompat" android:label="Tailscale" android:roundIcon="@mipmap/ic_launcher_round"> +fun AboutView() { + Scaffold { _ -> Column( verticalArrangement = Arrangement.spacedBy(space = 20.dp, alignment = Alignment.CenterVertically), horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.fillMaxWidth().fillMaxHeight().padding(innerPadding)) { + modifier = Modifier.fillMaxWidth().fillMaxHeight().safeContentPadding()) { Image( modifier = Modifier.width(100.dp) diff --git a/android/src/main/java/com/tailscale/ipn/ui/view/BugReportView.kt b/android/src/main/java/com/tailscale/ipn/ui/view/BugReportView.kt index 3b5ecad..f71e4ca 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/view/BugReportView.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/view/BugReportView.kt @@ -24,53 +24,50 @@ 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.LocalClipboardManager 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.font.FontFamily import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.withStyle 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.theme.ts_color_light_blue import com.tailscale.ipn.ui.util.defaultPaddingModifier import com.tailscale.ipn.ui.util.settingsRowModifier import com.tailscale.ipn.ui.viewModel.BugReportViewModel import kotlinx.coroutines.flow.StateFlow @Composable -fun BugReportView(nav: BackNavigation, model: BugReportViewModel = viewModel()) { +fun BugReportView(model: BugReportViewModel = viewModel()) { val handler = LocalUriHandler.current - Scaffold(topBar = { Header(R.string.bug_report_title, onBack = nav.onBack) }) { innerPadding -> - Column( - modifier = Modifier.padding(innerPadding).padding(24.dp).fillMaxWidth().fillMaxHeight()) { - ClickableText( - text = contactText(), - modifier = Modifier.fillMaxWidth(), - style = MaterialTheme.typography.bodyMedium, - onClick = { handler.openUri(Links.SUPPORT_URL) }) + Scaffold(topBar = { Header(R.string.bug_report_title) }) { innerPadding -> + Column(modifier = Modifier.padding(innerPadding).padding(8.dp).fillMaxWidth().fillMaxHeight()) { + ClickableText( + text = contactText(), + modifier = Modifier.fillMaxWidth(), + style = MaterialTheme.typography.bodyMedium, + onClick = { handler.openUri(Links.SUPPORT_URL) }) - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(8.dp)) - ReportIdRow(bugReportIdFlow = model.bugReportID) + ReportIdRow(bugReportIdFlow = model.bugReportID) - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(8.dp)) - Text( - text = stringResource(id = R.string.bug_report_id_desc), - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Left, - color = MaterialTheme.colorScheme.secondary, - style = MaterialTheme.typography.bodySmall) - } + Text( + text = stringResource(id = R.string.bug_report_id_desc), + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Left, + color = MaterialTheme.colorScheme.secondary, + style = MaterialTheme.typography.bodySmall) + } } } @@ -90,8 +87,7 @@ fun ReportIdRow(bugReportIdFlow: StateFlow) { Text( text = bugReportId.value, style = MaterialTheme.typography.titleMedium, - fontFamily = FontFamily.Monospace, - maxLines = 2, + maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = defaultPaddingModifier()) } @@ -109,10 +105,9 @@ fun contactText(): AnnotatedString { } pushStringAnnotation(tag = "reportLink", annotation = Links.SUPPORT_URL) - withStyle( - style = SpanStyle(color = ts_color_light_blue, textDecoration = TextDecoration.Underline)) { - append(stringResource(id = R.string.bug_report_instructions_linktext)) - } + withStyle(style = SpanStyle(color = Color.Blue)) { + append(stringResource(id = R.string.bug_report_instructions_linktext)) + } pop() withStyle(style = SpanStyle(color = MaterialTheme.colorScheme.primary)) { diff --git a/android/src/main/java/com/tailscale/ipn/ui/view/ExitNodePicker.kt b/android/src/main/java/com/tailscale/ipn/ui/view/ExitNodePicker.kt index d291a18..0be44ef 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/view/ExitNodePicker.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/view/ExitNodePicker.kt @@ -43,8 +43,7 @@ fun ExitNodePicker( model: ExitNodePickerViewModel = viewModel(factory = ExitNodePickerViewModelFactory(nav)) ) { LoadingIndicator.Wrap { - Scaffold(topBar = { Header(R.string.choose_exit_node, onBack = nav.onNavigateHome) }) { - innerPadding -> + Scaffold(topBar = { Header(R.string.choose_exit_node) }) { innerPadding -> val tailnetExitNodes = model.tailnetExitNodes.collectAsState() val mullvadExitNodes = model.mullvadExitNodesByCountryCode.collectAsState() val anyActive = model.anyActive.collectAsState() diff --git a/android/src/main/java/com/tailscale/ipn/ui/view/MDMSettingsDebugView.kt b/android/src/main/java/com/tailscale/ipn/ui/view/MDMSettingsDebugView.kt index ab65463..191f7a9 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/view/MDMSettingsDebugView.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/view/MDMSettingsDebugView.kt @@ -31,9 +31,8 @@ import com.tailscale.ipn.ui.viewModel.IpnViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable -fun MDMSettingsDebugView(nav: BackNavigation, model: IpnViewModel = viewModel()) { - Scaffold(topBar = { Header(R.string.current_mdm_settings, onBack = nav.onBack) }) { innerPadding - -> +fun MDMSettingsDebugView(model: IpnViewModel = viewModel()) { + Scaffold(topBar = { Header(R.string.current_mdm_settings) }) { innerPadding -> val mdmSettings = IpnViewModel.mdmSettings.collectAsState().value LazyColumn(modifier = Modifier.padding(innerPadding)) { items(enumValues()) { booleanSetting -> 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 699937f..a6f5221 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 @@ -10,23 +10,21 @@ 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.WindowInsets import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight import androidx.compose.material.icons.outlined.ArrowDropDown import androidx.compose.material.icons.outlined.Clear import androidx.compose.material.icons.outlined.Close import androidx.compose.material.icons.outlined.Search import androidx.compose.material.icons.outlined.Settings import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.ListItem @@ -34,6 +32,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.SearchBar import androidx.compose.material3.SearchBarDefaults +import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState @@ -69,61 +68,49 @@ data class MainViewNavigation( @Composable fun MainView(navigation: MainViewNavigation, viewModel: MainViewModel = viewModel()) { - Scaffold(contentWindowInsets = WindowInsets.Companion.statusBars) { paddingInsets -> - Column( - modifier = Modifier.fillMaxWidth().padding(paddingInsets), - verticalArrangement = Arrangement.Center) { - val state = viewModel.ipnState.collectAsState(initial = Ipn.State.NoState) - val user = viewModel.loggedInUser.collectAsState(initial = null) - - Row( - modifier = - Modifier.fillMaxWidth() - .background(MaterialTheme.colorScheme.secondaryContainer) - .padding(horizontal = 8.dp) - .padding(top = 10.dp), - verticalAlignment = Alignment.CenterVertically) { - val isOn = viewModel.vpnToggleState.collectAsState(initial = false) - if (state.value != Ipn.State.NeedsLogin && state.value != Ipn.State.NoState) { - TintedSwitch(onCheckedChange = { viewModel.toggleVpn() }, checked = isOn.value) - Spacer(Modifier.size(3.dp)) - } - - StateDisplay(viewModel.stateRes, viewModel.userName) + Scaffold { _ -> + Column(modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.Center) { + val state = viewModel.ipnState.collectAsState(initial = Ipn.State.NoState) + val user = viewModel.loggedInUser.collectAsState(initial = null) - Box( - modifier = Modifier.weight(1f).clickable { navigation.onNavigateToSettings() }, - contentAlignment = Alignment.CenterEnd) { - when (user.value) { - null -> SettingsButton(user.value) { navigation.onNavigateToSettings() } - else -> Avatar(profile = user.value, size = 36) - } - } - } + Row( + modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp), + verticalAlignment = Alignment.CenterVertically) { + val isOn = viewModel.vpnToggleState.collectAsState(initial = false) + if (state.value != Ipn.State.NeedsLogin && state.value != Ipn.State.NoState) { + Switch(onCheckedChange = { viewModel.toggleVpn() }, checked = isOn.value) + Spacer(Modifier.size(3.dp)) + } - when (state.value) { - Ipn.State.Running -> { + StateDisplay(viewModel.stateRes, viewModel.userName) - val selfPeerId = viewModel.selfPeerId.collectAsState(initial = "") - Row( - modifier = - Modifier.background(MaterialTheme.colorScheme.secondaryContainer) - .padding(top = 10.dp, bottom = 20.dp)) { - ExitNodeStatus( - navAction = navigation.onNavigateToExitNodes, viewModel = viewModel) + Box( + modifier = Modifier.weight(1f).clickable { navigation.onNavigateToSettings() }, + contentAlignment = Alignment.CenterEnd) { + when (user.value) { + null -> SettingsButton(user.value) { navigation.onNavigateToSettings() } + else -> Avatar(profile = user.value, size = 36) } - PeerList( - searchTerm = viewModel.searchTerm, - state = viewModel.ipnState, - peers = viewModel.peers, - selfPeer = selfPeerId.value, - onNavigateToPeerDetails = navigation.onNavigateToPeerDetails, - onSearch = { viewModel.searchPeers(it) }) - } - Ipn.State.Starting -> StartingView() - else -> ConnectView(user.value, { viewModel.toggleVpn() }, { viewModel.login {} }) + } } + + when (state.value) { + Ipn.State.Running -> { + + val selfPeerId = viewModel.selfPeerId.collectAsState(initial = "") + ExitNodeStatus(navAction = navigation.onNavigateToExitNodes, viewModel = viewModel) + PeerList( + searchTerm = viewModel.searchTerm, + state = viewModel.ipnState, + peers = viewModel.peers, + selfPeer = selfPeerId.value, + onNavigateToPeerDetails = navigation.onNavigateToPeerDetails, + onSearch = { viewModel.searchPeers(it) }) } + Ipn.State.Starting -> StartingView() + else -> ConnectView(user.value, { viewModel.toggleVpn() }, { viewModel.login {} }) + } + } } } @@ -148,17 +135,16 @@ fun ExitNodeStatus(navAction: () -> Unit, viewModel: MainViewModel) { Modifier.clickable { navAction() } .padding(horizontal = 8.dp) .clip(shape = RoundedCornerShape(10.dp, 10.dp, 10.dp, 10.dp)) - .background(MaterialTheme.colorScheme.background) + .background(MaterialTheme.colorScheme.secondaryContainer) .fillMaxWidth()) { - Column(modifier = Modifier.padding(vertical = 15.dp, horizontal = 18.dp)) { + Column(modifier = Modifier.padding(6.dp)) { Text( text = stringResource(id = R.string.exit_node), - color = MaterialTheme.colorScheme.secondary, - style = MaterialTheme.typography.titleSmall) + style = MaterialTheme.typography.titleMedium) Row(verticalAlignment = Alignment.CenterVertically) { Text( text = exitNode ?: stringResource(id = R.string.none), - style = MaterialTheme.typography.bodyLarge) + style = MaterialTheme.typography.bodyMedium) Icon( Icons.Outlined.ArrowDropDown, null, @@ -222,64 +208,62 @@ fun StartingView() { fun ConnectView(user: IpnLocal.LoginProfile?, connectAction: () -> Unit, loginAction: () -> Unit) { Row(horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth()) { Column( - horizontalAlignment = Alignment.CenterHorizontally, modifier = - Modifier.background(MaterialTheme.colorScheme.secondaryContainer).fillMaxWidth()) { - Column( - modifier = Modifier.padding(8.dp).fillMaxWidth(0.7f).fillMaxHeight(), - verticalArrangement = - Arrangement.spacedBy(8.dp, alignment = Alignment.CenterVertically), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - if (user != null && !user.isEmpty()) { - Icon( - painter = painterResource(id = R.drawable.power), - contentDescription = null, - modifier = Modifier.size(48.dp), - tint = MaterialTheme.colorScheme.secondary) - Text( - text = stringResource(id = R.string.not_connected), - fontSize = MaterialTheme.typography.titleMedium.fontSize, - fontWeight = FontWeight.SemiBold, - color = MaterialTheme.colorScheme.primary, - textAlign = TextAlign.Center, - fontFamily = MaterialTheme.typography.titleMedium.fontFamily) - val tailnetName = user.NetworkProfile?.DomainName ?: "" - Text( - stringResource(id = R.string.connect_to_tailnet, tailnetName), - fontSize = MaterialTheme.typography.titleMedium.fontSize, - fontWeight = FontWeight.Normal, - color = MaterialTheme.colorScheme.secondary, - textAlign = TextAlign.Center, - ) - Spacer(modifier = Modifier.size(1.dp)) - PrimaryActionButton(onClick = connectAction) { - Text( - text = stringResource(id = R.string.connect), - fontSize = MaterialTheme.typography.titleMedium.fontSize) - } - } else { - TailscaleLogoView(Modifier.size(50.dp)) - Spacer(modifier = Modifier.size(1.dp)) - Text( - text = stringResource(id = R.string.welcome_to_tailscale), - style = MaterialTheme.typography.titleMedium, - color = MaterialTheme.colorScheme.primary, - textAlign = TextAlign.Center) - Text( - stringResource(R.string.login_to_join_your_tailnet), - style = MaterialTheme.typography.titleSmall, - color = MaterialTheme.colorScheme.secondary, - textAlign = TextAlign.Center) - Spacer(modifier = Modifier.size(1.dp)) - PrimaryActionButton(onClick = loginAction) { - Text( - text = stringResource(id = R.string.log_in), - fontSize = MaterialTheme.typography.titleMedium.fontSize) - } - } - } + Modifier.background(MaterialTheme.colorScheme.secondaryContainer) + .padding(8.dp) + .fillMaxWidth(0.7f) + .fillMaxHeight(), + verticalArrangement = Arrangement.spacedBy(8.dp, alignment = Alignment.CenterVertically), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + if (user != null && !user.isEmpty()) { + Icon( + painter = painterResource(id = R.drawable.power), + contentDescription = null, + modifier = Modifier.size(48.dp), + tint = MaterialTheme.colorScheme.secondary) + Text( + text = stringResource(id = R.string.not_connected), + fontSize = MaterialTheme.typography.titleMedium.fontSize, + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.primary, + textAlign = TextAlign.Center, + fontFamily = MaterialTheme.typography.titleMedium.fontFamily) + val tailnetName = user.NetworkProfile?.DomainName ?: "" + Text( + stringResource(id = R.string.connect_to_tailnet, tailnetName), + fontSize = MaterialTheme.typography.titleMedium.fontSize, + fontWeight = FontWeight.Normal, + color = MaterialTheme.colorScheme.secondary, + textAlign = TextAlign.Center, + ) + Spacer(modifier = Modifier.size(1.dp)) + PrimaryActionButton(onClick = connectAction) { + Text( + text = stringResource(id = R.string.connect), + fontSize = MaterialTheme.typography.titleMedium.fontSize) } + } else { + TailscaleLogoView(Modifier.size(50.dp)) + Spacer(modifier = Modifier.size(1.dp)) + Text( + text = stringResource(id = R.string.welcome_to_tailscale), + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.primary, + textAlign = TextAlign.Center) + Text( + stringResource(R.string.login_to_join_your_tailnet), + style = MaterialTheme.typography.titleSmall, + color = MaterialTheme.colorScheme.secondary, + textAlign = TextAlign.Center) + Spacer(modifier = Modifier.size(1.dp)) + PrimaryActionButton(onClick = loginAction) { + Text( + text = stringResource(id = R.string.log_in), + fontSize = MaterialTheme.typography.titleMedium.fontSize) + } + } + } } } @@ -324,11 +308,9 @@ fun PeerList( trailingIcon = { if (searchTermStr.isNotEmpty()) ClearButton({ onSearch("") }) else CloseButton() }, - tonalElevation = 0.dp, - shadowElevation = 0.dp, - colors = - SearchBarDefaults.colors( - containerColor = Color.Transparent, dividerColor = Color.Transparent), + tonalElevation = 2.dp, + shadowElevation = 2.dp, + colors = SearchBarDefaults.colors(), modifier = Modifier.fillMaxWidth()) { LazyColumn( modifier = @@ -341,8 +323,7 @@ fun PeerList( Text( text = peerSet.user?.DisplayName ?: stringResource(id = R.string.unknown_user), - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.SemiBold) + style = MaterialTheme.typography.titleLarge) }) } peerSet.peers.forEach { peer -> @@ -363,20 +344,19 @@ fun PeerList( } Box( modifier = - Modifier.size(10.dp) + Modifier.size(8.dp) .background( color = color, shape = RoundedCornerShape(percent = 50))) {} - Spacer(modifier = Modifier.size(6.dp)) + Spacer(modifier = Modifier.size(8.dp)) Text(text = peer.ComputedName, style = MaterialTheme.typography.titleMedium) } }, supportingContent = { Text( text = peer.Addresses?.first()?.split("/")?.first() ?: "", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.secondary) - }) - HorizontalDivider(color = MaterialTheme.colorScheme.secondaryContainer) + style = MaterialTheme.typography.bodyMedium) + }, + trailingContent = { Icon(Icons.AutoMirrored.Filled.KeyboardArrowRight, null) }) } } } diff --git a/android/src/main/java/com/tailscale/ipn/ui/view/ManagedByView.kt b/android/src/main/java/com/tailscale/ipn/ui/view/ManagedByView.kt index 2b8b108..147a06b 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/view/ManagedByView.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/view/ManagedByView.kt @@ -7,7 +7,8 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.safeContentPadding -import androidx.compose.material3.Scaffold +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState @@ -21,8 +22,8 @@ import com.tailscale.ipn.mdm.StringSetting import com.tailscale.ipn.ui.viewModel.IpnViewModel @Composable -fun ManagedByView(nav: BackNavigation, model: IpnViewModel = viewModel()) { - Scaffold(topBar = { Header(R.string.managed_by, onBack = nav.onBack) }) { innerPadding -> +fun ManagedByView(model: IpnViewModel = viewModel()) { + Surface(color = MaterialTheme.colorScheme.surface) { Column( verticalArrangement = Arrangement.spacedBy(space = 20.dp, alignment = Alignment.CenterVertically), diff --git a/android/src/main/java/com/tailscale/ipn/ui/view/PeerDetails.kt b/android/src/main/java/com/tailscale/ipn/ui/view/PeerDetails.kt index c601905..be770cf 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/view/PeerDetails.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/view/PeerDetails.kt @@ -35,55 +35,52 @@ import com.tailscale.ipn.ui.viewModel.PeerDetailsViewModelFactory @Composable fun PeerDetails( - nav: BackNavigation, nodeId: String, model: PeerDetailsViewModel = viewModel(factory = PeerDetailsViewModelFactory(nodeId)) ) { - Scaffold(topBar = { Header(title = R.string.peer_details, onBack = nav.onBack) }) { innerPadding - -> - Column( - modifier = - Modifier.fillMaxWidth() - .padding(innerPadding) - .padding(horizontal = 16.dp) - .padding(top = 22.dp), - ) { - Text( - text = model.nodeName, - style = MaterialTheme.typography.titleLarge, - color = MaterialTheme.colorScheme.primary) - Row(verticalAlignment = Alignment.CenterVertically) { - Box( - modifier = - Modifier.size(8.dp) - .background( - color = model.connectedColor, shape = RoundedCornerShape(percent = 50))) {} - Spacer(modifier = Modifier.size(8.dp)) - Text( - text = stringResource(id = model.connectedStrRes), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.primary) - } - Column(modifier = Modifier.fillMaxHeight()) { - Text( - text = stringResource(id = R.string.addresses_section), - style = MaterialTheme.typography.titleMedium, - color = MaterialTheme.colorScheme.primary) - - Column(modifier = settingsRowModifier()) { - model.addresses.forEach { AddressRow(address = it.address, type = it.typeString) } + Scaffold( + topBar = { + Column( + modifier = Modifier.fillMaxWidth().padding(8.dp), + ) { + Text( + text = model.nodeName, + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.primary) + Row(verticalAlignment = Alignment.CenterVertically) { + Box( + modifier = + Modifier.size(8.dp) + .background( + color = model.connectedColor, + shape = RoundedCornerShape(percent = 50))) {} + Spacer(modifier = Modifier.size(8.dp)) + Text( + text = stringResource(id = model.connectedStrRes), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.primary) + } } + }) { innerPadding -> + Column(modifier = Modifier.padding(innerPadding).fillMaxHeight()) { + Text( + text = stringResource(id = R.string.addresses_section), + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.primary) - Spacer(modifier = Modifier.size(16.dp)) + Column(modifier = settingsRowModifier()) { + model.addresses.forEach { AddressRow(address = it.address, type = it.typeString) } + } + + Spacer(modifier = Modifier.size(16.dp)) - Column(modifier = settingsRowModifier()) { - model.info.forEach { - ValueRow(title = stringResource(id = it.titleRes), value = it.value.getString()) + Column(modifier = settingsRowModifier()) { + model.info.forEach { + ValueRow(title = stringResource(id = it.titleRes), value = it.value.getString()) + } } } } - } - } } @Composable @@ -91,29 +88,25 @@ fun AddressRow(address: String, type: String) { val localClipboardManager = LocalClipboardManager.current Row( - verticalAlignment = Alignment.CenterVertically, modifier = - Modifier.padding(horizontal = 8.dp, vertical = 8.dp) + Modifier.padding(horizontal = 8.dp, vertical = 4.dp) .clickable(onClick = { localClipboardManager.setText(AnnotatedString(address)) })) { Column { - Text(text = address) - Text( - text = type, - fontSize = MaterialTheme.typography.labelLarge.fontSize, - color = MaterialTheme.colorScheme.secondary) + Text(text = address, style = MaterialTheme.typography.titleMedium) + Text(text = type, style = MaterialTheme.typography.bodyMedium) } Box(modifier = Modifier.weight(1f), contentAlignment = Alignment.CenterEnd) { - Icon(Icons.Outlined.Share, null, tint = MaterialTheme.colorScheme.secondary) + Icon(Icons.Outlined.Share, null) } } } @Composable fun ValueRow(title: String, value: String) { - Row(modifier = Modifier.padding(horizontal = 8.dp, vertical = 8.dp).fillMaxWidth()) { - Text(text = title) + Row(modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp).fillMaxWidth()) { + Text(text = title, style = MaterialTheme.typography.titleMedium) Box(modifier = Modifier.weight(1f), contentAlignment = Alignment.CenterEnd) { - Text(text = value, color = MaterialTheme.colorScheme.secondary) + Text(text = value, style = MaterialTheme.typography.bodyMedium) } } } 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..8f76556 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 @@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.ClickableText import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold +import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState @@ -50,31 +51,29 @@ fun Settings( val user = viewModel.loggedInUser.collectAsState().value val isAdmin = viewModel.isAdmin.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) } - } + Scaffold(topBar = { Header(title = R.string.settings_title) }) { innerPadding -> + Column(modifier = Modifier.padding(innerPadding).fillMaxHeight()) { + UserView( + profile = user, + actionState = UserActionState.NAV, + onClick = viewModel.navigation.onNavigateToUserSwitcher) + if (isAdmin) { + Spacer(modifier = Modifier.height(4.dp)) + AdminTextView { handler.openUri(Links.ADMIN_URL) } + } - Spacer(modifier = Modifier.height(8.dp)) + 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) } - } - 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) } } + Spacer(modifier = Modifier.height(8.dp)) } + } + } } @Composable @@ -143,7 +142,7 @@ fun SettingRow(setting: Setting) { SettingType.SWITCH -> { Text(setting.title.getString()) Box(modifier = Modifier.weight(1f), contentAlignment = Alignment.CenterEnd) { - TintedSwitch(checked = swVal, onCheckedChange = setting.onToggle, enabled = enabled) + Switch(checked = swVal, onCheckedChange = setting.onToggle, enabled = enabled) } } SettingType.NAV -> { @@ -156,6 +155,7 @@ fun SettingRow(setting: Setting) { Box(modifier = Modifier.weight(1f), contentAlignment = Alignment.CenterEnd) { Text(text = txtVal, style = MaterialTheme.typography.bodyMedium) } + ChevronRight() } } } diff --git a/android/src/main/java/com/tailscale/ipn/ui/view/SharedViews.kt b/android/src/main/java/com/tailscale/ipn/ui/view/SharedViews.kt index 02af82c..3624240 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/view/SharedViews.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/view/SharedViews.kt @@ -4,11 +4,9 @@ package com.tailscale.ipn.ui.view import androidx.annotation.StringRes -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api @@ -22,31 +20,22 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -data class BackNavigation( - val onBack: () -> Unit, -) - // Header view for all secondary screens @OptIn(ExperimentalMaterial3Api::class) @Composable -fun Header(@StringRes title: Int, onBack: (() -> Unit)? = null) { +fun Header(@StringRes title: Int) { TopAppBar( colors = TopAppBarDefaults.topAppBarColors( containerColor = MaterialTheme.colorScheme.surfaceContainer, titleContentColor = MaterialTheme.colorScheme.primary, ), - title = { Text(stringResource(title)) }, - navigationIcon = { onBack?.let { BackArrow(action = it) } }, - ) + title = { Text(stringResource(title)) }) } @Composable -fun BackArrow(action: () -> Unit) { - Icon( - Icons.AutoMirrored.Filled.ArrowBack, - null, - modifier = Modifier.clickable { action() }.padding(start = 15.dp, end = 20.dp)) +fun ChevronRight() { + Icon(Icons.AutoMirrored.Filled.KeyboardArrowRight, null) } @Composable diff --git a/android/src/main/java/com/tailscale/ipn/ui/view/TintedSwitch.kt b/android/src/main/java/com/tailscale/ipn/ui/view/TintedSwitch.kt deleted file mode 100644 index 69f1ccb..0000000 --- a/android/src/main/java/com/tailscale/ipn/ui/view/TintedSwitch.kt +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package com.tailscale.ipn.ui.view - -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Switch -import androidx.compose.material3.SwitchDefaults -import androidx.compose.runtime.Composable -import com.tailscale.ipn.ui.theme.ts_color_light_blue - -@Composable -fun TintedSwitch(checked: Boolean, onCheckedChange: ((Boolean) -> Unit)?, enabled: Boolean = true) { - Switch( - checked = checked, - onCheckedChange = onCheckedChange, - enabled = enabled, - colors = - SwitchDefaults.colors( - checkedBorderColor = ts_color_light_blue, - checkedThumbColor = ts_color_light_blue, - checkedTrackColor = ts_color_light_blue.copy(alpha = 0.3f), - uncheckedTrackColor = MaterialTheme.colorScheme.secondaryContainer)) -} diff --git a/android/src/main/java/com/tailscale/ipn/ui/view/UserSwitcherView.kt b/android/src/main/java/com/tailscale/ipn/ui/view/UserSwitcherView.kt index 5cdf600..e0af570 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/view/UserSwitcherView.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/view/UserSwitcherView.kt @@ -25,12 +25,12 @@ import com.tailscale.ipn.ui.viewModel.UserSwitcherViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable -fun UserSwitcherView(nav: BackNavigation, viewModel: UserSwitcherViewModel = viewModel()) { +fun UserSwitcherView(viewModel: UserSwitcherViewModel = viewModel()) { val users = viewModel.loginProfiles.collectAsState().value val currentUser = viewModel.loggedInUser.collectAsState().value - Scaffold(topBar = { Header(R.string.accounts, onBack = nav.onBack) }) { innerPadding -> + Scaffold(topBar = { Header(R.string.accounts) }) { innerPadding -> Column( modifier = Modifier.padding(innerPadding).fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) { 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..646ed8a 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 @@ -62,7 +62,7 @@ fun UserView( when (actionState) { UserActionState.CURRENT -> CheckedIndicator() UserActionState.SWITCHING -> SimpleActivityIndicator(size = 26) - UserActionState.NAV -> Unit + UserActionState.NAV -> ChevronRight() UserActionState.NONE -> Unit } } 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 77f85b2..9e1ccf5 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 @@ -78,8 +78,7 @@ data class SettingsNav( val onNavigateToAbout: () -> Unit, val onNavigateToMDMSettings: () -> Unit, val onNavigateToManagedBy: () -> Unit, - val onNavigateToUserSwitcher: () -> Unit, - val onBackPressed: () -> Unit, + val onNavigateToUserSwitcher: () -> Unit ) class SettingsViewModelFactory(private val navigation: SettingsNav) : ViewModelProvider.Factory { diff --git a/android/src/main/res/values/strings.xml b/android/src/main/res/values/strings.xml index ad5d193..3fe5c6b 100644 --- a/android/src/main/res/values/strings.xml +++ b/android/src/main/res/values/strings.xml @@ -24,14 +24,13 @@ Terms of Service WireGuard is a registered trademark of Jason A. Donenfeld.\n\n© 2024 Tailscale Inc. All rights reserved.\nTailscale is a registered trademark of Tailscale Inc. The Tailscale App Icon - Managed By Report a Bug To report a bug,  - contact our support team  - and include the ID below. - This ID helps us find the event in our diagnostic logs. This process does not share any of your personally-identifiable information. + contact our support team  + and include the ID below. + This ID helps us find the event ino our diagnostic logs. This process does not share any of your personally-identifiable information. Settings @@ -42,17 +41,16 @@ Use Tailscale DNS - EXIT NODE + Exit Node Starting… "Connect again to talk to the other devices in the %1$s tailnet." Welcome to Tailscale Log in to join your tailnet and connect your devices. - + TAILSCALE ADDRESSES OS Key Expiry - Tailscale Addresses Current MDM Settings