mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2025-09-18 19:06:41 +00:00
Compare commits
5 commits
960c2360d2
...
a5f211ad4b
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a5f211ad4b | ||
![]() |
7b46d4342c | ||
![]() |
56ab8aa85a | ||
![]() |
c40354c62b | ||
![]() |
232a9316cd |
16 changed files with 295 additions and 130 deletions
|
@ -63,23 +63,13 @@ void testLogbook(String description) {
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
await tester.openPickerAndSelect<ApertureValue>(S.current.apertureValue, 'f/5.6');
|
await tester.openPickerAndSelect<ApertureValue>(S.current.apertureValue, 'f/5.6');
|
||||||
await tester.openPickerAndSelect<ShutterSpeedValue>(S.current.shutterSpeedValue, '1/125');
|
await tester.openPickerAndSelect<ShutterSpeedValue>(S.current.shutterSpeedValue, '1/125');
|
||||||
expect(
|
_expectPickerListTileValue(
|
||||||
find.descendant(
|
S.current.equipmentProfile,
|
||||||
of: find.byWidgetPredicate(
|
mockEquipmentProfiles.first.name,
|
||||||
(widget) => widget is PickerListTile && widget.title == S.current.equipmentProfile,
|
|
||||||
),
|
|
||||||
matching: find.text(mockEquipmentProfiles.first.name),
|
|
||||||
),
|
|
||||||
findsOneWidget,
|
|
||||||
);
|
);
|
||||||
expect(
|
_expectPickerListTileValue(
|
||||||
find.descendant(
|
S.current.film,
|
||||||
of: find.byWidgetPredicate(
|
mockFilms.first.name,
|
||||||
(widget) => widget is PickerListTile && widget.title == S.current.film,
|
|
||||||
),
|
|
||||||
matching: find.text(mockFilms.first.name),
|
|
||||||
),
|
|
||||||
findsOneWidget,
|
|
||||||
);
|
);
|
||||||
await tester.openPickerAndSelect<Film>(S.current.film, S.current.notSet);
|
await tester.openPickerAndSelect<Film>(S.current.film, S.current.notSet);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
@ -117,14 +107,9 @@ void testLogbook(String description) {
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
/// Verify the edits were saved
|
/// Verify the edits were saved
|
||||||
expect(
|
_expectPickerListTileValue(
|
||||||
find.descendant(
|
S.current.equipmentProfile,
|
||||||
of: find.byWidgetPredicate(
|
S.current.notSet,
|
||||||
(widget) => widget is PickerListTile && widget.title == S.current.equipmentProfile,
|
|
||||||
),
|
|
||||||
matching: find.text(S.current.notSet),
|
|
||||||
),
|
|
||||||
findsOneWidget,
|
|
||||||
);
|
);
|
||||||
expect(find.text('Test note'), findsOneWidget);
|
expect(find.text('Test note'), findsOneWidget);
|
||||||
expect(find.text('f/5.6'), findsOneWidget);
|
expect(find.text('f/5.6'), findsOneWidget);
|
||||||
|
@ -141,6 +126,138 @@ void testLogbook(String description) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@isTest
|
||||||
|
void testLogbookEquipmentProfileChanges(String description) {
|
||||||
|
setUp(() async {
|
||||||
|
SharedPreferences.setMockInitialValues({
|
||||||
|
UserPreferencesService.evSourceTypeKey: EvSourceType.camera.index,
|
||||||
|
UserPreferencesService.seenChangelogVersionKey: await const PlatformUtils().version,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
description,
|
||||||
|
(tester) async {
|
||||||
|
await tester.pumpApplication(
|
||||||
|
selectedEquipmentProfileId: mockEquipmentProfiles.first.id,
|
||||||
|
selectedFilmId: mockFilms.first.id,
|
||||||
|
customFilms: {},
|
||||||
|
);
|
||||||
|
await tester.takePhoto();
|
||||||
|
await tester.openSettings();
|
||||||
|
await tester.tapDescendantTextOf<SettingsScreen>(S.current.logbook);
|
||||||
|
await tester.tap(find.byType(LogbookPhotoGridTile).first);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await tester.ensureVisible(find.text(mockEquipmentProfiles.first.name));
|
||||||
|
_expectPickerListTileValue(
|
||||||
|
S.current.equipmentProfile,
|
||||||
|
mockEquipmentProfiles.first.name,
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.openPickerAndSelect<IEquipmentProfile>(S.current.equipmentProfile, mockEquipmentProfiles[1].name);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
_expectPickerListTileValue(
|
||||||
|
S.current.equipmentProfile,
|
||||||
|
mockEquipmentProfiles[1].name,
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.openPickerAndSelect<IEquipmentProfile>(
|
||||||
|
S.current.equipmentProfile,
|
||||||
|
mockPinholeEquipmentProfiles.first.name,
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
_expectPickerListTileValue(
|
||||||
|
S.current.equipmentProfile,
|
||||||
|
mockPinholeEquipmentProfiles.first.name,
|
||||||
|
);
|
||||||
|
_expectPickerListTileValue(
|
||||||
|
S.current.apertureValue,
|
||||||
|
ApertureValue(mockPinholeEquipmentProfiles.first.aperture, StopType.full).toString(),
|
||||||
|
reason: 'Aperture value must be automatically set when selecting a pinhole profile',
|
||||||
|
);
|
||||||
|
|
||||||
|
final aperturePickerFinder = find.descendant(
|
||||||
|
of: find.byWidgetPredicate(
|
||||||
|
(widget) => widget is PickerListTile && widget.title == S.current.apertureValue,
|
||||||
|
),
|
||||||
|
matching: find.byType(PickerListTile),
|
||||||
|
);
|
||||||
|
expect(aperturePickerFinder, findsOneWidget);
|
||||||
|
await tester.tap(aperturePickerFinder);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(
|
||||||
|
find.byType(DialogPicker<Optional<ApertureValue>>),
|
||||||
|
findsNothing,
|
||||||
|
reason: 'Aperture picker dialog must not open when pinhole profile is selected',
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.openPickerAndSelect<IEquipmentProfile>(
|
||||||
|
S.current.equipmentProfile,
|
||||||
|
mockPinholeEquipmentProfiles[1].name,
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
_expectPickerListTileValue(
|
||||||
|
S.current.equipmentProfile,
|
||||||
|
mockPinholeEquipmentProfiles[1].name,
|
||||||
|
);
|
||||||
|
|
||||||
|
_expectPickerListTileValue(
|
||||||
|
S.current.apertureValue,
|
||||||
|
ApertureValue(mockPinholeEquipmentProfiles[1].aperture, StopType.full).toString(),
|
||||||
|
reason: 'Aperture value must be updated when switching to a different pinhole profile',
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.tap(aperturePickerFinder);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(
|
||||||
|
find.byType(DialogPicker<Optional<ApertureValue>>),
|
||||||
|
findsNothing,
|
||||||
|
reason: 'Aperture picker dialog must not open when switching between pinhole profiles',
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.openPickerAndSelect<IEquipmentProfile>(S.current.equipmentProfile, mockEquipmentProfiles.first.name);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
_expectPickerListTileValue(
|
||||||
|
S.current.equipmentProfile,
|
||||||
|
mockEquipmentProfiles.first.name,
|
||||||
|
);
|
||||||
|
_expectPickerListTileValue(
|
||||||
|
S.current.apertureValue,
|
||||||
|
S.current.notSet,
|
||||||
|
reason: 'Aperture value must be cleared when switching from pinhole to regular profile',
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.tap(aperturePickerFinder);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(
|
||||||
|
find.byType(DialogPicker<Optional<ApertureValue>>),
|
||||||
|
findsOneWidget,
|
||||||
|
reason: 'Aperture picker dialog must open when regular profile is selected',
|
||||||
|
);
|
||||||
|
await tester.tap(find.text('Cancel'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await tester.openPickerAndSelect<IEquipmentProfile>(S.current.equipmentProfile, S.current.notSet);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
_expectPickerListTileValue(
|
||||||
|
S.current.equipmentProfile,
|
||||||
|
S.current.notSet,
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.tap(find.byIcon(Icons.save_outlined));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await tester.tap(find.byType(LogbookPhotoGridTile).first);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
_expectPickerListTileValue(
|
||||||
|
S.current.equipmentProfile,
|
||||||
|
S.current.notSet,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
extension on WidgetTester {
|
extension on WidgetTester {
|
||||||
Future<void> openPickerAndSelect<V>(String title, String valueToSelect) async {
|
Future<void> openPickerAndSelect<V>(String title, String valueToSelect) async {
|
||||||
await tap(find.text(title));
|
await tap(find.text(title));
|
||||||
|
@ -156,3 +273,16 @@ extension on WidgetTester {
|
||||||
await tapSelectButton();
|
await tapSelectButton();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _expectPickerListTileValue(String title, String value, {String? reason}) {
|
||||||
|
expect(
|
||||||
|
find.descendant(
|
||||||
|
of: find.byWidgetPredicate(
|
||||||
|
(widget) => widget is PickerListTile && widget.title == title,
|
||||||
|
),
|
||||||
|
matching: find.text(value),
|
||||||
|
),
|
||||||
|
findsOneWidget,
|
||||||
|
reason: reason,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ void testToggleLayoutFeatures(String description) {
|
||||||
);
|
);
|
||||||
expectExtremeExposurePairs(
|
expectExtremeExposurePairs(
|
||||||
'f/1.0 - 1/320',
|
'f/1.0 - 1/320',
|
||||||
'f/45 - 6"',
|
'f/45 - 6s',
|
||||||
reason: 'Aperture and shutter speed ranges must be reset to default values when equipment profile is reset',
|
reason: 'Aperture and shutter speed ranges must be reset to default values when equipment profile is reset',
|
||||||
);
|
);
|
||||||
expectExposurePairsListItem(
|
expectExposurePairsListItem(
|
||||||
|
@ -73,7 +73,7 @@ void testToggleLayoutFeatures(String description) {
|
||||||
expectExposurePairsListItem(
|
expectExposurePairsListItem(
|
||||||
tester,
|
tester,
|
||||||
'f/45',
|
'f/45',
|
||||||
'6"',
|
'6s',
|
||||||
reason:
|
reason:
|
||||||
'Aperture and shutter speed ranges must be reset to default values when equipment profile is reset.',
|
'Aperture and shutter speed ranges must be reset to default values when equipment profile is reset.',
|
||||||
);
|
);
|
||||||
|
@ -92,10 +92,10 @@ void testToggleLayoutFeatures(String description) {
|
||||||
(tester) async {
|
(tester) async {
|
||||||
await tester.pumpApplication();
|
await tester.pumpApplication();
|
||||||
await tester.takePhoto();
|
await tester.takePhoto();
|
||||||
expectExtremeExposurePairs('f/1.0 - 1/320', 'f/45 - 6"');
|
expectExtremeExposurePairs('f/1.0 - 1/320', 'f/45 - 6s');
|
||||||
expectExposurePairsListItem(tester, 'f/1.0', '1/320');
|
expectExposurePairsListItem(tester, 'f/1.0', '1/320');
|
||||||
await tester.scrollToTheLastExposurePair();
|
await tester.scrollToTheLastExposurePair();
|
||||||
expectExposurePairsListItem(tester, 'f/45', '6"');
|
expectExposurePairsListItem(tester, 'f/45', '6s');
|
||||||
|
|
||||||
// Disable layout feature
|
// Disable layout feature
|
||||||
await tester.toggleLayoutFeature(S.current.meteringScreenFeatureExtremeExposurePairs);
|
await tester.toggleLayoutFeature(S.current.meteringScreenFeatureExtremeExposurePairs);
|
||||||
|
@ -116,7 +116,7 @@ void testToggleLayoutFeatures(String description) {
|
||||||
expectExposurePairsListItem(
|
expectExposurePairsListItem(
|
||||||
tester,
|
tester,
|
||||||
'f/45',
|
'f/45',
|
||||||
'6"',
|
'6s',
|
||||||
reason:
|
reason:
|
||||||
'Exposure pairs list must not be affected by the visibility of the extreme exposure pairs container.',
|
'Exposure pairs list must not be affected by the visibility of the extreme exposure pairs container.',
|
||||||
);
|
);
|
||||||
|
@ -125,7 +125,7 @@ void testToggleLayoutFeatures(String description) {
|
||||||
await tester.toggleLayoutFeature(S.current.meteringScreenFeatureExtremeExposurePairs);
|
await tester.toggleLayoutFeature(S.current.meteringScreenFeatureExtremeExposurePairs);
|
||||||
expectExtremeExposurePairs(
|
expectExtremeExposurePairs(
|
||||||
'f/1.0 - 1/320',
|
'f/1.0 - 1/320',
|
||||||
'f/45 - 6"',
|
'f/45 - 6s',
|
||||||
reason:
|
reason:
|
||||||
'Exposure pairs list must not be affected by the visibility of the extreme exposure pairs container.',
|
'Exposure pairs list must not be affected by the visibility of the extreme exposure pairs container.',
|
||||||
);
|
);
|
||||||
|
@ -138,10 +138,10 @@ void testToggleLayoutFeatures(String description) {
|
||||||
await tester.pumpApplication(selectedFilmId: mockFilms.first.id);
|
await tester.pumpApplication(selectedFilmId: mockFilms.first.id);
|
||||||
await tester.takePhoto();
|
await tester.takePhoto();
|
||||||
expectPickerTitle<FilmPicker>(mockFilms.first.name);
|
expectPickerTitle<FilmPicker>(mockFilms.first.name);
|
||||||
expectExtremeExposurePairs('f/1.0 - 1/320', 'f/45 - 12"');
|
expectExtremeExposurePairs('f/1.0 - 1/320', 'f/45 - 12s');
|
||||||
expectExposurePairsListItem(tester, 'f/1.0', '1/320');
|
expectExposurePairsListItem(tester, 'f/1.0', '1/320');
|
||||||
await tester.scrollToTheLastExposurePair();
|
await tester.scrollToTheLastExposurePair();
|
||||||
expectExposurePairsListItem(tester, 'f/45', '12"');
|
expectExposurePairsListItem(tester, 'f/45', '12s');
|
||||||
|
|
||||||
// Disable layout feature
|
// Disable layout feature
|
||||||
await tester.toggleLayoutFeature(S.current.meteringScreenFeatureFilmPicker);
|
await tester.toggleLayoutFeature(S.current.meteringScreenFeatureFilmPicker);
|
||||||
|
@ -153,7 +153,7 @@ void testToggleLayoutFeatures(String description) {
|
||||||
);
|
);
|
||||||
expectExtremeExposurePairs(
|
expectExtremeExposurePairs(
|
||||||
'f/1.0 - 1/320',
|
'f/1.0 - 1/320',
|
||||||
'f/45 - 6"',
|
'f/45 - 6s',
|
||||||
reason: 'Shutter speed must not be affected by reciprocity when film is discarded.',
|
reason: 'Shutter speed must not be affected by reciprocity when film is discarded.',
|
||||||
);
|
);
|
||||||
expectExposurePairsListItem(
|
expectExposurePairsListItem(
|
||||||
|
@ -166,7 +166,7 @@ void testToggleLayoutFeatures(String description) {
|
||||||
expectExposurePairsListItem(
|
expectExposurePairsListItem(
|
||||||
tester,
|
tester,
|
||||||
'f/45',
|
'f/45',
|
||||||
'6"',
|
'6s',
|
||||||
reason: 'Shutter speed must not be affected by reciprocity when film is discarded.',
|
reason: 'Shutter speed must not be affected by reciprocity when film is discarded.',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -19,9 +19,10 @@ void main() {
|
||||||
mockCameraFocalLength();
|
mockCameraFocalLength();
|
||||||
});
|
});
|
||||||
|
|
||||||
testPurchases('Purchase & refund premium features');
|
testPurchases('Purchase & refund premium features test');
|
||||||
testGuardProTap('Guard Pro tap');
|
testGuardProTap('Guard Pro tap test');
|
||||||
testToggleLayoutFeatures('Toggle metering screen layout features');
|
testToggleLayoutFeatures('Toggle metering screen layout features test');
|
||||||
testLogbook('Logbook');
|
testLogbook('Logbook test');
|
||||||
testE2E('e2e');
|
testLogbookEquipmentProfileChanges('Logbook equipment profile changes test');
|
||||||
|
testE2E('e2e test');
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'dart:collection';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
@ -34,7 +35,7 @@ class LogbookPhotosProvider extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class LogbookPhotosProviderState extends State<LogbookPhotosProvider> {
|
class LogbookPhotosProviderState extends State<LogbookPhotosProvider> {
|
||||||
final Map<String, LogbookPhoto> _photos = {};
|
final LinkedHashMap<String, LogbookPhoto> _photos = LinkedHashMap();
|
||||||
bool _isEnabled = true;
|
bool _isEnabled = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -90,6 +91,9 @@ class LogbookPhotosProviderState extends State<LogbookPhotosProvider> {
|
||||||
name: path,
|
name: path,
|
||||||
timestamp: DateTime.timestamp(),
|
timestamp: DateTime.timestamp(),
|
||||||
ev: ev100,
|
ev: ev100,
|
||||||
|
apertureValue: equipmentProfile is PinholeEquipmentProfile
|
||||||
|
? ApertureValue(equipmentProfile.aperture, StopType.full)
|
||||||
|
: null,
|
||||||
iso: iso,
|
iso: iso,
|
||||||
nd: nd,
|
nd: nd,
|
||||||
coordinates: coordinates,
|
coordinates: coordinates,
|
||||||
|
|
|
@ -12,8 +12,15 @@ class LogbookPhotoEditBloc extends Bloc<LogbookPhotoEditEvent, LogbookPhotoEditS
|
||||||
LogbookPhotoEditBloc(
|
LogbookPhotoEditBloc(
|
||||||
this.photosProvider,
|
this.photosProvider,
|
||||||
LogbookPhoto photo,
|
LogbookPhoto photo,
|
||||||
|
IEquipmentProfile? equipmentProfile,
|
||||||
) : _originalPhoto = photo,
|
) : _originalPhoto = photo,
|
||||||
_newPhoto = photo,
|
_newPhoto = photo,
|
||||||
|
assert(
|
||||||
|
equipmentProfile == null ||
|
||||||
|
equipmentProfile is! PinholeEquipmentProfile ||
|
||||||
|
photo.apertureValue != null && equipmentProfile.aperture == photo.apertureValue!.rawValue,
|
||||||
|
"Aperture value must be the same as the equipment profile's aperture value if the equipment profile is a pinhole profile",
|
||||||
|
),
|
||||||
super(
|
super(
|
||||||
LogbookPhotoEditState(
|
LogbookPhotoEditState(
|
||||||
id: photo.id,
|
id: photo.id,
|
||||||
|
@ -25,7 +32,7 @@ class LogbookPhotoEditBloc extends Bloc<LogbookPhotoEditEvent, LogbookPhotoEditS
|
||||||
coordinates: photo.coordinates,
|
coordinates: photo.coordinates,
|
||||||
aperture: photo.apertureValue,
|
aperture: photo.apertureValue,
|
||||||
shutterSpeed: photo.shutterSpeedValue,
|
shutterSpeed: photo.shutterSpeedValue,
|
||||||
equipmentProfileId: photo.equipmentProfileId,
|
equipmentProfile: equipmentProfile,
|
||||||
filmId: photo.filmId,
|
filmId: photo.filmId,
|
||||||
note: photo.note,
|
note: photo.note,
|
||||||
canSave: false,
|
canSave: false,
|
||||||
|
@ -74,10 +81,22 @@ class LogbookPhotoEditBloc extends Bloc<LogbookPhotoEditEvent, LogbookPhotoEditS
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onEquipmentProfileChanged(LogbookPhotoEquipmentProfileChangedEvent event, Emitter emit) async {
|
Future<void> _onEquipmentProfileChanged(LogbookPhotoEquipmentProfileChangedEvent event, Emitter emit) async {
|
||||||
_newPhoto = _newPhoto.copyWith(equipmentProfileId: Optional(event.equipmentProfileId));
|
final equipmentProfile = event.equipmentProfile;
|
||||||
|
Optional<ApertureValue>? apertureValue;
|
||||||
|
if (state.equipmentProfile is PinholeEquipmentProfile &&
|
||||||
|
(equipmentProfile == null || equipmentProfile is EquipmentProfile)) {
|
||||||
|
apertureValue = const Optional(null);
|
||||||
|
} else if (equipmentProfile is PinholeEquipmentProfile) {
|
||||||
|
apertureValue = Optional(ApertureValue(equipmentProfile.aperture, StopType.full));
|
||||||
|
}
|
||||||
|
_newPhoto = _newPhoto.copyWith(
|
||||||
|
apertureValue: apertureValue,
|
||||||
|
equipmentProfileId: Optional(equipmentProfile?.id),
|
||||||
|
);
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
equipmentProfileId: Optional(event.equipmentProfileId),
|
aperture: apertureValue,
|
||||||
|
equipmentProfile: Optional(event.equipmentProfile),
|
||||||
canSave: _canSave(),
|
canSave: _canSave(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,7 +9,7 @@ class PickerListTile<T> extends StatelessWidget {
|
||||||
final T? selectedValue;
|
final T? selectedValue;
|
||||||
final List<T> values;
|
final List<T> values;
|
||||||
final String Function(T) titleAdapter;
|
final String Function(T) titleAdapter;
|
||||||
final ValueChanged<Optional<T>> onChanged;
|
final ValueChanged<Optional<T>>? onChanged;
|
||||||
|
|
||||||
const PickerListTile({
|
const PickerListTile({
|
||||||
required this.icon,
|
required this.icon,
|
||||||
|
@ -27,7 +27,8 @@ class PickerListTile<T> extends StatelessWidget {
|
||||||
leading: Icon(icon),
|
leading: Icon(icon),
|
||||||
title: Text(title),
|
title: Text(title),
|
||||||
trailing: Text(_titleAdapter(context, selectedValue)),
|
trailing: Text(_titleAdapter(context, selectedValue)),
|
||||||
onTap: () {
|
onTap: onChanged != null
|
||||||
|
? () {
|
||||||
showDialog<Optional<T>>(
|
showDialog<Optional<T>>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => DialogPicker<Optional<T>>(
|
builder: (_) => DialogPicker<Optional<T>>(
|
||||||
|
@ -44,10 +45,11 @@ class PickerListTile<T> extends StatelessWidget {
|
||||||
),
|
),
|
||||||
).then((value) {
|
).then((value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
onChanged(value);
|
onChanged!(value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,9 +23,9 @@ class LogbookPhotoNoteChangedEvent extends LogbookPhotoEditEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
class LogbookPhotoEquipmentProfileChangedEvent extends LogbookPhotoEditEvent {
|
class LogbookPhotoEquipmentProfileChangedEvent extends LogbookPhotoEditEvent {
|
||||||
final String? equipmentProfileId;
|
final IEquipmentProfile? equipmentProfile;
|
||||||
|
|
||||||
const LogbookPhotoEquipmentProfileChangedEvent(this.equipmentProfileId);
|
const LogbookPhotoEquipmentProfileChangedEvent(this.equipmentProfile);
|
||||||
}
|
}
|
||||||
|
|
||||||
class LogbookPhotoFilmChangedEvent extends LogbookPhotoEditEvent {
|
class LogbookPhotoFilmChangedEvent extends LogbookPhotoEditEvent {
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:lightmeter/providers/equipment_profile_provider.dart';
|
||||||
import 'package:lightmeter/providers/logbook_photos_provider.dart';
|
import 'package:lightmeter/providers/logbook_photos_provider.dart';
|
||||||
import 'package:lightmeter/screens/logbook_photo_edit/bloc_logbook_photo_edit.dart';
|
import 'package:lightmeter/screens/logbook_photo_edit/bloc_logbook_photo_edit.dart';
|
||||||
import 'package:lightmeter/screens/logbook_photo_edit/screen_logbook_photo_edit.dart';
|
import 'package:lightmeter/screens/logbook_photo_edit/screen_logbook_photo_edit.dart';
|
||||||
|
@ -25,6 +27,7 @@ class LogbookPhotoEditFlow extends StatelessWidget {
|
||||||
create: (_) => LogbookPhotoEditBloc(
|
create: (_) => LogbookPhotoEditBloc(
|
||||||
LogbookPhotosProvider.of(context),
|
LogbookPhotosProvider.of(context),
|
||||||
args.photo,
|
args.photo,
|
||||||
|
EquipmentProfiles.of(context).firstWhereOrNull((e) => e.id == args.photo.equipmentProfileId),
|
||||||
),
|
),
|
||||||
child: const LogbookPhotoEditScreen(),
|
child: const LogbookPhotoEditScreen(),
|
||||||
);
|
);
|
||||||
|
|
|
@ -224,13 +224,15 @@ class _AperturePickerListTile extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<LogbookPhotoEditBloc, LogbookPhotoEditState>(
|
return BlocBuilder<LogbookPhotoEditBloc, LogbookPhotoEditState>(
|
||||||
buildWhen: (previous, current) => previous.aperture != current.aperture,
|
buildWhen: (previous, current) => previous.aperture != current.aperture,
|
||||||
builder: (context, state) => PickerListTile(
|
builder: (context, state) => PickerListTile<ApertureValue>(
|
||||||
icon: Icons.camera_outlined,
|
icon: Icons.camera_outlined,
|
||||||
title: S.of(context).apertureValue,
|
title: S.of(context).apertureValue,
|
||||||
values: ApertureValue.values,
|
values: ApertureValue.values,
|
||||||
selectedValue: state.aperture,
|
selectedValue: state.aperture,
|
||||||
titleAdapter: (value) => value.toString(),
|
titleAdapter: (value) => value.toString(),
|
||||||
onChanged: (value) {
|
onChanged: state.equipmentProfile is PinholeEquipmentProfile
|
||||||
|
? null
|
||||||
|
: (value) {
|
||||||
context.read<LogbookPhotoEditBloc>().add(LogbookPhotoApertureChangedEvent(value.value));
|
context.read<LogbookPhotoEditBloc>().add(LogbookPhotoApertureChangedEvent(value.value));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -265,15 +267,15 @@ class _EquipmentProfilePickerListTile extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<LogbookPhotoEditBloc, LogbookPhotoEditState>(
|
return BlocBuilder<LogbookPhotoEditBloc, LogbookPhotoEditState>(
|
||||||
buildWhen: (previous, current) => previous.equipmentProfileId != current.equipmentProfileId,
|
buildWhen: (previous, current) => previous.equipmentProfile != current.equipmentProfile,
|
||||||
builder: (context, state) => PickerListTile(
|
builder: (context, state) => PickerListTile(
|
||||||
icon: Icons.camera_alt_outlined,
|
icon: Icons.camera_alt_outlined,
|
||||||
title: S.of(context).equipmentProfile,
|
title: S.of(context).equipmentProfile,
|
||||||
values: EquipmentProfiles.of(context).skip(1).toList(growable: false),
|
values: EquipmentProfiles.of(context).skip(1).toList(growable: false),
|
||||||
selectedValue: EquipmentProfiles.of(context).firstWhereOrNull((e) => e.id == state.equipmentProfileId),
|
selectedValue: state.equipmentProfile,
|
||||||
titleAdapter: (value) => value.name,
|
titleAdapter: (value) => value.name,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
context.read<LogbookPhotoEditBloc>().add(LogbookPhotoEquipmentProfileChangedEvent(value.value?.id));
|
context.read<LogbookPhotoEditBloc>().add(LogbookPhotoEquipmentProfileChangedEvent(value.value));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -10,7 +10,7 @@ class LogbookPhotoEditState {
|
||||||
final Coordinates? coordinates;
|
final Coordinates? coordinates;
|
||||||
final ApertureValue? aperture;
|
final ApertureValue? aperture;
|
||||||
final ShutterSpeedValue? shutterSpeed;
|
final ShutterSpeedValue? shutterSpeed;
|
||||||
final String? equipmentProfileId;
|
final IEquipmentProfile? equipmentProfile;
|
||||||
final String? filmId;
|
final String? filmId;
|
||||||
final String? note;
|
final String? note;
|
||||||
final bool canSave;
|
final bool canSave;
|
||||||
|
@ -26,7 +26,7 @@ class LogbookPhotoEditState {
|
||||||
this.coordinates,
|
this.coordinates,
|
||||||
this.aperture,
|
this.aperture,
|
||||||
this.shutterSpeed,
|
this.shutterSpeed,
|
||||||
this.equipmentProfileId,
|
this.equipmentProfile,
|
||||||
this.filmId,
|
this.filmId,
|
||||||
this.note,
|
this.note,
|
||||||
required this.canSave,
|
required this.canSave,
|
||||||
|
@ -37,7 +37,7 @@ class LogbookPhotoEditState {
|
||||||
String? name,
|
String? name,
|
||||||
Optional<ApertureValue>? aperture,
|
Optional<ApertureValue>? aperture,
|
||||||
Optional<ShutterSpeedValue>? shutterSpeed,
|
Optional<ShutterSpeedValue>? shutterSpeed,
|
||||||
Optional<String>? equipmentProfileId,
|
Optional<IEquipmentProfile>? equipmentProfile,
|
||||||
Optional<String>? filmId,
|
Optional<String>? filmId,
|
||||||
String? note,
|
String? note,
|
||||||
bool? canSave,
|
bool? canSave,
|
||||||
|
@ -52,7 +52,7 @@ class LogbookPhotoEditState {
|
||||||
nd: nd,
|
nd: nd,
|
||||||
aperture: aperture != null ? aperture.value : this.aperture,
|
aperture: aperture != null ? aperture.value : this.aperture,
|
||||||
shutterSpeed: shutterSpeed != null ? shutterSpeed.value : this.shutterSpeed,
|
shutterSpeed: shutterSpeed != null ? shutterSpeed.value : this.shutterSpeed,
|
||||||
equipmentProfileId: equipmentProfileId != null ? equipmentProfileId.value : this.equipmentProfileId,
|
equipmentProfile: equipmentProfile != null ? equipmentProfile.value : this.equipmentProfile,
|
||||||
filmId: filmId != null ? filmId.value : this.filmId,
|
filmId: filmId != null ? filmId.value : this.filmId,
|
||||||
note: note ?? this.note,
|
note: note ?? this.note,
|
||||||
canSave: canSave ?? this.canSave,
|
canSave: canSave ?? this.canSave,
|
||||||
|
|
|
@ -126,11 +126,13 @@ class CameraContainer extends StatelessWidget {
|
||||||
enabledFeaturesHeight += Dimens.readingContainerSingleValueHeight;
|
enabledFeaturesHeight += Dimens.readingContainerSingleValueHeight;
|
||||||
enabledFeaturesHeight += Dimens.paddingS;
|
enabledFeaturesHeight += Dimens.paddingS;
|
||||||
}
|
}
|
||||||
|
if (context.meteringFeature(MeteringScreenLayoutFeature.extremeExposurePairs)) {
|
||||||
if (EquipmentProfiles.selectedOf(context) is PinholeEquipmentProfile) {
|
if (EquipmentProfiles.selectedOf(context) is PinholeEquipmentProfile) {
|
||||||
enabledFeaturesHeight += Dimens.readingContainerSingleValueHeight;
|
enabledFeaturesHeight += Dimens.readingContainerSingleValueHeight;
|
||||||
enabledFeaturesHeight += Dimens.paddingS;
|
} else {
|
||||||
} else if (context.meteringFeature(MeteringScreenLayoutFeature.extremeExposurePairs)) {
|
|
||||||
enabledFeaturesHeight += Dimens.readingContainerDoubleValueHeight;
|
enabledFeaturesHeight += Dimens.readingContainerDoubleValueHeight;
|
||||||
|
}
|
||||||
|
|
||||||
enabledFeaturesHeight += Dimens.paddingS;
|
enabledFeaturesHeight += Dimens.paddingS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,6 +65,7 @@ class ExposurePairsList extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (exposurePairs.length > 1)
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 0,
|
top: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
|
|
|
@ -46,10 +46,10 @@ class ReadingsContainer extends StatelessWidget {
|
||||||
const EquipmentProfilePicker(),
|
const EquipmentProfilePicker(),
|
||||||
const _InnerPadding(),
|
const _InnerPadding(),
|
||||||
],
|
],
|
||||||
if (EquipmentProfiles.selectedOf(context) is PinholeEquipmentProfile) ...[
|
if (context.meteringFeature(MeteringScreenLayoutFeature.extremeExposurePairs)) ...[
|
||||||
ShutterSpeedContainer(shutterSpeedValue: fastest?.shutterSpeed),
|
if (EquipmentProfiles.selectedOf(context) is PinholeEquipmentProfile)
|
||||||
const _InnerPadding(),
|
ShutterSpeedContainer(shutterSpeedValue: fastest?.shutterSpeed)
|
||||||
] else if (context.meteringFeature(MeteringScreenLayoutFeature.extremeExposurePairs)) ...[
|
else
|
||||||
ExtremeExposurePairsContainer(
|
ExtremeExposurePairsContainer(
|
||||||
fastest: fastest,
|
fastest: fastest,
|
||||||
slowest: slowest,
|
slowest: slowest,
|
||||||
|
|
|
@ -24,19 +24,20 @@ class DialogPicker<T> extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DialogPickerState<T> extends State<DialogPicker<T>> {
|
class _DialogPickerState<T> extends State<DialogPicker<T>> {
|
||||||
late T _selected = widget.selectedValue;
|
T? _selected;
|
||||||
final ScrollController _scrollController = ScrollController();
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
final selectedIndex = widget.values.indexOf(widget.selectedValue);
|
||||||
final selectedIndex = widget.values.indexOf(_selected);
|
|
||||||
if (selectedIndex >= 0) {
|
if (selectedIndex >= 0) {
|
||||||
|
_selected = widget.selectedValue;
|
||||||
|
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||||
_scrollController.jumpTo((Dimens.grid56 * selectedIndex).clamp(0, _scrollController.position.maxScrollExtent));
|
_scrollController.jumpTo((Dimens.grid56 * selectedIndex).clamp(0, _scrollController.position.maxScrollExtent));
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
@ -93,7 +94,7 @@ class _DialogPickerState<T> extends State<DialogPicker<T>> {
|
||||||
child: Text(S.of(context).cancel),
|
child: Text(S.of(context).cancel),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(context).pop(_selected),
|
onPressed: _selected != null ? () => Navigator.of(context).pop(_selected) : null,
|
||||||
child: Text(S.of(context).select),
|
child: Text(S.of(context).select),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
26
pubspec.lock
26
pubspec.lock
|
@ -170,10 +170,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: built_value
|
name: built_value
|
||||||
sha256: "082001b5c3dc495d4a42f1d5789990505df20d8547d42507c29050af6933ee27"
|
sha256: a30f0a0e38671e89a492c44d005b5545b830a961575bbd8336d42869ff71066d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.10.1"
|
version: "8.12.0"
|
||||||
camera:
|
camera:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -861,8 +861,8 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: "feature/MLI-48"
|
ref: main
|
||||||
resolved-ref: "4a169640bff3d3a3206a2c352a75cbcea4871b1c"
|
resolved-ref: "3f5bae6d2500a746fb83ab345919095a815244d1"
|
||||||
url: "https://github.com/vodemn/m3_lightmeter_iap"
|
url: "https://github.com/vodemn/m3_lightmeter_iap"
|
||||||
source: git
|
source: git
|
||||||
version: "4.1.2+37"
|
version: "4.1.2+37"
|
||||||
|
@ -870,11 +870,11 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: "feature/MLR-18"
|
ref: "v2.5.0"
|
||||||
resolved-ref: "61bb3f8a9164d19f6e47c96fbea1cbe3aaf39fc3"
|
resolved-ref: "680affb45c5d03ed4fe61c30ee0d6e6fab0f2c12"
|
||||||
url: "https://github.com/vodemn/m3_lightmeter_resources"
|
url: "https://github.com/vodemn/m3_lightmeter_resources"
|
||||||
source: git
|
source: git
|
||||||
version: "2.4.0+13"
|
version: "2.5.0+14"
|
||||||
macros:
|
macros:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -967,10 +967,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: package_info_plus_platform_interface
|
name: package_info_plus_platform_interface
|
||||||
sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c"
|
sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.2.0"
|
version: "3.2.1"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1119,10 +1119,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: provider
|
name: provider
|
||||||
sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84"
|
sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.5"
|
version: "6.1.5+1"
|
||||||
pub_semver:
|
pub_semver:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1524,10 +1524,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: watcher
|
name: watcher
|
||||||
sha256: "0b7fd4a0bbc4b92641dbf20adfd7e3fd1398fe17102d94b674234563e110088a"
|
sha256: "5bf046f41320ac97a469d506261797f35254fa61c641741ef32dacda98b7d39c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
version: "1.1.3"
|
||||||
web:
|
web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -33,11 +33,11 @@ dependencies:
|
||||||
m3_lightmeter_iap:
|
m3_lightmeter_iap:
|
||||||
git:
|
git:
|
||||||
url: "https://github.com/vodemn/m3_lightmeter_iap"
|
url: "https://github.com/vodemn/m3_lightmeter_iap"
|
||||||
ref: feature/MLI-48
|
ref: main
|
||||||
m3_lightmeter_resources:
|
m3_lightmeter_resources:
|
||||||
git:
|
git:
|
||||||
url: "https://github.com/vodemn/m3_lightmeter_resources"
|
url: "https://github.com/vodemn/m3_lightmeter_resources"
|
||||||
ref: feature/MLR-18
|
ref: v2.5.0
|
||||||
map_launcher: 3.2.0
|
map_launcher: 3.2.0
|
||||||
material_color_utilities: 0.12.0
|
material_color_utilities: 0.12.0
|
||||||
package_info_plus: 8.1.3
|
package_info_plus: 8.1.3
|
||||||
|
|
Loading…
Reference in a new issue