Implemented search bar in split-tunneling page.

Signed-off-by: Danial Ramzan <danialramzan@gmail.com>

Signed-off-by: danialramzan <danialramzan@gmail.com>
pull/724/head
Danial Ramzan 2 months ago committed by danialramzan
parent 69d2ff1f9e
commit c9346412e6

@ -4,18 +4,29 @@
package com.tailscale.ipn.ui.view
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Clear
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.Checkbox
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilterChip
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SearchBarDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
@ -51,6 +62,15 @@ fun SplitTunnelAppPickerView(
val splitEnabled = remember { mutableStateOf(App.get().isSplitTunnelEnabled())}
val currentSplitMode = remember { mutableStateOf(App.get().getSplitTunnelMode())}
val searchQuery = remember { mutableStateOf("") }
val filteredApps = installedApps.filter { app ->
searchQuery.value.isBlank() ||
app.name.contains(searchQuery.value, ignoreCase = true) ||
app.packageName.contains(searchQuery.value, ignoreCase = true)
}
Scaffold(topBar = { Header(titleRes = R.string.split_tunneling, onBack = backToSettings) }) {
@ -111,7 +131,16 @@ fun SplitTunnelAppPickerView(
if (splitEnabled.value) {
item("resolversHeader") {
Row(modifier = Modifier.padding(16.dp)) {
Spacer(modifier = Modifier.height(8.dp))
AppSearchBar(
query = searchQuery.value,
onQueryChange = { searchQuery.value = it }
)
Row(modifier = Modifier.padding(horizontal = 8.dp)) {
FilterChip(
selected = currentSplitMode.value == SplitTunnelMode.EXCLUDE,
onClick = {
@ -123,6 +152,7 @@ fun SplitTunnelAppPickerView(
Spacer(modifier = Modifier.width(8.dp))
FilterChip(
selected = currentSplitMode.value == SplitTunnelMode.INCLUDE,
onClick = {
@ -145,7 +175,24 @@ fun SplitTunnelAppPickerView(
)
)
}
items(installedApps) { app ->
if (filteredApps.isEmpty()) {
item {
Box(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.surface)
.padding(vertical = 24.dp, horizontal = 16.dp)
) {
Text(
"No apps found",
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
items(filteredApps) { app ->
ListItem(
headlineContent = { Text(app.name, fontWeight = FontWeight.SemiBold) },
leadingContent = {
@ -191,7 +238,25 @@ fun SplitTunnelAppPickerView(
)
)
}
items(installedApps) { app ->
if (filteredApps.isEmpty()) {
item {
Box(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.surface)
.padding(vertical = 24.dp, horizontal = 16.dp)
) {
Text(
"No apps found",
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
items(filteredApps) { app ->
ListItem(
headlineContent = { Text(app.name, fontWeight = FontWeight.SemiBold) },
leadingContent = {
@ -225,6 +290,8 @@ fun SplitTunnelAppPickerView(
})
})
Lists.ItemDivider()
}
@ -234,4 +301,38 @@ fun SplitTunnelAppPickerView(
}
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppSearchBar(
query: String,
onQueryChange: (String) -> Unit
) {
OutlinedTextField(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp, vertical = 8.dp)
.background(MaterialTheme.colorScheme.surfaceContainer),
value = query,
onValueChange = onQueryChange,
leadingIcon = {
Icon(
imageVector = Icons.Default.Search,
contentDescription = "Search"
)
},
shape = SearchBarDefaults.dockedShape,
placeholder = { Text(stringResource(R.string.search_apps_ellipsis)) },
singleLine = true,
trailingIcon = {
if (query.isNotEmpty()) {
IconButton(onClick = { onQueryChange("") }) {
Icon(Icons.Default.Clear, contentDescription = "Clear search")
}
}
}
)
}

@ -20,6 +20,7 @@
<string name="warning">Warning</string>
<string name="search">Search</string>
<string name="search_ellipsis">Search...</string>
<string name="search_apps_ellipsis">Search apps...</string>
<string name="dismiss">Dismiss</string>
<string name="no_results">No results</string>
<string name="back">Back</string>

Loading…
Cancel
Save