From c75349feb5a0cfc3e6d4be87f407232cf21d19c3 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 11 May 2026 20:47:23 +0000 Subject: [PATCH] fix(nests): avatar circular on narrow grid cells MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The stage/audience LazyVerticalGrid uses Adaptive(80dp) cells while the avatar wants 75dp. Wrapping the avatar in an inner Box with `Modifier.padding(ringPadding=12dp)` reserved room for the speaking glow + outer ring, but it also reduced the avatar's max **width** to 56dp on a typical 82dp cell while leaving its height at the requested 75dp — Compose grid items are tight on width and free on height. Result: a 56×75 ellipse instead of a circle. Drop the inner padding and size the outer drawBehind Box explicitly to `avatarSize + ringPadding * 2`. AvatarAndBadges still sizes tight to the picture (so role/hand/mic/reaction badge corner alignment is unchanged), but no longer inherits a width-only constraint from the cell — the 75dp picture stays a circle. --- .../nests/room/stage/ParticipantsGrid.kt | 96 ++++++++++--------- 1 file changed, 50 insertions(+), 46 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/stage/ParticipantsGrid.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/stage/ParticipantsGrid.kt index d79d7eb292..805598f97e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/stage/ParticipantsGrid.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/stage/ParticipantsGrid.kt @@ -422,14 +422,18 @@ private fun MemberCell( label = "speaker-outer-ring-width", ) // Reserve enough space around the avatar Box to fit the outer ring - // and glow halo. Without this padding the rings clip against the + // and glow halo. Without this margin the rings clip against the // surrounding Surface / LazyVerticalGrid bounds (most visibly at // the top edge for the first row, where the stage card's rounded // corner cuts into the glow). The glow extends up to MAX_GLOW_RADIUS // past the avatar; the outer ring extends OUTER_RING_GAP + - // OUTER_RING_MAX_WIDTH past it. + // OUTER_RING_MAX_WIDTH past it. Applied as an explicit outer-Box + // size below rather than as inner padding so a width-constrained + // grid cell doesn't squish the avatar horizontally into an oval + // (cells are tight on width but free on height). val ringPadding = maxOf(MAX_GLOW_RADIUS.value, (OUTER_RING_GAP + OUTER_RING_MAX_WIDTH).value).dp + val outerBoxSize = avatarSize + ringPadding * 2 val avatarModifier = Modifier .border(animatedRingWidth, animatedRingColor, CircleShape) @@ -472,54 +476,54 @@ private fun MemberCell( modifier = modifier.fillMaxWidth().padding(vertical = 4.dp), ) { // Outer Box paints the glow halo + detached outer ring on a - // canvas that's bigger than the avatar by [ringPadding]. The - // inner Box keeps its tight-to-avatar bounds so badge corner - // alignment (TopStart, TopEnd, BottomCenter, BottomEnd) still - // tracks the avatar circle, not the padded outer area. + // canvas that's bigger than the avatar by [ringPadding]. Size + // is set explicitly so the AvatarAndBadges child (which sizes + // tight to the 75dp picture, keeping badge corner alignment + // against the avatar circle rather than the padded area) is + // centered without inheriting a width-only constraint from the + // grid cell — that asymmetry was rendering the avatar as an + // oval on cells narrower than [outerBoxSize]. Box( modifier = - Modifier.drawBehind { - val avatarRadiusPx = avatarSize.toPx() / 2f - val cx = size.width / 2f - val cy = size.height / 2f - if (animatedGlowAlpha > 0.001f) { - val extra = MAX_GLOW_RADIUS.toPx() * clampedLevel - drawCircle( - color = NEST_SPEAKING_COLOR.copy(alpha = animatedGlowAlpha), - radius = avatarRadiusPx + extra, - center = Offset(cx, cy), - ) - } - if (animatedOuterRingAlpha > 0.001f && animatedOuterRingWidth > 0.dp) { - val strokePx = animatedOuterRingWidth.toPx() - val ringRadius = avatarRadiusPx + OUTER_RING_GAP.toPx() + strokePx / 2f - drawCircle( - color = NEST_SPEAKING_COLOR.copy(alpha = animatedOuterRingAlpha), - radius = ringRadius, - center = Offset(cx, cy), - style = Stroke(width = strokePx), - ) - } - }, + Modifier + .size(outerBoxSize) + .drawBehind { + val avatarRadiusPx = avatarSize.toPx() / 2f + val cx = size.width / 2f + val cy = size.height / 2f + if (animatedGlowAlpha > 0.001f) { + val extra = MAX_GLOW_RADIUS.toPx() * clampedLevel + drawCircle( + color = NEST_SPEAKING_COLOR.copy(alpha = animatedGlowAlpha), + radius = avatarRadiusPx + extra, + center = Offset(cx, cy), + ) + } + if (animatedOuterRingAlpha > 0.001f && animatedOuterRingWidth > 0.dp) { + val strokePx = animatedOuterRingWidth.toPx() + val ringRadius = avatarRadiusPx + OUTER_RING_GAP.toPx() + strokePx / 2f + drawCircle( + color = NEST_SPEAKING_COLOR.copy(alpha = animatedOuterRingAlpha), + radius = ringRadius, + center = Offset(cx, cy), + style = Stroke(width = strokePx), + ) + } + }, contentAlignment = Alignment.Center, ) { - Box( - modifier = Modifier.padding(ringPadding), - contentAlignment = Alignment.Center, - ) { - AvatarAndBadges( - member = member, - avatarSize = avatarSize, - accountViewModel = accountViewModel, - avatarModifier = avatarModifier, - onClick = onClick, - onLongClick = onLongClick, - isConnecting = isConnecting, - showMicBadge = showMicBadge, - isSpeaking = isSpeaking, - reactions = reactions, - ) - } + AvatarAndBadges( + member = member, + avatarSize = avatarSize, + accountViewModel = accountViewModel, + avatarModifier = avatarModifier, + onClick = onClick, + onLongClick = onLongClick, + isConnecting = isConnecting, + showMicBadge = showMicBadge, + isSpeaking = isSpeaking, + reactions = reactions, + ) } UsernameDisplay( baseUser = user,