@@ -35,56 +35,48 @@ import com.tailscale.ipn.ui.model.IpnLocal
3535@OptIn(ExperimentalCoilApi ::class )
3636@Composable
3737fun Avatar (profile : IpnLocal .LoginProfile ? , size : Int = 50, action : (() -> Unit )? = null) {
38- var isFocused = remember { mutableStateOf(false ) }
39- val focusManager = LocalFocusManager .current
38+ var isFocused = remember { mutableStateOf(false ) }
39+ val focusManager = LocalFocusManager .current
4040
41- // Outer Box for the larger focusable and clickable area
42- Box (
43- contentAlignment = Alignment .Center ,
44- modifier = Modifier
45- .padding(4 .dp)
46- .size((size * 1.5f ).dp) // Focusable area is larger than the avatar
47- .clip(CircleShape ) // Ensure both the focus and click area are circular
48- .background(
49- if (isFocused.value) MaterialTheme .colorScheme.surface
50- else Color .Transparent ,
51- )
52- .onFocusChanged { focusState ->
53- isFocused.value = focusState.isFocused
54- }
55- .focusable() // Make this outer Box focusable (after onFocusChanged)
56- .clickable(
57- interactionSource = remember { MutableInteractionSource () },
58- indication = ripple(bounded = true ), // Apply ripple effect inside circular bounds
59- onClick = {
60- action?.invoke()
41+ // Outer Box for the larger focusable and clickable area
42+ Box (
43+ contentAlignment = Alignment .Center ,
44+ modifier =
45+ Modifier .padding(4 .dp)
46+ .size((size * 1.5f ).dp) // Focusable area is larger than the avatar
47+ .clip(CircleShape ) // Ensure both the focus and click area are circular
48+ .background(
49+ if (isFocused.value) MaterialTheme .colorScheme.surface else Color .Transparent ,
50+ )
51+ .onFocusChanged { focusState -> isFocused.value = focusState.isFocused }
52+ .focusable() // Make this outer Box focusable (after onFocusChanged)
53+ .clickable(
54+ interactionSource = remember { MutableInteractionSource () },
55+ indication = ripple(bounded = true ), // Apply ripple effect inside circular bounds
56+ onClick = {
57+ action?.invoke()
6158 focusManager.clearFocus() // Clear focus after clicking the avatar
62- }
63- )
64- ) {
59+ })) {
6560 // Inner Box to hold the avatar content (Icon or AsyncImage)
6661 Box (
6762 contentAlignment = Alignment .Center ,
68- modifier = Modifier
69- .size(size.dp)
70- .clip(CircleShape )
71- ) {
72- if (profile?.UserProfile ?.ProfilePicURL != null ) {
63+ modifier = Modifier .size(size.dp).clip(CircleShape )) {
64+ // Always display the default icon as a background layer
65+ Icon (
66+ imageVector = Icons .Default .Person ,
67+ contentDescription = stringResource(R .string.settings_title),
68+ modifier =
69+ Modifier .size((size * 0.8f ).dp)
70+ .clip(CircleShape ) // Icon size slightly smaller than the Box
71+ )
72+
73+ // Overlay the profile picture if available
74+ profile?.UserProfile ?.ProfilePicURL ?.let { url ->
7375 AsyncImage (
74- model = profile. UserProfile . ProfilePicURL ,
76+ model = url ,
7577 modifier = Modifier .size(size.dp).clip(CircleShape ),
76- contentDescription = null
77- )
78- } else {
79- Icon (
80- imageVector = Icons .Default .Person ,
81- contentDescription = stringResource(R .string.settings_title),
82- modifier = Modifier
83- .size((size * 0.8f ).dp)
84- .clip(CircleShape ) // Icon size slightly smaller than the Box
85- )
78+ contentDescription = null )
79+ }
8680 }
87- }
88- }
81+ }
8982}
90-
0 commit comments