android: create SearchView

The Material3 search bar doesn't open up a full screen view, but opens a view underneath the bar. To accomplish the full screen search page, we create a new SearchView and use a decoy search bar on the main view that navigates the user to the SearchView
Tapping on a search suggestion / result navigates to the PeerDetails, so this also updates the navigation for PeerDetails to use a back stack instead of always navigating back to the main screen

Updates tailscale/corp#18973

Signed-off-by: kari-ts <kari@tailscale.com>
kari/search
kari-ts 1 year ago
parent 3aaff20959
commit 6d07721cf9

@ -88,6 +88,7 @@ import com.tailscale.ipn.ui.theme.short
import com.tailscale.ipn.ui.theme.surfaceContainerListItem
import com.tailscale.ipn.ui.theme.warningButton
import com.tailscale.ipn.ui.theme.warningListItem
import com.tailscale.ipn.ui.util.AndroidTVUtil.isAndroidTV
import com.tailscale.ipn.ui.util.AutoResizingText
import com.tailscale.ipn.ui.util.Lists
import com.tailscale.ipn.ui.util.LoadingIndicator
@ -137,7 +138,7 @@ fun MainView(
val showKeyExpiry by viewModel.showExpiry.collectAsState(initial = false)
// Hide the header only on Android TV when the user needs to login
val hideHeader = (/*isAndroidTV() && */ state == Ipn.State.NeedsLogin)
val hideHeader = (isAndroidTV() && state == Ipn.State.NeedsLogin)
ListItem(
colors = MaterialTheme.colorScheme.surfaceContainerListItem,
@ -529,11 +530,11 @@ fun PeerList(
var isListFocussed by remember { mutableStateOf(false) }
val expandedPeer = viewModel.expandedMenuPeer.collectAsState()
val localClipboardManager = LocalClipboardManager.current
val enableSearch = true // !isAndroidTV()
val enableSearch = !isAndroidTV()
Column(modifier = Modifier.fillMaxSize()) {
if (enableSearch) {
SearchWithDynamicSuggestions(viewModel, onSearchBarClick)
Search(onSearchBarClick)
Spacer(modifier = Modifier.height(if (showNoResults) 0.dp else 8.dp))
}
@ -570,11 +571,11 @@ fun PeerList(
}
first = false
// if (isAndroidTV()) {
if (isAndroidTV()) {
item { NodesSectionHeader(peerSet = peerSet) }
/* } else {
} else {
stickyHeader { NodesSectionHeader(peerSet = peerSet) }
}*/
}
itemsWithDividers(peerSet.peers, key = { it.StableID }) { peer ->
ListItem(
@ -694,8 +695,7 @@ fun PromptPermissionsIfNecessary() {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SearchWithDynamicSuggestions(
viewModel: MainViewModel,
fun Search(
onSearchBarClick: () -> Unit // Callback for navigating to SearchView
) {
// Prevent multiple taps
@ -705,30 +705,27 @@ fun SearchWithDynamicSuggestions(
Box(
modifier =
Modifier.fillMaxWidth()
.height(56.dp) // Height matching Material Design search bar
.clip(RoundedCornerShape(28.dp)) // Fully rounded edges
.background(MaterialTheme.colorScheme.surface) // Surface background
.height(56.dp)
.clip(RoundedCornerShape(28.dp))
.background(MaterialTheme.colorScheme.surface)
.clickable(enabled = !isNavigating) { // Intercept taps
isNavigating = true
onSearchBarClick() // Trigger navigation
}
.padding(horizontal = 16.dp) // Padding for a clean look
) {
.padding(horizontal = 16.dp)) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxSize()) {
// Search Icon
Icon(
imageVector = Icons.Default.Search,
contentDescription = "Search",
contentDescription = stringResource(R.string.search),
tint = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(start = 16.dp))
Spacer(modifier = Modifier.width(8.dp))
// Placeholder Text
Text(
text = "Search...",
text = stringResource(R.string.search_ellipsis),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.weight(1f) // Fill remaining space
)
modifier = Modifier.weight(1f))
}
}
}

@ -44,15 +44,12 @@ import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.tailscale.ipn.R
import com.tailscale.ipn.ui.viewModel.MainViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SearchView(
viewModel: MainViewModel,
navController: NavController, // Use NavController for navigation
onNavigateBack: () -> Unit
) {
fun SearchView(viewModel: MainViewModel, navController: NavController, onNavigateBack: () -> Unit) {
val searchTerm by viewModel.searchTerm.collectAsState()
val filteredPeers by viewModel.peers.collectAsState()
val netmap by viewModel.netmap.collectAsState()
@ -84,7 +81,7 @@ fun SearchView(
focusManager.clearFocus()
keyboardController?.hide()
},
placeholder = { Text("Search") },
placeholder = { R.string.search },
leadingIcon = {
IconButton(
onClick = {

@ -18,6 +18,7 @@
<string name="_continue">Continue</string>
<string name="warning">Warning</string>
<string name="search">Search</string>
<string name="search_ellipsis">Search...</string>
<string name="dismiss">Dismiss</string>
<string name="no_results">No results</string>

Loading…
Cancel
Save