fix(mobile): fill uploaded avatars on the Agents page (#8882)

Uploaded images on the mobile Agents page now crop to fill the avatar
squircle, matching desktop. Animated avatars and bundled character
posters retain their existing sizing.

Validation: `just mobile-check mobile-test` passed (4,050 Flutter tests;
34 skipped), plus before/after rendering of the production card.
Regression coverage checks uploaded, preset and animated images.

Snapshots use a sample rectangular PNG in a local Flutter widget
preview, not an installed build.

| Before | After |
| --- | --- |
| ![Uploaded PNG leaves gaps in the avatar
frame](https://github.com/user-attachments/assets/8f8591a1-48fd-4496-99e6-fd1c978c79bc)
| ![Uploaded PNG fills the avatar
squircle](https://github.com/user-attachments/assets/c7a3191f-2cd1-43ec-876a-baeb7896bb45)
|

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz>
This commit is contained in:
klopez4212
2026-11-01 17:53:41 +00:00
committed by GitHub
parent 90772a43e9
commit e18d422d72
2 changed files with 58 additions and 2 deletions

View File

@@ -45,6 +45,9 @@ class AgentCard extends StatelessWidget {
style: context.textTheme.headlineMedium,
),
);
final portraitFit = usesInsetAgentArtwork(imageUrl)
? BoxFit.contain
: BoxFit.cover;
final portrait =
animation != null &&
!MediaQuery.disableAnimationsOf(context) &&
@@ -52,12 +55,12 @@ class AgentCard extends StatelessWidget {
? ProgressiveAnimatedAvatar(
descriptor: animation,
fallback: fallback,
fit: BoxFit.contain,
fit: portraitFit,
)
: AvatarImageContent(
imageUrl: poster,
fallback: fallback,
fit: BoxFit.contain,
fit: portraitFit,
);
return ClipRRect(
borderRadius: BorderRadius.circular(Radii.card),

View File

@@ -3,11 +3,64 @@ import 'dart:ui' show SemanticsAction;
import 'package:buzz/features/agents/agent_card.dart';
import 'package:buzz/features/agents/agent_card_grid.dart';
import 'package:buzz/shared/theme/theme.dart';
import 'package:buzz/shared/widgets/avatar_image.dart';
import 'package:buzz/shared/widgets/avatar_shape.dart';
import 'package:buzz/shared/widgets/progressive_animated_avatar.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
void main() {
testWidgets(
'uploaded portraits cover the frame while character artwork stays contained',
(tester) async {
for (final (url, expectedFit) in [
('https://example.com/portrait.png', BoxFit.cover),
(
'https://dwwgwmfqqjotj.cloudfront.net/avatars/20260925T154625166Z/poster/pollies/pollies-10.png',
BoxFit.contain,
),
(
'https://example.com/portrait.png#buzz-anim=https%3A%2F%2Fexample.com%2Fanimated.png',
BoxFit.contain,
),
]) {
await tester.pumpWidget(
ProviderScope(
child: MaterialApp(
theme: AppTheme.light(),
home: Center(
child: SizedBox(
width: 180,
height: 240,
child: AgentCard(
name: 'Portrait',
imageUrl: url,
onOpen: () {},
),
),
),
),
),
);
final portrait = find.descendant(
of: find.byType(AvatarClip),
matching: find.byType(AvatarImageContent),
);
expect(portrait, findsOneWidget);
expect(tester.widget<AvatarImageContent>(portrait).fit, expectedFit);
final animation = find.byType(ProgressiveAnimatedAvatar);
if (animation.evaluate().isNotEmpty) {
expect(
tester.widget<ProgressiveAnimatedAvatar>(animation).fit,
expectedFit,
);
}
expect(tester.takeException(), isNull);
}
},
);
testWidgets('whole card opens profile without stealing footer taps', (
tester,
) async {