mirror of
https://github.com/block/buzz.git
synced 2026-10-31 06:21:34 +00:00
## 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>
116 lines
3.6 KiB
Dart
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,
|
|
);
|
|
}
|
|
}
|