@@ -27,7 +27,10 @@ import androidx.compose.foundation.shape.RoundedCornerShape
27
27
import androidx.compose.material.icons.Icons
28
28
import androidx.compose.material.icons.filled.Search
29
29
import androidx.compose.material.icons.outlined.ArrowDropDown
30
+ import androidx.compose.material.icons.outlined.Clear
31
+ import androidx.compose.material.icons.outlined.Close
30
32
import androidx.compose.material.icons.outlined.Lock
33
+ import androidx.compose.material.icons.outlined.Search
31
34
import androidx.compose.material.icons.outlined.Settings
32
35
import androidx.compose.material3.Button
33
36
import androidx.compose.material3.DropdownMenu
@@ -39,6 +42,7 @@ import androidx.compose.material3.ListItem
39
42
import androidx.compose.material3.ListItemDefaults
40
43
import androidx.compose.material3.MaterialTheme
41
44
import androidx.compose.material3.ModalBottomSheet
45
+ import androidx.compose.material3.OutlinedTextField
42
46
import androidx.compose.material3.Scaffold
43
47
import androidx.compose.material3.Text
44
48
import androidx.compose.runtime.Composable
@@ -56,6 +60,7 @@ import androidx.compose.ui.draw.clip
56
60
import androidx.compose.ui.focus.onFocusChanged
57
61
import androidx.compose.ui.graphics.Color
58
62
import androidx.compose.ui.platform.LocalClipboardManager
63
+ import androidx.compose.ui.platform.LocalFocusManager
59
64
import androidx.compose.ui.res.painterResource
60
65
import androidx.compose.ui.res.stringResource
61
66
import androidx.compose.ui.text.SpanStyle
@@ -83,6 +88,7 @@ import com.tailscale.ipn.ui.theme.exitNodeToggleButton
83
88
import com.tailscale.ipn.ui.theme.listItem
84
89
import com.tailscale.ipn.ui.theme.minTextSize
85
90
import com.tailscale.ipn.ui.theme.primaryListItem
91
+ import com.tailscale.ipn.ui.theme.searchBarColors
86
92
import com.tailscale.ipn.ui.theme.secondaryButton
87
93
import com.tailscale.ipn.ui.theme.short
88
94
import com.tailscale.ipn.ui.theme.surfaceContainerListItem
@@ -98,6 +104,7 @@ import com.tailscale.ipn.ui.util.set
98
104
import com.tailscale.ipn.ui.viewModel.IpnViewModel.NodeState
99
105
import com.tailscale.ipn.ui.viewModel.MainViewModel
100
106
import com.tailscale.ipn.ui.viewModel.VpnViewModel
107
+ import com.tailscale.ipn.util.FeatureFlags
101
108
102
109
// Navigation actions for the MainView
103
110
data class MainViewNavigation (
@@ -215,7 +222,8 @@ fun MainView(
215
222
PeerList (
216
223
viewModel = viewModel,
217
224
onNavigateToPeerDetails = navigation.onNavigateToPeerDetails,
218
- onSearchBarClick = navigation.onNavigateToSearch)
225
+ onSearchBarClick = navigation.onNavigateToSearch,
226
+ onSearch = { viewModel.searchPeers(it) })
219
227
}
220
228
Ipn .State .NoState ,
221
229
Ipn .State .Starting -> StartingView ()
@@ -518,24 +526,69 @@ fun ConnectView(
518
526
fun PeerList (
519
527
viewModel : MainViewModel ,
520
528
onNavigateToPeerDetails : (Tailcfg .Node ) -> Unit ,
521
- onSearchBarClick : () -> Unit
529
+ onSearchBarClick : () -> Unit ,
530
+ onSearch : (String ) -> Unit
522
531
) {
523
532
val peerList by viewModel.peers.collectAsState(initial = emptyList<PeerSet >())
524
533
val searchTermStr by viewModel.searchTerm.collectAsState(initial = " " )
525
534
val showNoResults =
526
535
remember { derivedStateOf { searchTermStr.isNotEmpty() && peerList.isEmpty() } }.value
527
536
528
537
val netmap = viewModel.netmap.collectAsState()
538
+ val focusManager = LocalFocusManager .current
539
+ var isSearchFocussed by remember { mutableStateOf(false ) }
529
540
var isListFocussed by remember { mutableStateOf(false ) }
530
541
val expandedPeer = viewModel.expandedMenuPeer.collectAsState()
531
542
val localClipboardManager = LocalClipboardManager .current
532
543
val enableSearch = ! isAndroidTV()
533
544
534
545
Column (modifier = Modifier .fillMaxSize()) {
535
- if (enableSearch) {
546
+ if (enableSearch && FeatureFlags .isEnabled( " enable_new_search " ) ) {
536
547
Search (onSearchBarClick)
537
548
538
549
Spacer (modifier = Modifier .height(if (showNoResults) 0 .dp else 8 .dp))
550
+ } else {
551
+ if (enableSearch) {
552
+ Box (
553
+ modifier =
554
+ Modifier .fillMaxWidth().background(color = MaterialTheme .colorScheme.surface)) {
555
+ OutlinedTextField (
556
+ modifier =
557
+ Modifier .fillMaxWidth()
558
+ .padding(start = 16 .dp, end = 16 .dp, top = 16 .dp, bottom = 0 .dp)
559
+ .onFocusChanged { isSearchFocussed = it.isFocused },
560
+ singleLine = true ,
561
+ shape = MaterialTheme .shapes.extraLarge,
562
+ colors = MaterialTheme .colorScheme.searchBarColors,
563
+ leadingIcon = {
564
+ Icon (imageVector = Icons .Outlined .Search , contentDescription = " search" )
565
+ },
566
+ trailingIcon = {
567
+ if (isSearchFocussed) {
568
+ IconButton (
569
+ onClick = {
570
+ focusManager.clearFocus()
571
+ onSearch(" " )
572
+ }) {
573
+ Icon (
574
+ imageVector =
575
+ if (searchTermStr.isEmpty()) Icons .Outlined .Close
576
+ else Icons .Outlined .Clear ,
577
+ contentDescription = " clear search" ,
578
+ tint = MaterialTheme .colorScheme.onSurfaceVariant)
579
+ }
580
+ }
581
+ },
582
+ placeholder = {
583
+ Text (
584
+ text = stringResource(id = R .string.search),
585
+ style = MaterialTheme .typography.bodyLarge,
586
+ maxLines = 1 )
587
+ },
588
+ value = searchTermStr,
589
+ onValueChange = { onSearch(it) })
590
+ }
591
+ }
539
592
}
540
593
541
594
// Peers display
0 commit comments