Skip to content

Commit

Permalink
android: fix issue where default avatar wasn't shown (#558)
Browse files Browse the repository at this point in the history
Always render the default icon first so that if the profile picture is not loaded or has an issue, the default is shown.

Fixes tailscale/corp#24217

Signed-off-by: kari-ts <kari@tailscale.com>
  • Loading branch information
kari-ts authored Nov 9, 2024
1 parent 18ca09d commit 4c4148b
Showing 1 changed file with 47 additions and 37 deletions.
84 changes: 47 additions & 37 deletions android/src/main/java/com/tailscale/ipn/ui/view/Avatar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,43 +43,53 @@ fun Avatar(
var isFocused = remember { mutableStateOf(false) }
val focusManager = LocalFocusManager.current

// Determine the modifier based on whether the avatar is focusable
val outerModifier =
Modifier.then(
if (isFocusable) {
Modifier.padding(4.dp)
} else Modifier) // Add padding if focusable
.size((size * 1.5f).dp)
.clip(CircleShape)
.background(if (isFocused.value) MaterialTheme.colorScheme.surface else Color.Transparent)
.onFocusChanged { focusState -> isFocused.value = focusState.isFocused }
.then(if (isFocusable) Modifier.focusable() else Modifier) // Conditionally add focusable
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = ripple(bounded = true),
onClick = {
action?.invoke()
focusManager.clearFocus() // Clear focus after clicking
})

// Outer Box for the larger focusable and clickable area
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
// Outer Box for the larger focusable and clickable area
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.padding(4.dp)
.size((size * 1.5f).dp) // Focusable area is larger than the avatar
.clip(CircleShape) // Ensure both the focus and click area are circular
.background(
if (isFocused.value) MaterialTheme.colorScheme.surface
else Color.Transparent,
)
.onFocusChanged { focusState ->
isFocused.value = focusState.isFocused
}
.focusable() // Make this outer Box focusable (after onFocusChanged)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = ripple(bounded = true), // Apply ripple effect inside circular bounds
onClick = {
action?.invoke()
focusManager.clearFocus() // Clear focus after clicking the avatar
}
)
}
}
) {
// Inner Box to hold the avatar content (Icon or AsyncImage)
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.size(size.dp)
.clip(CircleShape)
) {
// Always display the default icon as a background layer
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
)

// Overlay the profile picture if available
profile?.UserProfile?.ProfilePicURL?.let { url ->
AsyncImage(
model = url,
modifier = Modifier.size(size.dp).clip(CircleShape),
contentDescription = null)
}
}
}
}

0 comments on commit 4c4148b

Please sign in to comment.