Merge branch 'main' of https://github.com/vodemn/m3_lightmeter into feature/ML-203

This commit is contained in:
Vadim 2025-07-18 19:30:14 +02:00
commit 10fc2146dc
17 changed files with 93 additions and 30 deletions

View file

@ -49,7 +49,7 @@ android {
defaultConfig {
minSdkVersion 23
targetSdkVersion 34
targetSdkVersion 35
ndk {
debugSymbolLevel 'FULL'
abiFilters 'armeabi-v7a', 'arm64-v8a'

View file

@ -20,6 +20,7 @@ class EquipmentProfilesStorageService {
List<ApertureValue>? apertureValues,
List<ShutterSpeedValue>? shutterSpeedValues,
double? lensZoom,
double? exposureOffset,
bool? isUsed,
}) async {}

View file

@ -69,6 +69,8 @@
"isoValuesFilterDescription": "Wähle die anzuzeigenden ISO Werte aus. (Beispielsweise die Meistverwendeten)",
"lensZoom": "Objektiv-Zoom",
"lensZoomDescription": "Wähle den Zoom, relativ zur Handykamera, dass mit dem Sucher der Kamera übereinstimmt.",
"exposureOffset": "Belichtungskorrektur",
"exposureOffsetDescription": "Stellen Sie die Belichtungskorrektur ein, um die genauesten Ergebnisse für die jeweilige Kamera zu erzielen.",
"equipmentProfile": "Ausrüstungsprofil",
"equipmentProfiles": "Ausrüstungsprofile",
"tapToAdd": "Tippe zum Hinzufügen",
@ -170,7 +172,6 @@
"editPhotoTitle": "Foto bearbeiten",
"date": "Datum",
"ndFilter": "ND Filter",
"film": "Film",
"note": "Notiz",
"notSet": "Nicht gesetzt",
"location": "Standort",

View file

@ -69,6 +69,8 @@
"isoValuesFilterDescription": "Select the ISO values to display. These may be your most commonly used values or those supported by your camera.",
"lensZoom": "Lens zoom",
"lensZoomDescription": "Set the zoom level relative to the phone's camera to match your camera's viewfinder.",
"exposureOffset": "Exposure offset",
"exposureOffsetDescription": "Set the exposure offset to get the most accurate results for the given camera.",
"equipmentProfile": "Equipment profile",
"equipmentProfiles": "Equipment profiles",
"tapToAdd": "Tap to add",
@ -170,7 +172,6 @@
"editPhotoTitle": "Edit Photo",
"date": "Date",
"ndFilter": "ND Filter",
"film": "Film",
"note": "Note",
"notSet": "Not set",
"location": "Location",

View file

@ -33,7 +33,7 @@
"calibration": "Calibration",
"calibrationMessage": "La précision des lectures mesurées par cette application dépend entièrement du matériel de l'appareil. Par conséquent, envisagez de tester cette application et de configurer les valeurs d'étalonnage des EV qui vous donneront les résultats de mesure souhaités.",
"calibrationMessageCameraOnly": "La précision des lectures mesurées par cette application dépend entièrement de la caméra arrière de l'appareil. Par conséquent, envisagez de tester cette application et de configurer une valeur d'étalonnage EV qui vous donnera les résultats de mesure souhaités.",
"camera": "Appareil photo",
"camera": "Caméra",
"lightSensor": "Capteur de lumière",
"showEv100": "Montrer EV\u2081\u2080\u2080",
"meteringScreenLayout": "Disposition de l'écran de mesure",
@ -69,6 +69,8 @@
"isoValuesFilterDescription": "Sélectionnez les valeurs ISO à afficher. Ce sont peut-être vos valeurs les plus couramment utilisées ou celles prises en charge par votre caméra.",
"lensZoom": "Zoom sur l'objectif",
"lensZoomDescription": "Réglez le niveau de zoom par rapport à l'appareil photo du téléphone pour qu'il corresponde au viseur de votre appareil photo.",
"exposureOffset": "Décalage dexposition",
"exposureOffsetDescription": "Définissez le décalage dexposition pour obtenir les résultats les plus précis selon lappareil photo.",
"equipmentProfile": "Profil de l'équipement",
"equipmentProfiles": "Profils de l'équipement",
"tapToAdd": "Appuie pour ajouter",
@ -161,7 +163,6 @@
"editPhotoTitle": "Modifier la photo",
"date": "Date",
"ndFilter": "Filtre ND",
"film": "Film",
"note": "Note",
"notSet": "Non défini",
"location": "Emplacement",

View file

@ -69,6 +69,8 @@
"isoValuesFilterDescription": "Выберите значения ISO для отображения. Это может быть наиболее часто используемые значения или значения, поддерживаемые вашей камерой.",
"lensZoom": "Зум объектива",
"lensZoomDescription": "Установите уровень зума относительно камеры телефона, чтобы он соответствовал видоискателю вашей камеры.",
"exposureOffset": "Смещение экспозиции",
"exposureOffsetDescription": "Установите смещение экспозиции для получения наиболее точных результатов с данной камерой.",
"equipmentProfile": "Оборудование",
"equipmentProfiles": "Профили оборудования",
"tapToAdd": "Нажмите, чтобы добавить",
@ -160,7 +162,6 @@
"editPhotoTitle": "Редактировать фото",
"date": "Дата",
"ndFilter": "ND фильтр",
"film": "Плёнка",
"note": "Заметка",
"notSet": "Не задано",
"location": "Местоположение",

View file

@ -69,6 +69,8 @@
"isoValuesFilterDescription": "选择要显示的 ISO 范围。",
"lensZoom": "镜头变焦",
"lensZoomDescription": "设置相对于手机摄像头的变焦焦距,使其与相机取景相匹配。",
"exposureOffset": "曝光补偿",
"exposureOffsetDescription": "设置曝光补偿,以获得针对所用相机的最准确结果。",
"equipmentProfile": "设备配置",
"equipmentProfiles": "设备配置",
"tapToAdd": "点击添加",
@ -158,7 +160,6 @@
"editPhotoTitle": "编辑照片",
"date": "日期",
"ndFilter": "ND 滤镜",
"film": "胶片",
"note": "备注",
"notSet": "未设置",
"location": "位置",

View file

@ -73,13 +73,13 @@ class EquipmentProfilesProviderState extends State<EquipmentProfilesProvider> {
final oldProfile = _customProfiles[profile.id]!.value;
await widget.storageService.updateEquipmentProfile(
id: profile.id,
name: oldProfile.name != profile.name ? profile.name : null,
apertureValues: oldProfile.apertureValues != profile.apertureValues ? profile.apertureValues : null,
shutterSpeedValues:
oldProfile.shutterSpeedValues != profile.shutterSpeedValues ? profile.shutterSpeedValues : null,
isoValues: oldProfile.isoValues != profile.isoValues ? profile.isoValues : null,
ndValues: oldProfile.ndValues != profile.ndValues ? profile.ndValues : null,
lensZoom: oldProfile.lensZoom != profile.lensZoom ? profile.lensZoom : null,
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);
setState(() {});
@ -183,3 +183,9 @@ class EquipmentProfiles extends InheritedModel<_EquipmentProfilesModelAspect> {
const DeepCollectionEquality().equals(oldWidget.profiles, profiles));
}
}
extension on Object {
T? changedOrNull<T>(T newValue) {
return this != newValue ? newValue : null;
}
}

View file

@ -42,6 +42,7 @@ class EquipmentProfileEditBloc extends Bloc<EquipmentProfileEditEvent, Equipment
isoValues: profile.isoValues,
ndValues: profile.ndValues,
lensZoom: profile.lensZoom,
exposureOffset: profile.exposureOffset,
canSave: false,
),
) {
@ -60,6 +61,8 @@ class EquipmentProfileEditBloc extends Bloc<EquipmentProfileEditEvent, Equipment
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():
@ -131,6 +134,16 @@ class EquipmentProfileEditBloc extends Bloc<EquipmentProfileEditEvent, Equipment
);
}
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) {
@ -143,6 +156,7 @@ class EquipmentProfileEditBloc extends Bloc<EquipmentProfileEditEvent, Equipment
shutterSpeedValues: state.shutterSpeedValues,
isoValues: state.isoValues,
lensZoom: state.lensZoom,
exposureOffset: state.exposureOffset,
),
);
} else {
@ -155,6 +169,7 @@ class EquipmentProfileEditBloc extends Bloc<EquipmentProfileEditEvent, Equipment
shutterSpeedValues: state.shutterSpeedValues,
isoValues: state.isoValues,
lensZoom: state.lensZoom,
exposureOffset: state.exposureOffset,
),
);
}
@ -173,6 +188,6 @@ class EquipmentProfileEditBloc extends Bloc<EquipmentProfileEditEvent, Equipment
}
bool _canSave(String name, double? lensZoom) {
return name.isNotEmpty && lensZoom != null && _newEquipmentProfile != _originalEquipmentProfile;
return name.isNotEmpty && _newEquipmentProfile != _originalEquipmentProfile;
}
}

View file

@ -40,6 +40,12 @@ class EquipmentProfileLensZoomChangedEvent extends EquipmentProfileEditEvent {
const EquipmentProfileLensZoomChangedEvent(this.lensZoom);
}
class EquipmentProfileExposureOffsetChangedEvent extends EquipmentProfileEditEvent {
final double exposureOffset;
const EquipmentProfileExposureOffsetChangedEvent(this.exposureOffset);
}
class EquipmentProfileSaveEvent extends EquipmentProfileEditEvent {
const EquipmentProfileSaveEvent();
}

View file

@ -10,9 +10,11 @@ import 'package:lightmeter/screens/equipment_profile_edit/components/slider_pick
import 'package:lightmeter/screens/equipment_profile_edit/event_equipment_profile_edit.dart';
import 'package:lightmeter/screens/equipment_profile_edit/flow_equipment_profile_edit.dart';
import 'package:lightmeter/screens/equipment_profile_edit/state_equipment_profile_edit.dart';
import 'package:lightmeter/screens/metering/components/camera_container/bloc_container_camera.dart';
import 'package:lightmeter/screens/shared/sliver_screen/screen_sliver.dart';
import 'package:lightmeter/screens/shared/text_field/widget_text_field.dart';
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 {
@ -108,6 +110,7 @@ class _EquipmentProfileEditScreenState extends State<EquipmentProfileEditScreen>
_ApertureValuesListTileBuilder(),
_ShutterSpeedValuesListTileBuilder(),
_LensZoomListTileBuilder(),
_ExposureOffsetListTileBuilder(),
],
),
),
@ -246,7 +249,7 @@ class _LensZoomListTileBuilder extends StatelessWidget {
title: S.of(context).lensZoom,
description: S.of(context).lensZoomDescription,
value: state.lensZoom,
range: const RangeValues(1, 7),
range: CameraContainerBloc.zoomMaxRange,
valueAdapter: (context, value) => value.toZoom(context),
onChanged: (value) {
context.read<EquipmentProfileEditBloc>().add(EquipmentProfileLensZoomChangedEvent(value));
@ -255,3 +258,24 @@ class _LensZoomListTileBuilder extends StatelessWidget {
);
}
}
class _ExposureOffsetListTileBuilder extends StatelessWidget {
const _ExposureOffsetListTileBuilder();
@override
Widget build(BuildContext context) {
return BlocBuilder<EquipmentProfileEditBloc, EquipmentProfileEditState>(
builder: (context, state) => SliderPickerListTile(
icon: Icons.light_mode_outlined,
title: S.of(context).exposureOffset,
description: S.of(context).exposureOffsetDescription,
value: state.exposureOffset,
range: CameraContainerBloc.exposureMaxRange,
valueAdapter: (context, value) => S.of(context).evValue(value.toStringSignedAsFixed(1)),
onChanged: (value) {
context.read<EquipmentProfileEditBloc>().add(EquipmentProfileExposureOffsetChangedEvent(value));
},
),
);
}
}

View file

@ -7,6 +7,7 @@ class EquipmentProfileEditState {
final List<ShutterSpeedValue> shutterSpeedValues;
final List<IsoValue> isoValues;
final double lensZoom;
final double exposureOffset;
final bool canSave;
final bool isLoading;
final EquipmentProfile? profileToCopy;
@ -18,6 +19,7 @@ class EquipmentProfileEditState {
required this.shutterSpeedValues,
required this.isoValues,
required this.lensZoom,
required this.exposureOffset,
required this.canSave,
this.isLoading = false,
this.profileToCopy,
@ -30,6 +32,7 @@ class EquipmentProfileEditState {
List<ShutterSpeedValue>? shutterSpeedValues,
List<IsoValue>? isoValues,
double? lensZoom,
double? exposureOffset,
bool? canSave,
bool? isLoading,
EquipmentProfile? profileToCopy,
@ -41,6 +44,7 @@ class EquipmentProfileEditState {
shutterSpeedValues: shutterSpeedValues ?? this.shutterSpeedValues,
isoValues: isoValues ?? this.isoValues,
lensZoom: lensZoom ?? this.lensZoom,
exposureOffset: exposureOffset ?? this.exposureOffset,
canSave: canSave ?? this.canSave,
isLoading: isLoading ?? this.isLoading,
profileToCopy: profileToCopy ?? this.profileToCopy,

View file

@ -29,11 +29,11 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
CameraDescription? _camera;
CameraController? _cameraController;
static const _maxZoom = 7.0;
static const zoomMaxRange = RangeValues(1, 7);
RangeValues? _zoomRange;
double _currentZoom = 1.0;
static const _exposureMaxRange = RangeValues(-4, 4);
static const exposureMaxRange = RangeValues(-4, 4);
RangeValues? _exposureOffsetRange;
double _exposureStep = 0.1;
double _currentExposureOffset = 0.0;
@ -89,8 +89,10 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
case final communication_states.EquipmentProfileChangedState communicationState:
if (state is CameraActiveState) {
add(ZoomChangedEvent(communicationState.profile.lensZoom));
add(ExposureOffsetChangedEvent(communicationState.profile.exposureOffset));
} else {
_currentZoom = communicationState.profile.lensZoom;
_currentExposureOffset = communicationState.profile.exposureOffset;
}
case communication_states.SettingsOpenedState():
_settingsOpened = true;
@ -152,8 +154,8 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
cameraController.getExposureOffsetStepSize(),
]).then((value) {
_exposureOffsetRange = RangeValues(
math.max(_exposureMaxRange.start, value[0]),
math.min(_exposureMaxRange.end, value[1]),
math.max(exposureMaxRange.start, value[0]),
math.min(exposureMaxRange.end, value[1]),
);
_currentExposureOffset = 0.0;
_exposureStep = value[2] == 0 ? 0.1 : value[2];
@ -167,7 +169,7 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
]).then((value) {
_zoomRange = RangeValues(
math.max(1.0, value[0]),
math.min(_maxZoom, value[1]),
math.min(zoomMaxRange.end, value[1]),
);
if (_currentZoom < _zoomRange!.start || _currentZoom > _zoomRange!.end) {
_currentZoom = _zoomRange!.start;

View file

@ -21,7 +21,7 @@ class ZoomSlider extends StatelessWidget {
range: range,
value: value,
onChanged: onChanged,
icon: Icons.search_outlined,
icon: Icons.zoom_in_outlined,
defaultValue: EquipmentProfiles.selectedOf(context).lensZoom,
rulerValueAdapter: (value) => value.toStringAsFixed(0),
valueAdapter: (value) => value.toZoom(context),

View file

@ -33,11 +33,11 @@ dependencies:
m3_lightmeter_iap:
git:
url: "https://github.com/vodemn/m3_lightmeter_iap"
ref: v2.1.3
ref: v2.2.0
m3_lightmeter_resources:
git:
url: "https://github.com/vodemn/m3_lightmeter_resources"
ref: v2.1.0
ref: v2.2.0
material_color_utilities: 0.12.0
package_info_plus: 8.1.3
permission_handler: 11.3.1

View file

@ -1,6 +1,6 @@
defaults -currentHost write -g AppleFontSmoothing -int 0
goldens=$(find ./test -name "*_golden_test.dart" -print)
for f in $goldens; do
flutter test "$f" --dart-define cameraStubImage=assets/camera_stub_image.jpg --update-goldens
fvm flutter test "$f" --dart-define cameraStubImage=assets/camera_stub_image.jpg --update-goldens
done
defaults -currentHost write -g AppleFontSmoothing -int 3

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB