mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2025-09-18 10:36:42 +00:00
Merge 960c2360d2
into 3aa0014b6a
This commit is contained in:
commit
c2f72bbf99
41 changed files with 926 additions and 436 deletions
|
@ -7,6 +7,7 @@ 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/equipment_profiles/components/equipment_profile_type_picker/widget_picker_equipment_profile_type.dart';
|
||||
import 'package:lightmeter/screens/metering/components/bottom_controls/widget_bottom_controls.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';
|
||||
|
@ -56,8 +57,7 @@ void testE2E(String description) {
|
|||
/// Create Praktica + Zenitar profile from scratch
|
||||
await tester.openSettings();
|
||||
await tester.tapDescendantTextOf<SettingsScreen>(S.current.equipmentProfiles);
|
||||
await tester.tap(find.byIcon(Icons.add_outlined).first);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.addEquipmentProfile(EquipmentProfileType.regular);
|
||||
await tester.enterProfileName(mockEquipmentProfiles[0].name);
|
||||
await tester.setIsoValues(mockEquipmentProfiles[0].isoValues);
|
||||
await tester.setNdValues(mockEquipmentProfiles[0].ndValues);
|
||||
|
@ -70,8 +70,7 @@ void testE2E(String description) {
|
|||
await tester.saveEdits();
|
||||
|
||||
/// Create Praktica + Jupiter profile from Zenitar profile
|
||||
await tester.tap(find.byIcon(Icons.edit_outlined));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.editEquipmentProfile(mockEquipmentProfiles[1].name);
|
||||
await tester.tap(find.byIcon(Icons.copy_outlined).first);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.enterProfileName(mockEquipmentProfiles[1].name);
|
||||
|
@ -90,7 +89,7 @@ void testE2E(String description) {
|
|||
|
||||
/// 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<EquipmentProfilePicker, EquipmentProfile>(mockEquipmentProfiles[0].name);
|
||||
await tester.openPickerAndSelect<EquipmentProfilePicker, IEquipmentProfile>(mockEquipmentProfiles[0].name);
|
||||
await tester.openPickerAndSelect<FilmPicker, Film>(mockFilms[0].name);
|
||||
await tester.openPickerAndSelect<IsoValuePicker, IsoValue>('400');
|
||||
expectPickerTitle<EquipmentProfilePicker>(mockEquipmentProfiles[0].name);
|
||||
|
@ -122,7 +121,7 @@ void testE2E(String description) {
|
|||
// );
|
||||
|
||||
/// Select another lens without ND
|
||||
await tester.openPickerAndSelect<EquipmentProfilePicker, EquipmentProfile>(mockEquipmentProfiles[1].name);
|
||||
await tester.openPickerAndSelect<EquipmentProfilePicker, IEquipmentProfile>(mockEquipmentProfiles[1].name);
|
||||
await tester.openPickerAndSelect<NdValuePicker, NdValue>('None');
|
||||
await _expectMeteringStateAndMeasure(
|
||||
tester,
|
||||
|
@ -152,8 +151,7 @@ void testE2E(String description) {
|
|||
/// Delete profile
|
||||
await tester.openSettings();
|
||||
await tester.tapDescendantTextOf<SettingsScreen>(S.current.equipmentProfiles);
|
||||
await tester.tap(find.byIcon(Icons.edit_outlined).first);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.editEquipmentProfile(mockEquipmentProfiles[0].name);
|
||||
await tester.deleteEdits();
|
||||
expect(find.text(mockEquipmentProfiles[0].name), findsNothing);
|
||||
expect(find.text(mockEquipmentProfiles[1].name), findsOneWidget);
|
||||
|
|
|
@ -6,7 +6,7 @@ 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/screens/equipment_profile_edit/screen_equipment_profile_edit.dart';
|
||||
import 'package:lightmeter/screens/equipment_profiles/components/equipment_profile_type_picker/widget_picker_equipment_profile_type.dart';
|
||||
import 'package:lightmeter/screens/lightmeter_pro/screen_lightmeter_pro.dart';
|
||||
import 'package:lightmeter/screens/logbook_photos/screen_logbook_photos.dart';
|
||||
import 'package:lightmeter/screens/settings/screen_settings.dart';
|
||||
|
@ -49,7 +49,7 @@ void testGuardProTap(String description) {
|
|||
await tester.navigatorPop(true);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(LightmeterProScreen), findsNothing);
|
||||
expect(find.byType(EquipmentProfileEditScreen), findsOneWidget);
|
||||
expect(find.byType(EquipmentProfilesTypePicker), findsOneWidget);
|
||||
await tester.navigatorPop();
|
||||
await tester.navigatorPop();
|
||||
|
||||
|
|
|
@ -104,8 +104,7 @@ void testLogbook(String description) {
|
|||
/// Got back and delete the equipment profile used to take the first picture
|
||||
await tester.navigatorPop();
|
||||
await tester.tapDescendantTextOf<SettingsScreen>(S.current.equipmentProfiles);
|
||||
await tester.tap(find.byIcon(Icons.edit_outlined).first);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.editEquipmentProfile(mockEquipmentProfiles.first.name);
|
||||
await tester.tap(find.byIcon(Icons.delete_outlined));
|
||||
await tester.pumpAndSettle(Dimens.durationML);
|
||||
expect(find.text(mockEquipmentProfiles[0].name), findsNothing);
|
||||
|
|
|
@ -15,6 +15,7 @@ class _MockGeolocationService extends Mock implements GeolocationService {}
|
|||
|
||||
class MockIAPProviders extends StatefulWidget {
|
||||
final TogglableMap<EquipmentProfile> equipmentProfiles;
|
||||
final TogglableMap<PinholeEquipmentProfile> pinholeEquipmentProfiles;
|
||||
final String selectedEquipmentProfileId;
|
||||
final TogglableMap<Film> predefinedFilms;
|
||||
final TogglableMap<FilmExponential> customFilms;
|
||||
|
@ -23,6 +24,7 @@ class MockIAPProviders extends StatefulWidget {
|
|||
|
||||
MockIAPProviders({
|
||||
TogglableMap<EquipmentProfile>? equipmentProfiles,
|
||||
TogglableMap<PinholeEquipmentProfile>? pinholeEquipmentProfiles,
|
||||
this.selectedEquipmentProfileId = '',
|
||||
TogglableMap<Film>? predefinedFilms,
|
||||
TogglableMap<FilmExponential>? customFilms,
|
||||
|
@ -30,6 +32,7 @@ class MockIAPProviders extends StatefulWidget {
|
|||
required this.child,
|
||||
super.key,
|
||||
}) : equipmentProfiles = equipmentProfiles ?? mockEquipmentProfiles.toTogglableMap(),
|
||||
pinholeEquipmentProfiles = pinholeEquipmentProfiles ?? mockPinholeEquipmentProfiles.toTogglableMap(),
|
||||
predefinedFilms = predefinedFilms ?? mockFilms.toTogglableMap(),
|
||||
customFilms = customFilms ?? mockFilms.toTogglableMap(),
|
||||
selectedFilmId = selectedFilmId ?? const FilmStub().id;
|
||||
|
@ -46,15 +49,20 @@ class _MockIAPProvidersState extends State<MockIAPProviders> {
|
|||
void initState() {
|
||||
super.initState();
|
||||
registerFallbackValue(defaultEquipmentProfile);
|
||||
registerFallbackValue(mockPinholeEquipmentProfiles.first);
|
||||
registerFallbackValue(defaultCustomPhotos.first);
|
||||
registerFallbackValue(ApertureValue.values.first);
|
||||
registerFallbackValue(ShutterSpeedValue.values.first);
|
||||
mockIapStorageService = _MockIapStorageService();
|
||||
when(() => mockIapStorageService.init()).thenAnswer((_) async {});
|
||||
|
||||
when(() => mockIapStorageService.getEquipmentProfiles()).thenAnswer((_) => Future.value(widget.equipmentProfiles));
|
||||
when(() => mockIapStorageService.selectedEquipmentProfileId).thenReturn(widget.selectedEquipmentProfileId);
|
||||
when(() => mockIapStorageService.getEquipmentProfiles()).thenAnswer((_) => Future.value(widget.equipmentProfiles));
|
||||
when(() => mockIapStorageService.getPinholeEquipmentProfiles())
|
||||
.thenAnswer((_) => Future.value(widget.pinholeEquipmentProfiles));
|
||||
when(() => mockIapStorageService.addEquipmentProfile(any<EquipmentProfile>())).thenAnswer((_) async {});
|
||||
when(() => mockIapStorageService.addPinholeEquipmentProfile(any<PinholeEquipmentProfile>()))
|
||||
.thenAnswer((_) async {});
|
||||
when(
|
||||
() => mockIapStorageService.updateEquipmentProfile(
|
||||
id: any<String>(named: 'id'),
|
||||
|
@ -62,7 +70,14 @@ class _MockIAPProvidersState extends State<MockIAPProviders> {
|
|||
isUsed: any<bool>(named: 'isUsed'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
when(
|
||||
() => mockIapStorageService.updatePinholeEquipmentProfile(
|
||||
id: any<String>(named: 'id'),
|
||||
name: any<String>(named: 'name'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
when(() => mockIapStorageService.deleteEquipmentProfile(any<String>())).thenAnswer((_) async {});
|
||||
when(() => mockIapStorageService.deletePinholeEquipmentProfile(any<String>())).thenAnswer((_) async {});
|
||||
|
||||
when(() => mockIapStorageService.getPredefinedFilms()).thenAnswer((_) => Future.value(widget.predefinedFilms));
|
||||
when(() => mockIapStorageService.getCustomFilms()).thenAnswer((_) => Future.value(widget.customFilms));
|
||||
|
@ -108,6 +123,9 @@ const defaultEquipmentProfile = EquipmentProfile(
|
|||
isoValues: IsoValue.values,
|
||||
);
|
||||
|
||||
final mockProfiles = [...mockEquipmentProfiles, ...mockPinholeEquipmentProfiles]
|
||||
..sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()));
|
||||
|
||||
final mockEquipmentProfiles = [
|
||||
EquipmentProfile(
|
||||
id: '1',
|
||||
|
@ -175,6 +193,31 @@ final mockEquipmentProfiles = [
|
|||
),
|
||||
];
|
||||
|
||||
final mockPinholeEquipmentProfiles = [
|
||||
PinholeEquipmentProfile(
|
||||
id: '3',
|
||||
name: 'Pinhole Camera f/64',
|
||||
aperture: 64.0,
|
||||
isoValues: [
|
||||
IsoValue.values[1],
|
||||
IsoValue.values[2],
|
||||
IsoValue.values[3],
|
||||
],
|
||||
ndValues: const [NdValue(0)],
|
||||
),
|
||||
PinholeEquipmentProfile(
|
||||
id: '4',
|
||||
name: 'Pinhole Camera f/128',
|
||||
aperture: 128.0,
|
||||
isoValues: [
|
||||
IsoValue.values[1],
|
||||
IsoValue.values[2],
|
||||
IsoValue.values[3],
|
||||
],
|
||||
ndValues: const [NdValue(0)],
|
||||
),
|
||||
];
|
||||
|
||||
const mockFilms = [
|
||||
_FilmMultiplying(id: '1', name: 'Mock film 1', iso: 100, reciprocityMultiplier: 2),
|
||||
_FilmMultiplying(id: '2', name: 'Mock film 2', iso: 400, reciprocityMultiplier: 2),
|
||||
|
|
|
@ -5,6 +5,7 @@ import 'package:lightmeter/application_wrapper.dart';
|
|||
import 'package:lightmeter/environment.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
import 'package:lightmeter/screens/equipment_profiles/components/equipment_profile_type_picker/widget_picker_equipment_profile_type.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/exposure_pairs_list/widget_list_exposure_pairs.dart';
|
||||
import 'package:lightmeter/screens/metering/screen_metering.dart';
|
||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
|
@ -117,3 +118,24 @@ extension WidgetTesterExposurePairsListActions on WidgetTester {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension WidgetTesterEquipmentProfilesActions on WidgetTester {
|
||||
Future<void> addEquipmentProfile(EquipmentProfileType type) async {
|
||||
await tap(find.byIcon(Icons.add_outlined).first);
|
||||
await pumpAndSettle();
|
||||
await tap(
|
||||
find.text(
|
||||
switch (type) {
|
||||
EquipmentProfileType.regular => S.current.camera,
|
||||
EquipmentProfileType.pinhole => S.current.pinholeCamera,
|
||||
},
|
||||
),
|
||||
);
|
||||
await tapSelectButton();
|
||||
}
|
||||
|
||||
Future<void> editEquipmentProfile(String name) async {
|
||||
await tap(find.byIcon(Icons.edit_outlined).at(mockProfiles.indexWhere((p) => p.name == name)));
|
||||
await pumpAndSettle();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,8 @@
|
|||
"calibrationMessage": "Die Messgenauigkeit hängt von der Gerätehardware ab. Testen Sie die App und kalibrieren Sie EV-Werte für beste Ergebnisse",
|
||||
"calibrationMessageCameraOnly": "Die Messgenauigkeit hängt von der Kamera ab. Testen Sie die App und kalibrieren Sie EV-Werte für beste Ergebnisse",
|
||||
"camera": "Kamera",
|
||||
"pinholeCamera": "Lochkamera",
|
||||
"equipmentProfileType": "Ausrüstungsprofiltyp",
|
||||
"lightSensor": "Lichtsensor",
|
||||
"showEv100": "EV\u2081\u2080\u2080 anzeigen",
|
||||
"meteringScreenLayout": "Messansicht Layout",
|
||||
|
@ -60,6 +62,7 @@
|
|||
"apertureValuesFilterDescription": "Wählen Sie den Blendenbereich für Ihr Objektiv",
|
||||
"ndFilters": "ND Filter",
|
||||
"ndFiltersFilterDescription": "Wählen Sie verfügbare ND-Filter",
|
||||
"shutterSpeed": "Belichtungszeit",
|
||||
"shutterSpeedValues": "Belichtungszeiten",
|
||||
"shutterSpeedValue": "Belichtungszeit",
|
||||
"shutterSpeedValuesFilterDescription": "Wählen Sie den Verschlusszeitenbereich für Ihre Kamera",
|
||||
|
|
|
@ -34,6 +34,8 @@
|
|||
"calibrationMessage": "Measurement accuracy depends on your device's hardware. Test the app and calibrate EV values for optimal results",
|
||||
"calibrationMessageCameraOnly": "Measurement accuracy depends on your device's camera. Test the app and calibrate EV values for optimal results",
|
||||
"camera": "Camera",
|
||||
"pinholeCamera": "Pinhole Camera",
|
||||
"equipmentProfileType": "Equipment profile type",
|
||||
"lightSensor": "Light sensor",
|
||||
"showEv100": "Show EV\u2081\u2080\u2080",
|
||||
"meteringScreenLayout": "Metering screen layout",
|
||||
|
@ -60,6 +62,7 @@
|
|||
"apertureValuesFilterDescription": "Select aperture range for your lens",
|
||||
"ndFilters": "ND filters",
|
||||
"ndFiltersFilterDescription": "Select available ND filters",
|
||||
"shutterSpeed": "Shutter speed",
|
||||
"shutterSpeedValues": "Shutter speed values",
|
||||
"shutterSpeedValue": "Shutter speed value",
|
||||
"shutterSpeedValuesFilterDescription": "Select shutter speed range for your camera",
|
||||
|
|
|
@ -34,6 +34,8 @@
|
|||
"calibrationMessage": "La précision dépend du matériel de l'appareil. Testez l'app et calibrez les valeurs EV pour de meilleurs résultats",
|
||||
"calibrationMessageCameraOnly": "La précision dépend de la caméra de l'appareil. Testez l'app et calibrez les valeurs EV pour de meilleurs résultats",
|
||||
"camera": "Caméra",
|
||||
"pinholeCamera": "Sténopé",
|
||||
"equipmentProfileType": "Type de profil d'équipement",
|
||||
"lightSensor": "Capteur de lumière",
|
||||
"showEv100": "Montrer EV\u2081\u2080\u2080",
|
||||
"meteringScreenLayout": "Disposition de l'écran de mesure",
|
||||
|
@ -60,6 +62,7 @@
|
|||
"apertureValuesFilterDescription": "Sélectionnez la plage d'ouverture pour votre objectif",
|
||||
"ndFilters": "Filtres ND",
|
||||
"ndFiltersFilterDescription": "Sélectionnez les filtres ND disponibles",
|
||||
"shutterSpeed": "Vitesse d'obturation",
|
||||
"shutterSpeedValues": "Valeurs de la vitesse d'obturation",
|
||||
"shutterSpeedValue": "Valeur de la vitesse d'obturation",
|
||||
"shutterSpeedValuesFilterDescription": "Sélectionnez la plage de vitesses pour votre appareil",
|
||||
|
|
|
@ -34,6 +34,8 @@
|
|||
"calibrationMessage": "Dokładność pomiaru zależy od sprzętu urządzenia. Przetestuj aplikację i skalibruj wartości EV dla optymalnych wyników",
|
||||
"calibrationMessageCameraOnly": "Dokładność pomiaru zależy od kamery urządzenia. Przetestuj aplikację i skalibruj wartości EV dla optymalnych wyników",
|
||||
"camera": "Kamera",
|
||||
"pinholeCamera": "Kamera otworkowa",
|
||||
"equipmentProfileType": "Typ profilu sprzętu",
|
||||
"lightSensor": "Czujnik światła",
|
||||
"showEv100": "Pokaż EV\u2081\u2080\u2080",
|
||||
"meteringScreenLayout": "Układ ekranu pomiaru",
|
||||
|
@ -60,6 +62,7 @@
|
|||
"apertureValuesFilterDescription": "Wybierz zakres przysłony dla swojego obiektywu",
|
||||
"ndFilters": "Filtry ND",
|
||||
"ndFiltersFilterDescription": "Wybierz dostępne filtry ND",
|
||||
"shutterSpeed": "Czas naświetlania",
|
||||
"shutterSpeedValues": "Czasy naświetlania",
|
||||
"shutterSpeedValue": "Czas naświetlania",
|
||||
"shutterSpeedValuesFilterDescription": "Wybierz zakres czasów naświetlania dla swojej kamery",
|
||||
|
|
|
@ -34,6 +34,8 @@
|
|||
"calibrationMessage": "Точность измерений данного приложения полностью зависит от точности камеры и датчика освещенности вашего устройства. Поэтому рекомендуется самостоятельно подобрать калибровочные значения, которые дадут желаемый результат измерений.",
|
||||
"calibrationMessageCameraOnly": "Точность измерений данного приложения полностью зависит от точности камеры вашего устройства. Поэтому рекомендуется самостоятельно подобрать калибровочное значение, которое даст желаемый результат измерений.",
|
||||
"camera": "Камера",
|
||||
"pinholeCamera": "Пинхол-камера",
|
||||
"equipmentProfileType": "Тип профиля оборудования",
|
||||
"lightSensor": "Датчик освещённости",
|
||||
"showEv100": "Показывать EV\u2081\u2080\u2080",
|
||||
"meteringScreenLayout": "Элементы главного экрана",
|
||||
|
@ -60,6 +62,7 @@
|
|||
"apertureValuesFilterDescription": "Выберите диапазон диафрагмы для вашего объектива",
|
||||
"ndFilters": "ND фильтры",
|
||||
"ndFiltersFilterDescription": "Выберите доступные ND фильтры",
|
||||
"shutterSpeed": "Выдержка",
|
||||
"shutterSpeedValues": "Значения выдержки",
|
||||
"shutterSpeedValue": "Значение выдержки",
|
||||
"shutterSpeedValuesFilterDescription": "Выберите диапазон выдержек для вашей камеры",
|
||||
|
|
|
@ -34,6 +34,8 @@
|
|||
"calibrationMessage": "测量精度取决于设备硬件。请测试并校准 EV 值以获得最佳结果。",
|
||||
"calibrationMessageCameraOnly": "测量精度取决于设备摄像头。请测试并校准 EV 值以获得最佳结果。",
|
||||
"camera": "相机",
|
||||
"pinholeCamera": "针孔相机",
|
||||
"equipmentProfileType": "设备配置类型",
|
||||
"lightSensor": "光线传感器",
|
||||
"showEv100": "显示 EV\u2081\u2080\u2080",
|
||||
"meteringScreenLayout": "布局",
|
||||
|
@ -60,6 +62,7 @@
|
|||
"apertureValuesFilterDescription": "选择镜头光圈范围",
|
||||
"ndFilters": "ND 滤镜",
|
||||
"ndFiltersFilterDescription": "选择可用的 ND 滤镜",
|
||||
"shutterSpeed": "快门速度",
|
||||
"shutterSpeedValues": "快门速度",
|
||||
"shutterSpeedValue": "快门速度",
|
||||
"shutterSpeedValuesFilterDescription": "选择相机快门范围",
|
||||
|
|
|
@ -34,11 +34,10 @@ class EquipmentProfilesProvider extends StatefulWidget {
|
|||
}
|
||||
|
||||
class EquipmentProfilesProviderState extends State<EquipmentProfilesProvider> {
|
||||
final TogglableMap<EquipmentProfile> _customProfiles = {};
|
||||
final TogglableMap<IEquipmentProfile> _profiles = {};
|
||||
String _selectedId = '';
|
||||
|
||||
EquipmentProfile get _selectedProfile =>
|
||||
_customProfiles[_selectedId]?.value ?? EquipmentProfilesProvider.defaultProfile;
|
||||
IEquipmentProfile get _selectedProfile => _profiles[_selectedId]?.value ?? EquipmentProfilesProvider.defaultProfile;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -49,7 +48,7 @@ class EquipmentProfilesProviderState extends State<EquipmentProfilesProvider> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return EquipmentProfiles(
|
||||
profiles: context.isPro ? _customProfiles : {},
|
||||
profiles: context.isPro ? _profiles : {},
|
||||
selected: context.isPro ? _selectedProfile : EquipmentProfilesProvider.defaultProfile,
|
||||
child: widget.child,
|
||||
);
|
||||
|
@ -57,61 +56,91 @@ class EquipmentProfilesProviderState extends State<EquipmentProfilesProvider> {
|
|||
|
||||
Future<void> _init() async {
|
||||
_selectedId = widget.storageService.selectedEquipmentProfileId;
|
||||
_customProfiles.addAll(await widget.storageService.getEquipmentProfiles());
|
||||
_profiles
|
||||
..addAll(await widget.storageService.getEquipmentProfiles())
|
||||
..addAll(await widget.storageService.getPinholeEquipmentProfiles());
|
||||
_sortProfiles();
|
||||
_discardSelectedIfNotIncluded();
|
||||
if (mounted) setState(() {});
|
||||
widget.onInitialized?.call();
|
||||
}
|
||||
|
||||
Future<void> addProfile(EquipmentProfile profile) async {
|
||||
await widget.storageService.addEquipmentProfile(profile);
|
||||
_customProfiles[profile.id] = (value: profile, isUsed: true);
|
||||
Future<void> addProfile(IEquipmentProfile profile) async {
|
||||
switch (profile) {
|
||||
case final PinholeEquipmentProfile profile:
|
||||
await widget.storageService.addPinholeEquipmentProfile(profile);
|
||||
case final EquipmentProfile profile:
|
||||
await widget.storageService.addEquipmentProfile(profile);
|
||||
}
|
||||
_profiles[profile.id] = (value: profile, isUsed: true);
|
||||
_sortProfiles();
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
Future<void> updateProfile(EquipmentProfile profile) async {
|
||||
final oldProfile = _customProfiles[profile.id]!.value;
|
||||
await widget.storageService.updateEquipmentProfile(
|
||||
id: profile.id,
|
||||
name: oldProfile.name.changedOrNull(profile.name),
|
||||
apertureValues: oldProfile.apertureValues.changedOrNull(profile.apertureValues),
|
||||
shutterSpeedValues: oldProfile.shutterSpeedValues.changedOrNull(profile.shutterSpeedValues),
|
||||
isoValues: oldProfile.isoValues.changedOrNull(profile.isoValues),
|
||||
ndValues: oldProfile.ndValues.changedOrNull(profile.ndValues),
|
||||
lensZoom: oldProfile.lensZoom.changedOrNull(profile.lensZoom),
|
||||
exposureOffset: oldProfile.exposureOffset.changedOrNull(profile.exposureOffset),
|
||||
);
|
||||
_customProfiles[profile.id] = (value: profile, isUsed: _customProfiles[profile.id]!.isUsed);
|
||||
Future<void> updateProfile(IEquipmentProfile profile) async {
|
||||
switch (profile) {
|
||||
case final PinholeEquipmentProfile profile:
|
||||
final oldProfile = _profiles[profile.id]!.value as PinholeEquipmentProfile;
|
||||
await widget.storageService.updatePinholeEquipmentProfile(
|
||||
id: profile.id,
|
||||
name: profile.name,
|
||||
aperture: oldProfile.aperture.changedOrNull(profile.aperture),
|
||||
isoValues: oldProfile.isoValues.changedOrNull(profile.isoValues),
|
||||
ndValues: oldProfile.ndValues.changedOrNull(profile.ndValues),
|
||||
lensZoom: oldProfile.lensZoom.changedOrNull(profile.lensZoom),
|
||||
exposureOffset: oldProfile.exposureOffset.changedOrNull(profile.exposureOffset),
|
||||
);
|
||||
case final EquipmentProfile profile:
|
||||
final oldProfile = _profiles[profile.id]!.value as EquipmentProfile;
|
||||
await widget.storageService.updateEquipmentProfile(
|
||||
id: profile.id,
|
||||
name: oldProfile.name.changedOrNull(profile.name),
|
||||
apertureValues: oldProfile.apertureValues.changedOrNull(profile.apertureValues),
|
||||
shutterSpeedValues: oldProfile.shutterSpeedValues.changedOrNull(profile.shutterSpeedValues),
|
||||
isoValues: oldProfile.isoValues.changedOrNull(profile.isoValues),
|
||||
ndValues: oldProfile.ndValues.changedOrNull(profile.ndValues),
|
||||
lensZoom: oldProfile.lensZoom.changedOrNull(profile.lensZoom),
|
||||
exposureOffset: oldProfile.exposureOffset.changedOrNull(profile.exposureOffset),
|
||||
);
|
||||
}
|
||||
final bool shouldSort = _profiles[profile.id]!.value.name != profile.name;
|
||||
_profiles[profile.id] = (value: profile, isUsed: _profiles[profile.id]!.isUsed);
|
||||
if (shouldSort) _sortProfiles();
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
Future<void> deleteProfile(EquipmentProfile profile) async {
|
||||
await widget.storageService.deleteEquipmentProfile(profile.id);
|
||||
Future<void> deleteProfile(IEquipmentProfile profile) async {
|
||||
if (profile.id == _selectedId) {
|
||||
_selectedId = EquipmentProfilesProvider.defaultProfile.id;
|
||||
widget.storageService.selectedEquipmentProfileId = EquipmentProfilesProvider.defaultProfile.id;
|
||||
}
|
||||
_customProfiles.remove(profile.id);
|
||||
switch (profile) {
|
||||
case final PinholeEquipmentProfile profile:
|
||||
await widget.storageService.deletePinholeEquipmentProfile(profile.id);
|
||||
case final EquipmentProfile profile:
|
||||
await widget.storageService.deleteEquipmentProfile(profile.id);
|
||||
}
|
||||
_profiles.remove(profile.id);
|
||||
_discardSelectedIfNotIncluded();
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void selectProfile(EquipmentProfile data) {
|
||||
if (_selectedId != data.id) {
|
||||
void selectProfile(String id) {
|
||||
if (_selectedId != id) {
|
||||
setState(() {
|
||||
_selectedId = data.id;
|
||||
_selectedId = id;
|
||||
});
|
||||
widget.storageService.selectedEquipmentProfileId = _selectedProfile.id;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> toggleProfile(EquipmentProfile profile, bool enabled) async {
|
||||
if (_customProfiles.containsKey(profile.id)) {
|
||||
_customProfiles[profile.id] = (value: profile, isUsed: enabled);
|
||||
Future<void> toggleProfile(String id, bool enabled) async {
|
||||
if (_profiles.containsKey(id)) {
|
||||
_profiles[id] = (value: _profiles[id]!.value, isUsed: enabled);
|
||||
await widget.storageService.updateEquipmentProfile(id: id, isUsed: enabled);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
await widget.storageService.updateEquipmentProfile(id: profile.id, isUsed: enabled);
|
||||
_discardSelectedIfNotIncluded();
|
||||
setState(() {});
|
||||
}
|
||||
|
@ -120,12 +149,19 @@ class EquipmentProfilesProviderState extends State<EquipmentProfilesProvider> {
|
|||
if (_selectedId == EquipmentProfilesProvider.defaultProfile.id) {
|
||||
return;
|
||||
}
|
||||
final isSelectedUsed = _customProfiles[_selectedId]?.isUsed ?? false;
|
||||
final isSelectedUsed = _profiles[_selectedId]?.isUsed ?? false;
|
||||
if (!isSelectedUsed) {
|
||||
_selectedId = EquipmentProfilesProvider.defaultProfile.id;
|
||||
widget.storageService.selectedEquipmentProfileId = _selectedId;
|
||||
}
|
||||
}
|
||||
|
||||
void _sortProfiles() {
|
||||
final sortedByName = _profiles.values.toList(growable: false)
|
||||
..sort((a, b) => a.value.name.toLowerCase().compareTo(b.value.name.toLowerCase()));
|
||||
_profiles.clear();
|
||||
_profiles.addEntries(sortedByName.map((e) => MapEntry(e.value.id, e)));
|
||||
}
|
||||
}
|
||||
|
||||
enum _EquipmentProfilesModelAspect {
|
||||
|
@ -135,8 +171,8 @@ enum _EquipmentProfilesModelAspect {
|
|||
}
|
||||
|
||||
class EquipmentProfiles extends InheritedModel<_EquipmentProfilesModelAspect> {
|
||||
final TogglableMap<EquipmentProfile> profiles;
|
||||
final EquipmentProfile selected;
|
||||
final TogglableMap<IEquipmentProfile> profiles;
|
||||
final IEquipmentProfile selected;
|
||||
|
||||
const EquipmentProfiles({
|
||||
required this.profiles,
|
||||
|
@ -145,10 +181,10 @@ class EquipmentProfiles extends InheritedModel<_EquipmentProfilesModelAspect> {
|
|||
});
|
||||
|
||||
/// _default + profiles create by the user
|
||||
static List<EquipmentProfile> of(BuildContext context) {
|
||||
static List<IEquipmentProfile> of(BuildContext context) {
|
||||
final model =
|
||||
InheritedModel.inheritFrom<EquipmentProfiles>(context, aspect: _EquipmentProfilesModelAspect.profiles)!;
|
||||
return List<EquipmentProfile>.from(
|
||||
return List<IEquipmentProfile>.from(
|
||||
[
|
||||
EquipmentProfilesProvider.defaultProfile,
|
||||
...model.profiles.values.map((p) => p.value),
|
||||
|
@ -156,10 +192,10 @@ class EquipmentProfiles extends InheritedModel<_EquipmentProfilesModelAspect> {
|
|||
);
|
||||
}
|
||||
|
||||
static List<EquipmentProfile> inUseOf(BuildContext context) {
|
||||
static List<IEquipmentProfile> inUseOf(BuildContext context) {
|
||||
final model =
|
||||
InheritedModel.inheritFrom<EquipmentProfiles>(context, aspect: _EquipmentProfilesModelAspect.profilesInUse)!;
|
||||
return List<EquipmentProfile>.from(
|
||||
return List<IEquipmentProfile>.from(
|
||||
[
|
||||
EquipmentProfilesProvider.defaultProfile,
|
||||
...model.profiles.values.where((p) => p.isUsed).map((p) => p.value),
|
||||
|
@ -167,7 +203,7 @@ class EquipmentProfiles extends InheritedModel<_EquipmentProfilesModelAspect> {
|
|||
);
|
||||
}
|
||||
|
||||
static EquipmentProfile selectedOf(BuildContext context) {
|
||||
static IEquipmentProfile selectedOf(BuildContext context) {
|
||||
return InheritedModel.inheritFrom<EquipmentProfiles>(context, aspect: _EquipmentProfilesModelAspect.selected)!
|
||||
.selected;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:lightmeter/providers/equipment_profile_provider.dart';
|
||||
import 'package:lightmeter/screens/equipment_profile_edit/event_equipment_profile_edit.dart';
|
||||
|
@ -5,189 +6,237 @@ import 'package:lightmeter/screens/equipment_profile_edit/state_equipment_profil
|
|||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class EquipmentProfileEditBloc extends Bloc<EquipmentProfileEditEvent, EquipmentProfileEditState> {
|
||||
sealed class IEquipmentProfileEditBloc<T extends IEquipmentProfile>
|
||||
extends Bloc<IEquipmentProfileEditEvent<T>, EquipmentProfileEditState<T>> {
|
||||
@protected
|
||||
final EquipmentProfilesProviderState profilesProvider;
|
||||
final EquipmentProfile _originalEquipmentProfile;
|
||||
EquipmentProfile _newEquipmentProfile;
|
||||
final bool _isEdit;
|
||||
@protected
|
||||
final T originalEquipmentProfile;
|
||||
@protected
|
||||
final bool isEdit;
|
||||
|
||||
factory EquipmentProfileEditBloc(
|
||||
EquipmentProfilesProviderState profilesProvider, {
|
||||
required EquipmentProfile? profile,
|
||||
required bool isEdit,
|
||||
}) =>
|
||||
profile != null
|
||||
? EquipmentProfileEditBloc._(
|
||||
profilesProvider,
|
||||
profile,
|
||||
isEdit,
|
||||
)
|
||||
: EquipmentProfileEditBloc._(
|
||||
profilesProvider,
|
||||
EquipmentProfilesProvider.defaultProfile,
|
||||
isEdit,
|
||||
);
|
||||
|
||||
EquipmentProfileEditBloc._(
|
||||
this.profilesProvider,
|
||||
EquipmentProfile profile,
|
||||
this._isEdit,
|
||||
) : _originalEquipmentProfile = profile,
|
||||
_newEquipmentProfile = profile,
|
||||
IEquipmentProfileEditBloc(
|
||||
this.profilesProvider, {
|
||||
required T profile,
|
||||
required this.isEdit,
|
||||
}) : originalEquipmentProfile = profile,
|
||||
super(
|
||||
EquipmentProfileEditState(
|
||||
name: profile.name,
|
||||
apertureValues: profile.apertureValues,
|
||||
shutterSpeedValues: profile.shutterSpeedValues,
|
||||
isoValues: profile.isoValues,
|
||||
ndValues: profile.ndValues,
|
||||
lensZoom: profile.lensZoom,
|
||||
exposureOffset: profile.exposureOffset,
|
||||
canSave: false,
|
||||
EquipmentProfileEditState<T>(
|
||||
profile: profile,
|
||||
hasChanges: false,
|
||||
isValid: true,
|
||||
),
|
||||
) {
|
||||
on<EquipmentProfileEditEvent>(
|
||||
(event, emit) async {
|
||||
switch (event) {
|
||||
case final EquipmentProfileNameChangedEvent e:
|
||||
await _onNameChanged(e, emit);
|
||||
case final EquipmentProfileApertureValuesChangedEvent e:
|
||||
await _onApertureValuesChanged(e, emit);
|
||||
case final EquipmentProfileShutterSpeedValuesChangedEvent e:
|
||||
await _onShutterSpeedValuesChanged(e, emit);
|
||||
case final EquipmentProfileIsoValuesChangedEvent e:
|
||||
await _onIsoValuesChanged(e, emit);
|
||||
case final EquipmentProfileNdValuesChangedEvent e:
|
||||
await _onNdValuesChanged(e, emit);
|
||||
case final EquipmentProfileLensZoomChangedEvent e:
|
||||
await _onLensZoomChanged(e, emit);
|
||||
case final EquipmentProfileExposureOffsetChangedEvent e:
|
||||
await _onExposureOffsetChanged(e, emit);
|
||||
case EquipmentProfileSaveEvent():
|
||||
await _onSave(event, emit);
|
||||
case EquipmentProfileCopyEvent():
|
||||
await _onCopy(event, emit);
|
||||
case EquipmentProfileDeleteEvent():
|
||||
await _onDelete(event, emit);
|
||||
}
|
||||
},
|
||||
on<IEquipmentProfileEditEvent<T>>(mapEventToState);
|
||||
}
|
||||
|
||||
@protected
|
||||
@mustCallSuper
|
||||
Future<void> mapEventToState(IEquipmentProfileEditEvent<T> event, Emitter emit) async {
|
||||
switch (event) {
|
||||
case EquipmentProfileSaveEvent():
|
||||
await _onSave(event, emit);
|
||||
case EquipmentProfileCopyEvent():
|
||||
await _onCopy(event, emit);
|
||||
case EquipmentProfileDeleteEvent():
|
||||
await _onDelete(event, emit);
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
Future<T> createProfile(String id);
|
||||
|
||||
@protected
|
||||
void emitProfile(T profile, Emitter emit) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
profile: profile,
|
||||
hasChanges: _hasChanges(profile),
|
||||
isValid: _isValid(profile),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onSave(EquipmentProfileSaveEvent<T> _, Emitter emit) async {
|
||||
emit(state.copyWith(isLoading: true));
|
||||
if (isEdit) {
|
||||
final newProfile = await createProfile(originalEquipmentProfile.id);
|
||||
assert(
|
||||
newProfile.id == originalEquipmentProfile.id,
|
||||
'The edited profile id must be the same as the original profile id',
|
||||
);
|
||||
await profilesProvider.updateProfile(newProfile);
|
||||
} else {
|
||||
final newId = const Uuid().v1();
|
||||
final newProfile = await createProfile(newId);
|
||||
assert(
|
||||
newProfile.id == newId,
|
||||
'A profile to be added must have a unique id.',
|
||||
);
|
||||
await profilesProvider.addProfile(newProfile);
|
||||
}
|
||||
emit(state.copyWith(isLoading: false));
|
||||
}
|
||||
|
||||
Future<void> _onCopy(EquipmentProfileCopyEvent<T> _, Emitter emit) async {
|
||||
emit(state.copyWith(isLoading: true));
|
||||
emit(state.copyWith(isLoading: false, profileToCopy: state.profile));
|
||||
}
|
||||
|
||||
Future<void> _onDelete(EquipmentProfileDeleteEvent<T> _, Emitter emit) async {
|
||||
emit(state.copyWith(isLoading: true));
|
||||
await profilesProvider.deleteProfile(originalEquipmentProfile);
|
||||
emit(state.copyWith(isLoading: false));
|
||||
}
|
||||
|
||||
bool _hasChanges(T profile) => profile != originalEquipmentProfile;
|
||||
|
||||
bool _isValid(T profile) => profile.name.isNotEmpty;
|
||||
}
|
||||
|
||||
class EquipmentProfileEditBloc extends IEquipmentProfileEditBloc<EquipmentProfile> {
|
||||
EquipmentProfileEditBloc(
|
||||
super.profilesProvider, {
|
||||
required super.profile,
|
||||
required super.isEdit,
|
||||
});
|
||||
|
||||
@override
|
||||
Future<void> mapEventToState(IEquipmentProfileEditEvent<EquipmentProfile> event, Emitter emit) async {
|
||||
switch (event) {
|
||||
case final EquipmentProfileNameChangedEvent e:
|
||||
await _onNameChanged(e, emit);
|
||||
case final EquipmentProfileApertureValuesChangedEvent e:
|
||||
await _onApertureValuesChanged(e, emit);
|
||||
case final EquipmentProfileShutterSpeedValuesChangedEvent e:
|
||||
await _onShutterSpeedValuesChanged(e, emit);
|
||||
case final EquipmentProfileIsoValuesChangedEvent e:
|
||||
await _onIsoValuesChanged(e, emit);
|
||||
case final EquipmentProfileNdValuesChangedEvent e:
|
||||
await _onNdValuesChanged(e, emit);
|
||||
case final EquipmentProfileLensZoomChangedEvent e:
|
||||
await _onLensZoomChanged(e, emit);
|
||||
case final EquipmentProfileExposureOffsetChangedEvent e:
|
||||
await _onExposureOffsetChanged(e, emit);
|
||||
default:
|
||||
return super.mapEventToState(event, emit);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<EquipmentProfile> createProfile(String id) async {
|
||||
return EquipmentProfile(
|
||||
id: id,
|
||||
name: state.profile.name,
|
||||
apertureValues: state.profile.apertureValues,
|
||||
shutterSpeedValues: state.profile.shutterSpeedValues,
|
||||
isoValues: state.profile.isoValues,
|
||||
ndValues: state.profile.ndValues,
|
||||
lensZoom: state.profile.lensZoom,
|
||||
exposureOffset: state.profile.exposureOffset,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onNameChanged(EquipmentProfileNameChangedEvent event, Emitter emit) async {
|
||||
_newEquipmentProfile = _newEquipmentProfile.copyWith(name: event.name);
|
||||
emit(
|
||||
state.copyWith(
|
||||
name: event.name,
|
||||
canSave: _canSave(event.name, state.lensZoom),
|
||||
),
|
||||
);
|
||||
emitProfile(state.profile.copyWith(name: event.name), emit);
|
||||
}
|
||||
|
||||
Future<void> _onApertureValuesChanged(EquipmentProfileApertureValuesChangedEvent event, Emitter emit) async {
|
||||
_newEquipmentProfile = _newEquipmentProfile.copyWith(apertureValues: event.apertureValues);
|
||||
emit(
|
||||
state.copyWith(
|
||||
apertureValues: event.apertureValues,
|
||||
canSave: _canSave(state.name, state.lensZoom),
|
||||
),
|
||||
);
|
||||
emitProfile(state.profile.copyWith(apertureValues: event.apertureValues), emit);
|
||||
}
|
||||
|
||||
Future<void> _onShutterSpeedValuesChanged(EquipmentProfileShutterSpeedValuesChangedEvent event, Emitter emit) async {
|
||||
_newEquipmentProfile = _newEquipmentProfile.copyWith(shutterSpeedValues: event.shutterSpeedValues);
|
||||
emitProfile(state.profile.copyWith(shutterSpeedValues: event.shutterSpeedValues), emit);
|
||||
}
|
||||
|
||||
Future<void> _onIsoValuesChanged(EquipmentProfileIsoValuesChangedEvent event, Emitter emit) async {
|
||||
emitProfile(state.profile.copyWith(isoValues: event.isoValues), emit);
|
||||
}
|
||||
|
||||
Future<void> _onNdValuesChanged(EquipmentProfileNdValuesChangedEvent event, Emitter emit) async {
|
||||
emitProfile(state.profile.copyWith(ndValues: event.ndValues), emit);
|
||||
}
|
||||
|
||||
Future<void> _onLensZoomChanged(EquipmentProfileLensZoomChangedEvent event, Emitter emit) async {
|
||||
emitProfile(state.profile.copyWith(lensZoom: event.lensZoom), emit);
|
||||
}
|
||||
|
||||
Future<void> _onExposureOffsetChanged(EquipmentProfileExposureOffsetChangedEvent event, Emitter emit) async {
|
||||
emitProfile(state.profile.copyWith(exposureOffset: event.exposureOffset), emit);
|
||||
}
|
||||
}
|
||||
|
||||
class PinholeEquipmentProfileEditBloc extends IEquipmentProfileEditBloc<PinholeEquipmentProfile> {
|
||||
PinholeEquipmentProfileEditBloc(
|
||||
super.profilesProvider, {
|
||||
required super.profile,
|
||||
required super.isEdit,
|
||||
});
|
||||
|
||||
@override
|
||||
Future<void> mapEventToState(IEquipmentProfileEditEvent<PinholeEquipmentProfile> event, Emitter emit) async {
|
||||
switch (event) {
|
||||
case final EquipmentProfileNameChangedEvent e:
|
||||
await _onNameChanged(e, emit);
|
||||
case final EquipmentProfileApertureValueChangedEvent e:
|
||||
await _onApertureValuesChanged(e, emit);
|
||||
case final EquipmentProfileIsoValuesChangedEvent e:
|
||||
await _onIsoValuesChanged(e, emit);
|
||||
case final EquipmentProfileNdValuesChangedEvent e:
|
||||
await _onNdValuesChanged(e, emit);
|
||||
case final EquipmentProfileLensZoomChangedEvent e:
|
||||
await _onLensZoomChanged(e, emit);
|
||||
case final EquipmentProfileExposureOffsetChangedEvent e:
|
||||
await _onExposureOffsetChanged(e, emit);
|
||||
default:
|
||||
return super.mapEventToState(event, emit);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PinholeEquipmentProfile> createProfile(String id) async {
|
||||
return PinholeEquipmentProfile(
|
||||
id: id,
|
||||
name: state.profile.name,
|
||||
aperture: state.profile.aperture,
|
||||
isoValues: state.profile.isoValues,
|
||||
ndValues: state.profile.ndValues,
|
||||
lensZoom: state.profile.lensZoom,
|
||||
exposureOffset: state.profile.exposureOffset,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onNameChanged(EquipmentProfileNameChangedEvent event, Emitter emit) async {
|
||||
emitProfile(state.profile.copyWith(name: event.name), emit);
|
||||
}
|
||||
|
||||
Future<void> _onApertureValuesChanged(EquipmentProfileApertureValueChangedEvent event, Emitter emit) async {
|
||||
emitProfile(state.profile.copyWith(aperture: event.aperture ?? 0), emit);
|
||||
|
||||
final profile = state.profile.copyWith(aperture: event.aperture);
|
||||
emit(
|
||||
state.copyWith(
|
||||
shutterSpeedValues: event.shutterSpeedValues,
|
||||
canSave: _canSave(state.name, state.lensZoom),
|
||||
profile: profile,
|
||||
hasChanges: _hasChanges(profile),
|
||||
isValid: _isValid(profile) && event.aperture != null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onIsoValuesChanged(EquipmentProfileIsoValuesChangedEvent event, Emitter emit) async {
|
||||
_newEquipmentProfile = _newEquipmentProfile.copyWith(isoValues: event.isoValues);
|
||||
emit(
|
||||
state.copyWith(
|
||||
isoValues: event.isoValues,
|
||||
canSave: _canSave(state.name, state.lensZoom),
|
||||
),
|
||||
);
|
||||
emitProfile(state.profile.copyWith(isoValues: event.isoValues), emit);
|
||||
}
|
||||
|
||||
Future<void> _onNdValuesChanged(EquipmentProfileNdValuesChangedEvent event, Emitter emit) async {
|
||||
_newEquipmentProfile = _newEquipmentProfile.copyWith(ndValues: event.ndValues);
|
||||
emit(
|
||||
state.copyWith(
|
||||
ndValues: event.ndValues,
|
||||
canSave: _canSave(state.name, state.lensZoom),
|
||||
),
|
||||
);
|
||||
emitProfile(state.profile.copyWith(ndValues: event.ndValues), emit);
|
||||
}
|
||||
|
||||
Future<void> _onLensZoomChanged(EquipmentProfileLensZoomChangedEvent event, Emitter emit) async {
|
||||
_newEquipmentProfile = _newEquipmentProfile.copyWith(lensZoom: event.lensZoom);
|
||||
emit(
|
||||
state.copyWith(
|
||||
lensZoom: event.lensZoom,
|
||||
canSave: _canSave(state.name, event.lensZoom),
|
||||
),
|
||||
);
|
||||
emitProfile(state.profile.copyWith(lensZoom: event.lensZoom), emit);
|
||||
}
|
||||
|
||||
Future<void> _onExposureOffsetChanged(EquipmentProfileExposureOffsetChangedEvent event, Emitter emit) async {
|
||||
_newEquipmentProfile = _newEquipmentProfile.copyWith(exposureOffset: event.exposureOffset);
|
||||
emit(
|
||||
state.copyWith(
|
||||
exposureOffset: event.exposureOffset,
|
||||
canSave: _canSave(state.name, event.exposureOffset),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onSave(EquipmentProfileSaveEvent _, Emitter emit) async {
|
||||
emit(state.copyWith(isLoading: true));
|
||||
if (_isEdit) {
|
||||
await profilesProvider.updateProfile(
|
||||
EquipmentProfile(
|
||||
id: _originalEquipmentProfile.id,
|
||||
name: state.name,
|
||||
apertureValues: state.apertureValues,
|
||||
ndValues: state.ndValues,
|
||||
shutterSpeedValues: state.shutterSpeedValues,
|
||||
isoValues: state.isoValues,
|
||||
lensZoom: state.lensZoom,
|
||||
exposureOffset: state.exposureOffset,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await profilesProvider.addProfile(
|
||||
EquipmentProfile(
|
||||
id: const Uuid().v1(),
|
||||
name: state.name,
|
||||
apertureValues: state.apertureValues,
|
||||
ndValues: state.ndValues,
|
||||
shutterSpeedValues: state.shutterSpeedValues,
|
||||
isoValues: state.isoValues,
|
||||
lensZoom: state.lensZoom,
|
||||
exposureOffset: state.exposureOffset,
|
||||
),
|
||||
);
|
||||
}
|
||||
emit(state.copyWith(isLoading: false));
|
||||
}
|
||||
|
||||
Future<void> _onCopy(EquipmentProfileCopyEvent _, Emitter emit) async {
|
||||
emit(state.copyWith(isLoading: true));
|
||||
emit(state.copyWith(isLoading: false, profileToCopy: _originalEquipmentProfile));
|
||||
}
|
||||
|
||||
Future<void> _onDelete(EquipmentProfileDeleteEvent _, Emitter emit) async {
|
||||
emit(state.copyWith(isLoading: true));
|
||||
await profilesProvider.deleteProfile(_newEquipmentProfile);
|
||||
emit(state.copyWith(isLoading: false));
|
||||
}
|
||||
|
||||
bool _canSave(String name, double? lensZoom) {
|
||||
return name.isNotEmpty && _newEquipmentProfile != _originalEquipmentProfile;
|
||||
emitProfile(state.profile.copyWith(exposureOffset: event.exposureOffset), emit);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
import 'package:lightmeter/screens/shared/text_field/widget_text_field.dart';
|
||||
|
||||
class EquipmentProfileApertureInput extends StatefulWidget {
|
||||
final double? value;
|
||||
final ValueChanged<double?> onChanged;
|
||||
|
||||
const EquipmentProfileApertureInput({
|
||||
super.key,
|
||||
required this.value,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
State<EquipmentProfileApertureInput> createState() => _EquipmentProfileApertureInputState();
|
||||
}
|
||||
|
||||
class _EquipmentProfileApertureInputState extends State<EquipmentProfileApertureInput> {
|
||||
TextStyle get style =>
|
||||
Theme.of(context).textTheme.bodyMedium!.copyWith(color: Theme.of(context).listTileTheme.textColor);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.camera),
|
||||
title: Text(
|
||||
S.of(context).apertureValue,
|
||||
style: Theme.of(context).listTileTheme.titleTextStyle,
|
||||
),
|
||||
trailing: SizedBox(
|
||||
width: _textInputWidth(context),
|
||||
child: LightmeterTextField(
|
||||
initialValue: widget.value?.toString() ?? '',
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp("[0-9.]")),
|
||||
LengthLimitingTextInputFormatter(6),
|
||||
],
|
||||
onChanged: (value) {
|
||||
final parsed = double.tryParse(value);
|
||||
widget.onChanged(parsed != null && parsed > 0 ? parsed : null);
|
||||
},
|
||||
validator: (value) {
|
||||
final parsed = double.tryParse(value);
|
||||
if (parsed != null && parsed > 0) {
|
||||
return null;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
textAlign: TextAlign.end,
|
||||
style: style,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
double _textInputWidth(BuildContext context) {
|
||||
final textPainter = TextPainter(
|
||||
text: TextSpan(text: widget.value.toString(), style: style),
|
||||
maxLines: 1,
|
||||
textDirection: TextDirection.ltr,
|
||||
)..layout();
|
||||
return textPainter.maxIntrinsicWidth + Dimens.grid4;
|
||||
}
|
||||
}
|
|
@ -1,59 +1,65 @@
|
|||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
|
||||
sealed class EquipmentProfileEditEvent {
|
||||
const EquipmentProfileEditEvent();
|
||||
sealed class IEquipmentProfileEditEvent<T extends IEquipmentProfile> {
|
||||
const IEquipmentProfileEditEvent();
|
||||
}
|
||||
|
||||
class EquipmentProfileNameChangedEvent extends EquipmentProfileEditEvent {
|
||||
class EquipmentProfileNameChangedEvent<T extends IEquipmentProfile> extends IEquipmentProfileEditEvent<T> {
|
||||
final String name;
|
||||
|
||||
const EquipmentProfileNameChangedEvent(this.name);
|
||||
}
|
||||
|
||||
class EquipmentProfileIsoValuesChangedEvent extends EquipmentProfileEditEvent {
|
||||
class EquipmentProfileIsoValuesChangedEvent<T extends IEquipmentProfile> extends IEquipmentProfileEditEvent<T> {
|
||||
final List<IsoValue> isoValues;
|
||||
|
||||
const EquipmentProfileIsoValuesChangedEvent(this.isoValues);
|
||||
}
|
||||
|
||||
class EquipmentProfileNdValuesChangedEvent extends EquipmentProfileEditEvent {
|
||||
class EquipmentProfileNdValuesChangedEvent<T extends IEquipmentProfile> extends IEquipmentProfileEditEvent<T> {
|
||||
final List<NdValue> ndValues;
|
||||
|
||||
const EquipmentProfileNdValuesChangedEvent(this.ndValues);
|
||||
}
|
||||
|
||||
class EquipmentProfileApertureValuesChangedEvent extends EquipmentProfileEditEvent {
|
||||
class EquipmentProfileApertureValuesChangedEvent extends IEquipmentProfileEditEvent<EquipmentProfile> {
|
||||
final List<ApertureValue> apertureValues;
|
||||
|
||||
const EquipmentProfileApertureValuesChangedEvent(this.apertureValues);
|
||||
}
|
||||
|
||||
class EquipmentProfileShutterSpeedValuesChangedEvent extends EquipmentProfileEditEvent {
|
||||
class EquipmentProfileApertureValueChangedEvent extends IEquipmentProfileEditEvent<PinholeEquipmentProfile> {
|
||||
final double? aperture;
|
||||
|
||||
const EquipmentProfileApertureValueChangedEvent(this.aperture);
|
||||
}
|
||||
|
||||
class EquipmentProfileShutterSpeedValuesChangedEvent extends IEquipmentProfileEditEvent<EquipmentProfile> {
|
||||
final List<ShutterSpeedValue> shutterSpeedValues;
|
||||
|
||||
const EquipmentProfileShutterSpeedValuesChangedEvent(this.shutterSpeedValues);
|
||||
}
|
||||
|
||||
class EquipmentProfileLensZoomChangedEvent extends EquipmentProfileEditEvent {
|
||||
class EquipmentProfileLensZoomChangedEvent<T extends IEquipmentProfile> extends IEquipmentProfileEditEvent<T> {
|
||||
final double lensZoom;
|
||||
|
||||
const EquipmentProfileLensZoomChangedEvent(this.lensZoom);
|
||||
}
|
||||
|
||||
class EquipmentProfileExposureOffsetChangedEvent extends EquipmentProfileEditEvent {
|
||||
class EquipmentProfileExposureOffsetChangedEvent<T extends IEquipmentProfile> extends IEquipmentProfileEditEvent<T> {
|
||||
final double exposureOffset;
|
||||
|
||||
const EquipmentProfileExposureOffsetChangedEvent(this.exposureOffset);
|
||||
}
|
||||
|
||||
class EquipmentProfileSaveEvent extends EquipmentProfileEditEvent {
|
||||
class EquipmentProfileSaveEvent<T extends IEquipmentProfile> extends IEquipmentProfileEditEvent<T> {
|
||||
const EquipmentProfileSaveEvent();
|
||||
}
|
||||
|
||||
class EquipmentProfileCopyEvent extends EquipmentProfileEditEvent {
|
||||
class EquipmentProfileCopyEvent<T extends IEquipmentProfile> extends IEquipmentProfileEditEvent<T> {
|
||||
const EquipmentProfileCopyEvent();
|
||||
}
|
||||
|
||||
class EquipmentProfileDeleteEvent extends EquipmentProfileEditEvent {
|
||||
class EquipmentProfileDeleteEvent<T extends IEquipmentProfile> extends IEquipmentProfileEditEvent<T> {
|
||||
const EquipmentProfileDeleteEvent();
|
||||
}
|
||||
|
|
|
@ -7,11 +7,14 @@ import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
|||
|
||||
enum EquipmentProfileEditType { add, edit }
|
||||
|
||||
class EquipmentProfileEditArgs {
|
||||
class EquipmentProfileEditArgs<T extends IEquipmentProfile> {
|
||||
final EquipmentProfileEditType editType;
|
||||
final EquipmentProfile? profile;
|
||||
final T profile;
|
||||
|
||||
const EquipmentProfileEditArgs({required this.editType, this.profile});
|
||||
const EquipmentProfileEditArgs({
|
||||
required this.editType,
|
||||
required this.profile,
|
||||
});
|
||||
}
|
||||
|
||||
class EquipmentProfileEditFlow extends StatelessWidget {
|
||||
|
@ -25,13 +28,50 @@ class EquipmentProfileEditFlow extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (_) => EquipmentProfileEditBloc(
|
||||
EquipmentProfilesProvider.of(context),
|
||||
profile: args.profile,
|
||||
isEdit: _isEdit,
|
||||
switch (args.profile) {
|
||||
case final EquipmentProfile profile:
|
||||
return _IEquipmentProfileBlocProvider<EquipmentProfile, EquipmentProfileEditBloc>(
|
||||
create: (_) => EquipmentProfileEditBloc(
|
||||
EquipmentProfilesProvider.of(context),
|
||||
profile: profile,
|
||||
isEdit: _isEdit,
|
||||
),
|
||||
isEdit: _isEdit,
|
||||
);
|
||||
case final PinholeEquipmentProfile profile:
|
||||
return _IEquipmentProfileBlocProvider<PinholeEquipmentProfile, PinholeEquipmentProfileEditBloc>(
|
||||
create: (_) => PinholeEquipmentProfileEditBloc(
|
||||
EquipmentProfilesProvider.of(context),
|
||||
profile: profile,
|
||||
isEdit: _isEdit,
|
||||
),
|
||||
isEdit: _isEdit,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _IEquipmentProfileBlocProvider<T extends IEquipmentProfile, V extends IEquipmentProfileEditBloc<T>>
|
||||
extends StatelessWidget {
|
||||
final V Function(BuildContext context) create;
|
||||
final bool isEdit;
|
||||
|
||||
const _IEquipmentProfileBlocProvider({
|
||||
required this.create,
|
||||
required this.isEdit,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<IEquipmentProfileEditBloc<T>>(
|
||||
create: create,
|
||||
child: Builder(
|
||||
builder: (context) => BlocProvider<V>.value(
|
||||
value: context.read<IEquipmentProfileEditBloc<T>>() as V,
|
||||
child: EquipmentProfileEditScreen<T>(isEdit: isEdit),
|
||||
),
|
||||
),
|
||||
child: EquipmentProfileEditScreen(isEdit: _isEdit),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'package:lightmeter/generated/l10n.dart';
|
|||
import 'package:lightmeter/navigation/routes.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
import 'package:lightmeter/screens/equipment_profile_edit/bloc_equipment_profile_edit.dart';
|
||||
import 'package:lightmeter/screens/equipment_profile_edit/components/aperture_input/widget_input_aperture_equipment_profile.dart';
|
||||
import 'package:lightmeter/screens/equipment_profile_edit/components/filter_list_tile/widget_list_tile_filter.dart';
|
||||
import 'package:lightmeter/screens/equipment_profile_edit/components/range_picker_list_tile/widget_list_tile_range_picker.dart';
|
||||
import 'package:lightmeter/screens/equipment_profile_edit/components/slider_picker_list_tile/widget_list_tile_slider_picker.dart';
|
||||
|
@ -17,7 +18,7 @@ import 'package:lightmeter/utils/double_to_zoom.dart';
|
|||
import 'package:lightmeter/utils/to_string_signed.dart';
|
||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
|
||||
class EquipmentProfileEditScreen extends StatefulWidget {
|
||||
class EquipmentProfileEditScreen<T extends IEquipmentProfile> extends StatefulWidget {
|
||||
final bool isEdit;
|
||||
|
||||
const EquipmentProfileEditScreen({
|
||||
|
@ -26,13 +27,13 @@ class EquipmentProfileEditScreen extends StatefulWidget {
|
|||
});
|
||||
|
||||
@override
|
||||
State<EquipmentProfileEditScreen> createState() => _EquipmentProfileEditScreenState();
|
||||
State<EquipmentProfileEditScreen<T>> createState() => _EquipmentProfileEditScreenState<T>();
|
||||
}
|
||||
|
||||
class _EquipmentProfileEditScreenState extends State<EquipmentProfileEditScreen> {
|
||||
class _EquipmentProfileEditScreenState<T extends IEquipmentProfile> extends State<EquipmentProfileEditScreen<T>> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocConsumer<EquipmentProfileEditBloc, EquipmentProfileEditState>(
|
||||
return BlocConsumer<IEquipmentProfileEditBloc<T>, EquipmentProfileEditState<T>>(
|
||||
listenWhen: (previous, current) => previous.isLoading != current.isLoading,
|
||||
listener: (context, state) {
|
||||
if (state.isLoading) {
|
||||
|
@ -41,9 +42,9 @@ class _EquipmentProfileEditScreenState extends State<EquipmentProfileEditScreen>
|
|||
if (state.profileToCopy != null) {
|
||||
Navigator.of(context).pushReplacementNamed(
|
||||
NavigationRoutes.equipmentProfileEditScreen.name,
|
||||
arguments: EquipmentProfileEditArgs(
|
||||
arguments: EquipmentProfileEditArgs<T>(
|
||||
editType: EquipmentProfileEditType.add,
|
||||
profile: state.profileToCopy,
|
||||
profile: state.profileToCopy!,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
|
@ -57,33 +58,34 @@ class _EquipmentProfileEditScreenState extends State<EquipmentProfileEditScreen>
|
|||
child: SliverScreen(
|
||||
title: Text(widget.isEdit ? S.of(context).editEquipmentProfileTitle : S.of(context).addEquipmentProfileTitle),
|
||||
appBarActions: [
|
||||
BlocBuilder<EquipmentProfileEditBloc, EquipmentProfileEditState>(
|
||||
buildWhen: (previous, current) => previous.canSave != current.canSave,
|
||||
BlocBuilder<IEquipmentProfileEditBloc<T>, EquipmentProfileEditState<T>>(
|
||||
buildWhen: (previous, current) =>
|
||||
previous.hasChanges != current.hasChanges || previous.isValid != current.isValid,
|
||||
builder: (context, state) => IconButton(
|
||||
onPressed: state.canSave
|
||||
onPressed: state.hasChanges && state.isValid
|
||||
? () {
|
||||
context.read<EquipmentProfileEditBloc>().add(const EquipmentProfileSaveEvent());
|
||||
context.read<IEquipmentProfileEditBloc<T>>().add(const EquipmentProfileSaveEvent());
|
||||
}
|
||||
: null,
|
||||
icon: const Icon(Icons.save_outlined),
|
||||
),
|
||||
),
|
||||
if (widget.isEdit)
|
||||
BlocBuilder<EquipmentProfileEditBloc, EquipmentProfileEditState>(
|
||||
buildWhen: (previous, current) => previous.canSave != current.canSave,
|
||||
BlocBuilder<IEquipmentProfileEditBloc<T>, EquipmentProfileEditState<T>>(
|
||||
buildWhen: (previous, current) => previous.isValid != current.isValid,
|
||||
builder: (context, state) => IconButton(
|
||||
onPressed: state.canSave
|
||||
? null
|
||||
: () {
|
||||
context.read<EquipmentProfileEditBloc>().add(const EquipmentProfileCopyEvent());
|
||||
},
|
||||
onPressed: state.isValid
|
||||
? () {
|
||||
context.read<IEquipmentProfileEditBloc<T>>().add(const EquipmentProfileCopyEvent());
|
||||
}
|
||||
: null,
|
||||
icon: const Icon(Icons.copy_outlined),
|
||||
),
|
||||
),
|
||||
if (widget.isEdit)
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
context.read<EquipmentProfileEditBloc>().add(const EquipmentProfileDeleteEvent());
|
||||
context.read<IEquipmentProfileEditBloc<T>>().add(const EquipmentProfileDeleteEvent());
|
||||
},
|
||||
icon: const Icon(Icons.delete_outlined),
|
||||
),
|
||||
|
@ -102,15 +104,19 @@ class _EquipmentProfileEditScreenState extends State<EquipmentProfileEditScreen>
|
|||
padding: const EdgeInsets.symmetric(vertical: Dimens.paddingM),
|
||||
child: Opacity(
|
||||
opacity: state.isLoading ? Dimens.disabledOpacity : Dimens.enabledOpacity,
|
||||
child: const Column(
|
||||
child: Column(
|
||||
children: [
|
||||
_NameFieldBuilder(),
|
||||
_IsoValuesListTileBuilder(),
|
||||
_NdValuesListTileBuilder(),
|
||||
_ApertureValuesListTileBuilder(),
|
||||
_ShutterSpeedValuesListTileBuilder(),
|
||||
_LensZoomListTileBuilder(),
|
||||
_ExposureOffsetListTileBuilder(),
|
||||
_NameFieldBuilder<T>(),
|
||||
if (state.profile is PinholeEquipmentProfile)
|
||||
const _ApertureValueListTileBuilder()
|
||||
else ...[
|
||||
const _ApertureValuesListTileBuilder(),
|
||||
const _ShutterSpeedValuesListTileBuilder(),
|
||||
],
|
||||
_IsoValuesListTileBuilder<T>(),
|
||||
_NdValuesListTileBuilder<T>(),
|
||||
_LensZoomListTileBuilder<T>(),
|
||||
_ExposureOffsetListTileBuilder<T>(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -126,12 +132,13 @@ class _EquipmentProfileEditScreenState extends State<EquipmentProfileEditScreen>
|
|||
}
|
||||
}
|
||||
|
||||
class _NameFieldBuilder extends StatelessWidget {
|
||||
class _NameFieldBuilder<T extends IEquipmentProfile> extends StatelessWidget {
|
||||
const _NameFieldBuilder();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<EquipmentProfileEditBloc, EquipmentProfileEditState>(
|
||||
return BlocBuilder<IEquipmentProfileEditBloc<T>, EquipmentProfileEditState<T>>(
|
||||
buildWhen: (previous, current) => previous.profile.name != current.profile.name,
|
||||
builder: (context, state) => Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: Dimens.paddingM,
|
||||
|
@ -141,13 +148,13 @@ class _NameFieldBuilder extends StatelessWidget {
|
|||
),
|
||||
child: LightmeterTextField(
|
||||
autofocus: true,
|
||||
initialValue: state.name,
|
||||
initialValue: state.profile.name,
|
||||
maxLength: 48,
|
||||
hintText: S.of(context).name,
|
||||
style: Theme.of(context).listTileTheme.titleTextStyle,
|
||||
leading: const Icon(Icons.edit_outlined),
|
||||
onChanged: (value) {
|
||||
context.read<EquipmentProfileEditBloc>().add(EquipmentProfileNameChangedEvent(value));
|
||||
context.read<IEquipmentProfileEditBloc<T>>().add(EquipmentProfileNameChangedEvent<T>(value));
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -155,40 +162,40 @@ class _NameFieldBuilder extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
class _IsoValuesListTileBuilder extends StatelessWidget {
|
||||
class _IsoValuesListTileBuilder<T extends IEquipmentProfile> extends StatelessWidget {
|
||||
const _IsoValuesListTileBuilder();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<EquipmentProfileEditBloc, EquipmentProfileEditState>(
|
||||
return BlocBuilder<IEquipmentProfileEditBloc<T>, EquipmentProfileEditState<T>>(
|
||||
builder: (context, state) => FilterListTile<IsoValue>(
|
||||
icon: Icons.iso_outlined,
|
||||
title: S.of(context).isoValues,
|
||||
description: S.of(context).isoValuesFilterDescription,
|
||||
values: IsoValue.values,
|
||||
selectedValues: state.isoValues,
|
||||
selectedValues: state.profile.isoValues,
|
||||
onChanged: (value) {
|
||||
context.read<EquipmentProfileEditBloc>().add(EquipmentProfileIsoValuesChangedEvent(value));
|
||||
context.read<IEquipmentProfileEditBloc<T>>().add(EquipmentProfileIsoValuesChangedEvent<T>(value));
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _NdValuesListTileBuilder extends StatelessWidget {
|
||||
class _NdValuesListTileBuilder<T extends IEquipmentProfile> extends StatelessWidget {
|
||||
const _NdValuesListTileBuilder();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<EquipmentProfileEditBloc, EquipmentProfileEditState>(
|
||||
return BlocBuilder<IEquipmentProfileEditBloc<T>, EquipmentProfileEditState<T>>(
|
||||
builder: (context, state) => FilterListTile<NdValue>(
|
||||
icon: Icons.filter_b_and_w_outlined,
|
||||
title: S.of(context).ndFilters,
|
||||
description: S.of(context).ndFiltersFilterDescription,
|
||||
values: NdValue.values,
|
||||
selectedValues: state.ndValues,
|
||||
selectedValues: state.profile.ndValues,
|
||||
onChanged: (value) {
|
||||
context.read<EquipmentProfileEditBloc>().add(EquipmentProfileNdValuesChangedEvent(value));
|
||||
context.read<IEquipmentProfileEditBloc<T>>().add(EquipmentProfileNdValuesChangedEvent<T>(value));
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@ -200,13 +207,13 @@ class _ApertureValuesListTileBuilder extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<EquipmentProfileEditBloc, EquipmentProfileEditState>(
|
||||
return BlocBuilder<EquipmentProfileEditBloc, EquipmentProfileEditState<EquipmentProfile>>(
|
||||
builder: (context, state) => RangePickerListTile<ApertureValue>(
|
||||
icon: Icons.camera_outlined,
|
||||
title: S.of(context).apertureValues,
|
||||
description: S.of(context).apertureValuesFilterDescription,
|
||||
values: ApertureValue.values,
|
||||
selectedValues: state.apertureValues,
|
||||
selectedValues: state.profile.apertureValues,
|
||||
onChanged: (value) {
|
||||
context.read<EquipmentProfileEditBloc>().add(EquipmentProfileApertureValuesChangedEvent(value));
|
||||
},
|
||||
|
@ -215,18 +222,34 @@ class _ApertureValuesListTileBuilder extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
class _ApertureValueListTileBuilder extends StatelessWidget {
|
||||
const _ApertureValueListTileBuilder();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<PinholeEquipmentProfileEditBloc, EquipmentProfileEditState<PinholeEquipmentProfile>>(
|
||||
builder: (context, state) => EquipmentProfileApertureInput(
|
||||
value: state.profile.aperture,
|
||||
onChanged: (value) {
|
||||
context.read<PinholeEquipmentProfileEditBloc>().add(EquipmentProfileApertureValueChangedEvent(value));
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ShutterSpeedValuesListTileBuilder extends StatelessWidget {
|
||||
const _ShutterSpeedValuesListTileBuilder();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<EquipmentProfileEditBloc, EquipmentProfileEditState>(
|
||||
return BlocBuilder<EquipmentProfileEditBloc, EquipmentProfileEditState<EquipmentProfile>>(
|
||||
builder: (context, state) => RangePickerListTile<ShutterSpeedValue>(
|
||||
icon: Icons.shutter_speed_outlined,
|
||||
title: S.of(context).shutterSpeedValues,
|
||||
description: S.of(context).shutterSpeedValuesFilterDescription,
|
||||
values: ShutterSpeedValue.values,
|
||||
selectedValues: state.shutterSpeedValues,
|
||||
selectedValues: state.profile.shutterSpeedValues,
|
||||
trailingAdapter: (context, value) =>
|
||||
value.value == 1 ? S.of(context).shutterSpeedManualShort : value.toString(),
|
||||
dialogValueAdapter: (context, value) => value.value == 1 ? S.of(context).shutterSpeedManual : value.toString(),
|
||||
|
@ -238,42 +261,44 @@ class _ShutterSpeedValuesListTileBuilder extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
class _LensZoomListTileBuilder extends StatelessWidget {
|
||||
class _LensZoomListTileBuilder<T extends IEquipmentProfile> extends StatelessWidget {
|
||||
const _LensZoomListTileBuilder();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<EquipmentProfileEditBloc, EquipmentProfileEditState>(
|
||||
return BlocBuilder<IEquipmentProfileEditBloc<T>, EquipmentProfileEditState<T>>(
|
||||
buildWhen: (previous, current) => previous.profile.lensZoom != current.profile.lensZoom,
|
||||
builder: (context, state) => SliderPickerListTile(
|
||||
icon: Icons.zoom_in_outlined,
|
||||
title: S.of(context).lensZoom,
|
||||
description: S.of(context).lensZoomDescription,
|
||||
value: state.lensZoom,
|
||||
value: state.profile.lensZoom,
|
||||
range: CameraContainerBloc.zoomMaxRange,
|
||||
valueAdapter: (context, value) => value.toZoom(context),
|
||||
onChanged: (value) {
|
||||
context.read<EquipmentProfileEditBloc>().add(EquipmentProfileLensZoomChangedEvent(value));
|
||||
context.read<IEquipmentProfileEditBloc<T>>().add(EquipmentProfileLensZoomChangedEvent<T>(value));
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ExposureOffsetListTileBuilder extends StatelessWidget {
|
||||
class _ExposureOffsetListTileBuilder<T extends IEquipmentProfile> extends StatelessWidget {
|
||||
const _ExposureOffsetListTileBuilder();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<EquipmentProfileEditBloc, EquipmentProfileEditState>(
|
||||
return BlocBuilder<IEquipmentProfileEditBloc<T>, EquipmentProfileEditState<T>>(
|
||||
buildWhen: (previous, current) => previous.profile.exposureOffset != current.profile.exposureOffset,
|
||||
builder: (context, state) => SliderPickerListTile(
|
||||
icon: Icons.light_mode_outlined,
|
||||
title: S.of(context).exposureOffset,
|
||||
description: S.of(context).exposureOffsetDescription,
|
||||
value: state.exposureOffset,
|
||||
value: state.profile.exposureOffset,
|
||||
range: CameraContainerBloc.exposureMaxRange,
|
||||
valueAdapter: (context, value) => S.of(context).evValue(value.toStringSignedAsFixed(1)),
|
||||
onChanged: (value) {
|
||||
context.read<EquipmentProfileEditBloc>().add(EquipmentProfileExposureOffsetChangedEvent(value));
|
||||
context.read<IEquipmentProfileEditBloc<T>>().add(EquipmentProfileExposureOffsetChangedEvent<T>(value));
|
||||
},
|
||||
),
|
||||
);
|
||||
|
|
|
@ -1,52 +1,32 @@
|
|||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
|
||||
class EquipmentProfileEditState {
|
||||
final String name;
|
||||
final List<ApertureValue> apertureValues;
|
||||
final List<NdValue> ndValues;
|
||||
final List<ShutterSpeedValue> shutterSpeedValues;
|
||||
final List<IsoValue> isoValues;
|
||||
final double lensZoom;
|
||||
final double exposureOffset;
|
||||
final bool canSave;
|
||||
class EquipmentProfileEditState<T extends IEquipmentProfile> {
|
||||
final T profile;
|
||||
final T? profileToCopy;
|
||||
final bool hasChanges;
|
||||
final bool isValid;
|
||||
final bool isLoading;
|
||||
final EquipmentProfile? profileToCopy;
|
||||
|
||||
const EquipmentProfileEditState({
|
||||
required this.name,
|
||||
required this.apertureValues,
|
||||
required this.ndValues,
|
||||
required this.shutterSpeedValues,
|
||||
required this.isoValues,
|
||||
required this.lensZoom,
|
||||
required this.exposureOffset,
|
||||
required this.canSave,
|
||||
required this.profile,
|
||||
required this.hasChanges,
|
||||
required this.isValid,
|
||||
this.isLoading = false,
|
||||
this.profileToCopy,
|
||||
});
|
||||
|
||||
EquipmentProfileEditState copyWith({
|
||||
String? name,
|
||||
List<ApertureValue>? apertureValues,
|
||||
List<NdValue>? ndValues,
|
||||
List<ShutterSpeedValue>? shutterSpeedValues,
|
||||
List<IsoValue>? isoValues,
|
||||
double? lensZoom,
|
||||
double? exposureOffset,
|
||||
bool? canSave,
|
||||
EquipmentProfileEditState<T> copyWith({
|
||||
T? profile,
|
||||
T? profileToCopy,
|
||||
bool? isValid,
|
||||
bool? hasChanges,
|
||||
bool? isLoading,
|
||||
EquipmentProfile? profileToCopy,
|
||||
}) =>
|
||||
EquipmentProfileEditState(
|
||||
name: name ?? this.name,
|
||||
apertureValues: apertureValues ?? this.apertureValues,
|
||||
ndValues: ndValues ?? this.ndValues,
|
||||
shutterSpeedValues: shutterSpeedValues ?? this.shutterSpeedValues,
|
||||
isoValues: isoValues ?? this.isoValues,
|
||||
lensZoom: lensZoom ?? this.lensZoom,
|
||||
exposureOffset: exposureOffset ?? this.exposureOffset,
|
||||
canSave: canSave ?? this.canSave,
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
EquipmentProfileEditState<T>(
|
||||
profile: profile ?? this.profile,
|
||||
profileToCopy: profileToCopy ?? this.profileToCopy,
|
||||
isValid: isValid ?? this.isValid,
|
||||
hasChanges: hasChanges ?? this.hasChanges,
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/screens/settings/components/shared/dialog_picker/widget_dialog_picker.dart';
|
||||
|
||||
enum EquipmentProfileType { regular, pinhole }
|
||||
|
||||
class EquipmentProfilesTypePicker extends StatelessWidget {
|
||||
const EquipmentProfilesTypePicker._();
|
||||
|
||||
static Future<EquipmentProfileType?> show(BuildContext context) {
|
||||
return showDialog<EquipmentProfileType>(
|
||||
context: context,
|
||||
builder: (_) => const EquipmentProfilesTypePicker._(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DialogPicker<EquipmentProfileType>(
|
||||
icon: Icons.camera_alt_outlined,
|
||||
title: S.of(context).equipmentProfileType,
|
||||
selectedValue: EquipmentProfileType.regular,
|
||||
values: EquipmentProfileType.values,
|
||||
titleAdapter: (context, value) => switch (value) {
|
||||
EquipmentProfileType.regular => S.of(context).camera,
|
||||
EquipmentProfileType.pinhole => S.of(context).pinholeCamera,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import 'package:lightmeter/navigation/routes.dart';
|
|||
import 'package:lightmeter/providers/equipment_profile_provider.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
import 'package:lightmeter/screens/equipment_profile_edit/flow_equipment_profile_edit.dart';
|
||||
import 'package:lightmeter/screens/equipment_profiles/components/equipment_profile_type_picker/widget_picker_equipment_profile_type.dart';
|
||||
import 'package:lightmeter/screens/shared/sliver_placeholder/widget_sliver_placeholder.dart';
|
||||
import 'package:lightmeter/screens/shared/sliver_screen/screen_sliver.dart';
|
||||
import 'package:lightmeter/utils/guard_pro_tap.dart';
|
||||
|
@ -45,15 +46,34 @@ class _EquipmentProfilesScreenState extends State<EquipmentProfilesScreen> with
|
|||
guardProTap(
|
||||
context,
|
||||
() {
|
||||
Navigator.of(context).pushNamed(
|
||||
NavigationRoutes.equipmentProfileEditScreen.name,
|
||||
arguments: const EquipmentProfileEditArgs(editType: EquipmentProfileEditType.add),
|
||||
);
|
||||
EquipmentProfilesTypePicker.show(context).then((value) {
|
||||
if (value != null && mounted) {
|
||||
Navigator.of(context).pushNamed(
|
||||
NavigationRoutes.equipmentProfileEditScreen.name,
|
||||
arguments: switch (value) {
|
||||
EquipmentProfileType.regular => const EquipmentProfileEditArgs(
|
||||
editType: EquipmentProfileEditType.add,
|
||||
profile: EquipmentProfilesProvider.defaultProfile,
|
||||
),
|
||||
EquipmentProfileType.pinhole => EquipmentProfileEditArgs(
|
||||
editType: EquipmentProfileEditType.add,
|
||||
profile: PinholeEquipmentProfile(
|
||||
id: EquipmentProfilesProvider.defaultProfile.id,
|
||||
name: EquipmentProfilesProvider.defaultProfile.name,
|
||||
aperture: 22,
|
||||
isoValues: EquipmentProfilesProvider.defaultProfile.isoValues,
|
||||
ndValues: EquipmentProfilesProvider.defaultProfile.ndValues,
|
||||
),
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _editProfile(EquipmentProfile profile) {
|
||||
void _editProfile(IEquipmentProfile profile) {
|
||||
Navigator.of(context).pushNamed(
|
||||
NavigationRoutes.equipmentProfileEditScreen.name,
|
||||
arguments: EquipmentProfileEditArgs(
|
||||
|
@ -65,9 +85,9 @@ class _EquipmentProfilesScreenState extends State<EquipmentProfilesScreen> with
|
|||
}
|
||||
|
||||
class _EquipmentProfilesListBuilder extends StatelessWidget {
|
||||
final List<EquipmentProfile> values;
|
||||
final void Function(EquipmentProfile profile) onEdit;
|
||||
final void Function(EquipmentProfile profile, bool value) onCheckbox;
|
||||
final List<IEquipmentProfile> values;
|
||||
final void Function(IEquipmentProfile profile) onEdit;
|
||||
final void Function(String id, bool value) onCheckbox;
|
||||
|
||||
const _EquipmentProfilesListBuilder({
|
||||
required this.values,
|
||||
|
@ -102,7 +122,7 @@ class _EquipmentProfilesListBuilder extends StatelessWidget {
|
|||
title: Text(values[index].name),
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
value: EquipmentProfiles.inUseOf(context).contains(values[index]),
|
||||
onChanged: (value) => onCheckbox(values[index], value ?? false),
|
||||
onChanged: (value) => onCheckbox(values[index].id, value ?? false),
|
||||
secondary: IconButton(
|
||||
onPressed: () => onEdit(values[index]),
|
||||
icon: const Icon(Icons.edit_outlined),
|
||||
|
|
|
@ -74,10 +74,10 @@ class LogbookPhotoEditBloc extends Bloc<LogbookPhotoEditEvent, LogbookPhotoEditS
|
|||
}
|
||||
|
||||
Future<void> _onEquipmentProfileChanged(LogbookPhotoEquipmentProfileChangedEvent event, Emitter emit) async {
|
||||
_newPhoto = _newPhoto.copyWith(equipmentProfileId: Optional(event.equipmentProfile?.id));
|
||||
_newPhoto = _newPhoto.copyWith(equipmentProfileId: Optional(event.equipmentProfileId));
|
||||
emit(
|
||||
state.copyWith(
|
||||
equipmentProfileId: Optional(event.equipmentProfile?.id),
|
||||
equipmentProfileId: Optional(event.equipmentProfileId),
|
||||
canSave: _canSave(),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -23,9 +23,9 @@ class LogbookPhotoNoteChangedEvent extends LogbookPhotoEditEvent {
|
|||
}
|
||||
|
||||
class LogbookPhotoEquipmentProfileChangedEvent extends LogbookPhotoEditEvent {
|
||||
final EquipmentProfile? equipmentProfile;
|
||||
final String? equipmentProfileId;
|
||||
|
||||
const LogbookPhotoEquipmentProfileChangedEvent(this.equipmentProfile);
|
||||
const LogbookPhotoEquipmentProfileChangedEvent(this.equipmentProfileId);
|
||||
}
|
||||
|
||||
class LogbookPhotoFilmChangedEvent extends LogbookPhotoEditEvent {
|
||||
|
|
|
@ -273,7 +273,7 @@ class _EquipmentProfilePickerListTile extends StatelessWidget {
|
|||
selectedValue: EquipmentProfiles.of(context).firstWhereOrNull((e) => e.id == state.equipmentProfileId),
|
||||
titleAdapter: (value) => value.name,
|
||||
onChanged: (value) {
|
||||
context.read<LogbookPhotoEditBloc>().add(LogbookPhotoEquipmentProfileChangedEvent(value.value));
|
||||
context.read<LogbookPhotoEditBloc>().add(LogbookPhotoEquipmentProfileChangedEvent(value.value?.id));
|
||||
},
|
||||
),
|
||||
);
|
||||
|
|
|
@ -19,7 +19,7 @@ class MeasureEvent extends ScreenEvent {
|
|||
}
|
||||
|
||||
class EquipmentProfileChangedEvent extends ScreenEvent {
|
||||
final EquipmentProfile profile;
|
||||
final IEquipmentProfile profile;
|
||||
|
||||
const EquipmentProfileChangedEvent(this.profile);
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ class MeasureState extends SourceState {
|
|||
}
|
||||
|
||||
class EquipmentProfileChangedState extends SourceState {
|
||||
final EquipmentProfile profile;
|
||||
final IEquipmentProfile profile;
|
||||
|
||||
const EquipmentProfileChangedState(this.profile);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:lightmeter/data/models/exposure_pair.dart';
|
|||
import 'package:lightmeter/data/models/feature.dart';
|
||||
import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
|
||||
import 'package:lightmeter/platform_config.dart';
|
||||
import 'package:lightmeter/providers/equipment_profile_provider.dart';
|
||||
import 'package:lightmeter/providers/remote_config_provider.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
import 'package:lightmeter/screens/metering/components/camera_container/bloc_container_camera.dart';
|
||||
|
@ -125,7 +126,10 @@ class CameraContainer extends StatelessWidget {
|
|||
enabledFeaturesHeight += Dimens.readingContainerSingleValueHeight;
|
||||
enabledFeaturesHeight += Dimens.paddingS;
|
||||
}
|
||||
if (context.meteringFeature(MeteringScreenLayoutFeature.extremeExposurePairs)) {
|
||||
if (EquipmentProfiles.selectedOf(context) is PinholeEquipmentProfile) {
|
||||
enabledFeaturesHeight += Dimens.readingContainerSingleValueHeight;
|
||||
enabledFeaturesHeight += Dimens.paddingS;
|
||||
} else if (context.meteringFeature(MeteringScreenLayoutFeature.extremeExposurePairs)) {
|
||||
enabledFeaturesHeight += Dimens.readingContainerDoubleValueHeight;
|
||||
enabledFeaturesHeight += Dimens.paddingS;
|
||||
}
|
||||
|
|
|
@ -10,13 +10,13 @@ class EquipmentProfilePicker extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedDialogPicker<EquipmentProfile>(
|
||||
return AnimatedDialogPicker<IEquipmentProfile>(
|
||||
icon: Icons.camera_alt_outlined,
|
||||
title: S.of(context).equipmentProfile,
|
||||
selectedValue: EquipmentProfiles.selectedOf(context),
|
||||
values: EquipmentProfiles.inUseOf(context),
|
||||
itemTitleBuilder: (_, value) => Text(value.id.isEmpty ? S.of(context).none : value.name),
|
||||
onChanged: EquipmentProfilesProvider.of(context).selectProfile,
|
||||
onChanged: (profile) => EquipmentProfilesProvider.of(context).selectProfile(profile.id),
|
||||
closedChild: ReadingValueContainer.singleValue(
|
||||
value: ReadingValue(
|
||||
label: S.of(context).equipmentProfile,
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/providers/films_provider.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/reading_value_container/widget_container_reading_value.dart';
|
||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
|
||||
class ShutterSpeedContainer extends StatelessWidget {
|
||||
final ShutterSpeedValue? shutterSpeedValue;
|
||||
|
||||
const ShutterSpeedContainer({
|
||||
required this.shutterSpeedValue,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ReadingValueContainer.singleValue(
|
||||
value: ReadingValue(
|
||||
label: S.of(context).shutterSpeed,
|
||||
value: _shutterSpeed(context),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _shutterSpeed(BuildContext context) {
|
||||
if (shutterSpeedValue case final shutterSpeedValue?) {
|
||||
return Films.selectedOf(context).reciprocityFailure(shutterSpeedValue).toString();
|
||||
} else {
|
||||
return '-';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ import 'package:lightmeter/screens/metering/components/shared/readings_container
|
|||
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/lightmeter_pro_badge/widget_badge_lightmeter_pro.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/shutter_speed_container/widget_container_shutter_speed.dart';
|
||||
import 'package:lightmeter/utils/context_utils.dart';
|
||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
|
||||
|
@ -45,7 +46,10 @@ class ReadingsContainer extends StatelessWidget {
|
|||
const EquipmentProfilePicker(),
|
||||
const _InnerPadding(),
|
||||
],
|
||||
if (context.meteringFeature(MeteringScreenLayoutFeature.extremeExposurePairs)) ...[
|
||||
if (EquipmentProfiles.selectedOf(context) is PinholeEquipmentProfile) ...[
|
||||
ShutterSpeedContainer(shutterSpeedValue: fastest?.shutterSpeed),
|
||||
const _InnerPadding(),
|
||||
] else if (context.meteringFeature(MeteringScreenLayoutFeature.extremeExposurePairs)) ...[
|
||||
ExtremeExposurePairsContainer(
|
||||
fastest: fastest,
|
||||
slowest: slowest,
|
||||
|
|
|
@ -5,7 +5,7 @@ sealed class MeteringEvent {
|
|||
}
|
||||
|
||||
class EquipmentProfileChangedEvent extends MeteringEvent {
|
||||
final EquipmentProfile equipmentProfileData;
|
||||
final IEquipmentProfile equipmentProfileData;
|
||||
|
||||
const EquipmentProfileChangedEvent(this.equipmentProfileData);
|
||||
}
|
||||
|
|
|
@ -169,12 +169,22 @@ class MeteringContainerBuidler extends StatelessWidget {
|
|||
static List<ExposurePair> buildExposureValues(
|
||||
double ev,
|
||||
StopType stopType,
|
||||
EquipmentProfile equipmentProfile,
|
||||
IEquipmentProfile equipmentProfile,
|
||||
) {
|
||||
if (ev.isNaN || ev.isInfinite) {
|
||||
return List.empty();
|
||||
}
|
||||
|
||||
if (equipmentProfile.id != "" && equipmentProfile is PinholeEquipmentProfile) {
|
||||
final t = pow(equipmentProfile.aperture, 2) / pow(2, ev);
|
||||
return [
|
||||
ExposurePair(
|
||||
ApertureValue(equipmentProfile.aperture, StopType.full),
|
||||
ShutterSpeedValue(t, false, StopType.full),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/// Depending on the `stopType` the exposure pairs list length is multiplied by 1,2 or 3
|
||||
final int evSteps = (ev * (stopType.index + 1)).round();
|
||||
|
||||
|
@ -236,31 +246,31 @@ class MeteringContainerBuidler extends StatelessWidget {
|
|||
);
|
||||
|
||||
/// Full equipment profile, nothing to cut
|
||||
if (equipmentProfile.id == "") {
|
||||
return exposurePairs;
|
||||
if (equipmentProfile.id != "" && equipmentProfile is EquipmentProfile) {
|
||||
final equipmentApertureValues = equipmentProfile.apertureValues.whereStopType(stopType);
|
||||
final equipmentShutterSpeedValues = equipmentProfile.shutterSpeedValues.whereStopType(stopType);
|
||||
|
||||
final startCutEV = max(
|
||||
exposurePairs.first.aperture.difference(equipmentApertureValues.first),
|
||||
exposurePairs.first.shutterSpeed.difference(equipmentShutterSpeedValues.first),
|
||||
);
|
||||
final endCutEV = max(
|
||||
equipmentApertureValues.last.difference(exposurePairs.last.aperture),
|
||||
equipmentShutterSpeedValues.last != ShutterSpeedValue.values.last
|
||||
? equipmentShutterSpeedValues.last.difference(exposurePairs.last.shutterSpeed)
|
||||
: double.negativeInfinity,
|
||||
);
|
||||
|
||||
final startCut = (startCutEV * (stopType.index + 1)).round().clamp(0, itemsCount);
|
||||
final endCut = (endCutEV * (stopType.index + 1)).round().clamp(0, itemsCount);
|
||||
|
||||
if (startCut > itemsCount - endCut) {
|
||||
return const [];
|
||||
}
|
||||
return exposurePairs.sublist(startCut, itemsCount - endCut);
|
||||
}
|
||||
|
||||
final equipmentApertureValues = equipmentProfile.apertureValues.whereStopType(stopType);
|
||||
final equipmentShutterSpeedValues = equipmentProfile.shutterSpeedValues.whereStopType(stopType);
|
||||
|
||||
final startCutEV = max(
|
||||
exposurePairs.first.aperture.difference(equipmentApertureValues.first),
|
||||
exposurePairs.first.shutterSpeed.difference(equipmentShutterSpeedValues.first),
|
||||
);
|
||||
final endCutEV = max(
|
||||
equipmentApertureValues.last.difference(exposurePairs.last.aperture),
|
||||
equipmentShutterSpeedValues.last != ShutterSpeedValue.values.last
|
||||
? equipmentShutterSpeedValues.last.difference(exposurePairs.last.shutterSpeed)
|
||||
: double.negativeInfinity,
|
||||
);
|
||||
|
||||
final startCut = (startCutEV * (stopType.index + 1)).round().clamp(0, itemsCount);
|
||||
final endCut = (endCutEV * (stopType.index + 1)).round().clamp(0, itemsCount);
|
||||
|
||||
if (startCut > itemsCount - endCut) {
|
||||
return const [];
|
||||
}
|
||||
return exposurePairs.sublist(startCut, itemsCount - endCut);
|
||||
return exposurePairs;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import 'package:lightmeter/providers/equipment_profile_provider.dart';
|
|||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
|
||||
class EquipmentProfileListener extends StatefulWidget {
|
||||
final ValueChanged<EquipmentProfile> onDidChangeDependencies;
|
||||
final ValueChanged<IEquipmentProfile> onDidChangeDependencies;
|
||||
final Widget child;
|
||||
|
||||
const EquipmentProfileListener({
|
||||
|
|
|
@ -34,7 +34,7 @@ class MeteringScreenLayoutListTile extends StatelessWidget {
|
|||
.toList(growable: false),
|
||||
onSave: (value) {
|
||||
if (!value[MeteringScreenLayoutFeature.equipmentProfiles]!) {
|
||||
EquipmentProfilesProvider.of(context).selectProfile(EquipmentProfiles.of(context).first);
|
||||
EquipmentProfilesProvider.of(context).selectProfile(EquipmentProfiles.of(context).first.id);
|
||||
}
|
||||
if (!value[MeteringScreenLayoutFeature.filmPicker]!) {
|
||||
FilmsProvider.of(context).selectFilm(const FilmStub());
|
||||
|
|
|
@ -8,6 +8,7 @@ class LightmeterTextField extends StatefulWidget {
|
|||
this.hintText,
|
||||
this.initialValue,
|
||||
this.inputFormatters,
|
||||
this.validator,
|
||||
this.leading,
|
||||
this.maxLength,
|
||||
this.maxLines = 1,
|
||||
|
@ -21,6 +22,7 @@ class LightmeterTextField extends StatefulWidget {
|
|||
final String? hintText;
|
||||
final String? initialValue;
|
||||
final List<TextInputFormatter>? inputFormatters;
|
||||
final String? Function(String)? validator;
|
||||
final Widget? leading;
|
||||
final int? maxLength;
|
||||
final int? maxLines;
|
||||
|
@ -62,6 +64,8 @@ class _LightmeterTextFieldState extends State<LightmeterTextField> {
|
|||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return '';
|
||||
} else if (widget.validator != null) {
|
||||
return widget.validator!(value);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -861,8 +861,8 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: "v4.1.2"
|
||||
resolved-ref: "6bb16eb49232649eca02704b3a40890b0184d8b5"
|
||||
ref: "feature/MLI-48"
|
||||
resolved-ref: "4a169640bff3d3a3206a2c352a75cbcea4871b1c"
|
||||
url: "https://github.com/vodemn/m3_lightmeter_iap"
|
||||
source: git
|
||||
version: "4.1.2+37"
|
||||
|
@ -870,8 +870,8 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: "v2.4.0"
|
||||
resolved-ref: cc9ae43a7859398a6ab2ecf7f8713153dbfd99cd
|
||||
ref: "feature/MLR-18"
|
||||
resolved-ref: "61bb3f8a9164d19f6e47c96fbea1cbe3aaf39fc3"
|
||||
url: "https://github.com/vodemn/m3_lightmeter_resources"
|
||||
source: git
|
||||
version: "2.4.0+13"
|
||||
|
|
|
@ -33,11 +33,11 @@ dependencies:
|
|||
m3_lightmeter_iap:
|
||||
git:
|
||||
url: "https://github.com/vodemn/m3_lightmeter_iap"
|
||||
ref: v4.1.2
|
||||
ref: feature/MLI-48
|
||||
m3_lightmeter_resources:
|
||||
git:
|
||||
url: "https://github.com/vodemn/m3_lightmeter_resources"
|
||||
ref: v2.4.0
|
||||
ref: feature/MLR-18
|
||||
map_launcher: 3.2.0
|
||||
material_color_utilities: 0.12.0
|
||||
package_info_plus: 8.1.3
|
||||
|
|
|
@ -218,14 +218,9 @@ void main() {
|
|||
testWidgets(
|
||||
'Generate timer screenshot',
|
||||
(tester) async {
|
||||
const timerExposurePair = ExposurePair(
|
||||
ApertureValue(16, StopType.full),
|
||||
ShutterSpeedValue(8, false, StopType.full),
|
||||
);
|
||||
await mockSharedPrefs(
|
||||
iso: 100,
|
||||
nd: 8,
|
||||
calibration: -0.3,
|
||||
calibration: -2.3,
|
||||
theme: ThemeType.light,
|
||||
color: _lightThemeColor,
|
||||
);
|
||||
|
@ -236,13 +231,22 @@ void main() {
|
|||
);
|
||||
|
||||
await tester.takePhoto();
|
||||
await tester.scrollToExposurePair(
|
||||
ev: 5,
|
||||
exposurePair: timerExposurePair,
|
||||
|
||||
final exposurePairs = MeteringContainerBuidler.buildExposureValues(
|
||||
5,
|
||||
StopType.third,
|
||||
defaultEquipmentProfile,
|
||||
);
|
||||
await tester.tap(find.text(_mockFilm.reciprocityFailure(timerExposurePair.shutterSpeed).toString()));
|
||||
final timerExposurePair = exposurePairs.firstWhere((e) => e.aperture == const ApertureValue(16, StopType.full));
|
||||
await tester.scrollToExposurePair(
|
||||
exposurePairs: exposurePairs,
|
||||
exposurePair: exposurePairs.firstWhere((e) => e.aperture == const ApertureValue(16, StopType.full)),
|
||||
);
|
||||
|
||||
final correctedShutterSpeed = _mockFilm.reciprocityFailure(timerExposurePair.shutterSpeed);
|
||||
await tester.tap(find.text(correctedShutterSpeed.toString()));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.mockTimerResumedState(timerExposurePair.shutterSpeed);
|
||||
await tester.mockTimerResumedState(correctedShutterSpeed);
|
||||
await tester.takeScreenshotLight(binding, 'timer');
|
||||
},
|
||||
);
|
||||
|
@ -257,32 +261,28 @@ extension on WidgetTester {
|
|||
_takeScreenshot(binding, name, _themeDark);
|
||||
|
||||
Future<void> _takeScreenshot(IntegrationTestWidgetsFlutterBinding binding, String name, ThemeData theme) async {
|
||||
final Color backgroundColor = theme.colorScheme.surface;
|
||||
await binding.takeScreenshot(
|
||||
ScreenshotArgs(
|
||||
name: name,
|
||||
deviceName: const String.fromEnvironment('deviceName'),
|
||||
platformFolder: _platformFolder,
|
||||
backgroundColor: backgroundColor.toInt().toRadixString(16),
|
||||
isDark: theme.brightness == Brightness.dark,
|
||||
).toString(),
|
||||
);
|
||||
await pumpAndSettle();
|
||||
const deviceName = String.fromEnvironment('deviceName');
|
||||
if (deviceName.isNotEmpty) {
|
||||
final Color backgroundColor = theme.colorScheme.surface;
|
||||
await binding.takeScreenshot(
|
||||
ScreenshotArgs(
|
||||
name: name,
|
||||
deviceName: deviceName,
|
||||
platformFolder: _platformFolder,
|
||||
backgroundColor: backgroundColor.toInt().toRadixString(16),
|
||||
isDark: theme.brightness == Brightness.dark,
|
||||
).toString(),
|
||||
);
|
||||
await pumpAndSettle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension on WidgetTester {
|
||||
Future<void> scrollToExposurePair({
|
||||
double ev = mockPhotoEv100,
|
||||
EquipmentProfile equipmentProfile = defaultEquipmentProfile,
|
||||
required List<ExposurePair> exposurePairs,
|
||||
required ExposurePair exposurePair,
|
||||
}) async {
|
||||
final exposurePairs = MeteringContainerBuidler.buildExposureValues(
|
||||
ev,
|
||||
StopType.third,
|
||||
equipmentProfile,
|
||||
);
|
||||
|
||||
await scrollUntilVisible(
|
||||
find.byWidgetPredicate((widget) => widget is Row && widget.key == ValueKey(exposurePairs.indexOf(exposurePair))),
|
||||
56,
|
||||
|
|
|
@ -16,7 +16,7 @@ void main() {
|
|||
});
|
||||
|
||||
setUp(() {
|
||||
registerFallbackValue(_customProfiles.first);
|
||||
registerFallbackValue(_mockEquipmentProfiles.first);
|
||||
when(() => storageService.addEquipmentProfile(any<EquipmentProfile>())).thenAnswer((_) async {});
|
||||
when(
|
||||
() => storageService.updateEquipmentProfile(
|
||||
|
@ -26,7 +26,10 @@ void main() {
|
|||
),
|
||||
).thenAnswer((_) async {});
|
||||
when(() => storageService.deleteEquipmentProfile(any<String>())).thenAnswer((_) async {});
|
||||
when(() => storageService.getEquipmentProfiles()).thenAnswer((_) => Future.value(_customProfiles.toTogglableMap()));
|
||||
when(() => storageService.getEquipmentProfiles())
|
||||
.thenAnswer((_) => Future.value(_mockEquipmentProfiles.toTogglableMap()));
|
||||
when(() => storageService.getPinholeEquipmentProfiles())
|
||||
.thenAnswer((_) => Future.value(_mockPinholeEquipmentProfiles.toTogglableMap()));
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
|
@ -62,17 +65,15 @@ void main() {
|
|||
'EquipmentProfilesProvider dependency on IAPProductStatus',
|
||||
() {
|
||||
setUp(() {
|
||||
when(() => storageService.selectedEquipmentProfileId).thenReturn(_customProfiles.first.id);
|
||||
when(() => storageService.getEquipmentProfiles())
|
||||
.thenAnswer((_) => Future.value(_customProfiles.toTogglableMap()));
|
||||
when(() => storageService.selectedEquipmentProfileId).thenReturn(_mockProfiles.first.id);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Pro - show all saved profiles',
|
||||
(tester) async {
|
||||
await pumpTestWidget(tester, true);
|
||||
expectEquipmentProfilesCount(_customProfiles.length + 1);
|
||||
expectSelectedEquipmentProfileName(_customProfiles.first.name);
|
||||
expectEquipmentProfilesCount(_mockProfiles.length + 1);
|
||||
expectSelectedEquipmentProfileName(_mockProfiles.first.name);
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -90,19 +91,19 @@ void main() {
|
|||
testWidgets(
|
||||
'toggleProfile',
|
||||
(tester) async {
|
||||
when(() => storageService.selectedEquipmentProfileId).thenReturn(_customProfiles.first.id);
|
||||
when(() => storageService.selectedEquipmentProfileId).thenReturn(_mockEquipmentProfiles.first.id);
|
||||
await pumpTestWidget(tester, true);
|
||||
expectEquipmentProfilesCount(_customProfiles.length + 1);
|
||||
expectEquipmentProfilesInUseCount(_customProfiles.length + 1);
|
||||
expectSelectedEquipmentProfileName(_customProfiles.first.name);
|
||||
expectEquipmentProfilesCount(_mockProfiles.length + 1);
|
||||
expectEquipmentProfilesInUseCount(_mockProfiles.length + 1);
|
||||
expectSelectedEquipmentProfileName(_mockEquipmentProfiles.first.name);
|
||||
|
||||
await tester.equipmentProfilesProvider.toggleProfile(_customProfiles.first, false);
|
||||
await tester.equipmentProfilesProvider.toggleProfile(_mockEquipmentProfiles.first.id, false);
|
||||
await tester.pump();
|
||||
expectEquipmentProfilesCount(_customProfiles.length + 1);
|
||||
expectEquipmentProfilesInUseCount(_customProfiles.length + 1 - 1);
|
||||
expectEquipmentProfilesCount(_mockProfiles.length + 1);
|
||||
expectEquipmentProfilesInUseCount(_mockProfiles.length + 1 - 1);
|
||||
expectSelectedEquipmentProfileName('');
|
||||
|
||||
verify(() => storageService.updateEquipmentProfile(id: _customProfiles.first.id, isUsed: false)).called(1);
|
||||
verify(() => storageService.updateEquipmentProfile(id: _mockEquipmentProfiles.first.id, isUsed: false)).called(1);
|
||||
verify(() => storageService.selectedEquipmentProfileId = '').called(1);
|
||||
},
|
||||
);
|
||||
|
@ -111,6 +112,7 @@ void main() {
|
|||
'EquipmentProfilesProvider CRUD',
|
||||
(tester) async {
|
||||
when(() => storageService.getEquipmentProfiles()).thenAnswer((_) async => {});
|
||||
when(() => storageService.getPinholeEquipmentProfiles()).thenAnswer((_) async => {});
|
||||
when(() => storageService.selectedEquipmentProfileId).thenReturn('');
|
||||
|
||||
await pumpTestWidget(tester, true);
|
||||
|
@ -118,44 +120,45 @@ void main() {
|
|||
expectSelectedEquipmentProfileName('');
|
||||
|
||||
/// Add first profile and verify
|
||||
await tester.equipmentProfilesProvider.addProfile(_customProfiles.first);
|
||||
await tester.equipmentProfilesProvider.addProfile(_mockEquipmentProfiles.first);
|
||||
await tester.pump();
|
||||
expectEquipmentProfilesCount(2);
|
||||
expectSelectedEquipmentProfileName('');
|
||||
verify(() => storageService.addEquipmentProfile(any<EquipmentProfile>())).called(1);
|
||||
|
||||
/// Add the other profiles and select the 1st one
|
||||
for (final profile in _customProfiles.skip(1)) {
|
||||
for (final profile in _mockEquipmentProfiles.skip(1)) {
|
||||
await tester.equipmentProfilesProvider.addProfile(profile);
|
||||
}
|
||||
tester.equipmentProfilesProvider.selectProfile(_customProfiles.first);
|
||||
tester.equipmentProfilesProvider.selectProfile(_mockEquipmentProfiles.first.id);
|
||||
await tester.pump();
|
||||
expectEquipmentProfilesCount(1 + _customProfiles.length);
|
||||
expectSelectedEquipmentProfileName(_customProfiles.first.name);
|
||||
expectEquipmentProfilesCount(1 + _mockEquipmentProfiles.length);
|
||||
expectSelectedEquipmentProfileName(_mockEquipmentProfiles.first.name);
|
||||
|
||||
/// Edit the selected profile
|
||||
final updatedName = "${_customProfiles.first} updated";
|
||||
await tester.equipmentProfilesProvider.updateProfile(_customProfiles.first.copyWith(name: updatedName));
|
||||
final updatedName = "${_mockEquipmentProfiles.first} updated";
|
||||
await tester.equipmentProfilesProvider.updateProfile(_mockEquipmentProfiles.first.copyWith(name: updatedName));
|
||||
await tester.pump();
|
||||
expectEquipmentProfilesCount(1 + _customProfiles.length);
|
||||
expectEquipmentProfilesCount(1 + _mockEquipmentProfiles.length);
|
||||
expectSelectedEquipmentProfileName(updatedName);
|
||||
verify(() => storageService.updateEquipmentProfile(id: _customProfiles.first.id, name: updatedName)).called(1);
|
||||
verify(() => storageService.updateEquipmentProfile(id: _mockEquipmentProfiles.first.id, name: updatedName))
|
||||
.called(1);
|
||||
|
||||
/// Delete a non-selected profile
|
||||
await tester.equipmentProfilesProvider.deleteProfile(_customProfiles.last);
|
||||
await tester.equipmentProfilesProvider.deleteProfile(_mockEquipmentProfiles.last);
|
||||
await tester.pump();
|
||||
expectEquipmentProfilesCount(1 + _customProfiles.length - 1);
|
||||
expectEquipmentProfilesCount(1 + _mockEquipmentProfiles.length - 1);
|
||||
expectSelectedEquipmentProfileName(updatedName);
|
||||
verifyNever(() => storageService.selectedEquipmentProfileId = '');
|
||||
verify(() => storageService.deleteEquipmentProfile(_customProfiles.last.id)).called(1);
|
||||
verify(() => storageService.deleteEquipmentProfile(_mockEquipmentProfiles.last.id)).called(1);
|
||||
|
||||
/// Delete the selected profile
|
||||
await tester.equipmentProfilesProvider.deleteProfile(_customProfiles.first);
|
||||
await tester.equipmentProfilesProvider.deleteProfile(_mockEquipmentProfiles.first);
|
||||
await tester.pump();
|
||||
expectEquipmentProfilesCount(1 + _customProfiles.length - 2);
|
||||
expectEquipmentProfilesCount(1 + _mockEquipmentProfiles.length - 2);
|
||||
expectSelectedEquipmentProfileName('');
|
||||
verify(() => storageService.selectedEquipmentProfileId = '').called(1);
|
||||
verify(() => storageService.deleteEquipmentProfile(_customProfiles.first.id)).called(1);
|
||||
verify(() => storageService.deleteEquipmentProfile(_mockEquipmentProfiles.first.id)).called(1);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -221,7 +224,9 @@ class _SelectedEquipmentProfile extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
final List<EquipmentProfile> _customProfiles = [
|
||||
final List<IEquipmentProfile> _mockProfiles = [..._mockEquipmentProfiles, ..._mockPinholeEquipmentProfiles];
|
||||
|
||||
final List<EquipmentProfile> _mockEquipmentProfiles = [
|
||||
const EquipmentProfile(
|
||||
id: '1',
|
||||
name: 'Test 1',
|
||||
|
@ -291,3 +296,42 @@ final List<EquipmentProfile> _customProfiles = [
|
|||
],
|
||||
),
|
||||
];
|
||||
|
||||
final _mockPinholeEquipmentProfiles = [
|
||||
const PinholeEquipmentProfile(
|
||||
id: '3',
|
||||
name: 'Pinhole Camera f/64',
|
||||
aperture: 64.0,
|
||||
isoValues: [
|
||||
IsoValue(100, StopType.full),
|
||||
IsoValue(200, StopType.full),
|
||||
IsoValue(400, StopType.full),
|
||||
IsoValue(800, StopType.full),
|
||||
],
|
||||
ndValues: [
|
||||
NdValue(0),
|
||||
NdValue(2),
|
||||
NdValue(4),
|
||||
],
|
||||
),
|
||||
const PinholeEquipmentProfile(
|
||||
id: '4',
|
||||
name: 'Pinhole Camera f/128',
|
||||
aperture: 128.0,
|
||||
isoValues: [
|
||||
IsoValue(50, StopType.full),
|
||||
IsoValue(100, StopType.full),
|
||||
IsoValue(200, StopType.full),
|
||||
IsoValue(400, StopType.full),
|
||||
IsoValue(800, StopType.full),
|
||||
IsoValue(1600, StopType.full),
|
||||
],
|
||||
ndValues: [
|
||||
NdValue(0),
|
||||
NdValue(1),
|
||||
NdValue(2),
|
||||
NdValue(4),
|
||||
NdValue(8),
|
||||
],
|
||||
),
|
||||
];
|
||||
|
|
|
@ -40,6 +40,7 @@ void main() {
|
|||
|
||||
when(() => storageService.selectedEquipmentProfileId).thenReturn('');
|
||||
when(() => storageService.getEquipmentProfiles()).thenAnswer((_) => Future.value({}));
|
||||
when(() => storageService.getPinholeEquipmentProfiles()).thenAnswer((_) => Future.value({}));
|
||||
|
||||
when(() => storageService.selectedFilmId).thenReturn(const FilmStub().id);
|
||||
when(() => storageService.getPredefinedFilms()).thenAnswer((_) => Future.value({}));
|
||||
|
|
|
@ -19,7 +19,10 @@ void main() {
|
|||
|
||||
setUpAll(() {
|
||||
storageService = _MockEquipmentProfilesStorageService();
|
||||
when(() => storageService.getEquipmentProfiles()).thenAnswer((_) async => _mockEquipmentProfiles.toTogglableMap());
|
||||
when(() => storageService.getEquipmentProfiles())
|
||||
.thenAnswer((_) async => _mockEquipmentProfiles.toList().toTogglableMap());
|
||||
when(() => storageService.getPinholeEquipmentProfiles())
|
||||
.thenAnswer((_) async => _mockPinholeEquipmentProfiles.toList().toTogglableMap());
|
||||
when(() => storageService.selectedEquipmentProfileId).thenReturn('');
|
||||
});
|
||||
|
||||
|
@ -48,7 +51,7 @@ void main() {
|
|||
expectReadingValueContainerText(S.current.equipmentProfile);
|
||||
await tester.openAnimatedPicker<EquipmentProfilePicker>();
|
||||
expect(find.byIcon(Icons.camera_alt_outlined), findsOneWidget);
|
||||
expectDialogPickerText<EquipmentProfile>(S.current.equipmentProfile);
|
||||
expectDialogPickerText<IEquipmentProfile>(S.current.equipmentProfile);
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -62,7 +65,7 @@ void main() {
|
|||
await pumpApplication(tester);
|
||||
expectReadingValueContainerText(S.current.none);
|
||||
await tester.openAnimatedPicker<EquipmentProfilePicker>();
|
||||
expectRadioListTile<EquipmentProfile>(S.current.none, isSelected: true);
|
||||
expectRadioListTile<IEquipmentProfile>(S.current.none, isSelected: true);
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -73,7 +76,18 @@ void main() {
|
|||
await pumpApplication(tester);
|
||||
expectReadingValueContainerText(_mockEquipmentProfiles.first.name);
|
||||
await tester.openAnimatedPicker<EquipmentProfilePicker>();
|
||||
expectRadioListTile<EquipmentProfile>(_mockEquipmentProfiles.first.name, isSelected: true);
|
||||
expectRadioListTile<IEquipmentProfile>(_mockEquipmentProfiles.first.name, isSelected: true);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'Pinhole Camera f/64',
|
||||
(tester) async {
|
||||
when(() => storageService.selectedEquipmentProfileId).thenReturn(_mockPinholeEquipmentProfiles.first.id);
|
||||
await pumpApplication(tester);
|
||||
expectReadingValueContainerText(_mockPinholeEquipmentProfiles.first.name);
|
||||
await tester.openAnimatedPicker<EquipmentProfilePicker>();
|
||||
expectRadioListTile<IEquipmentProfile>(_mockPinholeEquipmentProfiles.first.name, isSelected: true);
|
||||
},
|
||||
);
|
||||
},
|
||||
|
@ -84,10 +98,13 @@ void main() {
|
|||
(tester) async {
|
||||
when(() => storageService.getEquipmentProfiles())
|
||||
.thenAnswer((_) async => _mockEquipmentProfiles.skip(1).toList().toTogglableMap());
|
||||
when(() => storageService.getPinholeEquipmentProfiles())
|
||||
.thenAnswer((_) async => _mockPinholeEquipmentProfiles.skip(1).toList().toTogglableMap());
|
||||
await pumpApplication(tester);
|
||||
await tester.openAnimatedPicker<EquipmentProfilePicker>();
|
||||
expectRadioListTile<EquipmentProfile>(S.current.none, isSelected: true);
|
||||
expectRadioListTile<EquipmentProfile>(_mockEquipmentProfiles[1].name);
|
||||
expectRadioListTile<IEquipmentProfile>(S.current.none, isSelected: true);
|
||||
expectRadioListTile<IEquipmentProfile>(_mockEquipmentProfiles[1].name);
|
||||
expectRadioListTile<IEquipmentProfile>(_mockPinholeEquipmentProfiles[1].name);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -126,3 +143,42 @@ final _mockEquipmentProfiles = [
|
|||
isoValues: IsoValue.values,
|
||||
),
|
||||
];
|
||||
|
||||
final _mockPinholeEquipmentProfiles = [
|
||||
const PinholeEquipmentProfile(
|
||||
id: '3',
|
||||
name: 'Pinhole Camera f/64',
|
||||
aperture: 64.0,
|
||||
isoValues: [
|
||||
IsoValue(100, StopType.full),
|
||||
IsoValue(200, StopType.full),
|
||||
IsoValue(400, StopType.full),
|
||||
IsoValue(800, StopType.full),
|
||||
],
|
||||
ndValues: [
|
||||
NdValue(0),
|
||||
NdValue(2),
|
||||
NdValue(4),
|
||||
],
|
||||
),
|
||||
const PinholeEquipmentProfile(
|
||||
id: '4',
|
||||
name: 'Pinhole Camera f/128',
|
||||
aperture: 128.0,
|
||||
isoValues: [
|
||||
IsoValue(50, StopType.full),
|
||||
IsoValue(100, StopType.full),
|
||||
IsoValue(200, StopType.full),
|
||||
IsoValue(400, StopType.full),
|
||||
IsoValue(800, StopType.full),
|
||||
IsoValue(1600, StopType.full),
|
||||
],
|
||||
ndValues: [
|
||||
NdValue(0),
|
||||
NdValue(1),
|
||||
NdValue(2),
|
||||
NdValue(4),
|
||||
NdValue(8),
|
||||
],
|
||||
),
|
||||
];
|
||||
|
|
|
@ -13,7 +13,7 @@ class _MockEquipmentProfilesStorageService extends Mock implements IapStorageSer
|
|||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
final storageService = _MockEquipmentProfilesStorageService();
|
||||
final onDidChangeDependencies = MockValueChanged<EquipmentProfile>();
|
||||
final onDidChangeDependencies = MockValueChanged<IEquipmentProfile>();
|
||||
|
||||
setUp(() {
|
||||
registerFallbackValue(_customProfiles.first);
|
||||
|
@ -26,6 +26,7 @@ void main() {
|
|||
).thenAnswer((_) async {});
|
||||
when(() => storageService.deleteEquipmentProfile(any<String>())).thenAnswer((_) async {});
|
||||
when(() => storageService.getEquipmentProfiles()).thenAnswer((_) => Future.value(_customProfiles.toTogglableMap()));
|
||||
when(() => storageService.getPinholeEquipmentProfiles()).thenAnswer((_) => Future.value({}));
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
|
@ -56,7 +57,7 @@ void main() {
|
|||
when(() => storageService.selectedEquipmentProfileId).thenReturn('');
|
||||
await pumpTestWidget(tester);
|
||||
|
||||
tester.equipmentProfilesProvider.selectProfile(_customProfiles[0]);
|
||||
tester.equipmentProfilesProvider.selectProfile(_customProfiles[0].id);
|
||||
await tester.pump();
|
||||
verify(() => onDidChangeDependencies.onChanged(_customProfiles[0])).called(1);
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue