diff --git a/.vscode/launch.json b/.vscode/launch.json index 77049175..b3fbc2e1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,7 +9,7 @@ "request": "launch", "type": "dart", "program": "lib/main.dart", - "args": ["-d", "chrome"] + "args": ["-d", "chrome", "--web-renderer", "html"] } ] } diff --git a/assets/images/photo_frame.png b/assets/images/photo_frame.png deleted file mode 100644 index 98ab5961..00000000 Binary files a/assets/images/photo_frame.png and /dev/null differ diff --git a/assets/images/photo_frame_mobile.png b/assets/images/photo_frame_mobile.png deleted file mode 100644 index 811968c7..00000000 Binary files a/assets/images/photo_frame_mobile.png and /dev/null differ diff --git a/lib/photobooth/widgets/framed_photobooth_photo.dart b/lib/photobooth/widgets/framed_photobooth_photo.dart deleted file mode 100644 index 034d1fc4..00000000 --- a/lib/photobooth/widgets/framed_photobooth_photo.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'dart:math' as math; - -import 'package:flutter/material.dart'; -import 'package:io_photobooth/photobooth/photobooth.dart'; - -const _mobileMargin = EdgeInsets.all(15); -const _mobilePadding = EdgeInsets.only( - bottom: 30, - left: 19, - right: 10, - top: 10, -); - -const _desktopMargin = EdgeInsets.all(20); -const _desktopPadding = EdgeInsets.only( - bottom: 30, - left: 39, - right: 19, - top: 5, -); - -/// A widget that displays [CharactersLayer] and [StickersLayer] on top of -/// the raw [image] took from the camera. -/// -/// The [FramedPhotoboothPhoto] widget is styled to mimic a framed card photo. -class FramedPhotoboothPhoto extends StatelessWidget { - const FramedPhotoboothPhoto({ - Key? key, - required this.image, - required this.aspectRatio, - this.isTilted = true, - }) : super(key: key); - - final double aspectRatio; - final String image; - final bool isTilted; - - @override - Widget build(BuildContext context) { - final isMobile = aspectRatio < 1; - var photo = Center( - child: AspectRatio( - aspectRatio: aspectRatio, - child: Container( - margin: isMobile ? _mobileMargin : _desktopMargin, - padding: isMobile ? _mobilePadding : _desktopPadding, - decoration: BoxDecoration( - image: DecorationImage( - fit: BoxFit.cover, - image: AssetImage( - isMobile - ? 'assets/images/photo_frame_mobile.png' - : 'assets/images/photo_frame.png', - ), - ), - ), - child: PhotoboothPhoto(image: image), - ), - ), - ); - if (!isTilted) return photo; - return Transform( - alignment: const Alignment(0, -3 / 4), - transform: Matrix4.identity()..rotateZ(-5 * (math.pi / 180)), - child: photo, - ); - } -} diff --git a/lib/photobooth/widgets/widgets.dart b/lib/photobooth/widgets/widgets.dart index e78e2788..e21d2271 100644 --- a/lib/photobooth/widgets/widgets.dart +++ b/lib/photobooth/widgets/widgets.dart @@ -2,7 +2,6 @@ export 'animated_characters/animated_characters.dart'; export 'character_icon_button.dart'; export 'characters_caption.dart'; export 'characters_layer.dart'; -export 'framed_photobooth_photo.dart'; export 'photobooth_background.dart'; export 'photobooth_error.dart'; export 'photobooth_photo.dart'; diff --git a/lib/share/view/share_bottom_sheet.dart b/lib/share/view/share_bottom_sheet.dart index d8d153ba..75ae1c42 100644 --- a/lib/share/view/share_bottom_sheet.dart +++ b/lib/share/view/share_bottom_sheet.dart @@ -1,7 +1,7 @@ -import 'package:camera/camera.dart'; +import 'dart:typed_data'; + import 'package:flutter/material.dart'; import 'package:io_photobooth/l10n/l10n.dart'; -import 'package:io_photobooth/photobooth/photobooth.dart'; import 'package:io_photobooth/share/share.dart'; import 'package:photobooth_ui/photobooth_ui.dart'; @@ -11,7 +11,7 @@ class ShareBottomSheet extends StatelessWidget { required this.image, }) : super(key: key); - final CameraImage image; + final Uint8List image; @override Widget build(BuildContext context) { @@ -36,14 +36,8 @@ class ShareBottomSheet extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: [ - const SizedBox(height: 32), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 25.0), - child: FramedPhotoboothPhoto( - aspectRatio: PhotoboothAspectRatio.portrait, - image: image.data, - ), - ), + const SizedBox(height: 60), + SharePreviewPhoto(image: image), const SizedBox(height: 60), SelectableText( l10n.shareDialogHeading, diff --git a/lib/share/view/share_dialog.dart b/lib/share/view/share_dialog.dart index 64f9f97c..63679dc6 100644 --- a/lib/share/view/share_dialog.dart +++ b/lib/share/view/share_dialog.dart @@ -1,19 +1,14 @@ -import 'package:camera/camera.dart'; +import 'dart:typed_data'; + import 'package:flutter/material.dart'; import 'package:io_photobooth/l10n/l10n.dart'; -import 'package:io_photobooth/photobooth/photobooth.dart'; import 'package:io_photobooth/share/share.dart'; import 'package:photobooth_ui/photobooth_ui.dart'; class ShareDialog extends StatelessWidget { - const ShareDialog({ - Key? key, - required this.aspectRatio, - required this.image, - }) : super(key: key); + const ShareDialog({Key? key, required this.image}) : super(key: key); - final double aspectRatio; - final CameraImage image; + final Uint8List image; @override Widget build(BuildContext context) { @@ -44,14 +39,7 @@ class ShareDialog extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Container( - height: 430, - width: 600, - child: FramedPhotoboothPhoto( - aspectRatio: aspectRatio, - image: image.data, - ), - ), + SharePreviewPhoto(image: image), const SizedBox(height: 60), SelectableText( l10n.shareDialogHeading, diff --git a/lib/share/widgets/share_body.dart b/lib/share/widgets/share_body.dart index 4a1f4d49..90d2b45f 100644 --- a/lib/share/widgets/share_body.dart +++ b/lib/share/widgets/share_body.dart @@ -1,4 +1,5 @@ -import 'package:camera/camera.dart'; +import 'dart:typed_data'; + import 'package:cross_file/cross_file.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -18,6 +19,9 @@ class ShareBody extends StatelessWidget { final compositeStatus = context.select( (ShareBloc bloc) => bloc.state.compositeStatus, ); + final compositedImage = context.select( + (ShareBloc bloc) => bloc.state.bytes, + ); final isUploadSuccess = context.select( (ShareBloc bloc) => bloc.state.uploadStatus.isSuccess, ); @@ -55,12 +59,14 @@ class ShareBody extends StatelessWidget { ), child: ShareCopyableLink(link: shareUrl), ), - if (image != null && file != null) + if (compositedImage != null && file != null) ResponsiveLayoutBuilder( - small: (_, __) => - MobileButtonsLayout(image: image, file: file), + small: (_, __) => MobileButtonsLayout( + image: compositedImage, + file: file, + ), large: (_, __) => DesktopButtonsLayout( - image: image, + image: compositedImage, file: file, ), ), @@ -87,7 +93,7 @@ class DesktopButtonsLayout extends StatelessWidget { required this.file, }) : super(key: key); - final CameraImage image; + final Uint8List image; final XFile file; @override @@ -113,7 +119,7 @@ class MobileButtonsLayout extends StatelessWidget { required this.file, }) : super(key: key); - final CameraImage image; + final Uint8List image; final XFile file; @override diff --git a/lib/share/widgets/share_button.dart b/lib/share/widgets/share_button.dart index f9b61ae7..55066b7c 100644 --- a/lib/share/widgets/share_button.dart +++ b/lib/share/widgets/share_button.dart @@ -1,4 +1,5 @@ -import 'package:camera/camera.dart'; +import 'dart:typed_data'; + import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:io_photobooth/l10n/l10n.dart'; @@ -16,8 +17,8 @@ class ShareButton extends StatelessWidget { }) : platformHelper = platformHelper ?? PlatformHelper(), super(key: key); - /// Raw image from camera - final CameraImage image; + /// Composited image + final Uint8List image; /// Optional [PlatformHelper] instance. final PlatformHelper platformHelper; @@ -36,10 +37,7 @@ class ShareButton extends StatelessWidget { BlocProvider.value(value: context.read()), BlocProvider.value(value: context.read()), ], - child: ShareDialog( - aspectRatio: PhotoboothAspectRatio.landscape, - image: image, - ), + child: ShareDialog(image: image), ), portraitChild: MultiBlocProvider( providers: [ diff --git a/lib/share/widgets/share_preview_photo.dart b/lib/share/widgets/share_preview_photo.dart new file mode 100644 index 00000000..f26c2823 --- /dev/null +++ b/lib/share/widgets/share_preview_photo.dart @@ -0,0 +1,36 @@ +import 'dart:math' as math; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:photobooth_ui/photobooth_ui.dart'; + +class SharePreviewPhoto extends StatelessWidget { + const SharePreviewPhoto({Key? key, required this.image}) : super(key: key); + + final Uint8List image; + + @override + Widget build(BuildContext context) { + return Transform.rotate( + angle: -5 * (math.pi / 180), + child: Container( + constraints: const BoxConstraints(maxWidth: 600, maxHeight: 400), + decoration: const BoxDecoration( + boxShadow: [ + BoxShadow( + color: PhotoboothColors.black54, + blurRadius: 7, + offset: Offset(-3, 9), + spreadRadius: 1, + ), + ], + ), + child: Image.memory( + image, + isAntiAlias: true, + filterQuality: FilterQuality.high, + ), + ), + ); + } +} diff --git a/lib/share/widgets/widgets.dart b/lib/share/widgets/widgets.dart index 2ddc0782..64a12002 100644 --- a/lib/share/widgets/widgets.dart +++ b/lib/share/widgets/widgets.dart @@ -7,6 +7,7 @@ export 'share_button.dart'; export 'share_caption.dart'; export 'share_copyable_link.dart'; export 'share_heading.dart'; +export 'share_preview_photo.dart'; export 'share_progress_overlay.dart'; export 'share_social_media_clarification.dart'; export 'share_state_listener.dart'; diff --git a/test/photobooth/widgets/framed_photobooth_photo_test.dart b/test/photobooth/widgets/framed_photobooth_photo_test.dart deleted file mode 100644 index 73f0187d..00000000 --- a/test/photobooth/widgets/framed_photobooth_photo_test.dart +++ /dev/null @@ -1,66 +0,0 @@ -// ignore_for_file: prefer_const_constructors - -import 'package:camera/camera.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:io_photobooth/photobooth/photobooth.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:photobooth_ui/photobooth_ui.dart'; - -import '../../helpers/helpers.dart'; - -class FakePhotoboothEvent extends Fake implements PhotoboothEvent {} - -class FakePhotoboothState extends Fake implements PhotoboothState {} - -void main() { - const width = 1; - const height = 1; - const data = ''; - const image = CameraImage(width: width, height: height, data: data); - const aspectRatio = PhotoboothAspectRatio.landscape; - - late PhotoboothBloc photoboothBloc; - - setUpAll(() { - registerFallbackValue(FakePhotoboothEvent()); - registerFallbackValue(FakePhotoboothState()); - }); - - setUp(() { - photoboothBloc = MockPhotoboothBloc(); - when(() => photoboothBloc.state).thenReturn(PhotoboothState(image: image)); - }); - - group('FramedPhotoboothPhoto', () { - testWidgets('displays PhotoboothPhoto', (tester) async { - await tester.pumpApp( - FramedPhotoboothPhoto(aspectRatio: aspectRatio, image: data), - photoboothBloc: photoboothBloc, - ); - expect(find.byType(PhotoboothPhoto), findsOneWidget); - }); - - testWidgets('transform image by default', (tester) async { - await tester.pumpApp( - FramedPhotoboothPhoto(aspectRatio: aspectRatio, image: data), - photoboothBloc: photoboothBloc, - ); - expect(find.byType(Transform), findsOneWidget); - }); - - testWidgets( - 'does not transform image ' - 'when isTilted is false', (tester) async { - await tester.pumpApp( - FramedPhotoboothPhoto( - aspectRatio: aspectRatio, - image: data, - isTilted: false, - ), - photoboothBloc: photoboothBloc, - ); - expect(find.byType(Transform), findsNothing); - }); - }); -} diff --git a/test/share/view/share_bottom_sheet_test.dart b/test/share/view/share_bottom_sheet_test.dart index b14a9d41..ed98138b 100644 --- a/test/share/view/share_bottom_sheet_test.dart +++ b/test/share/view/share_bottom_sheet_test.dart @@ -1,4 +1,6 @@ // ignore_for_file: prefer_const_constructors +import 'dart:typed_data'; + import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -17,6 +19,7 @@ void main() { const height = 1; const data = ''; const image = CameraImage(width: width, height: height, data: data); + final bytes = Uint8List.fromList(transparentImage); late PhotoboothBloc photoboothBloc; @@ -33,7 +36,7 @@ void main() { group('ShareBottomSheet', () { testWidgets('displays heading', (tester) async { await tester.pumpApp( - Scaffold(body: ShareBottomSheet(image: image)), + Scaffold(body: ShareBottomSheet(image: bytes)), photoboothBloc: photoboothBloc, ); expect(find.byKey(Key('shareBottomSheet_heading')), findsOneWidget); @@ -41,7 +44,7 @@ void main() { testWidgets('displays subheading', (tester) async { await tester.pumpApp( - Scaffold(body: ShareBottomSheet(image: image)), + Scaffold(body: ShareBottomSheet(image: bytes)), photoboothBloc: photoboothBloc, ); expect(find.byKey(Key('shareBottomSheet_subheading')), findsOneWidget); @@ -49,7 +52,7 @@ void main() { testWidgets('displays a TwitterButton', (tester) async { await tester.pumpApp( - Scaffold(body: ShareBottomSheet(image: image)), + Scaffold(body: ShareBottomSheet(image: bytes)), photoboothBloc: photoboothBloc, ); expect(find.byType(TwitterButton), findsOneWidget); @@ -57,7 +60,7 @@ void main() { testWidgets('displays a FacebookButton', (tester) async { await tester.pumpApp( - Scaffold(body: ShareBottomSheet(image: image)), + Scaffold(body: ShareBottomSheet(image: bytes)), photoboothBloc: photoboothBloc, ); expect(find.byType(FacebookButton), findsOneWidget); @@ -65,7 +68,7 @@ void main() { testWidgets('taps on close will dismiss the popup', (tester) async { await tester.pumpApp( - Scaffold(body: ShareBottomSheet(image: image)), + Scaffold(body: ShareBottomSheet(image: bytes)), photoboothBloc: photoboothBloc, ); await tester.tap(find.byIcon(Icons.clear)); diff --git a/test/share/view/share_dialog_test.dart b/test/share/view/share_dialog_test.dart index dd3373d7..bee46af2 100644 --- a/test/share/view/share_dialog_test.dart +++ b/test/share/view/share_dialog_test.dart @@ -1,11 +1,12 @@ // ignore_for_file: prefer_const_constructors +import 'dart:typed_data'; + import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:io_photobooth/photobooth/photobooth.dart'; import 'package:io_photobooth/share/share.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:photobooth_ui/photobooth_ui.dart'; import '../../helpers/helpers.dart'; @@ -18,7 +19,7 @@ void main() { const height = 1; const data = ''; const image = CameraImage(width: width, height: height, data: data); - const aspectRatio = PhotoboothAspectRatio.landscape; + final bytes = Uint8List.fromList(transparentImage); late PhotoboothBloc photoboothBloc; @@ -35,7 +36,7 @@ void main() { group('ShareDialog', () { testWidgets('displays heading', (tester) async { await tester.pumpApp( - Material(child: ShareDialog(aspectRatio: aspectRatio, image: image)), + Material(child: ShareDialog(image: bytes)), photoboothBloc: photoboothBloc, ); expect(find.byKey(Key('shareDialog_heading')), findsOneWidget); @@ -43,7 +44,7 @@ void main() { testWidgets('displays subheading', (tester) async { await tester.pumpApp( - Material(child: ShareDialog(aspectRatio: aspectRatio, image: image)), + Material(child: ShareDialog(image: bytes)), photoboothBloc: photoboothBloc, ); expect(find.byKey(Key('shareDialog_subheading')), findsOneWidget); @@ -51,7 +52,7 @@ void main() { testWidgets('displays a TwitterButton', (tester) async { await tester.pumpApp( - Material(child: ShareDialog(aspectRatio: aspectRatio, image: image)), + Material(child: ShareDialog(image: bytes)), photoboothBloc: photoboothBloc, ); expect(find.byType(TwitterButton), findsOneWidget); @@ -59,7 +60,7 @@ void main() { testWidgets('displays a FacebookButton', (tester) async { await tester.pumpApp( - Material(child: ShareDialog(aspectRatio: aspectRatio, image: image)), + Material(child: ShareDialog(image: bytes)), photoboothBloc: photoboothBloc, ); expect(find.byType(FacebookButton), findsOneWidget); @@ -67,7 +68,7 @@ void main() { testWidgets('taps on close will dismiss the popup', (tester) async { await tester.pumpApp( - Material(child: ShareDialog(aspectRatio: aspectRatio, image: image)), + Material(child: ShareDialog(image: bytes)), photoboothBloc: photoboothBloc, ); await tester.tap(find.byIcon(Icons.clear)); diff --git a/test/share/view/share_page_test.dart b/test/share/view/share_page_test.dart index 06e31caf..a13b133b 100644 --- a/test/share/view/share_page_test.dart +++ b/test/share/view/share_page_test.dart @@ -146,6 +146,7 @@ void main() { setUp(() { when(() => shareBloc.state).thenReturn(ShareState( compositeStatus: ShareStatus.success, + bytes: Uint8List(0), file: file, )); }); @@ -328,10 +329,6 @@ void main() { headers: const {}, ), ).thenAnswer((_) async => true); - when(() => shareBloc.state).thenReturn(ShareState( - compositeStatus: ShareStatus.success, - file: file, - )); tester.setDisplaySize(Size(2500, 2500)); await tester.pumpApp( ShareView(), diff --git a/test/share/widgets/share_button_test.dart b/test/share/widgets/share_button_test.dart index 451dd91d..c5e35f93 100644 --- a/test/share/widgets/share_button_test.dart +++ b/test/share/widgets/share_button_test.dart @@ -1,4 +1,6 @@ // ignore_for_file: prefer_const_constructors +import 'dart:typed_data'; + import 'package:bloc_test/bloc_test.dart'; import 'package:camera/camera.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -23,6 +25,7 @@ void main() { const height = 1; const data = ''; const image = CameraImage(width: width, height: height, data: data); + final bytes = Uint8List.fromList(transparentImage); late PhotoboothBloc photoboothBloc; late PlatformHelper platformHelper; @@ -45,10 +48,7 @@ void main() { when(() => platformHelper.isMobile).thenReturn(true); await tester.pumpApp( - ShareButton( - image: image, - platformHelper: platformHelper, - ), + ShareButton(image: bytes), photoboothBloc: photoboothBloc, ); @@ -66,7 +66,7 @@ void main() { await tester.pumpApp( ShareButton( - image: image, + image: bytes, platformHelper: platformHelper, ), photoboothBloc: photoboothBloc, @@ -85,7 +85,7 @@ void main() { tester.setLandscapeDisplaySize(); await tester.pumpApp( ShareButton( - image: image, + image: bytes, platformHelper: platformHelper, ), photoboothBloc: photoboothBloc,