mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2025-09-18 17:36:40 +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.openPickerAndSelect<ApertureValue>(S.current.apertureValue, 'f/5.6');
|
||||
await tester.openPickerAndSelect<ShutterSpeedValue>(S.current.shutterSpeedValue, '1/125');
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byWidgetPredicate(
|
||||
(widget) => widget is PickerListTile && widget.title == S.current.equipmentProfile,
|
||||
),
|
||||
matching: find.text(mockEquipmentProfiles.first.name),
|
||||
),
|
||||
findsOneWidget,
|
||||
_expectPickerListTileValue(
|
||||
S.current.equipmentProfile,
|
||||
mockEquipmentProfiles.first.name,
|
||||
);
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byWidgetPredicate(
|
||||
(widget) => widget is PickerListTile && widget.title == S.current.film,
|
||||
),
|
||||
matching: find.text(mockFilms.first.name),
|
||||
),
|
||||
findsOneWidget,
|
||||
_expectPickerListTileValue(
|
||||
S.current.film,
|
||||
mockFilms.first.name,
|
||||
);
|
||||
await tester.openPickerAndSelect<Film>(S.current.film, S.current.notSet);
|
||||
await tester.pumpAndSettle();
|
||||
|
@ -117,14 +107,9 @@ void testLogbook(String description) {
|
|||
await tester.pumpAndSettle();
|
||||
|
||||
/// Verify the edits were saved
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byWidgetPredicate(
|
||||
(widget) => widget is PickerListTile && widget.title == S.current.equipmentProfile,
|
||||
),
|
||||
matching: find.text(S.current.notSet),
|
||||
),
|
||||
findsOneWidget,
|
||||
_expectPickerListTileValue(
|
||||
S.current.equipmentProfile,
|
||||
S.current.notSet,
|
||||
);
|
||||
expect(find.text('Test note'), 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 {
|
||||
Future<void> openPickerAndSelect<V>(String title, String valueToSelect) async {
|
||||
await tap(find.text(title));
|
||||
|
@ -156,3 +273,16 @@ extension on WidgetTester {
|
|||
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(
|
||||
'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',
|
||||
);
|
||||
expectExposurePairsListItem(
|
||||
|
@ -73,7 +73,7 @@ void testToggleLayoutFeatures(String description) {
|
|||
expectExposurePairsListItem(
|
||||
tester,
|
||||
'f/45',
|
||||
'6"',
|
||||
'6s',
|
||||
reason:
|
||||
'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 {
|
||||
await tester.pumpApplication();
|
||||
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');
|
||||
await tester.scrollToTheLastExposurePair();
|
||||
expectExposurePairsListItem(tester, 'f/45', '6"');
|
||||
expectExposurePairsListItem(tester, 'f/45', '6s');
|
||||
|
||||
// Disable layout feature
|
||||
await tester.toggleLayoutFeature(S.current.meteringScreenFeatureExtremeExposurePairs);
|
||||
|
@ -116,7 +116,7 @@ void testToggleLayoutFeatures(String description) {
|
|||
expectExposurePairsListItem(
|
||||
tester,
|
||||
'f/45',
|
||||
'6"',
|
||||
'6s',
|
||||
reason:
|
||||
'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);
|
||||
expectExtremeExposurePairs(
|
||||
'f/1.0 - 1/320',
|
||||
'f/45 - 6"',
|
||||
'f/45 - 6s',
|
||||
reason:
|
||||
'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.takePhoto();
|
||||
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');
|
||||
await tester.scrollToTheLastExposurePair();
|
||||
expectExposurePairsListItem(tester, 'f/45', '12"');
|
||||
expectExposurePairsListItem(tester, 'f/45', '12s');
|
||||
|
||||
// Disable layout feature
|
||||
await tester.toggleLayoutFeature(S.current.meteringScreenFeatureFilmPicker);
|
||||
|
@ -153,7 +153,7 @@ void testToggleLayoutFeatures(String description) {
|
|||
);
|
||||
expectExtremeExposurePairs(
|
||||
'f/1.0 - 1/320',
|
||||
'f/45 - 6"',
|
||||
'f/45 - 6s',
|
||||
reason: 'Shutter speed must not be affected by reciprocity when film is discarded.',
|
||||
);
|
||||
expectExposurePairsListItem(
|
||||
|
@ -166,7 +166,7 @@ void testToggleLayoutFeatures(String description) {
|
|||
expectExposurePairsListItem(
|
||||
tester,
|
||||
'f/45',
|
||||
'6"',
|
||||
'6s',
|
||||
reason: 'Shutter speed must not be affected by reciprocity when film is discarded.',
|
||||
);
|
||||
|
||||
|
|
|
@ -19,9 +19,10 @@ void main() {
|
|||
mockCameraFocalLength();
|
||||
});
|
||||
|
||||
testPurchases('Purchase & refund premium features');
|
||||
testGuardProTap('Guard Pro tap');
|
||||
testToggleLayoutFeatures('Toggle metering screen layout features');
|
||||
testLogbook('Logbook');
|
||||
testE2E('e2e');
|
||||
testPurchases('Purchase & refund premium features test');
|
||||
testGuardProTap('Guard Pro tap test');
|
||||
testToggleLayoutFeatures('Toggle metering screen layout features test');
|
||||
testLogbook('Logbook test');
|
||||
testLogbookEquipmentProfileChanges('Logbook equipment profile changes test');
|
||||
testE2E('e2e test');
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'dart:collection';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
|
@ -34,7 +35,7 @@ class LogbookPhotosProvider extends StatefulWidget {
|
|||
}
|
||||
|
||||
class LogbookPhotosProviderState extends State<LogbookPhotosProvider> {
|
||||
final Map<String, LogbookPhoto> _photos = {};
|
||||
final LinkedHashMap<String, LogbookPhoto> _photos = LinkedHashMap();
|
||||
bool _isEnabled = true;
|
||||
|
||||
@override
|
||||
|
@ -90,6 +91,9 @@ class LogbookPhotosProviderState extends State<LogbookPhotosProvider> {
|
|||
name: path,
|
||||
timestamp: DateTime.timestamp(),
|
||||
ev: ev100,
|
||||
apertureValue: equipmentProfile is PinholeEquipmentProfile
|
||||
? ApertureValue(equipmentProfile.aperture, StopType.full)
|
||||
: null,
|
||||
iso: iso,
|
||||
nd: nd,
|
||||
coordinates: coordinates,
|
||||
|
|
|
@ -12,8 +12,15 @@ class LogbookPhotoEditBloc extends Bloc<LogbookPhotoEditEvent, LogbookPhotoEditS
|
|||
LogbookPhotoEditBloc(
|
||||
this.photosProvider,
|
||||
LogbookPhoto photo,
|
||||
IEquipmentProfile? equipmentProfile,
|
||||
) : _originalPhoto = 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(
|
||||
LogbookPhotoEditState(
|
||||
id: photo.id,
|
||||
|
@ -25,7 +32,7 @@ class LogbookPhotoEditBloc extends Bloc<LogbookPhotoEditEvent, LogbookPhotoEditS
|
|||
coordinates: photo.coordinates,
|
||||
aperture: photo.apertureValue,
|
||||
shutterSpeed: photo.shutterSpeedValue,
|
||||
equipmentProfileId: photo.equipmentProfileId,
|
||||
equipmentProfile: equipmentProfile,
|
||||
filmId: photo.filmId,
|
||||
note: photo.note,
|
||||
canSave: false,
|
||||
|
@ -74,10 +81,22 @@ class LogbookPhotoEditBloc extends Bloc<LogbookPhotoEditEvent, LogbookPhotoEditS
|
|||
}
|
||||
|
||||
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(
|
||||
state.copyWith(
|
||||
equipmentProfileId: Optional(event.equipmentProfileId),
|
||||
aperture: apertureValue,
|
||||
equipmentProfile: Optional(event.equipmentProfile),
|
||||
canSave: _canSave(),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -9,7 +9,7 @@ class PickerListTile<T> extends StatelessWidget {
|
|||
final T? selectedValue;
|
||||
final List<T> values;
|
||||
final String Function(T) titleAdapter;
|
||||
final ValueChanged<Optional<T>> onChanged;
|
||||
final ValueChanged<Optional<T>>? onChanged;
|
||||
|
||||
const PickerListTile({
|
||||
required this.icon,
|
||||
|
@ -27,27 +27,29 @@ class PickerListTile<T> extends StatelessWidget {
|
|||
leading: Icon(icon),
|
||||
title: Text(title),
|
||||
trailing: Text(_titleAdapter(context, selectedValue)),
|
||||
onTap: () {
|
||||
showDialog<Optional<T>>(
|
||||
context: context,
|
||||
builder: (_) => DialogPicker<Optional<T>>(
|
||||
icon: icon,
|
||||
title: title,
|
||||
selectedValue: Optional(selectedValue),
|
||||
values: [
|
||||
/// `const Optional(null)` for some reason is not equal to a non-const `Optional(null)`
|
||||
// ignore: prefer_const_constructors
|
||||
Optional(null),
|
||||
...values.toSet().map((e) => Optional(e)),
|
||||
],
|
||||
titleAdapter: (context, value) => _titleAdapter(context, value.value),
|
||||
),
|
||||
).then((value) {
|
||||
if (value != null) {
|
||||
onChanged(value);
|
||||
}
|
||||
});
|
||||
},
|
||||
onTap: onChanged != null
|
||||
? () {
|
||||
showDialog<Optional<T>>(
|
||||
context: context,
|
||||
builder: (_) => DialogPicker<Optional<T>>(
|
||||
icon: icon,
|
||||
title: title,
|
||||
selectedValue: Optional(selectedValue),
|
||||
values: [
|
||||
/// `const Optional(null)` for some reason is not equal to a non-const `Optional(null)`
|
||||
// ignore: prefer_const_constructors
|
||||
Optional(null),
|
||||
...values.toSet().map((e) => Optional(e)),
|
||||
],
|
||||
titleAdapter: (context, value) => _titleAdapter(context, value.value),
|
||||
),
|
||||
).then((value) {
|
||||
if (value != null) {
|
||||
onChanged!(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -23,9 +23,9 @@ class LogbookPhotoNoteChangedEvent extends LogbookPhotoEditEvent {
|
|||
}
|
||||
|
||||
class LogbookPhotoEquipmentProfileChangedEvent extends LogbookPhotoEditEvent {
|
||||
final String? equipmentProfileId;
|
||||
final IEquipmentProfile? equipmentProfile;
|
||||
|
||||
const LogbookPhotoEquipmentProfileChangedEvent(this.equipmentProfileId);
|
||||
const LogbookPhotoEquipmentProfileChangedEvent(this.equipmentProfile);
|
||||
}
|
||||
|
||||
class LogbookPhotoFilmChangedEvent extends LogbookPhotoEditEvent {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.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/screens/logbook_photo_edit/bloc_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(
|
||||
LogbookPhotosProvider.of(context),
|
||||
args.photo,
|
||||
EquipmentProfiles.of(context).firstWhereOrNull((e) => e.id == args.photo.equipmentProfileId),
|
||||
),
|
||||
child: const LogbookPhotoEditScreen(),
|
||||
);
|
||||
|
|
|
@ -224,15 +224,17 @@ class _AperturePickerListTile extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<LogbookPhotoEditBloc, LogbookPhotoEditState>(
|
||||
buildWhen: (previous, current) => previous.aperture != current.aperture,
|
||||
builder: (context, state) => PickerListTile(
|
||||
builder: (context, state) => PickerListTile<ApertureValue>(
|
||||
icon: Icons.camera_outlined,
|
||||
title: S.of(context).apertureValue,
|
||||
values: ApertureValue.values,
|
||||
selectedValue: state.aperture,
|
||||
titleAdapter: (value) => value.toString(),
|
||||
onChanged: (value) {
|
||||
context.read<LogbookPhotoEditBloc>().add(LogbookPhotoApertureChangedEvent(value.value));
|
||||
},
|
||||
onChanged: state.equipmentProfile is PinholeEquipmentProfile
|
||||
? null
|
||||
: (value) {
|
||||
context.read<LogbookPhotoEditBloc>().add(LogbookPhotoApertureChangedEvent(value.value));
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -265,15 +267,15 @@ class _EquipmentProfilePickerListTile extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<LogbookPhotoEditBloc, LogbookPhotoEditState>(
|
||||
buildWhen: (previous, current) => previous.equipmentProfileId != current.equipmentProfileId,
|
||||
buildWhen: (previous, current) => previous.equipmentProfile != current.equipmentProfile,
|
||||
builder: (context, state) => PickerListTile(
|
||||
icon: Icons.camera_alt_outlined,
|
||||
title: S.of(context).equipmentProfile,
|
||||
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,
|
||||
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 ApertureValue? aperture;
|
||||
final ShutterSpeedValue? shutterSpeed;
|
||||
final String? equipmentProfileId;
|
||||
final IEquipmentProfile? equipmentProfile;
|
||||
final String? filmId;
|
||||
final String? note;
|
||||
final bool canSave;
|
||||
|
@ -26,7 +26,7 @@ class LogbookPhotoEditState {
|
|||
this.coordinates,
|
||||
this.aperture,
|
||||
this.shutterSpeed,
|
||||
this.equipmentProfileId,
|
||||
this.equipmentProfile,
|
||||
this.filmId,
|
||||
this.note,
|
||||
required this.canSave,
|
||||
|
@ -37,7 +37,7 @@ class LogbookPhotoEditState {
|
|||
String? name,
|
||||
Optional<ApertureValue>? aperture,
|
||||
Optional<ShutterSpeedValue>? shutterSpeed,
|
||||
Optional<String>? equipmentProfileId,
|
||||
Optional<IEquipmentProfile>? equipmentProfile,
|
||||
Optional<String>? filmId,
|
||||
String? note,
|
||||
bool? canSave,
|
||||
|
@ -52,7 +52,7 @@ class LogbookPhotoEditState {
|
|||
nd: nd,
|
||||
aperture: aperture != null ? aperture.value : this.aperture,
|
||||
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,
|
||||
note: note ?? this.note,
|
||||
canSave: canSave ?? this.canSave,
|
||||
|
|
|
@ -126,11 +126,13 @@ class CameraContainer extends StatelessWidget {
|
|||
enabledFeaturesHeight += Dimens.readingContainerSingleValueHeight;
|
||||
enabledFeaturesHeight += Dimens.paddingS;
|
||||
}
|
||||
if (EquipmentProfiles.selectedOf(context) is PinholeEquipmentProfile) {
|
||||
enabledFeaturesHeight += Dimens.readingContainerSingleValueHeight;
|
||||
enabledFeaturesHeight += Dimens.paddingS;
|
||||
} else if (context.meteringFeature(MeteringScreenLayoutFeature.extremeExposurePairs)) {
|
||||
enabledFeaturesHeight += Dimens.readingContainerDoubleValueHeight;
|
||||
if (context.meteringFeature(MeteringScreenLayoutFeature.extremeExposurePairs)) {
|
||||
if (EquipmentProfiles.selectedOf(context) is PinholeEquipmentProfile) {
|
||||
enabledFeaturesHeight += Dimens.readingContainerSingleValueHeight;
|
||||
} else {
|
||||
enabledFeaturesHeight += Dimens.readingContainerDoubleValueHeight;
|
||||
}
|
||||
|
||||
enabledFeaturesHeight += Dimens.paddingS;
|
||||
}
|
||||
|
||||
|
|
|
@ -65,26 +65,27 @@ class ExposurePairsList extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) => Align(
|
||||
alignment: index == 0
|
||||
? Alignment.bottomCenter
|
||||
: (index == exposurePairs.length - 1 ? Alignment.topCenter : Alignment.center),
|
||||
child: SizedBox(
|
||||
height: index == 0 || index == exposurePairs.length - 1
|
||||
? constraints.maxHeight / 2
|
||||
: constraints.maxHeight,
|
||||
child: ColoredBox(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
child: const SizedBox(width: 1),
|
||||
if (exposurePairs.length > 1)
|
||||
Positioned(
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) => Align(
|
||||
alignment: index == 0
|
||||
? Alignment.bottomCenter
|
||||
: (index == exposurePairs.length - 1 ? Alignment.topCenter : Alignment.center),
|
||||
child: SizedBox(
|
||||
height: index == 0 || index == exposurePairs.length - 1
|
||||
? constraints.maxHeight / 2
|
||||
: constraints.maxHeight,
|
||||
child: ColoredBox(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
child: const SizedBox(width: 1),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
|
|
@ -46,14 +46,14 @@ class ReadingsContainer extends StatelessWidget {
|
|||
const EquipmentProfilePicker(),
|
||||
const _InnerPadding(),
|
||||
],
|
||||
if (EquipmentProfiles.selectedOf(context) is PinholeEquipmentProfile) ...[
|
||||
ShutterSpeedContainer(shutterSpeedValue: fastest?.shutterSpeed),
|
||||
const _InnerPadding(),
|
||||
] else if (context.meteringFeature(MeteringScreenLayoutFeature.extremeExposurePairs)) ...[
|
||||
ExtremeExposurePairsContainer(
|
||||
fastest: fastest,
|
||||
slowest: slowest,
|
||||
),
|
||||
if (context.meteringFeature(MeteringScreenLayoutFeature.extremeExposurePairs)) ...[
|
||||
if (EquipmentProfiles.selectedOf(context) is PinholeEquipmentProfile)
|
||||
ShutterSpeedContainer(shutterSpeedValue: fastest?.shutterSpeed)
|
||||
else
|
||||
ExtremeExposurePairsContainer(
|
||||
fastest: fastest,
|
||||
slowest: slowest,
|
||||
),
|
||||
const _InnerPadding(),
|
||||
],
|
||||
if (context.meteringFeature(MeteringScreenLayoutFeature.filmPicker)) ...[
|
||||
|
|
|
@ -24,18 +24,19 @@ class DialogPicker<T> extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _DialogPickerState<T> extends State<DialogPicker<T>> {
|
||||
late T _selected = widget.selectedValue;
|
||||
T? _selected;
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
final selectedIndex = widget.values.indexOf(_selected);
|
||||
if (selectedIndex >= 0) {
|
||||
final selectedIndex = widget.values.indexOf(widget.selectedValue);
|
||||
if (selectedIndex >= 0) {
|
||||
_selected = widget.selectedValue;
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
_scrollController.jumpTo((Dimens.grid56 * selectedIndex).clamp(0, _scrollController.position.maxScrollExtent));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -93,7 +94,7 @@ class _DialogPickerState<T> extends State<DialogPicker<T>> {
|
|||
child: Text(S.of(context).cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(_selected),
|
||||
onPressed: _selected != null ? () => Navigator.of(context).pop(_selected) : null,
|
||||
child: Text(S.of(context).select),
|
||||
),
|
||||
],
|
||||
|
|
26
pubspec.lock
26
pubspec.lock
|
@ -170,10 +170,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: built_value
|
||||
sha256: "082001b5c3dc495d4a42f1d5789990505df20d8547d42507c29050af6933ee27"
|
||||
sha256: a30f0a0e38671e89a492c44d005b5545b830a961575bbd8336d42869ff71066d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.10.1"
|
||||
version: "8.12.0"
|
||||
camera:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -861,8 +861,8 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: "feature/MLI-48"
|
||||
resolved-ref: "4a169640bff3d3a3206a2c352a75cbcea4871b1c"
|
||||
ref: main
|
||||
resolved-ref: "3f5bae6d2500a746fb83ab345919095a815244d1"
|
||||
url: "https://github.com/vodemn/m3_lightmeter_iap"
|
||||
source: git
|
||||
version: "4.1.2+37"
|
||||
|
@ -870,11 +870,11 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: "feature/MLR-18"
|
||||
resolved-ref: "61bb3f8a9164d19f6e47c96fbea1cbe3aaf39fc3"
|
||||
ref: "v2.5.0"
|
||||
resolved-ref: "680affb45c5d03ed4fe61c30ee0d6e6fab0f2c12"
|
||||
url: "https://github.com/vodemn/m3_lightmeter_resources"
|
||||
source: git
|
||||
version: "2.4.0+13"
|
||||
version: "2.5.0+14"
|
||||
macros:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -967,10 +967,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_platform_interface
|
||||
sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c"
|
||||
sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
version: "3.2.1"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1119,10 +1119,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: provider
|
||||
sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84"
|
||||
sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.5"
|
||||
version: "6.1.5+1"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1524,10 +1524,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
sha256: "0b7fd4a0bbc4b92641dbf20adfd7e3fd1398fe17102d94b674234563e110088a"
|
||||
sha256: "5bf046f41320ac97a469d506261797f35254fa61c641741ef32dacda98b7d39c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
version: "1.1.3"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -33,11 +33,11 @@ dependencies:
|
|||
m3_lightmeter_iap:
|
||||
git:
|
||||
url: "https://github.com/vodemn/m3_lightmeter_iap"
|
||||
ref: feature/MLI-48
|
||||
ref: main
|
||||
m3_lightmeter_resources:
|
||||
git:
|
||||
url: "https://github.com/vodemn/m3_lightmeter_resources"
|
||||
ref: feature/MLR-18
|
||||
ref: v2.5.0
|
||||
map_launcher: 3.2.0
|
||||
material_color_utilities: 0.12.0
|
||||
package_info_plus: 8.1.3
|
||||
|
|
Loading…
Reference in a new issue