android/ui: use compose getValue syntactic sugar consistently (#367)

Updates #cleanup

"by stateFlow" is syntactic sugar for" = stateFlow.value" and is more
idiomatic.  There should be no functional difference here.  Just\
changed where it can be for consistency.

Signed-off-by: Jonathan Nobels <jonathan@tailscale.com>
pull/407/head
Jonathan Nobels 1 month ago committed by GitHub
parent 999c6f2357
commit b37492a547
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState import androidx.compose.runtime.produceState
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -40,7 +41,7 @@ object LoadingIndicator {
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
content() content()
val isLoading = loading.collectAsState().value val isLoading by loading.collectAsState()
if (isLoading) { if (isLoading) {
Box(Modifier.clickable {}.matchParentSize().background(Color.Gray.copy(alpha = 0.0f))) Box(Modifier.clickable {}.matchParentSize().background(Color.Gray.copy(alpha = 0.0f)))
@ -54,7 +55,8 @@ object LoadingIndicator {
Column( Column(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally) { horizontalAlignment = Alignment.CenterHorizontally) {
TailscaleLogoView(true, usesOnBackgroundColors = false, Modifier.size(72.dp).alpha(0.4f)) TailscaleLogoView(
true, usesOnBackgroundColors = false, Modifier.size(72.dp).alpha(0.4f))
} }
} }
} }

@ -14,6 +14,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@ -36,7 +37,7 @@ import com.tailscale.ipn.ui.viewModel.BugReportViewModel
@Composable @Composable
fun BugReportView(backToSettings: BackNavigation, model: BugReportViewModel = viewModel()) { fun BugReportView(backToSettings: BackNavigation, model: BugReportViewModel = viewModel()) {
val handler = LocalUriHandler.current val handler = LocalUriHandler.current
val bugReportID = model.bugReportID.collectAsState().value val bugReportID by model.bugReportID.collectAsState()
Scaffold(topBar = { Header(R.string.bug_report_title, onBack = backToSettings) }) { innerPadding Scaffold(topBar = { Header(R.string.bug_report_title, onBack = backToSettings) }) { innerPadding
-> ->

@ -53,7 +53,7 @@ fun LoginWithCustomControlURLView(
onBack = backToSettings, onBack = backToSettings,
) )
}) { innerPadding -> }) { innerPadding ->
val error = viewModel.errorDialog.collectAsState().value val error by viewModel.errorDialog.collectAsState()
val strings = val strings =
LoginViewStrings( LoginViewStrings(
title = stringResource(id = R.string.custom_control_menu), title = stringResource(id = R.string.custom_control_menu),
@ -85,7 +85,7 @@ fun LoginWithAuthKeyView(
onBack = backToSettings, onBack = backToSettings,
) )
}) { innerPadding -> }) { innerPadding ->
val error = viewModel.errorDialog.collectAsState().value val error by viewModel.errorDialog.collectAsState()
val strings = val strings =
LoginViewStrings( LoginViewStrings(
title = stringResource(id = R.string.auth_key_title), title = stringResource(id = R.string.auth_key_title),

@ -14,6 +14,7 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@ -41,7 +42,7 @@ fun DNSSettingsView(
backToSettings: BackNavigation, backToSettings: BackNavigation,
model: DNSSettingsViewModel = viewModel(factory = DNSSettingsViewModelFactory()) model: DNSSettingsViewModel = viewModel(factory = DNSSettingsViewModelFactory())
) { ) {
val state: DNSEnablementState = model.enablementState.collectAsState().value val state: DNSEnablementState by model.enablementState.collectAsState()
val resolvers = model.dnsConfig.collectAsState().value?.Resolvers ?: emptyList() val resolvers = model.dnsConfig.collectAsState().value?.Resolvers ?: emptyList()
val domains = model.dnsConfig.collectAsState().value?.Domains ?: emptyList() val domains = model.dnsConfig.collectAsState().value?.Domains ?: emptyList()
val routes: List<ViewableRoute> = val routes: List<ViewableRoute> =
@ -49,7 +50,7 @@ fun DNSSettingsView(
entry.value?.let { resolvers -> ViewableRoute(name = entry.key, resolvers) } ?: run { null } entry.value?.let { resolvers -> ViewableRoute(name = entry.key, resolvers) } ?: run { null }
} ?: emptyList() } ?: emptyList()
val useCorpDNS = Notifier.prefs.collectAsState().value?.CorpDNS == true val useCorpDNS = Notifier.prefs.collectAsState().value?.CorpDNS == true
val dnsSettingsMDMDisposition = MDMSettings.useTailscaleDNSSettings.flow.collectAsState().value val dnsSettingsMDMDisposition by MDMSettings.useTailscaleDNSSettings.flow.collectAsState()
Scaffold(topBar = { Header(R.string.dns_settings, onBack = backToSettings) }) { innerPadding -> Scaffold(topBar = { Header(R.string.dns_settings, onBack = backToSettings) }) { innerPadding ->
LoadingIndicator.Wrap { LoadingIndicator.Wrap {

@ -17,6 +17,7 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
@ -42,14 +43,13 @@ fun ExitNodePicker(
LoadingIndicator.Wrap { LoadingIndicator.Wrap {
Scaffold(topBar = { Header(R.string.choose_exit_node, onBack = nav.onNavigateBackHome) }) { Scaffold(topBar = { Header(R.string.choose_exit_node, onBack = nav.onNavigateBackHome) }) {
innerPadding -> innerPadding ->
val tailnetExitNodes = model.tailnetExitNodes.collectAsState().value val tailnetExitNodes by model.tailnetExitNodes.collectAsState()
val mullvadExitNodesByCountryCode = model.mullvadExitNodesByCountryCode.collectAsState().value val mullvadExitNodesByCountryCode by model.mullvadExitNodesByCountryCode.collectAsState()
val mullvadExitNodeCount = model.mullvadExitNodeCount.collectAsState().value val mullvadExitNodeCount by model.mullvadExitNodeCount.collectAsState()
val anyActive = model.anyActive.collectAsState() val anyActive by model.anyActive.collectAsState()
val allowLANAccess = Notifier.prefs.collectAsState().value?.ExitNodeAllowLANAccess == true val allowLANAccess = Notifier.prefs.collectAsState().value?.ExitNodeAllowLANAccess == true
val showRunAsExitNode = MDMSettings.runExitNode.flow.collectAsState().value val showRunAsExitNode by MDMSettings.runExitNode.flow.collectAsState()
val allowLanAccessMDMDisposition = val allowLanAccessMDMDisposition by MDMSettings.exitNodeAllowLANAccess.flow.collectAsState()
MDMSettings.exitNodeAllowLANAccess.flow.collectAsState().value
LazyColumn(modifier = Modifier.padding(innerPadding)) { LazyColumn(modifier = Modifier.padding(innerPadding)) {
item(key = "header") { item(key = "header") {
@ -58,7 +58,7 @@ fun ExitNodePicker(
ExitNodePickerViewModel.ExitNode( ExitNodePickerViewModel.ExitNode(
label = stringResource(R.string.none), label = stringResource(R.string.none),
online = true, online = true,
selected = !anyActive.value, selected = !anyActive,
)) ))
if (showRunAsExitNode == ShowHide.Show) { if (showRunAsExitNode == ShowHide.Show) {

@ -20,6 +20,7 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@ -38,7 +39,7 @@ import com.tailscale.ipn.ui.viewModel.LoginQRViewModel
fun LoginQRView(onDismiss: () -> Unit = {}, model: LoginQRViewModel = viewModel()) { fun LoginQRView(onDismiss: () -> Unit = {}, model: LoginQRViewModel = viewModel()) {
Surface(color = MaterialTheme.colorScheme.scrim, modifier = Modifier.fillMaxSize()) { Surface(color = MaterialTheme.colorScheme.scrim, modifier = Modifier.fillMaxSize()) {
Dialog(onDismissRequest = onDismiss) { Dialog(onDismissRequest = onDismiss) {
val image = model.qrCode.collectAsState() val image by model.qrCode.collectAsState()
Column( Column(
modifier = modifier =
@ -57,7 +58,7 @@ fun LoginQRView(onDismiss: () -> Unit = {}, model: LoginQRViewModel = viewModel(
.background(MaterialTheme.colorScheme.onSurface) .background(MaterialTheme.colorScheme.onSurface)
.fillMaxWidth(), .fillMaxWidth(),
contentAlignment = Alignment.Center) { contentAlignment = Alignment.Center) {
image.value?.let { image?.let {
Image( Image(
bitmap = it, bitmap = it,
contentDescription = "Scan to login", contentDescription = "Scan to login",

@ -12,6 +12,7 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
@ -38,7 +39,7 @@ fun MDMSettingsDebugView(backToSettings: BackNavigation, model: IpnViewModel = v
@Composable @Composable
fun MDMSettingView(setting: MDMSetting<*>) { fun MDMSettingView(setting: MDMSetting<*>) {
val value = setting.flow.collectAsState().value val value by setting.flow.collectAsState()
ListItem( ListItem(
headlineContent = { Text(setting.localizedTitle, maxLines = 3) }, headlineContent = { Text(setting.localizedTitle, maxLines = 3) },
supportingContent = { supportingContent = {

@ -40,13 +40,13 @@ import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
@ -62,7 +62,6 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.tailscale.ipn.R import com.tailscale.ipn.R
import com.tailscale.ipn.mdm.MDMSettings import com.tailscale.ipn.mdm.MDMSettings
@ -89,7 +88,6 @@ import com.tailscale.ipn.ui.util.PeerSet
import com.tailscale.ipn.ui.util.flag import com.tailscale.ipn.ui.util.flag
import com.tailscale.ipn.ui.util.itemsWithDividers import com.tailscale.ipn.ui.util.itemsWithDividers
import com.tailscale.ipn.ui.viewModel.MainViewModel import com.tailscale.ipn.ui.viewModel.MainViewModel
import android.util.Log
// Navigation actions for the MainView // Navigation actions for the MainView
data class MainViewNavigation( data class MainViewNavigation(
@ -105,14 +103,14 @@ fun MainView(
navigation: MainViewNavigation, navigation: MainViewNavigation,
viewModel: MainViewModel viewModel: MainViewModel
) { ) {
LoadingIndicator.Wrap { LoadingIndicator.Wrap {
Scaffold(contentWindowInsets = WindowInsets.Companion.statusBars) { paddingInsets -> Scaffold(contentWindowInsets = WindowInsets.Companion.statusBars) { paddingInsets ->
Column( Column(
modifier = Modifier.fillMaxWidth().padding(paddingInsets), modifier = Modifier.fillMaxWidth().padding(paddingInsets),
verticalArrangement = Arrangement.Center) { verticalArrangement = Arrangement.Center) {
// Assume VPN has been prepared. Whether or not it has been prepared cannot be known until permission has been granted to prepare the VPN. // Assume VPN has been prepared. Whether or not it has been prepared cannot be known
val isPrepared by viewModel.vpnPrepared.collectAsState(initial=true) // until permission has been granted to prepare the VPN.
val isPrepared by viewModel.vpnPrepared.collectAsState(initial = true)
val isOn by viewModel.vpnToggleState.collectAsState(initial = false) val isOn by viewModel.vpnToggleState.collectAsState(initial = false)
val state by viewModel.ipnState.collectAsState(initial = Ipn.State.NoState) val state by viewModel.ipnState.collectAsState(initial = Ipn.State.NoState)
val user by viewModel.loggedInUser.collectAsState(initial = null) val user by viewModel.loggedInUser.collectAsState(initial = null)
@ -195,8 +193,8 @@ fun MainView(
{ viewModel.toggleVpn() }, { viewModel.toggleVpn() },
{ viewModel.login() }, { viewModel.login() },
loginAtUrl, loginAtUrl,
netmap?.SelfNode, netmap?.SelfNode,
{viewModel.showVPNPermissionLauncherIfUnauthorized()}) { viewModel.showVPNPermissionLauncherIfUnauthorized() })
} }
} }
} }
@ -206,17 +204,17 @@ fun MainView(
@Composable @Composable
fun ExitNodeStatus(navAction: () -> Unit, viewModel: MainViewModel) { fun ExitNodeStatus(navAction: () -> Unit, viewModel: MainViewModel) {
val maybePrefs = viewModel.prefs.collectAsState() val maybePrefs by viewModel.prefs.collectAsState()
val netmap = viewModel.netmap.collectAsState() val netmap by viewModel.netmap.collectAsState()
// There's nothing to render if we haven't loaded the prefs yet // There's nothing to render if we haven't loaded the prefs yet
val prefs = maybePrefs.value ?: return val prefs = maybePrefs ?: return
// The activeExitNode is the source of truth. The selectedExitNode is only relevant if we // The activeExitNode is the source of truth. The selectedExitNode is only relevant if we
// don't have an active node. // don't have an active node.
val chosenExitNodeId = prefs.activeExitNodeID ?: prefs.selectedExitNodeID val chosenExitNodeId = prefs.activeExitNodeID ?: prefs.selectedExitNodeID
val exitNodePeer = chosenExitNodeId?.let { id -> netmap.value?.Peers?.find { it.StableID == id } } val exitNodePeer = chosenExitNodeId?.let { id -> netmap?.Peers?.find { it.StableID == id } }
val location = exitNodePeer?.Hostinfo?.Location val location = exitNodePeer?.Hostinfo?.Location
val name = exitNodePeer?.ComputedName val name = exitNodePeer?.ComputedName
@ -319,7 +317,7 @@ fun ConnectView(
if (!isPrepared) { if (!isPrepared) {
showVPNPermissionLauncherIfUnauthorized() showVPNPermissionLauncherIfUnauthorized()
} }
} }
Row(horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth()) { Row(horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth()) {
Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxWidth()) { Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxWidth()) {
Column( Column(
@ -327,7 +325,7 @@ fun ConnectView(
verticalArrangement = Arrangement.spacedBy(8.dp, alignment = Alignment.CenterVertically), verticalArrangement = Arrangement.spacedBy(8.dp, alignment = Alignment.CenterVertically),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
if (!isPrepared) { if (!isPrepared) {
TailscaleLogoView(modifier = Modifier.size(50.dp)) TailscaleLogoView(modifier = Modifier.size(50.dp))
Spacer(modifier = Modifier.size(1.dp)) Spacer(modifier = Modifier.size(1.dp))
Text( Text(
@ -426,10 +424,10 @@ fun PeerList(
onNavigateToPeerDetails: (Tailcfg.Node) -> Unit, onNavigateToPeerDetails: (Tailcfg.Node) -> Unit,
onSearch: (String) -> Unit onSearch: (String) -> Unit
) { ) {
val peerList = viewModel.peers.collectAsState(initial = emptyList<PeerSet>()) val peerList by viewModel.peers.collectAsState(initial = emptyList<PeerSet>())
val searchTermStr by viewModel.searchTerm.collectAsState(initial = "") val searchTermStr by viewModel.searchTerm.collectAsState(initial = "")
val showNoResults = val showNoResults =
remember { derivedStateOf { searchTermStr.isNotEmpty() && peerList.value.isEmpty() } }.value remember { derivedStateOf { searchTermStr.isNotEmpty() && peerList.isEmpty() } }.value
val netmap = viewModel.netmap.collectAsState() val netmap = viewModel.netmap.collectAsState()
@ -490,7 +488,7 @@ fun PeerList(
} }
var first = true var first = true
peerList.value.forEach { peerSet -> peerList.forEach { peerSet ->
if (!first) { if (!first) {
item(key = "user_divider_${peerSet.user?.ID ?: 0L}") { Lists.ItemDivider() } item(key = "user_divider_${peerSet.user?.ID ?: 0L}") { Lists.ItemDivider() }
} }

@ -10,6 +10,7 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
@ -29,10 +30,10 @@ fun MullvadExitNodePicker(
nav: ExitNodePickerNav, nav: ExitNodePickerNav,
model: ExitNodePickerViewModel = viewModel(factory = ExitNodePickerViewModelFactory(nav)) model: ExitNodePickerViewModel = viewModel(factory = ExitNodePickerViewModelFactory(nav))
) { ) {
val mullvadExitNodes = model.mullvadExitNodesByCountryCode.collectAsState() val mullvadExitNodes by model.mullvadExitNodesByCountryCode.collectAsState()
val bestAvailableByCountry = model.mullvadBestAvailableByCountry.collectAsState() val bestAvailableByCountry by model.mullvadBestAvailableByCountry.collectAsState()
mullvadExitNodes.value[countryCode]?.toList()?.let { nodes -> mullvadExitNodes[countryCode]?.toList()?.let { nodes ->
val any = nodes.first() val any = nodes.first()
LoadingIndicator.Wrap { LoadingIndicator.Wrap {
@ -44,7 +45,7 @@ fun MullvadExitNodePicker(
}) { innerPadding -> }) { innerPadding ->
LazyColumn(modifier = Modifier.padding(innerPadding)) { LazyColumn(modifier = Modifier.padding(innerPadding)) {
if (nodes.size > 1) { if (nodes.size > 1) {
val bestAvailableNode = bestAvailableByCountry.value[countryCode]!! val bestAvailableNode = bestAvailableByCountry[countryCode]!!
item { item {
ExitNodeItem( ExitNodeItem(
model, model,

@ -17,6 +17,7 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
@ -40,11 +41,11 @@ fun MullvadExitNodePickerList(
topBar = { topBar = {
Header(R.string.choose_mullvad_exit_node, onBack = nav.onNavigateBackToExitNodes) Header(R.string.choose_mullvad_exit_node, onBack = nav.onNavigateBackToExitNodes)
}) { innerPadding -> }) { innerPadding ->
val mullvadExitNodes = model.mullvadExitNodesByCountryCode.collectAsState() val mullvadExitNodes by model.mullvadExitNodesByCountryCode.collectAsState()
LazyColumn(modifier = Modifier.padding(innerPadding)) { LazyColumn(modifier = Modifier.padding(innerPadding)) {
val sortedCountries = val sortedCountries =
mullvadExitNodes.value.entries.toList().sortedBy { mullvadExitNodes.entries.toList().sortedBy {
it.value.first().country.lowercase() it.value.first().country.lowercase()
} }
itemsWithDividers(sortedCountries) { (countryCode, nodes) -> itemsWithDividers(sortedCountries) { (countryCode, nodes) ->

@ -21,6 +21,7 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
@ -39,7 +40,7 @@ fun RunExitNodeView(
nav: ExitNodePickerNav, nav: ExitNodePickerNav,
model: RunExitNodeViewModel = viewModel(factory = RunExitNodeViewModelFactory()) model: RunExitNodeViewModel = viewModel(factory = RunExitNodeViewModelFactory())
) { ) {
val isRunningExitNode = model.isRunningExitNode.collectAsState().value val isRunningExitNode by model.isRunningExitNode.collectAsState()
Scaffold( Scaffold(
topBar = { Header(R.string.run_as_exit_node, onBack = nav.onNavigateBackToExitNodes) }) { topBar = { Header(R.string.run_as_exit_node, onBack = nav.onNavigateBackToExitNodes) }) {
@ -49,7 +50,11 @@ fun RunExitNodeView(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = verticalArrangement =
Arrangement.spacedBy(24.dp, alignment = Alignment.CenterVertically), Arrangement.spacedBy(24.dp, alignment = Alignment.CenterVertically),
modifier = Modifier.padding(innerPadding).padding(24.dp).fillMaxHeight().verticalScroll(rememberScrollState())) { modifier =
Modifier.padding(innerPadding)
.padding(24.dp)
.fillMaxHeight()
.verticalScroll(rememberScrollState())) {
RunExitNodeGraphic() RunExitNodeGraphic()
if (isRunningExitNode) { if (isRunningExitNode) {

@ -14,6 +14,7 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
@ -39,14 +40,14 @@ import com.tailscale.ipn.ui.viewModel.SettingsViewModel
@Composable @Composable
fun SettingsView(settingsNav: SettingsNav, viewModel: SettingsViewModel = viewModel()) { fun SettingsView(settingsNav: SettingsNav, viewModel: SettingsViewModel = viewModel()) {
val handler = LocalUriHandler.current val handler = LocalUriHandler.current
val user = viewModel.loggedInUser.collectAsState().value
val isAdmin = viewModel.isAdmin.collectAsState().value val user by viewModel.loggedInUser.collectAsState()
val managedByOrganization = viewModel.managedByOrganization.collectAsState().value val isAdmin by viewModel.isAdmin.collectAsState()
val tailnetLockEnabled = viewModel.tailNetLockEnabled.collectAsState().value val managedByOrganization by viewModel.managedByOrganization.collectAsState()
val corpDNSEnabled = viewModel.corpDNSEnabled.collectAsState().value val tailnetLockEnabled by viewModel.tailNetLockEnabled.collectAsState()
val isVPNPrepared = viewModel.vpnPrepared.collectAsState().value val corpDNSEnabled by viewModel.corpDNSEnabled.collectAsState()
val isVPNPrepared by viewModel.vpnPrepared.collectAsState()
val showTailnetLock = MDMSettings.manageTailnetLock.flow.collectAsState().value val showTailnetLock by MDMSettings.manageTailnetLock.flow.collectAsState()
Scaffold( Scaffold(
topBar = { topBar = {

@ -19,6 +19,7 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
@ -57,7 +58,7 @@ fun TaildropView(
when (viewModel.state.collectAsState().value) { when (viewModel.state.collectAsState().value) {
Ipn.State.Running -> { Ipn.State.Running -> {
val peers = viewModel.myPeers.collectAsState().value val peers by viewModel.myPeers.collectAsState()
val context = LocalContext.current val context = LocalContext.current
FileSharePeerList( FileSharePeerList(
peers = peers, peers = peers,

@ -14,6 +14,7 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
@ -41,9 +42,9 @@ fun TailnetLockSetupView(
backToSettings: BackNavigation, backToSettings: BackNavigation,
model: TailnetLockSetupViewModel = viewModel(factory = TailnetLockSetupViewModelFactory()) model: TailnetLockSetupViewModel = viewModel(factory = TailnetLockSetupViewModelFactory())
) { ) {
val statusItems = model.statusItems.collectAsState().value val statusItems by model.statusItems.collectAsState()
val nodeKey = model.nodeKey.collectAsState().value val nodeKey by model.nodeKey.collectAsState()
val tailnetLockKey = model.tailnetLockKey.collectAsState().value val tailnetLockKey by model.tailnetLockKey.collectAsState()
val tailnetLockTlPubKey = tailnetLockKey.replace("nlpub", "tlpub") val tailnetLockTlPubKey = tailnetLockKey.replace("nlpub", "tlpub")
Scaffold(topBar = { Header(R.string.tailnet_lock, onBack = backToSettings) }) { innerPadding -> Scaffold(topBar = { Header(R.string.tailnet_lock, onBack = backToSettings) }) { innerPadding ->

@ -22,6 +22,7 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -46,9 +47,9 @@ data class UserSwitcherNav(
@Composable @Composable
fun UserSwitcherView(nav: UserSwitcherNav, viewModel: UserSwitcherViewModel = viewModel()) { fun UserSwitcherView(nav: UserSwitcherNav, viewModel: UserSwitcherViewModel = viewModel()) {
val users = viewModel.loginProfiles.collectAsState().value val users by viewModel.loginProfiles.collectAsState()
val currentUser = viewModel.loggedInUser.collectAsState().value val currentUser by viewModel.loggedInUser.collectAsState()
val showHeaderMenu = viewModel.showHeaderMenu.collectAsState().value val showHeaderMenu by viewModel.showHeaderMenu.collectAsState()
Scaffold( Scaffold(
topBar = { topBar = {
@ -70,7 +71,7 @@ fun UserSwitcherView(nav: UserSwitcherNav, viewModel: UserSwitcherViewModel = vi
Column( Column(
modifier = Modifier.padding(innerPadding).fillMaxWidth(), modifier = Modifier.padding(innerPadding).fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp)) { verticalArrangement = Arrangement.spacedBy(8.dp)) {
val showErrorDialog = viewModel.errorDialog.collectAsState().value val showErrorDialog by viewModel.errorDialog.collectAsState()
// Show the error overlay if need be // Show the error overlay if need be
showErrorDialog?.let { showErrorDialog?.let {
@ -149,7 +150,7 @@ fun FusMenu(
onAuthKeyClick: () -> Unit, onAuthKeyClick: () -> Unit,
viewModel: UserSwitcherViewModel viewModel: UserSwitcherViewModel
) { ) {
val expanded = viewModel.showHeaderMenu.collectAsState().value val expanded by viewModel.showHeaderMenu.collectAsState()
DropdownMenu( DropdownMenu(
expanded = expanded, expanded = expanded,
@ -173,7 +174,9 @@ fun FusMenu(
@Composable @Composable
fun MenuItem(text: String, onClick: () -> Unit) { fun MenuItem(text: String, onClick: () -> Unit) {
DropdownMenuItem( DropdownMenuItem(
modifier = Modifier.padding(horizontal = 8.dp, vertical = 0.dp), onClick = onClick, text = { Text(text = text) }) modifier = Modifier.padding(horizontal = 8.dp, vertical = 0.dp),
onClick = onClick,
text = { Text(text = text) })
} }
@Composable @Composable

@ -153,7 +153,7 @@ class TaildropViewModel(
fun TrailingContentForPeer(peerId: String) { fun TrailingContentForPeer(peerId: String) {
// Check our outgoing files for the peer and determine the state of the transfer. // Check our outgoing files for the peer and determine the state of the transfer.
val transfers = this.transfers.collectAsState().value.filter { it.PeerID == peerId } val transfers = this.transfers.collectAsState().value.filter { it.PeerID == peerId }
var status: TransferState = transferState(transfers) ?: return val status: TransferState = transferState(transfers) ?: return
// Still no status? Nothing to render for this peer // Still no status? Nothing to render for this peer

Loading…
Cancel
Save