android: fix avatar padding (#559)

Update Avatar to take isFocusable as a parameter, allowing us to make the avatar focusable in the main view but not in the settings / user switcher view. This fixes the issue where the padding is too big in the settings / user switcher view.

Fixes tailscale/corp#24370

Signed-off-by: kari-ts <kari@tailscale.com>
pull/550/head^2
kari-ts 1 year ago committed by GitHub
parent ba306bf883
commit bd745b5254
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -34,57 +34,52 @@ import com.tailscale.ipn.ui.model.IpnLocal
@OptIn(ExperimentalCoilApi::class) @OptIn(ExperimentalCoilApi::class)
@Composable @Composable
fun Avatar(profile: IpnLocal.LoginProfile?, size: Int = 50, action: (() -> Unit)? = null) { fun Avatar(
var isFocused = remember { mutableStateOf(false) } profile: IpnLocal.LoginProfile?,
val focusManager = LocalFocusManager.current size: Int = 50,
action: (() -> Unit)? = null,
isFocusable: Boolean = false
) {
var isFocused = remember { mutableStateOf(false) }
val focusManager = LocalFocusManager.current
// Outer Box for the larger focusable and clickable area // Determine the modifier based on whether the avatar is focusable
Box( val outerModifier =
contentAlignment = Alignment.Center, Modifier.then(
modifier = Modifier if (isFocusable) {
.padding(4.dp) Modifier.padding(4.dp)
.size((size * 1.5f).dp) // Focusable area is larger than the avatar } else Modifier) // Add padding if focusable
.clip(CircleShape) // Ensure both the focus and click area are circular .size((size * 1.5f).dp)
.background( .clip(CircleShape)
if (isFocused.value) MaterialTheme.colorScheme.surface .background(if (isFocused.value) MaterialTheme.colorScheme.surface else Color.Transparent)
else Color.Transparent, .onFocusChanged { focusState -> isFocused.value = focusState.isFocused }
) .then(if (isFocusable) Modifier.focusable() else Modifier) // Conditionally add focusable
.onFocusChanged { focusState -> .clickable(
isFocused.value = focusState.isFocused interactionSource = remember { MutableInteractionSource() },
} indication = ripple(bounded = true),
.focusable() // Make this outer Box focusable (after onFocusChanged) onClick = {
.clickable( action?.invoke()
interactionSource = remember { MutableInteractionSource() }, focusManager.clearFocus() // Clear focus after clicking
indication = ripple(bounded = true), // Apply ripple effect inside circular bounds })
onClick = {
action?.invoke() // Outer Box for the larger focusable and clickable area
focusManager.clearFocus() // Clear focus after clicking the avatar Box(contentAlignment = Alignment.Center, modifier = outerModifier) {
} // Inner Box to hold the avatar content (Icon or AsyncImage)
Box(contentAlignment = Alignment.Center, modifier = Modifier.size(size.dp).clip(CircleShape)) {
if (profile?.UserProfile?.ProfilePicURL != null) {
AsyncImage(
model = profile.UserProfile.ProfilePicURL,
modifier = Modifier.size(size.dp).clip(CircleShape),
contentDescription = null)
} else {
Icon(
imageVector = Icons.Default.Person,
contentDescription = stringResource(R.string.settings_title),
modifier =
Modifier.size((size * 0.8f).dp)
.clip(CircleShape) // Icon size slightly smaller than the Box
) )
) { }
// Inner Box to hold the avatar content (Icon or AsyncImage)
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.size(size.dp)
.clip(CircleShape)
) {
if (profile?.UserProfile?.ProfilePicURL != null) {
AsyncImage(
model = profile.UserProfile.ProfilePicURL,
modifier = Modifier.size(size.dp).clip(CircleShape),
contentDescription = null
)
} else {
Icon(
imageVector = Icons.Default.Person,
contentDescription = stringResource(R.string.settings_title),
modifier = Modifier
.size((size * 0.8f).dp)
.clip(CircleShape) // Icon size slightly smaller than the Box
)
}
}
} }
}
} }

@ -45,8 +45,15 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.SearchBar import androidx.compose.material3.SearchBar
import androidx.compose.material3.SearchBarDefaults import androidx.compose.material3.SearchBarDefaults
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.* import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
@ -188,7 +195,7 @@ fun MainView(
when (user) { when (user) {
null -> SettingsButton { navigation.onNavigateToSettings() } null -> SettingsButton { navigation.onNavigateToSettings() }
else -> { else -> {
Avatar(profile = user, size = 36) { navigation.onNavigateToSettings() } Avatar(profile = user, size = 36, { navigation.onNavigateToSettings() }, isFocusable=true)
} }
} }
} }

Loading…
Cancel
Save