import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:lightmeter/data/models/ev_source_type.dart'; import 'package:lightmeter/data/models/metering_screen_layout_config.dart'; import 'package:lightmeter/data/shared_prefs_service.dart'; import 'package:lightmeter/generated/l10n.dart'; import 'package:lightmeter/res/dimens.dart'; import 'package:lightmeter/screens/metering/components/bottom_controls/components/measure_button/widget_button_measure.dart'; import 'package:lightmeter/screens/metering/components/shared/readings_container/components/equipment_profile_picker/widget_picker_equipment_profiles.dart'; import 'package:lightmeter/screens/metering/components/shared/readings_container/components/film_picker/widget_picker_film.dart'; import 'package:lightmeter/screens/metering/components/shared/readings_container/components/iso_picker/widget_picker_iso.dart'; import 'package:lightmeter/screens/metering/components/shared/readings_container/components/nd_picker/widget_picker_nd.dart'; import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/components/dialog_picker/widget_picker_dialog.dart'; import 'package:lightmeter/screens/settings/components/shared/dialog_filter/widget_dialog_filter.dart'; import 'package:lightmeter/screens/settings/components/shared/dialog_range_picker/widget_dialog_picker_range.dart'; import 'package:lightmeter/screens/settings/screen_settings.dart'; import 'package:lightmeter/utils/double_to_zoom.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; import 'package:meta/meta.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../integration_test/utils/widget_tester_actions.dart'; import 'mocks/paid_features_mock.dart'; import 'utils/expectations.dart'; @isTest void testE2E(String description) { setUp(() { SharedPreferences.setMockInitialValues({ /// Metering values UserPreferencesService.evSourceTypeKey: EvSourceType.camera.index, UserPreferencesService.meteringScreenLayoutKey: json.encode( { MeteringScreenLayoutFeature.equipmentProfiles: true, MeteringScreenLayoutFeature.extremeExposurePairs: true, MeteringScreenLayoutFeature.filmPicker: true, }.toJson(), ), }); }); testWidgets( description, (tester) async { await tester.pumpApplication(equipmentProfiles: [], filmsInUse: []); /// Create Praktica + Zenitar profile from scratch await tester.openSettings(); await tester.tapDescendantTextOf(S.current.equipmentProfiles); await tester.tap(find.byIcon(Icons.add).first); await tester.pumpAndSettle(); await tester.setProfileName(mockEquipmentProfiles[0].name); await tester.expandEquipmentProfileContainer(mockEquipmentProfiles[0].name); await tester.setIsoValues(0, mockEquipmentProfiles[0].isoValues); await tester.setNdValues(0, mockEquipmentProfiles[0].ndValues); await tester.setApertureValues(0, mockEquipmentProfiles[0].apertureValues); await tester.setShutterSpeedValues(0, mockEquipmentProfiles[0].shutterSpeedValues); await tester.setZoomValue(0, mockEquipmentProfiles[0].lensZoom); expect(find.text('x1.91'), findsOneWidget); expect(find.text('f/1.7 - f/16'), findsOneWidget); expect(find.text('1/1000 - B'), findsOneWidget); /// Create Praktica + Jupiter profile from Zenitar profile await tester.tap(find.byIcon(Icons.copy).first); await tester.pumpAndSettle(); await tester.setProfileName(mockEquipmentProfiles[1].name); await tester.expandEquipmentProfileContainer(mockEquipmentProfiles[1].name); await tester.setApertureValues(1, mockEquipmentProfiles[1].apertureValues); await tester.setZoomValue(1, mockEquipmentProfiles[1].lensZoom); expect(find.text('x5.02'), findsOneWidget); expect(find.text('f/3.5 - f/22'), findsOneWidget); expect(find.text('1/1000 - B'), findsNWidgets(2)); await tester.navigatorPop(); /// Select some films await tester.tap(find.text(S.current.filmsInUse)); await tester.pumpAndSettle(); await tester.setDialogFilterValues([mockFilms[0], mockFilms[1]], deselectAll: false); await tester.navigatorPop(); /// Select some initial settings according to the selected gear and film /// Then take a photo and verify, that exposure pairs range and EV matches the selected settings await tester.openPickerAndSelect(mockEquipmentProfiles[0].name); await tester.openPickerAndSelect(mockFilms[0].name); await tester.openPickerAndSelect('400'); expectPickerTitle(mockEquipmentProfiles[0].name); expectPickerTitle(mockFilms[0].name); expectPickerTitle('400'); await tester.takePhoto(); await _expectMeteringState( tester, equipmentProfile: mockEquipmentProfiles[0], film: mockFilms[0], fastest: 'f/1.8 - 1/400', slowest: 'f/16 - 1/5', iso: '400', nd: 'None', ev: mockPhotoEv100 + 2, ); /// Add ND to shoot another scene await tester.openPickerAndSelect('2'); await _expectMeteringStateAndMeasure( tester, equipmentProfile: mockEquipmentProfiles[0], film: mockFilms[0], fastest: 'f/1.8 - 1/200', slowest: 'f/16 - 1/2.5', iso: '400', nd: '2', ev: mockPhotoEv100 + 2 - 1, ); /// Select another lens without ND await tester.openPickerAndSelect(mockEquipmentProfiles[1].name); await tester.openPickerAndSelect('None'); await _expectMeteringStateAndMeasure( tester, equipmentProfile: mockEquipmentProfiles[1], film: mockFilms[0], fastest: 'f/3.5 - 1/100', slowest: 'f/22 - 1/2.5', iso: '400', nd: 'None', ev: mockPhotoEv100 + 2, ); /// Set another film and another ISO await tester.openPickerAndSelect('200'); await tester.openPickerAndSelect(mockFilms[1].name); await _expectMeteringStateAndMeasure( tester, equipmentProfile: mockEquipmentProfiles[1], film: mockFilms[1], fastest: 'f/3.5 - 1/50', slowest: 'f/22 - 1/1.3', iso: '200', nd: 'None', ev: mockPhotoEv100 + 1, ); }, ); } extension EquipmentProfileActions on WidgetTester { Future expandEquipmentProfileContainer(String name) async { await tap(find.text(name)); await pump(Dimens.durationM); } Future setProfileName(String name) async { await enterText(find.byType(TextField), name); await pump(); await tapSaveButton(); } Future setIsoValues(int profileIndex, List values) => _openAndSetDialogFilterValues(profileIndex, S.current.isoValues, values); Future setNdValues(int profileIndex, List values) => _openAndSetDialogFilterValues(profileIndex, S.current.ndFilters, values); Future _openAndSetDialogFilterValues( int profileIndex, String listTileTitle, List valuesToSelect, { bool deselectAll = true, }) async { await tap(find.text(listTileTitle).at(profileIndex)); await pumpAndSettle(); await setDialogFilterValues(valuesToSelect, deselectAll: deselectAll); } Future setApertureValues(int profileIndex, List values) => _setDialogRangePickerValues(profileIndex, S.current.apertureValues, values); Future setShutterSpeedValues(int profileIndex, List values) => _setDialogRangePickerValues(profileIndex, S.current.shutterSpeedValues, values); Future setZoomValue(int profileIndex, double value) => _setDialogSliderPickerValue(profileIndex, S.current.lensZoom, value); } extension on WidgetTester { Future openPickerAndSelect

(String valueToSelect) async { await openAnimatedPicker

(); await tapDescendantTextOf>(valueToSelect); await tapSelectButton(); } Future setDialogFilterValues( List valuesToSelect, { bool deselectAll = true, }) async { if (deselectAll) { await tap(find.byIcon(Icons.deselect)); await pump(); } for (final value in valuesToSelect) { final listTile = find.descendant(of: find.byType(CheckboxListTile), matching: find.text(value.toString())); await scrollUntilVisible( listTile, 56, scrollable: find.descendant(of: find.byType(DialogFilter), matching: find.byType(Scrollable)), ); await tap(listTile); await pump(); } await tapSaveButton(); } Future _setDialogRangePickerValues( int profileIndex, String listTileTitle, List valuesToSelect, ) async { await tap(find.text(listTileTitle).at(profileIndex)); await pumpAndSettle(); final dialog = widget>(find.byType(DialogRangePicker)); final sliderFinder = find.byType(RangeSlider); final divisions = widget(sliderFinder).divisions!; final trackWidth = getSize(sliderFinder).width - (2 * Dimens.paddingL); final trackStep = trackWidth / divisions; final start = valuesToSelect.first; final oldStart = dialog.values.indexWhere((e) => e.value == dialog.selectedValues.first.value) * trackStep; final newStart = dialog.values.indexWhere((e) => e.value == start.value) * trackStep; await dragFrom( getTopLeft(sliderFinder) + Offset(Dimens.paddingL + oldStart, getSize(sliderFinder).height / 2), Offset(newStart - oldStart, 0), ); await pump(); final end = valuesToSelect.last; final oldEnd = dialog.values.indexWhere((e) => e.value == dialog.selectedValues.last.value) * trackStep; final newEnd = dialog.values.indexWhere((e) => e.value == end.value) * trackStep; await dragFrom( getTopLeft(sliderFinder) + Offset(Dimens.paddingL + oldEnd, getSize(sliderFinder).height / 2), Offset(newEnd - oldEnd, 0), ); await pump(); await tapSaveButton(); } Future _setDialogSliderPickerValue( int profileIndex, String listTileTitle, double value, ) async { await tap(find.text(listTileTitle).at(profileIndex)); await pumpAndSettle(); final sliderFinder = find.byType(Slider); final trackWidth = getSize(sliderFinder).width - (2 * Dimens.paddingL); final trackStep = trackWidth / (widget(sliderFinder).max - widget(sliderFinder).min); final oldValue = widget(sliderFinder).value; final oldStart = (oldValue - 1) * trackStep; final newStart = (value - 1) * trackStep; await dragFrom( getTopLeft(sliderFinder) + Offset(Dimens.paddingL + oldStart, getSize(sliderFinder).height / 2), Offset(newStart - oldStart, 0), ); await pump(); await tapSaveButton(); } } Future _expectMeteringState( WidgetTester tester, { required EquipmentProfile equipmentProfile, required Film film, required String fastest, required String slowest, required String iso, required String nd, required double ev, String? reason, }) async { expectPickerTitle(equipmentProfile.name); expectPickerTitle(film.name); expectExtremeExposurePairs(fastest, slowest); expectPickerTitle(iso); expectPickerTitle(nd); expectExposurePairsListItem(tester, fastest.split(' - ')[0], fastest.split(' - ')[1]); await tester.scrollToTheLastExposurePair(equipmentProfile: equipmentProfile); expectExposurePairsListItem(tester, slowest.split(' - ')[0], slowest.split(' - ')[1]); expectMeasureButton(ev); expect(find.text(equipmentProfile.lensZoom.toZoom()), findsOneWidget); } Future _expectMeteringStateAndMeasure( WidgetTester tester, { required EquipmentProfile equipmentProfile, required Film film, required String fastest, required String slowest, required String iso, required String nd, required double ev, }) async { await _expectMeteringState( tester, equipmentProfile: equipmentProfile, film: film, fastest: fastest, slowest: slowest, iso: iso, nd: nd, ev: ev, ); await tester.takePhoto(); await _expectMeteringState( tester, equipmentProfile: equipmentProfile, film: film, fastest: fastest, slowest: slowest, iso: iso, nd: nd, ev: ev, reason: 'Metering screen state must be the same before and after the measurement assuming that the scene is exactly the same.', ); } void expectMeasureButton(double ev) { find.descendant( of: find.byType(MeteringMeasureButton), matching: find.text('${ev.toStringAsFixed(1)}\n${S.current.ev}'), ); }