You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
99 lines
4.0 KiB
Kotlin
99 lines
4.0 KiB
Kotlin
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package com.tailscale.ipn.ui.view
|
|
|
|
import androidx.compose.foundation.clickable
|
|
import androidx.compose.foundation.layout.Box
|
|
import androidx.compose.foundation.layout.padding
|
|
import androidx.compose.foundation.lazy.LazyColumn
|
|
import androidx.compose.material.icons.Icons
|
|
import androidx.compose.material.icons.outlined.Check
|
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
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.stringResource
|
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
import com.tailscale.ipn.R
|
|
import com.tailscale.ipn.ui.util.LoadingIndicator
|
|
import com.tailscale.ipn.ui.util.flag
|
|
import com.tailscale.ipn.ui.util.itemsWithDividers
|
|
import com.tailscale.ipn.ui.viewModel.ExitNodePickerNav
|
|
import com.tailscale.ipn.ui.viewModel.ExitNodePickerViewModel
|
|
import com.tailscale.ipn.ui.viewModel.ExitNodePickerViewModelFactory
|
|
import com.tailscale.ipn.ui.viewModel.selected
|
|
|
|
@OptIn(ExperimentalMaterial3Api::class)
|
|
@Composable
|
|
fun MullvadExitNodePickerList(
|
|
nav: ExitNodePickerNav,
|
|
model: ExitNodePickerViewModel = viewModel(factory = ExitNodePickerViewModelFactory(nav))
|
|
) {
|
|
LoadingIndicator.Wrap {
|
|
Scaffold(
|
|
topBar = {
|
|
Header(R.string.choose_mullvad_exit_node, onBack = nav.onNavigateBackToMullvad)
|
|
}) { innerPadding ->
|
|
val mullvadExitNodes = model.mullvadExitNodesByCountryCode.collectAsState()
|
|
|
|
LazyColumn(modifier = Modifier.padding(innerPadding)) {
|
|
val sortedCountries =
|
|
mullvadExitNodes.value.entries.toList().sortedBy {
|
|
it.value.first().country.lowercase()
|
|
}
|
|
itemsWithDividers(sortedCountries) { (countryCode, nodes) ->
|
|
val first = nodes.first()
|
|
|
|
// TODO(oxtoacart): the modifier on the ListItem occasionally causes a crash
|
|
// with java.lang.ClassCastException: androidx.compose.ui.ComposedModifier cannot be
|
|
// cast
|
|
// to androidx.compose.runtime.RecomposeScopeImpl
|
|
// Wrapping it in a Box eliminates this. It appears to be some kind of
|
|
// interaction between the LazyList and the modifier.
|
|
Box {
|
|
ListItem(
|
|
modifier =
|
|
Modifier.clickable {
|
|
if (nodes.size > 1) {
|
|
nav.onNavigateToMullvadCountry(countryCode)
|
|
} else {
|
|
model.setExitNode(first)
|
|
}
|
|
},
|
|
leadingContent = {
|
|
Text(
|
|
countryCode.flag(),
|
|
style = MaterialTheme.typography.titleLarge,
|
|
)
|
|
},
|
|
headlineContent = {
|
|
Text(first.country, style = MaterialTheme.typography.bodyMedium)
|
|
},
|
|
supportingContent = {
|
|
Text(
|
|
if (nodes.size == 1) first.city
|
|
else "${nodes.size} ${stringResource(R.string.cities_available)}",
|
|
style = MaterialTheme.typography.bodyMedium)
|
|
},
|
|
trailingContent = {
|
|
if (nodes.size > 1 && nodes.selected || first.selected) {
|
|
if (nodes.selected) {
|
|
Icon(
|
|
Icons.Outlined.Check,
|
|
contentDescription = stringResource(R.string.selected))
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|