diff --git a/.gitignore b/.gitignore index 3fd9832..703a8b6 100644 --- a/.gitignore +++ b/.gitignore @@ -59,4 +59,5 @@ ios/firebase_app_id_file.json ios/Runner/GoogleService-Info.plist /lib/firebase_options.dart -coverage/ \ No newline at end of file +coverage/ +screenshots/ \ No newline at end of file diff --git a/assets/camera_stub_image.jpg b/assets/camera_stub_image.jpg new file mode 100644 index 0000000..70b289f Binary files /dev/null and b/assets/camera_stub_image.jpg differ diff --git a/integration_test/generate_screenshots.dart b/integration_test/generate_screenshots.dart index 059ee9c..5f64796 100644 --- a/integration_test/generate_screenshots.dart +++ b/integration_test/generate_screenshots.dart @@ -61,7 +61,7 @@ void main() { setUpAll(() { mockUserPreferencesService = _MockUserPreferencesService(); - when(() => mockUserPreferencesService.evSourceType).thenReturn(EvSourceType.sensor); + when(() => mockUserPreferencesService.evSourceType).thenReturn(EvSourceType.camera); when(() => mockUserPreferencesService.stopType).thenReturn(StopType.third); when(() => mockUserPreferencesService.locale).thenReturn(SupportedLocale.en); when(() => mockUserPreferencesService.caffeine).thenReturn(true); @@ -143,48 +143,48 @@ void main() { /// Generates several screenshots with the light theme /// and the additionally the first one with the dark theme void generateScreenshots(Color color) { - testWidgets( - '${color.value}_light', - (tester) async { - when(() => mockUserPreferencesService.primaryColor).thenReturn(color); - await pumpApplication(tester); + testWidgets('${color.value}_light', (tester) async { + when(() => mockUserPreferencesService.themeType).thenReturn(ThemeType.light); + when(() => mockUserPreferencesService.primaryColor).thenReturn(color); + await pumpApplication(tester); - //await tester.takeScreenshot(binding, '${color.value}_metering_reflected'); + await tester.takePhoto(); + await tester.takeScreenshot(binding, '${color.value}_metering_reflected'); - await tester.tap(find.byType(MeteringMeasureButton)); - await tester.tap(find.byType(MeteringMeasureButton)); - await tester.takeScreenshot(binding, '${color.value}_metering_incident'); + await tester.tap(find.byTooltip(S.current.tooltipUseLightSensor)); + await tester.pumpAndSettle(); + await tester.tap(find.byType(MeteringMeasureButton)); + await tester.tap(find.byType(MeteringMeasureButton)); + await tester.takeScreenshot(binding, '${color.value}_metering_incident'); - expect(find.byType(IsoValuePicker), findsOneWidget); - await tester.tap(find.byType(IsoValuePicker)); - await tester.pumpAndSettle(Dimens.durationL); - expect(find.byType(DialogPicker), findsOneWidget); - await tester.takeScreenshot(binding, '${color.value}_metering_iso_picker'); + expect(find.byType(IsoValuePicker), findsOneWidget); + await tester.tap(find.byType(IsoValuePicker)); + await tester.pumpAndSettle(Dimens.durationL); + expect(find.byType(DialogPicker), findsOneWidget); + await tester.takeScreenshot(binding, '${color.value}_metering_iso_picker'); - await tester.tapCancelButton(); - expect(find.byType(DialogPicker), findsNothing); - expect(find.byTooltip(S.current.tooltipOpenSettings), findsOneWidget); - await tester.tap(find.byTooltip(S.current.tooltipOpenSettings)); - await tester.pumpAndSettle(); - expect(find.byType(SettingsScreen), findsOneWidget); - await tester.takeScreenshot(binding, '${color.value}_settings'); + await tester.tapCancelButton(); + expect(find.byType(DialogPicker), findsNothing); + expect(find.byTooltip(S.current.tooltipOpenSettings), findsOneWidget); + await tester.tap(find.byTooltip(S.current.tooltipOpenSettings)); + await tester.pumpAndSettle(); + expect(find.byType(SettingsScreen), findsOneWidget); + await tester.takeScreenshot(binding, '${color.value}_settings'); - await tester.tapListTile(S.current.meteringScreenLayout); - await tester.takeScreenshot(binding, '${color.value}_settings_metering_screen_layout'); + await tester.tapListTile(S.current.meteringScreenLayout); + await tester.takeScreenshot(binding, '${color.value}_settings_metering_screen_layout'); - await tester.tapCancelButton(); - await tester.tapListTile(S.current.equipmentProfiles); - expect(find.byType(EquipmentProfilesScreen), findsOneWidget); - await tester.tap(find.byType(EquipmentProfileContainer).first); - await tester.pumpAndSettle(); - await tester.takeScreenshot(binding, '${color.value}-equipment_profiles'); + await tester.tapCancelButton(); + await tester.tapListTile(S.current.equipmentProfiles); + expect(find.byType(EquipmentProfilesScreen), findsOneWidget); + await tester.tap(find.byType(EquipmentProfileContainer).first); + await tester.pumpAndSettle(); + await tester.takeScreenshot(binding, '${color.value}-equipment_profiles'); - await tester.tap(find.byIcon(Icons.iso).first); - await tester.pumpAndSettle(); - await tester.takeScreenshot(binding, '${color.value}_equipment_profiles_iso_picker'); - }, - skip: true, - ); + await tester.tap(find.byIcon(Icons.iso).first); + await tester.pumpAndSettle(); + await tester.takeScreenshot(binding, '${color.value}_equipment_profiles_iso_picker'); + }); testWidgets( '${color.value}_dark', @@ -193,8 +193,11 @@ void main() { when(() => mockUserPreferencesService.primaryColor).thenReturn(color); await pumpApplication(tester); - //await tester.takeScreenshot(binding, '${color.value}_metering_reflected'); + await tester.takePhoto(); + await tester.takeScreenshot(binding, '${color.value}_metering_reflected'); + await tester.tap(find.byTooltip(S.current.tooltipUseLightSensor)); + await tester.pumpAndSettle(); await tester.tap(find.byType(MeteringMeasureButton)); await tester.tap(find.byType(MeteringMeasureButton)); await tester.takeScreenshot(binding, '${color.value}_metering_incident_dark'); @@ -217,6 +220,12 @@ extension on WidgetTester { await pumpAndSettle(); } + Future takePhoto() async { + await tap(find.byType(MeteringMeasureButton)); + await pump(const Duration(seconds: 2)); // wait for circular progress indicator + await pumpAndSettle(); + } + Future tapCancelButton() async { final cancelButton = find.byWidgetPredicate( (widget) => diff --git a/integration_test/generate_screenshots.sh b/integration_test/generate_screenshots.sh index 4c62a11..8296e96 100644 --- a/integration_test/generate_screenshots.sh +++ b/integration_test/generate_screenshots.sh @@ -1,5 +1,6 @@ flutter drive \ --dart-define="cameraPreviewAspectRatio=240/320" \ + --dart-define="cameraStubImage=assets/camera_stub_image.jpg" \ --driver=test_driver/screenshot_driver.dart \ --target=integration_test/generate_screenshots.dart \ --profile \ diff --git a/lib/platform_config.dart b/lib/platform_config.dart index 7d98a4b..def5a80 100644 --- a/lib/platform_config.dart +++ b/lib/platform_config.dart @@ -5,4 +5,6 @@ class PlatformConfig { final rational = const String.fromEnvironment('cameraPreviewAspectRatio').split('/'); return int.parse(rational[0]) / int.parse(rational[1]); } + + static String get cameraStubImage => const String.fromEnvironment('cameraStubImage'); } diff --git a/lib/screens/metering/components/camera_container/bloc_container_camera.dart b/lib/screens/metering/components/camera_container/bloc_container_camera.dart index 0313391..7f5f430 100644 --- a/lib/screens/metering/components/camera_container/bloc_container_camera.dart +++ b/lib/screens/metering/components/camera_container/bloc_container_camera.dart @@ -7,8 +7,10 @@ import 'dart:typed_data'; import 'package:camera/camera.dart'; import 'package:exif/exif.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:lightmeter/interactors/metering_interactor.dart'; +import 'package:lightmeter/platform_config.dart'; import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart'; import 'package:lightmeter/screens/metering/communication/event_communication_metering.dart' as communication_event; @@ -32,7 +34,7 @@ class CameraContainerBloc extends EvSourceBlocBase !(_cameraController == null || - !_cameraController!.value.isInitialized || - _cameraController!.value.isTakingPicture); + bool get _canTakePhoto => + PlatformConfig.cameraStubImage.isNotEmpty || + !(_cameraController == null || + !_cameraController!.value.isInitialized || + _cameraController!.value.isTakingPicture); Future _takePhoto() async { try { // https://github.com/flutter/flutter/issues/84957#issuecomment-1661155095 - await _cameraController!.setFocusMode(FocusMode.locked); - await _cameraController!.setExposureMode(ExposureMode.locked); - final file = await _cameraController!.takePicture(); - await _cameraController!.setFocusMode(FocusMode.auto); - await _cameraController!.setExposureMode(ExposureMode.auto); - final Uint8List bytes = await file.readAsBytes(); - Directory(file.path).deleteSync(recursive: true); + late final Uint8List bytes; + if (PlatformConfig.cameraStubImage.isNotEmpty) { + bytes = (await rootBundle.load(PlatformConfig.cameraStubImage)).buffer.asUint8List(); + } else { + await _cameraController!.setFocusMode(FocusMode.locked); + await _cameraController!.setExposureMode(ExposureMode.locked); + final file = await _cameraController!.takePicture(); + await _cameraController!.setFocusMode(FocusMode.auto); + await _cameraController!.setExposureMode(ExposureMode.auto); + bytes = await file.readAsBytes(); + Directory(file.path).deleteSync(recursive: true); + } final tags = await readExifFromBytes(bytes); final iso = double.tryParse("${tags["EXIF ISOSpeedRatings"]}"); diff --git a/lib/screens/metering/components/camera_container/components/camera_preview/widget_camera_preview.dart b/lib/screens/metering/components/camera_container/components/camera_preview/widget_camera_preview.dart index 1ae0ca9..3e9538f 100644 --- a/lib/screens/metering/components/camera_container/components/camera_preview/widget_camera_preview.dart +++ b/lib/screens/metering/components/camera_container/components/camera_preview/widget_camera_preview.dart @@ -69,6 +69,9 @@ class _CameraPreviewBuilderState extends State<_CameraPreviewBuilder> { @override Widget build(BuildContext context) { + if (PlatformConfig.cameraStubImage.isNotEmpty) { + return Image.asset(PlatformConfig.cameraStubImage); + } return ValueListenableBuilder( valueListenable: _initializedNotifier, builder: (context, value, child) => value diff --git a/pubspec.yaml b/pubspec.yaml index 4aef28d..0c216fc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -54,6 +54,8 @@ dev_dependencies: flutter: uses-material-design: true + assets: + - assets/camera_stub_image.jpg flutter_intl: enabled: true diff --git a/test_driver/screenshot_driver.dart b/test_driver/screenshot_driver.dart index 9028e1f..3b11ee3 100644 --- a/test_driver/screenshot_driver.dart +++ b/test_driver/screenshot_driver.dart @@ -4,6 +4,31 @@ import 'package:integration_test/integration_test_driver_extended.dart'; Future main() async { try { + final bool adbExists = Process.runSync('which', ['adb']).exitCode == 0; + if (!adbExists) { + log(r'This test needs ADB to exist on the $PATH. Skipping...'); + exit(0); + } + final deviceId = await Process.run('adb', ["-s", 'shell', 'devices']).then((value) { + if (value.stdout is String) { + return RegExp(r"(?:List of devices attached\n)([A-Z0-9]*)(?:\sdevice\n)") + .firstMatch(value.stdout as String)! + .group(1); + } + }); + if (deviceId == null) { + log('This test needs at least one device connected'); + exit(0); + } + await Process.run('adb', [ + "-s", + deviceId, // https://github.com/flutter/flutter/issues/86295#issuecomment-1192766368 + 'shell', + 'pm', + 'grant', + 'com.vodemn.lightmeter.dev', + 'android.permission.CAMERA' + ]); await integrationDriver( onScreenshot: (name, bytes, [args]) async { final File image = await File('screenshots/$name.png').create(recursive: true);