diff --git a/android/src/main/java/com/tailscale/ipn/ui/view/Avatar.kt b/android/src/main/java/com/tailscale/ipn/ui/view/Avatar.kt index 6f2a1c35cb..a7d65bd43c 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/view/Avatar.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/view/Avatar.kt @@ -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) + } + } } }