ML-46 Add reciprocity failure formulas for some films (#47)

* added `Film` model with reciprocity formulas

* added `FeaturesConfig`

* added film picker

* unused import

* get ISO and ND from equipment profile

* udpate iso on film changed

* typo
This commit is contained in:
Vadim 2023-04-01 22:04:55 +03:00 committed by GitHub
parent 6bf059ed4d
commit be0617a99c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 381 additions and 54 deletions

228
lib/data/models/film.dart Normal file
View file

@ -0,0 +1,228 @@
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
double log10(double x) => log(x) / log(10);
double log10polynomian(
double x,
double a,
double b,
double c,
) =>
a * pow(log10(x), 2) + b * log10(x) + c;
/// Only Ilford films have reciprocity formulas provided by the manufacturer:
/// https://www.ilfordphoto.com/wp/wp-content/uploads/2017/06/Reciprocity-Failure-Compensation.pdf
///
/// Reciprocity formulas for Fomapan films and Kodak films are from here:
/// https://www.flickr.com/groups/86738082@N00/discuss/72157626050157470/
///
/// Cinema films like Kodak 5222/7222 Double-X and respective CineStill films (cause they are basically a modification of Kodak)
/// do not have any reciprocity failure information, as these films are ment to be used in cinema
/// with appropriate light and pretty short shutter speeds.
///
class Film {
final String name;
final int iso;
const Film(this.name, this.iso);
const Film.other()
: name = '',
iso = 0;
@override
String toString() => name;
ShutterSpeedValue reciprocityFailure(ShutterSpeedValue shutterSpeed) {
if (shutterSpeed.isFraction) {
return shutterSpeed;
} else {
return ShutterSpeedValue(
reciprocityFormula(shutterSpeed.rawValue),
shutterSpeed.isFraction,
shutterSpeed.stopType,
);
}
}
@protected
double reciprocityFormula(double t) => t;
static const List<Film> values = [
Film.other(),
FomapanFilm.creative100(),
FomapanFilm.creative200(),
FomapanFilm.action400(),
IlfordFilm.ortho(),
IlfordFilm.delta100(),
IlfordFilm.delta400(),
IlfordFilm.delta3200(),
IlfordFilm.fp4(),
IlfordFilm.hp5(),
IlfordFilm.panf(),
IlfordFilm.sfx200(),
IlfordFilm.xp2super(),
IlfordFilm.pan100(),
IlfordFilm.pan400(),
KodakFilm.tmax100(),
KodakFilm.tmax400(),
KodakFilm.tmax3200(),
KodakFilm.trix320(),
KodakFilm.trix400(),
];
}
/// https://www.tate.org.uk/documents/598/page_6_7_agfa_stocks_0.pdf
/// https://www.filmwasters.com/forum/index.php?topic=5298.0
// {{1,1.87},{2,3.73},{3,8.06},{4,13.93},{5,21.28},{6,23.00},{7,30.12},{8,38.05},{9,44.75},{10,50.12},{20,117},{30,202},{40,293},{50,413},{60,547},{70,694},{80,853},{90,1022},{100,1202}};
class AgfaFilm extends Film {
final double a;
final double b;
final double c;
const AgfaFilm.apx100()
: a = 1,
b = 5,
c = 2,
super('Agfa APX 100', 100);
const AgfaFilm.apx400()
: a = 1.5,
b = 4.5,
c = 3,
super('Agfa APX 400', 400);
@override
double reciprocityFormula(double t) => t * log10polynomian(t, a, b, c);
}
class FomapanFilm extends Film {
final double a;
final double b;
final double c;
/// https://www.foma.cz/en/fomapan-100
const FomapanFilm.creative100()
: a = 1,
b = 5,
c = 2,
super('Fomapan CREATIVE 100', 100);
/// https://www.foma.cz/en/fomapan-200
const FomapanFilm.creative200()
: a = 1.5,
b = 4.5,
c = 3,
super('Fomapan CREATIVE 200', 400);
/// https://www.foma.cz/en/fomapan-100
const FomapanFilm.action400()
: a = -1.25,
b = 5.75,
c = 1.5,
super('Fomapan ACTION 400', 400);
}
class IlfordFilm extends Film {
final double reciprocityPower;
/// https://www.ilfordphoto.com/amfile/file/download/file/1948/product/1650/
const IlfordFilm.ortho()
: reciprocityPower = 1.25,
super('Ilford ORTHO+', 80);
/// https://www.ilfordphoto.com/amfile/file/download/file/1919/product/686/
const IlfordFilm.fp4()
: reciprocityPower = 1.26,
super('Ilford FP4+', 125);
/// https://www.ilfordphoto.com/amfile/file/download/file/1903/product/691/
const IlfordFilm.hp5()
: reciprocityPower = 1.31,
super('Ilford HP5+', 400);
/// https://www.ilfordphoto.com/amfile/file/download/file/3/product/679/
const IlfordFilm.delta100()
: reciprocityPower = 1.26,
super('Ilford DELTA 100', 100);
/// https://www.ilfordphoto.com/amfile/file/download/file/1915/product/684/
const IlfordFilm.delta400()
: reciprocityPower = 1.41,
super('Ilford DELTA 400', 400);
/// https://www.ilfordphoto.com/amfile/file/download/file/1913/product/682/
const IlfordFilm.delta3200()
: reciprocityPower = 1.33,
super('Ilford DELTA 3200', 3200);
/// https://www.ilfordphoto.com/amfile/file/download/file/1905/product/699/
const IlfordFilm.panf()
: reciprocityPower = 1.33,
super('Ilford Pan F+', 50);
/// https://www.ilfordphoto.com/amfile/file/download/file/1907/product/701/
const IlfordFilm.sfx200()
: reciprocityPower = 1.31,
super('Ilford SFX 200', 200);
/// https://www.ilfordphoto.com/amfile/file/download/file/1909/product/703/
const IlfordFilm.xp2super()
: reciprocityPower = 1.31,
super('Ilford XP2 SUPER', 400);
/// https://www.ilfordphoto.com/amfile/file/download/file/1958/product/696/
const IlfordFilm.pan100()
: reciprocityPower = 1.26,
super('Kentemere 100', 100);
/// https://www.ilfordphoto.com/amfile/file/download/file/1959/product/697/
const IlfordFilm.pan400()
: reciprocityPower = 1.30,
super('Kentemere 400', 100);
@override
double reciprocityFormula(double t) => pow(t, reciprocityPower).toDouble();
}
class KodakFilm extends Film {
final double a;
final double b;
final double c;
const KodakFilm.tmax100()
: a = 1 / 6,
b = 0,
c = 4 / 3,
super('Kodak T-MAX 100', 100);
const KodakFilm.tmax400()
: a = 2 / 3,
b = -1 / 2,
c = 4 / 3,
super('Kodak T-MAX 400', 400);
const KodakFilm.tmax3200()
: a = 7 / 6,
b = -1,
c = 4 / 3,
super('Kodak T-MAX 3200', 3200);
const KodakFilm.trix320()
: a = 2,
b = 1,
c = 2,
super('Kodak TRI-X 320', 320);
const KodakFilm.trix400()
: a = 2,
b = 1,
c = 2,
super('Kodak TRI-X 400', 400);
@override
double reciprocityFormula(double t) => t * log10polynomian(t, a, b, c);
}

View file

@ -4,6 +4,7 @@ import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'models/ev_source_type.dart';
import 'models/film.dart';
import 'models/theme_type.dart';
class UserPreferencesService {
@ -13,6 +14,7 @@ class UserPreferencesService {
static const _evSourceTypeKey = "evSourceType";
static const _cameraEvCalibrationKey = "cameraEvCalibration";
static const _lightSensorEvCalibrationKey = "lightSensorEvCalibration";
static const _filmKey = "film";
static const _caffeineKey = "caffeine";
static const _hapticsKey = "haptics";
@ -105,6 +107,12 @@ class UserPreferencesService {
bool get dynamicColor => _sharedPreferences.getBool(_dynamicColorKey) ?? false;
set dynamicColor(bool value) => _sharedPreferences.setBool(_dynamicColorKey, value);
Film get film => Film.values.firstWhere(
(e) => e.name == _sharedPreferences.getString(_filmKey),
orElse: () => Film.values.first,
);
set film(Film value) => _sharedPreferences.setString(_filmKey, value.name);
String get selectedEquipmentProfileId => '';
set selectedEquipmentProfileId(String id) {}

3
lib/features.dart Normal file
View file

@ -0,0 +1,3 @@
class FeaturesConfig {
static const bool equipmentProfilesEnabled = false;
}

View file

@ -34,6 +34,7 @@
"calibrationMessageCameraOnly": "The accuracy of the readings measured by this application depends entirely on the rear camera of the device. Therefore, consider testing this application and setting up an EV calibration value that will give you the desired measurement results.",
"camera": "Camera",
"lightSensor": "Light sensor",
"film": "Film",
"equipment": "Equipment",
"equipmentProfileName": "Equipment profile name",
"equipmentProfileNameHint": "Praktica MTL5B",

View file

@ -34,6 +34,7 @@
"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": "Caméra",
"lightSensor": "Capteur de lumière",
"film": "Pellicule",
"equipment": "Équipement",
"equipmentProfileName": "Nom du profil de l'équipement",
"equipmentProfileNameHint": "Praktica MTL5B",

View file

@ -34,6 +34,7 @@
"calibrationMessageCameraOnly": "Точность измерений данного приложения полностью зависит от точности камеры вашего устройства. Поэтому рекомендуется самостоятельно подобрать калибровочное значение, которое даст желаемый результат измерений.",
"camera": "Камера",
"lightSensor": "Датчик освещённости",
"film": "Пленка",
"equipment": "Оборудование",
"equipmentProfileName": "Название профиля",
"equipmentProfileNameHint": "Praktica MTL5B",

View file

@ -32,7 +32,7 @@ class Dimens {
// TopBar
/// Probably this is a bad practice, but with text size locked, the height is always 212
static const double readingContainerSingleValueHeight = 76;
static const double readingContainerDefaultHeight = 212;
static const double readingContainerDefaultHeight = 288;
// `CenteredSlider`
static const double cameraSliderTrackHeight = grid4;

View file

@ -3,6 +3,7 @@ import 'dart:math';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:lightmeter/data/models/exposure_pair.dart';
import 'package:lightmeter/data/models/film.dart';
import 'package:lightmeter/data/shared_prefs_service.dart';
import 'package:lightmeter/interactors/metering_interactor.dart';
import 'package:lightmeter/screens/metering/communication/event_communication_metering.dart'
@ -31,6 +32,7 @@ class MeteringBloc extends Bloc<MeteringEvent, MeteringState> {
late IsoValue _iso = _userPreferencesService.iso;
late NdValue _nd = _userPreferencesService.ndFilter;
late Film _film = _userPreferencesService.film;
double _ev = 0.0;
bool _isMeteringInProgress = false;
@ -42,10 +44,11 @@ class MeteringBloc extends Bloc<MeteringEvent, MeteringState> {
this.stopType,
) : super(
MeteringEndedState(
iso: _userPreferencesService.iso,
ev: 0.0,
film: _userPreferencesService.film,
iso: _userPreferencesService.iso,
nd: _userPreferencesService.ndFilter,
exposurePairs: [],
exposurePairs: const [],
),
) {
_communicationSubscription = _communicationBloc.stream
@ -55,6 +58,7 @@ class MeteringBloc extends Bloc<MeteringEvent, MeteringState> {
on<EquipmentProfileChangedEvent>(_onEquipmentProfileChanged);
on<StopTypeChangedEvent>(_onStopTypeChanged);
on<FilmChangedEvent>(_onFilmChanged);
on<IsoChangedEvent>(_onIsoChanged);
on<NdChangedEvent>(_onNdChanged);
on<MeasureEvent>(_onMeasure);
@ -100,7 +104,23 @@ class MeteringBloc extends Bloc<MeteringEvent, MeteringState> {
_emitMeasuredState(emit);
}
void _onFilmChanged(FilmChangedEvent event, Emitter emit) {
if (_iso.value != event.data.iso) {
final newIso = isoValues.firstWhere(
(e) => e.value == event.data.iso,
orElse: () => _iso,
);
add(IsoChangedEvent(newIso));
}
_film = event.data;
_userPreferencesService.film = event.data;
_emitMeasuredState(emit);
}
void _onIsoChanged(IsoChangedEvent event, Emitter emit) {
if (event.isoValue.value != _film.iso) {
_film = Film.values.first;
}
_userPreferencesService.iso = event.isoValue;
_ev = _ev + log2(event.isoValue.value / _iso.value);
_iso = event.isoValue;
@ -130,14 +150,16 @@ class MeteringBloc extends Bloc<MeteringEvent, MeteringState> {
void _emitMeasuredState(Emitter emit) {
emit(_isMeteringInProgress
? MeteringInProgressState(
iso: _iso,
ev: _ev,
film: _film,
iso: _iso,
nd: _nd,
exposurePairs: _buildExposureValues(_ev),
)
: MeteringEndedState(
iso: _iso,
ev: _ev,
film: _film,
iso: _iso,
nd: _nd,
exposurePairs: _buildExposureValues(_ev),
));
@ -188,7 +210,7 @@ class MeteringBloc extends Bloc<MeteringEvent, MeteringState> {
itemsCount,
(index) => ExposurePair(
_apertureValues[index + apertureOffset],
_shutterSpeedValues[index + shutterSpeedOffset],
_film.reciprocityFailure(_shutterSpeedValues[index + shutterSpeedOffset]),
),
growable: false,
);

View file

@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:lightmeter/data/models/exposure_pair.dart';
import 'package:lightmeter/data/models/film.dart';
import 'package:lightmeter/interactors/metering_interactor.dart';
import 'package:lightmeter/providers/equipment_profile_provider.dart';
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
@ -12,8 +12,10 @@ import 'widget_container_camera.dart';
class CameraContainerProvider extends StatelessWidget {
final ExposurePair? fastest;
final ExposurePair? slowest;
final Film film;
final IsoValue iso;
final NdValue nd;
final ValueChanged<Film> onFilmChanged;
final ValueChanged<IsoValue> onIsoChanged;
final ValueChanged<NdValue> onNdChanged;
final List<ExposurePair> exposurePairs;
@ -21,8 +23,10 @@ class CameraContainerProvider extends StatelessWidget {
const CameraContainerProvider({
required this.fastest,
required this.slowest,
required this.film,
required this.iso,
required this.nd,
required this.onFilmChanged,
required this.onIsoChanged,
required this.onNdChanged,
required this.exposurePairs,
@ -40,10 +44,10 @@ class CameraContainerProvider extends StatelessWidget {
child: CameraContainer(
fastest: fastest,
slowest: slowest,
isoValues: EquipmentProfile.of(context).isoValues,
film: film,
iso: iso,
ndValues: EquipmentProfile.of(context).ndValues,
nd: nd,
onFilmChanged: onFilmChanged,
onIsoChanged: onIsoChanged,
onNdChanged: onNdChanged,
exposurePairs: exposurePairs,

View file

@ -1,8 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:lightmeter/data/models/exposure_pair.dart';
import 'package:lightmeter/data/models/film.dart';
import 'package:lightmeter/features.dart';
import 'package:lightmeter/platform_config.dart';
import 'package:lightmeter/providers/equipment_profile_provider.dart';
import 'package:lightmeter/res/dimens.dart';
import 'package:lightmeter/screens/metering/components/camera_container/components/camera_view/widget_camera_view.dart';
import 'package:lightmeter/screens/metering/components/camera_container/models/camera_error_type.dart';
@ -21,10 +22,10 @@ import 'state_container_camera.dart';
class CameraContainer extends StatelessWidget {
final ExposurePair? fastest;
final ExposurePair? slowest;
final List<IsoValue> isoValues;
final Film film;
final IsoValue iso;
final List<NdValue> ndValues;
final NdValue nd;
final ValueChanged<Film> onFilmChanged;
final ValueChanged<IsoValue> onIsoChanged;
final ValueChanged<NdValue> onNdChanged;
final List<ExposurePair> exposurePairs;
@ -32,10 +33,10 @@ class CameraContainer extends StatelessWidget {
const CameraContainer({
required this.fastest,
required this.slowest,
required this.isoValues,
required this.film,
required this.iso,
required this.ndValues,
required this.nd,
required this.onFilmChanged,
required this.onIsoChanged,
required this.onNdChanged,
required this.exposurePairs,
@ -49,7 +50,7 @@ class CameraContainer extends StatelessWidget {
PlatformConfig.cameraPreviewAspectRatio;
double topBarOverflow = Dimens.readingContainerDefaultHeight - cameraViewHeight;
if (EquipmentProfiles.of(context).isNotEmpty) {
if (FeaturesConfig.equipmentProfilesEnabled) {
topBarOverflow += Dimens.readingContainerSingleValueHeight;
topBarOverflow += Dimens.paddingS;
}
@ -60,10 +61,10 @@ class CameraContainer extends StatelessWidget {
readingsContainer: ReadingsContainer(
fastest: fastest,
slowest: slowest,
isoValues: isoValues,
film: film,
iso: iso,
ndValues: ndValues,
nd: nd,
onFilmChanged: onFilmChanged,
onIsoChanged: onIsoChanged,
onNdChanged: onNdChanged,
),

View file

@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:lightmeter/data/models/exposure_pair.dart';
import 'package:lightmeter/data/models/film.dart';
import 'package:lightmeter/interactors/metering_interactor.dart';
import 'package:lightmeter/providers/equipment_profile_provider.dart';
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
@ -12,8 +12,10 @@ import 'widget_container_light_sensor.dart';
class LightSensorContainerProvider extends StatelessWidget {
final ExposurePair? fastest;
final ExposurePair? slowest;
final Film film;
final IsoValue iso;
final NdValue nd;
final ValueChanged<Film> onFilmChanged;
final ValueChanged<IsoValue> onIsoChanged;
final ValueChanged<NdValue> onNdChanged;
final List<ExposurePair> exposurePairs;
@ -21,8 +23,10 @@ class LightSensorContainerProvider extends StatelessWidget {
const LightSensorContainerProvider({
required this.fastest,
required this.slowest,
required this.film,
required this.iso,
required this.nd,
required this.onFilmChanged,
required this.onIsoChanged,
required this.onNdChanged,
required this.exposurePairs,
@ -40,10 +44,10 @@ class LightSensorContainerProvider extends StatelessWidget {
child: LightSensorContainer(
fastest: fastest,
slowest: slowest,
isoValues: EquipmentProfile.of(context).isoValues,
film: film,
iso: iso,
ndValues: EquipmentProfile.of(context).ndValues,
nd: nd,
onFilmChanged: onFilmChanged,
onIsoChanged: onIsoChanged,
onNdChanged: onNdChanged,
exposurePairs: exposurePairs,

View file

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/data/models/exposure_pair.dart';
import 'package:lightmeter/data/models/film.dart';
import 'package:lightmeter/res/dimens.dart';
import 'package:lightmeter/screens/metering/components/shared/exposure_pairs_list/widget_list_exposure_pairs.dart';
import 'package:lightmeter/screens/metering/components/shared/metering_top_bar/widget_top_bar_metering.dart';
@ -9,10 +10,10 @@ import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class LightSensorContainer extends StatelessWidget {
final ExposurePair? fastest;
final ExposurePair? slowest;
final List<IsoValue> isoValues;
final Film film;
final IsoValue iso;
final List<NdValue> ndValues;
final NdValue nd;
final ValueChanged<Film> onFilmChanged;
final ValueChanged<IsoValue> onIsoChanged;
final ValueChanged<NdValue> onNdChanged;
final List<ExposurePair> exposurePairs;
@ -20,10 +21,10 @@ class LightSensorContainer extends StatelessWidget {
const LightSensorContainer({
required this.fastest,
required this.slowest,
required this.isoValues,
required this.film,
required this.iso,
required this.ndValues,
required this.nd,
required this.onFilmChanged,
required this.onIsoChanged,
required this.onNdChanged,
required this.exposurePairs,
@ -38,10 +39,10 @@ class LightSensorContainer extends StatelessWidget {
readingsContainer: ReadingsContainer(
fastest: fastest,
slowest: slowest,
isoValues: isoValues,
film: film,
iso: iso,
ndValues: ndValues,
nd: nd,
onFilmChanged: onFilmChanged,
onIsoChanged: onIsoChanged,
onNdChanged: onNdChanged,
),

View file

@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/data/models/exposure_pair.dart';
import 'package:lightmeter/data/models/film.dart';
import 'package:lightmeter/features.dart';
import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/providers/equipment_profile_provider.dart';
import 'package:lightmeter/res/dimens.dart';
@ -12,20 +14,20 @@ import 'components/reading_value_container/widget_container_reading_value.dart';
class ReadingsContainer extends StatelessWidget {
final ExposurePair? fastest;
final ExposurePair? slowest;
final List<IsoValue> isoValues;
final Film film;
final IsoValue iso;
final List<NdValue> ndValues;
final NdValue nd;
final ValueChanged<Film> onFilmChanged;
final ValueChanged<IsoValue> onIsoChanged;
final ValueChanged<NdValue> onNdChanged;
const ReadingsContainer({
required this.fastest,
required this.slowest,
required this.isoValues,
required this.film,
required this.iso,
required this.ndValues,
required this.nd,
required this.onFilmChanged,
required this.onIsoChanged,
required this.onNdChanged,
super.key,
@ -36,12 +38,8 @@ class ReadingsContainer extends StatelessWidget {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (EquipmentProfiles.of(context).isNotEmpty) ...[
_EquipmentProfilePicker(
selectedValue: EquipmentProfile.of(context),
values: EquipmentProfiles.of(context),
onChanged: EquipmentProfileProvider.of(context).setProfile,
),
if (FeaturesConfig.equipmentProfilesEnabled) ...[
const _EquipmentProfilePicker(),
const _InnerPadding(),
],
ReadingValueContainer(
@ -57,12 +55,18 @@ class ReadingsContainer extends StatelessWidget {
],
),
const _InnerPadding(),
_FilmPicker(
values: Film.values,
selectedValue: film,
onChanged: onFilmChanged,
),
const _InnerPadding(),
Row(
children: [
Expanded(
child: _IsoValuePicker(
selectedValue: iso,
values: isoValues,
values: EquipmentProfile.of(context).isoValues,
onChanged: onIsoChanged,
),
),
@ -70,7 +74,7 @@ class ReadingsContainer extends StatelessWidget {
Expanded(
child: _NdValuePicker(
selectedValue: nd,
values: ndValues,
values: EquipmentProfile.of(context).ndValues,
onChanged: onNdChanged,
),
),
@ -86,28 +90,51 @@ class _InnerPadding extends SizedBox {
}
class _EquipmentProfilePicker extends StatelessWidget {
final List<EquipmentProfileData> values;
final EquipmentProfileData selectedValue;
final ValueChanged<EquipmentProfileData> onChanged;
const _EquipmentProfilePicker({
required this.selectedValue,
required this.values,
required this.onChanged,
});
const _EquipmentProfilePicker();
@override
Widget build(BuildContext context) {
return AnimatedDialogPicker<EquipmentProfileData>(
title: S.of(context).equipmentProfile,
selectedValue: selectedValue,
values: values,
selectedValue: EquipmentProfile.of(context),
values: EquipmentProfiles.of(context),
itemTitleBuilder: (_, value) => Text(value.id.isEmpty ? S.of(context).none : value.name),
onChanged: onChanged,
onChanged: EquipmentProfileProvider.of(context).setProfile,
closedChild: ReadingValueContainer.singleValue(
value: ReadingValue(
label: S.of(context).equipmentProfile,
value: selectedValue.id.isEmpty ? S.of(context).none : selectedValue.name,
value: EquipmentProfile.of(context).id.isEmpty
? S.of(context).none
: EquipmentProfile.of(context).name,
),
),
);
}
}
class _FilmPicker extends StatelessWidget {
final List<Film> values;
final Film selectedValue;
final ValueChanged<Film> onChanged;
const _FilmPicker({
required this.values,
required this.selectedValue,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
return AnimatedDialogPicker<Film>(
title: S.of(context).film,
selectedValue: selectedValue,
values: values,
itemTitleBuilder: (_, value) => Text(value.name.isEmpty ? S.of(context).none : value.name),
onChanged: onChanged,
closedChild: ReadingValueContainer.singleValue(
value: ReadingValue(
label: S.of(context).film,
value: selectedValue.name.isEmpty ? S.of(context).none : selectedValue.name,
),
),
);

View file

@ -1,3 +1,4 @@
import 'package:lightmeter/data/models/film.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
abstract class MeteringEvent {
@ -16,6 +17,12 @@ class EquipmentProfileChangedEvent extends MeteringEvent {
const EquipmentProfileChangedEvent(this.equipmentProfileData);
}
class FilmChangedEvent extends MeteringEvent {
final Film data;
const FilmChangedEvent(this.data);
}
class IsoChangedEvent extends MeteringEvent {
final IsoValue isoValue;

View file

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:lightmeter/data/models/ev_source_type.dart';
import 'package:lightmeter/data/models/exposure_pair.dart';
import 'package:lightmeter/data/models/film.dart';
import 'package:lightmeter/environment.dart';
import 'package:lightmeter/providers/equipment_profile_provider.dart';
import 'package:lightmeter/providers/ev_source_type_provider.dart';
@ -44,8 +45,10 @@ class _MeteringScreenState extends State<MeteringScreen> {
? _MeteringContainerBuidler(
fastest: state.fastest,
slowest: state.slowest,
film: state.film,
iso: state.iso,
nd: state.nd,
onFilmChanged: (value) => _bloc.add(FilmChangedEvent(value)),
onIsoChanged: (value) => _bloc.add(IsoChangedEvent(value)),
onNdChanged: (value) => _bloc.add(NdChangedEvent(value)),
exposurePairs: state.exposurePairs,
@ -73,8 +76,10 @@ class _MeteringScreenState extends State<MeteringScreen> {
class _MeteringContainerBuidler extends StatelessWidget {
final ExposurePair? fastest;
final ExposurePair? slowest;
final Film film;
final IsoValue iso;
final NdValue nd;
final ValueChanged<Film> onFilmChanged;
final ValueChanged<IsoValue> onIsoChanged;
final ValueChanged<NdValue> onNdChanged;
final List<ExposurePair> exposurePairs;
@ -82,8 +87,10 @@ class _MeteringContainerBuidler extends StatelessWidget {
const _MeteringContainerBuidler({
required this.fastest,
required this.slowest,
required this.film,
required this.iso,
required this.nd,
required this.onFilmChanged,
required this.onIsoChanged,
required this.onNdChanged,
required this.exposurePairs,
@ -95,8 +102,10 @@ class _MeteringContainerBuidler extends StatelessWidget {
? CameraContainerProvider(
fastest: fastest,
slowest: slowest,
film: film,
iso: iso,
nd: nd,
onFilmChanged: onFilmChanged,
onIsoChanged: onIsoChanged,
onNdChanged: onNdChanged,
exposurePairs: exposurePairs,
@ -104,8 +113,10 @@ class _MeteringContainerBuidler extends StatelessWidget {
: LightSensorContainerProvider(
fastest: fastest,
slowest: slowest,
film: film,
iso: iso,
nd: nd,
onFilmChanged: onFilmChanged,
onIsoChanged: onIsoChanged,
onNdChanged: onNdChanged,
exposurePairs: exposurePairs,

View file

@ -1,6 +1,9 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/data/models/exposure_pair.dart';
import 'package:lightmeter/data/models/film.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
@immutable
abstract class MeteringState {
const MeteringState();
}
@ -11,12 +14,14 @@ class LoadingState extends MeteringState {
abstract class MeteringDataState extends MeteringState {
final double ev;
final Film film;
final IsoValue iso;
final NdValue nd;
final List<ExposurePair> exposurePairs;
const MeteringDataState({
required this.ev,
required this.film,
required this.iso,
required this.nd,
required this.exposurePairs,
@ -27,8 +32,9 @@ abstract class MeteringDataState extends MeteringState {
}
class MeteringInProgressState extends MeteringDataState {
MeteringInProgressState({
const MeteringInProgressState({
required super.ev,
required super.film,
required super.iso,
required super.nd,
required super.exposurePairs,
@ -36,8 +42,9 @@ class MeteringInProgressState extends MeteringDataState {
}
class MeteringEndedState extends MeteringDataState {
MeteringEndedState({
const MeteringEndedState({
required super.ev,
required super.film,
required super.iso,
required super.nd,
required super.exposurePairs,

View file

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/features.dart';
import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/screens/settings/components/shared/settings_section/widget_settings_section.dart';
@ -16,7 +17,7 @@ class MeteringSettingsSection extends StatelessWidget {
children: const [
StopTypeListTile(),
CalibrationListTile(),
EquipmentProfilesListTile(),
if (FeaturesConfig.equipmentProfilesEnabled) EquipmentProfilesListTile(),
],
);
}