Skip to content

Commit

Permalink
android: refine search
Browse files Browse the repository at this point in the history
-improve transition
-clean up search input spacing to match other elements
-match search results page styling to machines page
-fix issue where search suggestions were propagating to main view
-flip new search flag On

Fixes tailscale/corp#18973

Signed-off-by: kari-ts <kari@tailscale.com>
  • Loading branch information
kari-ts committed Jan 31, 2025
1 parent fee71b4 commit cac58de
Show file tree
Hide file tree
Showing 11 changed files with 256 additions and 194 deletions.
1 change: 1 addition & 0 deletions android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
android:label="Tailscale"
android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:enableOnBackInvokedCallback="true"
android:theme="@style/Theme.App.SplashScreen">
<activity
android:name="MainActivity"
Expand Down
4 changes: 2 additions & 2 deletions android/src/main/java/com/tailscale/ipn/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ import com.tailscale.ipn.ui.notifier.HealthNotifier
import com.tailscale.ipn.ui.notifier.Notifier
import com.tailscale.ipn.ui.viewModel.VpnViewModel
import com.tailscale.ipn.ui.viewModel.VpnViewModelFactory
import com.tailscale.ipn.util.TSLog
import com.tailscale.ipn.util.FeatureFlags
import com.tailscale.ipn.util.TSLog
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
Expand Down Expand Up @@ -192,7 +192,7 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner {
val hideDisconnectAction = MDMSettings.forceEnabled.flow.first()
}
TSLog.init(this)
FeatureFlags.initialize(mapOf("enable_new_search" to false))
FeatureFlags.initialize(mapOf("enable_new_search" to true))
}

private fun initViewModels() {
Expand Down
29 changes: 23 additions & 6 deletions android/src/main/java/com/tailscale/ipn/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@ import android.content.res.Configuration.SCREENLAYOUT_SIZE_LARGE
import android.content.res.Configuration.SCREENLAYOUT_SIZE_MASK
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContract
import androidx.annotation.RequiresApi
import androidx.browser.customtabs.CustomTabsIntent
import androidx.compose.animation.core.LinearOutSlowInEasing
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
Expand Down Expand Up @@ -111,6 +114,7 @@ class MainActivity : ComponentActivity() {
// simply opening the URL. This should be consumed once it has been handled.
private val loginQRCode: StateFlow<String?> = MutableStateFlow(null)

@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@SuppressLint("SourceLockedOrientationActivity")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand Down Expand Up @@ -146,24 +150,37 @@ class MainActivity : ComponentActivity() {
viewModel.setVpnPermissionLauncher(vpnPermissionLauncher)

setContent {
navController = rememberNavController()

AppTheme {
navController = rememberNavController()
Surface(color = MaterialTheme.colorScheme.inverseSurface) { // Background for the letterbox
Surface(modifier = Modifier.universalFit()) { // Letterbox for AndroidTV
NavHost(
navController = navController,
startDestination = "main",
enterTransition = {
slideInHorizontally(animationSpec = tween(150), initialOffsetX = { it })
slideInHorizontally(
animationSpec = tween(250, easing = LinearOutSlowInEasing),
initialOffsetX = { it }) +
fadeIn(animationSpec = tween(500, easing = LinearOutSlowInEasing))
},
exitTransition = {
slideOutHorizontally(animationSpec = tween(150), targetOffsetX = { -it })
slideOutHorizontally(
animationSpec = tween(250, easing = LinearOutSlowInEasing),
targetOffsetX = { -it }) +
fadeOut(animationSpec = tween(500, easing = LinearOutSlowInEasing))
},
popEnterTransition = {
slideInHorizontally(animationSpec = tween(150), initialOffsetX = { -it })
slideInHorizontally(
animationSpec = tween(250, easing = LinearOutSlowInEasing),
initialOffsetX = { -it }) +
fadeIn(animationSpec = tween(500, easing = LinearOutSlowInEasing))
},
popExitTransition = {
slideOutHorizontally(animationSpec = tween(150), targetOffsetX = { it })
slideOutHorizontally(
animationSpec = tween(250, easing = LinearOutSlowInEasing),
targetOffsetX = { it }) +
fadeOut(animationSpec = tween(500, easing = LinearOutSlowInEasing))
}) {
fun backTo(route: String): () -> Unit = {
navController.popBackStack(route = route, inclusive = false)
Expand All @@ -186,7 +203,7 @@ class MainActivity : ComponentActivity() {
onNavigateToDNSSettings = { navController.navigate("dnsSettings") },
onNavigateToSplitTunneling = { navController.navigate("splitTunneling") },
onNavigateToTailnetLock = { navController.navigate("tailnetLock") },
onNavigateToSubnetRouting = { navController.navigate("subnetRouting")},
onNavigateToSubnetRouting = { navController.navigate("subnetRouting") },
onNavigateToMDMSettings = { navController.navigate("mdmSettings") },
onNavigateToManagedBy = { navController.navigate("managedBy") },
onNavigateToUserSwitcher = { navController.navigate("userSwitcher") },
Expand Down
3 changes: 2 additions & 1 deletion android/src/main/java/com/tailscale/ipn/ui/util/Lists.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ object Lists {

@Composable
fun ItemDivider() {
HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant)
HorizontalDivider(
color = MaterialTheme.colorScheme.outlineVariant, modifier = Modifier.fillMaxWidth())
}

@Composable
Expand Down
2 changes: 1 addition & 1 deletion android/src/main/java/com/tailscale/ipn/ui/view/Avatar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ fun Avatar(
modifier =
Modifier.conditional(AndroidTVUtil.isAndroidTV(), { padding(4.dp) })
.conditional(
AndroidTVUtil.isAndroidTV() && isFocusable,
AndroidTVUtil.isAndroidTV() && isFocusable,
{
size((size * 1.5f).dp) // Focusable area is larger than the avatar
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ enum class ErrorDialogType {

@Composable
fun ErrorDialog(type: ErrorDialogType, action: () -> Unit = {}) {
ErrorDialog(
ErrorDialog(
title = type.title,
message = stringResource(id = type.message),
buttonText = type.buttonText,
Expand All @@ -68,7 +68,7 @@ fun ErrorDialog(
@StringRes buttonText: Int = R.string.ok,
onDismiss: () -> Unit = {}
) {
ErrorDialog(
ErrorDialog(
title = title,
message = stringResource(id = message),
buttonText = buttonText,
Expand Down
67 changes: 41 additions & 26 deletions android/src/main/java/com/tailscale/ipn/ui/view/MainView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.outlined.ArrowDropDown
import androidx.compose.material.icons.outlined.Clear
import androidx.compose.material.icons.outlined.Close
Expand All @@ -46,6 +45,7 @@ import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
Expand Down Expand Up @@ -545,8 +545,6 @@ fun PeerList(
Column(modifier = Modifier.fillMaxSize()) {
if (enableSearch && FeatureFlags.isEnabled("enable_new_search")) {
Search(onSearchBarClick)

Spacer(modifier = Modifier.height(if (showNoResults) 0.dp else 8.dp))
} else {
if (enableSearch) {
Box(
Expand Down Expand Up @@ -748,37 +746,54 @@ fun PromptPermissionsIfNecessary() {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Search(
onSearchBarClick: () -> Unit // Callback for navigating to SearchView
onSearchBarClick: () -> Unit, // Callback for navigating to SearchView
backgroundColor: Color = MaterialTheme.colorScheme.background // Default background color
) {
// Prevent multiple taps
var isNavigating by remember { mutableStateOf(false) }

// Outer Box to handle clicks
Box(
modifier =
Modifier.fillMaxWidth()
.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)) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxSize()) {
Icon(
imageVector = Icons.Default.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 = stringResource(R.string.search_ellipsis),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.weight(1f))
}
.padding(top = 8.dp)) {
Box(
modifier =
Modifier.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp, top = 16.dp)
.height(56.dp)
.clip(MaterialTheme.shapes.extraLarge) // Rounded corners for search bar
.background(backgroundColor) // Search bar background
.clickable(enabled = !isNavigating) { // Intercept taps
isNavigating = true
onSearchBarClick()
}
.padding(horizontal = 16.dp) // Internal padding
) {
Row(
verticalAlignment = Alignment.CenterVertically, // Ensure icon aligns with text
modifier = Modifier.fillMaxSize()) {
// Leading Icon
Icon(
imageVector = Icons.Outlined.Search,
contentDescription = "Search",
tint = MaterialTheme.colorScheme.onSurfaceVariant,
modifier =
Modifier.padding(start = 0.dp) // Optional start padding for alignment
)
Spacer(modifier = Modifier.width(4.dp))

// Placeholder Text
Text(
text = stringResource(R.string.search_ellipsis),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f) // Ensure text takes up remaining space
)
}
}
}
}

Expand Down
Loading

0 comments on commit cac58de

Please sign in to comment.