fix(mobile): clip preset agent avatar previews to the squircle

Catalog avatars in the picker and Create agent previews rendered unclipped,
so the default create preview did not match the created agent. Let
PresetAgentAvatar own the agent clip and assert the catalog state in the
picker journey.

Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@localhost>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tom Brow <tomb@squareup.com>
This commit is contained in:
Tom Brow
2026-10-31 10:55:41 -07:00
parent 00b1ec6cc1
commit 355ebca27e
2 changed files with 35 additions and 26 deletions

View File

@@ -7,6 +7,7 @@ import 'package:video_player/video_player.dart';
import '../animated_avatar.dart'; import '../animated_avatar.dart';
import 'avatar_image.dart'; import 'avatar_image.dart';
import 'avatar_shape.dart';
import 'android_transparent_avatar.dart'; import 'android_transparent_avatar.dart';
/// Plays the transparent video paired with a bundled character poster. /// Plays the transparent video paired with a bundled character poster.
@@ -98,21 +99,24 @@ class PresetAgentAvatar extends HookWidget {
return SizedBox.square( return SizedBox.square(
dimension: size, dimension: size,
child: Transform.scale( child: AvatarClip(
scale: animatedAvatarArtworkScale, isAgent: true,
child: isAndroid && videoUrl != null child: Transform.scale(
? AndroidTransparentAvatar( scale: animatedAvatarArtworkScale,
key: ValueKey(videoUrl), child: isAndroid && videoUrl != null
url: videoUrl, ? AndroidTransparentAvatar(
playing: playing, key: ValueKey(videoUrl),
fallback: AvatarImageContent( url: videoUrl,
imageUrl: poster, playing: playing,
fallback: fallback, fallback: AvatarImageContent(
), imageUrl: poster,
) fallback: fallback,
: controller != null && ready.value == controller ),
? VideoPlayer(controller) )
: AvatarImageContent(imageUrl: poster, fallback: fallback), : controller != null && ready.value == controller
? VideoPlayer(controller)
: AvatarImageContent(imageUrl: poster, fallback: fallback),
),
), ),
); );
} }

View File

@@ -67,6 +67,21 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(selection.url, isNot(agentAvatarCatalog.first.url)); expect(selection.url, isNot(agentAvatarCatalog.first.url));
final character = selection.url; final character = selection.url;
// Every preview uses the same squircle the agent gets once created.
void expectSquirclePreview() {
final previewClip = tester.widget<ClipPath>(
find.descendant(
of: find.bySemanticsLabel('Selected agent avatar'),
matching: find.byType(ClipPath),
),
);
expect(
(previewClip.clipper! as ShapeBorderClipper).shape,
avatarShape(isAgent: true),
);
}
expectSquirclePreview();
await tester.runAsync(() async { await tester.runAsync(() async {
rendered = Completer<void>(); rendered = Completer<void>();
tester tester
@@ -77,17 +92,7 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(selection.bytes, isNotNull); expect(selection.bytes, isNotNull);
// The preview uses the same squircle the agent gets once created. expectSquirclePreview();
final previewClip = tester.widget<ClipPath>(
find.descendant(
of: find.bySemanticsLabel('Selected agent avatar'),
matching: find.byType(ClipPath),
),
);
expect(
(previewClip.clipper! as ShapeBorderClipper).shape,
avatarShape(isAgent: true),
);
expect( expect(
(tester (tester
.widget<GridView>( .widget<GridView>(