Files
buzz/mobile/test/features/profile/profile_avatar_draft_test.dart
klopez4212 9aa332af03 Add inline profile camera capture (#6680)
## Summary

- add inline profile photo capture with smooth avatar-to-viewfinder
transitions
- add close, flip, shutter, retry, and use-photo states with haptics and
front-camera mirroring
- polish iOS liquid-glass controls and avatar editor motion

### Related issue

Follow-up to #6583.

### Testing

- just mobile-check
- just mobile-test (1,743 tests)
- built, installed, and launched on Pixel 10 and iPhone Air

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Code Reviewer <037593536284cf40e221c96c931e9877d4166d54f6bb84e5341a86d7fd5d05a4@buzz.block.builderlab.xyz>
Signed-off-by: Perci <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@buzz.block.builderlab.xyz>
Signed-off-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz>
Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Signed-off-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
Co-authored-by: Code Reviewer <037593536284cf40e221c96c931e9877d4166d54f6bb84e5341a86d7fd5d05a4@buzz.block.builderlab.xyz>
Co-authored-by: Tyler <109685178+tlongwell-block@users.noreply.github.com>
Co-authored-by: Perci <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@buzz.block.builderlab.xyz>
Co-authored-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz>
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Co-authored-by: morgmart <98432065+morgmart@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-08-25 14:58:55 +01:00

116 lines
3.6 KiB
Dart

import 'package:buzz/features/profile/profile_avatar_draft.dart';
import 'package:buzz/shared/animated_avatar.dart';
import 'package:buzz/shared/relay/relay.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
test('animated draft retries only the failed upload part', () async {
final service = _PartiallyFailingUploadService();
addTearDown(service.dispose);
final draft = ProfileAnimatedAvatarDraft(
poster: Uint8List.fromList([1]),
animation: Uint8List.fromList([2]),
);
await expectLater(draft.upload(service), throwsException);
expect(service.uploadedParts, ['poster', 'animation']);
final url = await draft.upload(service);
expect(service.uploadedParts, ['poster', 'animation', 'animation']);
expect(parseAnimatedAvatarUrl(url)?.posterUrl, 'https://relay/poster.png');
expect(
parseAnimatedAvatarUrl(url)?.animationUrl,
'https://relay/animation.png',
);
expect(await draft.upload(service), url);
expect(service.uploadedParts, ['poster', 'animation', 'animation']);
});
test('animated draft reuploads every part for a new community', () async {
final first = _RecordingUploadService('first');
final second = _RecordingUploadService('second');
addTearDown(first.dispose);
addTearDown(second.dispose);
final draft = ProfileAnimatedAvatarDraft(
poster: Uint8List.fromList([1]),
animation: Uint8List.fromList([2]),
);
final firstUrl = await draft.upload(first);
final secondUrl = await draft.upload(second);
expect(first.uploadedParts, ['poster', 'animation']);
expect(second.uploadedParts, ['poster', 'animation']);
expect(firstUrl, contains('https://first.example/'));
expect(secondUrl, contains('https://second.example/'));
});
}
final class _RecordingUploadService extends MediaUploadService {
_RecordingUploadService(this.community)
: super(
baseUrl: 'https://$community.example',
nsec: null,
pickGalleryImage: () async => null,
pickGalleryVideo: () async => null,
);
final String community;
final uploadedParts = <String>[];
@override
Future<BlobDescriptor> uploadBytes(
Uint8List bytes, {
required String mimeType,
ValueChanged<double>? onProgress,
UploadCancellationToken? cancellationToken,
}) async {
final part = bytes.single == 1 ? 'poster' : 'animation';
uploadedParts.add(part);
return BlobDescriptor(
url: 'https://$community.example/$part.png',
sha256: '$community-$part',
size: bytes.length,
type: mimeType,
uploaded: 1,
);
}
}
final class _PartiallyFailingUploadService extends MediaUploadService {
_PartiallyFailingUploadService()
: super(
baseUrl: 'https://relay.example',
nsec: null,
pickGalleryImage: () async => null,
pickGalleryVideo: () async => null,
);
final uploadedParts = <String>[];
var _failedAnimationOnce = false;
@override
Future<BlobDescriptor> uploadBytes(
Uint8List bytes, {
required String mimeType,
ValueChanged<double>? onProgress,
UploadCancellationToken? cancellationToken,
}) async {
final part = bytes.single == 1 ? 'poster' : 'animation';
uploadedParts.add(part);
if (part == 'animation' && !_failedAnimationOnce) {
_failedAnimationOnce = true;
throw Exception('animation upload failed');
}
return BlobDescriptor(
url: 'https://relay/$part.png',
sha256: '$part-hash',
size: bytes.length,
type: mimeType,
uploaded: 1,
);
}
}