add/edit pinhole profiles

This commit is contained in:
Vadim 2025-09-03 20:41:21 +02:00
parent 3aa0014b6a
commit 76a6754a3b
12 changed files with 338 additions and 254 deletions

View file

@ -34,6 +34,8 @@
"calibrationMessage": "Die Messgenauigkeit hängt von der Gerätehardware ab. Testen Sie die App und kalibrieren Sie EV-Werte für beste Ergebnisse",
"calibrationMessageCameraOnly": "Die Messgenauigkeit hängt von der Kamera ab. Testen Sie die App und kalibrieren Sie EV-Werte für beste Ergebnisse",
"camera": "Kamera",
"pinholeCamera": "Lochkamera",
"equipmentProfileType": "Ausrüstungsprofiltyp",
"lightSensor": "Lichtsensor",
"showEv100": "EV\u2081\u2080\u2080 anzeigen",
"meteringScreenLayout": "Messansicht Layout",

View file

@ -34,6 +34,8 @@
"calibrationMessage": "Measurement accuracy depends on your device's hardware. Test the app and calibrate EV values for optimal results",
"calibrationMessageCameraOnly": "Measurement accuracy depends on your device's camera. Test the app and calibrate EV values for optimal results",
"camera": "Camera",
"pinholeCamera": "Pinhole Camera",
"equipmentProfileType": "Equipment profile type",
"lightSensor": "Light sensor",
"showEv100": "Show EV\u2081\u2080\u2080",
"meteringScreenLayout": "Metering screen layout",

View file

@ -34,6 +34,8 @@
"calibrationMessage": "La précision dépend du matériel de l'appareil. Testez l'app et calibrez les valeurs EV pour de meilleurs résultats",
"calibrationMessageCameraOnly": "La précision dépend de la caméra de l'appareil. Testez l'app et calibrez les valeurs EV pour de meilleurs résultats",
"camera": "Caméra",
"pinholeCamera": "Sténopé",
"equipmentProfileType": "Type de profil d'équipement",
"lightSensor": "Capteur de lumière",
"showEv100": "Montrer EV\u2081\u2080\u2080",
"meteringScreenLayout": "Disposition de l'écran de mesure",

View file

@ -34,6 +34,8 @@
"calibrationMessage": "Dokładność pomiaru zależy od sprzętu urządzenia. Przetestuj aplikację i skalibruj wartości EV dla optymalnych wyników",
"calibrationMessageCameraOnly": "Dokładność pomiaru zależy od kamery urządzenia. Przetestuj aplikację i skalibruj wartości EV dla optymalnych wyników",
"camera": "Kamera",
"pinholeCamera": "Kamera otworkowa",
"equipmentProfileType": "Typ profilu sprzętu",
"lightSensor": "Czujnik światła",
"showEv100": "Pokaż EV\u2081\u2080\u2080",
"meteringScreenLayout": "Układ ekranu pomiaru",

View file

@ -34,6 +34,8 @@
"calibrationMessage": "Точность измерений данного приложения полностью зависит от точности камеры и датчика освещенности вашего устройства. Поэтому рекомендуется самостоятельно подобрать калибровочные значения, которые дадут желаемый результат измерений.",
"calibrationMessageCameraOnly": "Точность измерений данного приложения полностью зависит от точности камеры вашего устройства. Поэтому рекомендуется самостоятельно подобрать калибровочное значение, которое даст желаемый результат измерений.",
"camera": "Камера",
"pinholeCamera": "Пинхол-камера",
"equipmentProfileType": "Тип профиля оборудования",
"lightSensor": "Датчик освещённости",
"showEv100": "Показывать EV\u2081\u2080\u2080",
"meteringScreenLayout": "Элементы главного экрана",

View file

@ -34,6 +34,8 @@
"calibrationMessage": "测量精度取决于设备硬件。请测试并校准 EV 值以获得最佳结果。",
"calibrationMessageCameraOnly": "测量精度取决于设备摄像头。请测试并校准 EV 值以获得最佳结果。",
"camera": "相机",
"pinholeCamera": "针孔相机",
"equipmentProfileType": "设备配置类型",
"lightSensor": "光线传感器",
"showEv100": "显示 EV\u2081\u2080\u2080",
"meteringScreenLayout": "布局",

View file

@ -1,3 +1,4 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:lightmeter/providers/equipment_profile_provider.dart';
import 'package:lightmeter/screens/equipment_profile_edit/event_equipment_profile_edit.dart';
@ -5,189 +6,199 @@ import 'package:lightmeter/screens/equipment_profile_edit/state_equipment_profil
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
import 'package:uuid/uuid.dart';
class EquipmentProfileEditBloc extends Bloc<EquipmentProfileEditEvent, EquipmentProfileEditState> {
sealed class IEquipmentProfileEditBloc<T extends IEquipmentProfile>
extends Bloc<IEquipmentProfileEditEvent<T>, EquipmentProfileEditState<T>> {
@protected
final EquipmentProfilesProviderState profilesProvider;
final EquipmentProfile _originalEquipmentProfile;
EquipmentProfile _newEquipmentProfile;
final bool _isEdit;
@protected
final T originalEquipmentProfile;
@protected
final bool isEdit;
factory EquipmentProfileEditBloc(
EquipmentProfilesProviderState profilesProvider, {
required EquipmentProfile? profile,
required bool isEdit,
}) =>
profile != null
? EquipmentProfileEditBloc._(
profilesProvider,
profile,
isEdit,
)
: EquipmentProfileEditBloc._(
profilesProvider,
EquipmentProfilesProvider.defaultProfile,
isEdit,
);
EquipmentProfileEditBloc._(
this.profilesProvider,
EquipmentProfile profile,
this._isEdit,
) : _originalEquipmentProfile = profile,
_newEquipmentProfile = profile,
IEquipmentProfileEditBloc(
this.profilesProvider, {
required T profile,
required this.isEdit,
}) : originalEquipmentProfile = profile,
super(
EquipmentProfileEditState(
name: profile.name,
apertureValues: profile.apertureValues,
shutterSpeedValues: profile.shutterSpeedValues,
isoValues: profile.isoValues,
ndValues: profile.ndValues,
lensZoom: profile.lensZoom,
exposureOffset: profile.exposureOffset,
EquipmentProfileEditState<T>(
profile: profile,
canSave: false,
),
) {
on<EquipmentProfileEditEvent>(
(event, emit) async {
switch (event) {
case final EquipmentProfileNameChangedEvent e:
await _onNameChanged(e, emit);
case final EquipmentProfileApertureValuesChangedEvent e:
await _onApertureValuesChanged(e, emit);
case final EquipmentProfileShutterSpeedValuesChangedEvent e:
await _onShutterSpeedValuesChanged(e, emit);
case final EquipmentProfileIsoValuesChangedEvent e:
await _onIsoValuesChanged(e, emit);
case final EquipmentProfileNdValuesChangedEvent e:
await _onNdValuesChanged(e, emit);
case final EquipmentProfileLensZoomChangedEvent e:
await _onLensZoomChanged(e, emit);
case final EquipmentProfileExposureOffsetChangedEvent e:
await _onExposureOffsetChanged(e, emit);
case EquipmentProfileSaveEvent():
await _onSave(event, emit);
case EquipmentProfileCopyEvent():
await _onCopy(event, emit);
case EquipmentProfileDeleteEvent():
await _onDelete(event, emit);
}
},
on<IEquipmentProfileEditEvent<T>>(mapEventToState);
}
@protected
@mustCallSuper
Future<void> mapEventToState(IEquipmentProfileEditEvent<T> event, Emitter emit) async {
switch (event) {
case EquipmentProfileSaveEvent():
await _onSave(event, emit);
case EquipmentProfileCopyEvent():
await _onCopy(event, emit);
case EquipmentProfileDeleteEvent():
await _onDelete(event, emit);
default:
}
}
@protected
Future<T> createProfile(String id);
@protected
void emitProfile(T profile, Emitter emit) {
emit(
state.copyWith(
profile: profile,
canSave: _canSave(profile),
),
);
}
Future<void> _onSave(EquipmentProfileSaveEvent<T> _, Emitter emit) async {
emit(state.copyWith(isLoading: true));
final profileId = isEdit ? originalEquipmentProfile.id : const Uuid().v1();
final newProfile = await createProfile(profileId);
assert(newProfile.id == profileId, 'The new profile id must be the same as the original profile id');
await profilesProvider.addProfile(newProfile);
emit(state.copyWith(isLoading: false));
}
Future<void> _onCopy(EquipmentProfileCopyEvent<T> _, Emitter emit) async {
emit(state.copyWith(isLoading: true));
emit(state.copyWith(isLoading: false, profileToCopy: state.profile));
}
Future<void> _onDelete(EquipmentProfileDeleteEvent<T> _, Emitter emit) async {
emit(state.copyWith(isLoading: true));
await profilesProvider.deleteProfile(originalEquipmentProfile);
emit(state.copyWith(isLoading: false));
}
bool _canSave(T profile) => profile != originalEquipmentProfile;
}
class EquipmentProfileEditBloc extends IEquipmentProfileEditBloc<EquipmentProfile> {
EquipmentProfileEditBloc(
super.profilesProvider, {
required super.profile,
required super.isEdit,
});
@override
Future<void> mapEventToState(IEquipmentProfileEditEvent<EquipmentProfile> event, Emitter emit) async {
switch (event) {
case final EquipmentProfileNameChangedEvent e:
await _onNameChanged(e, emit);
case final EquipmentProfileApertureValuesChangedEvent e:
await _onApertureValuesChanged(e, emit);
case final EquipmentProfileShutterSpeedValuesChangedEvent e:
await _onShutterSpeedValuesChanged(e, emit);
case final EquipmentProfileIsoValuesChangedEvent e:
await _onIsoValuesChanged(e, emit);
case final EquipmentProfileNdValuesChangedEvent e:
await _onNdValuesChanged(e, emit);
case final EquipmentProfileLensZoomChangedEvent e:
await _onLensZoomChanged(e, emit);
case final EquipmentProfileExposureOffsetChangedEvent e:
await _onExposureOffsetChanged(e, emit);
default:
return super.mapEventToState(event, emit);
}
}
@override
Future<EquipmentProfile> createProfile(String id) async {
return EquipmentProfile(
id: id,
name: state.profile.name,
apertureValues: state.profile.apertureValues,
shutterSpeedValues: state.profile.shutterSpeedValues,
isoValues: state.profile.isoValues,
ndValues: state.profile.ndValues,
lensZoom: state.profile.lensZoom,
exposureOffset: state.profile.exposureOffset,
);
}
Future<void> _onNameChanged(EquipmentProfileNameChangedEvent event, Emitter emit) async {
_newEquipmentProfile = _newEquipmentProfile.copyWith(name: event.name);
emit(
state.copyWith(
name: event.name,
canSave: _canSave(event.name, state.lensZoom),
),
);
emitProfile(state.profile.copyWith(name: event.name), emit);
}
Future<void> _onApertureValuesChanged(EquipmentProfileApertureValuesChangedEvent event, Emitter emit) async {
_newEquipmentProfile = _newEquipmentProfile.copyWith(apertureValues: event.apertureValues);
emit(
state.copyWith(
apertureValues: event.apertureValues,
canSave: _canSave(state.name, state.lensZoom),
),
);
emitProfile(state.profile.copyWith(apertureValues: event.apertureValues), emit);
}
Future<void> _onShutterSpeedValuesChanged(EquipmentProfileShutterSpeedValuesChangedEvent event, Emitter emit) async {
_newEquipmentProfile = _newEquipmentProfile.copyWith(shutterSpeedValues: event.shutterSpeedValues);
emit(
state.copyWith(
shutterSpeedValues: event.shutterSpeedValues,
canSave: _canSave(state.name, state.lensZoom),
),
);
emitProfile(state.profile.copyWith(shutterSpeedValues: event.shutterSpeedValues), emit);
}
Future<void> _onIsoValuesChanged(EquipmentProfileIsoValuesChangedEvent event, Emitter emit) async {
_newEquipmentProfile = _newEquipmentProfile.copyWith(isoValues: event.isoValues);
emit(
state.copyWith(
isoValues: event.isoValues,
canSave: _canSave(state.name, state.lensZoom),
),
);
emitProfile(state.profile.copyWith(isoValues: event.isoValues), emit);
}
Future<void> _onNdValuesChanged(EquipmentProfileNdValuesChangedEvent event, Emitter emit) async {
_newEquipmentProfile = _newEquipmentProfile.copyWith(ndValues: event.ndValues);
emit(
state.copyWith(
ndValues: event.ndValues,
canSave: _canSave(state.name, state.lensZoom),
),
);
emitProfile(state.profile.copyWith(ndValues: event.ndValues), emit);
}
Future<void> _onLensZoomChanged(EquipmentProfileLensZoomChangedEvent event, Emitter emit) async {
_newEquipmentProfile = _newEquipmentProfile.copyWith(lensZoom: event.lensZoom);
emit(
state.copyWith(
lensZoom: event.lensZoom,
canSave: _canSave(state.name, event.lensZoom),
),
);
emitProfile(state.profile.copyWith(lensZoom: event.lensZoom), emit);
}
Future<void> _onExposureOffsetChanged(EquipmentProfileExposureOffsetChangedEvent event, Emitter emit) async {
_newEquipmentProfile = _newEquipmentProfile.copyWith(exposureOffset: event.exposureOffset);
emit(
state.copyWith(
exposureOffset: event.exposureOffset,
canSave: _canSave(state.name, event.exposureOffset),
),
emitProfile(state.profile.copyWith(exposureOffset: event.exposureOffset), emit);
}
}
class PinholeEquipmentProfileEditBloc extends IEquipmentProfileEditBloc<PinholeEquipmentProfile> {
PinholeEquipmentProfileEditBloc(
super.profilesProvider, {
required super.profile,
required super.isEdit,
});
@override
Future<void> mapEventToState(IEquipmentProfileEditEvent<PinholeEquipmentProfile> event, Emitter emit) async {
switch (event) {
case final EquipmentProfileNameChangedEvent e:
await _onNameChanged(e, emit);
case final EquipmentProfileApertureValuesChangedEvent e:
await _onApertureValuesChanged(e, emit);
case final EquipmentProfileLensZoomChangedEvent e:
await _onLensZoomChanged(e, emit);
case final EquipmentProfileExposureOffsetChangedEvent e:
await _onExposureOffsetChanged(e, emit);
default:
return super.mapEventToState(event, emit);
}
}
@override
Future<PinholeEquipmentProfile> createProfile(String id) async {
return PinholeEquipmentProfile(
id: id,
name: state.profile.name,
aperture: state.profile.aperture,
isoValues: state.profile.isoValues,
lensZoom: state.profile.lensZoom,
exposureOffset: state.profile.exposureOffset,
);
}
Future<void> _onSave(EquipmentProfileSaveEvent _, Emitter emit) async {
emit(state.copyWith(isLoading: true));
if (_isEdit) {
await profilesProvider.updateProfile(
EquipmentProfile(
id: _originalEquipmentProfile.id,
name: state.name,
apertureValues: state.apertureValues,
ndValues: state.ndValues,
shutterSpeedValues: state.shutterSpeedValues,
isoValues: state.isoValues,
lensZoom: state.lensZoom,
exposureOffset: state.exposureOffset,
),
);
} else {
await profilesProvider.addProfile(
EquipmentProfile(
id: const Uuid().v1(),
name: state.name,
apertureValues: state.apertureValues,
ndValues: state.ndValues,
shutterSpeedValues: state.shutterSpeedValues,
isoValues: state.isoValues,
lensZoom: state.lensZoom,
exposureOffset: state.exposureOffset,
),
);
}
emit(state.copyWith(isLoading: false));
Future<void> _onNameChanged(EquipmentProfileNameChangedEvent event, Emitter emit) async {
emitProfile(state.profile.copyWith(name: event.name), emit);
}
Future<void> _onCopy(EquipmentProfileCopyEvent _, Emitter emit) async {
emit(state.copyWith(isLoading: true));
emit(state.copyWith(isLoading: false, profileToCopy: _originalEquipmentProfile));
Future<void> _onApertureValuesChanged(EquipmentProfileApertureValuesChangedEvent event, Emitter emit) async {
//emitProfile(state.profile.copyWith(apertureValues: event.apertureValues), emit);
}
Future<void> _onDelete(EquipmentProfileDeleteEvent _, Emitter emit) async {
emit(state.copyWith(isLoading: true));
await profilesProvider.deleteProfile(_newEquipmentProfile);
emit(state.copyWith(isLoading: false));
Future<void> _onLensZoomChanged(EquipmentProfileLensZoomChangedEvent event, Emitter emit) async {
emitProfile(state.profile.copyWith(lensZoom: event.lensZoom), emit);
}
bool _canSave(String name, double? lensZoom) {
return name.isNotEmpty && _newEquipmentProfile != _originalEquipmentProfile;
Future<void> _onExposureOffsetChanged(EquipmentProfileExposureOffsetChangedEvent event, Emitter emit) async {
emitProfile(state.profile.copyWith(exposureOffset: event.exposureOffset), emit);
}
}

View file

@ -1,59 +1,59 @@
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
sealed class EquipmentProfileEditEvent {
const EquipmentProfileEditEvent();
sealed class IEquipmentProfileEditEvent<T extends IEquipmentProfile> {
const IEquipmentProfileEditEvent();
}
class EquipmentProfileNameChangedEvent extends EquipmentProfileEditEvent {
class EquipmentProfileNameChangedEvent<T extends IEquipmentProfile> extends IEquipmentProfileEditEvent<T> {
final String name;
const EquipmentProfileNameChangedEvent(this.name);
}
class EquipmentProfileIsoValuesChangedEvent extends EquipmentProfileEditEvent {
class EquipmentProfileIsoValuesChangedEvent<T extends IEquipmentProfile> extends IEquipmentProfileEditEvent<T> {
final List<IsoValue> isoValues;
const EquipmentProfileIsoValuesChangedEvent(this.isoValues);
}
class EquipmentProfileNdValuesChangedEvent extends EquipmentProfileEditEvent {
class EquipmentProfileNdValuesChangedEvent extends IEquipmentProfileEditEvent<EquipmentProfile> {
final List<NdValue> ndValues;
const EquipmentProfileNdValuesChangedEvent(this.ndValues);
}
class EquipmentProfileApertureValuesChangedEvent extends EquipmentProfileEditEvent {
class EquipmentProfileApertureValuesChangedEvent extends IEquipmentProfileEditEvent<EquipmentProfile> {
final List<ApertureValue> apertureValues;
const EquipmentProfileApertureValuesChangedEvent(this.apertureValues);
}
class EquipmentProfileShutterSpeedValuesChangedEvent extends EquipmentProfileEditEvent {
class EquipmentProfileShutterSpeedValuesChangedEvent extends IEquipmentProfileEditEvent<EquipmentProfile> {
final List<ShutterSpeedValue> shutterSpeedValues;
const EquipmentProfileShutterSpeedValuesChangedEvent(this.shutterSpeedValues);
}
class EquipmentProfileLensZoomChangedEvent extends EquipmentProfileEditEvent {
class EquipmentProfileLensZoomChangedEvent<T extends IEquipmentProfile> extends IEquipmentProfileEditEvent<T> {
final double lensZoom;
const EquipmentProfileLensZoomChangedEvent(this.lensZoom);
}
class EquipmentProfileExposureOffsetChangedEvent extends EquipmentProfileEditEvent {
class EquipmentProfileExposureOffsetChangedEvent<T extends IEquipmentProfile> extends IEquipmentProfileEditEvent<T> {
final double exposureOffset;
const EquipmentProfileExposureOffsetChangedEvent(this.exposureOffset);
}
class EquipmentProfileSaveEvent extends EquipmentProfileEditEvent {
class EquipmentProfileSaveEvent<T extends IEquipmentProfile> extends IEquipmentProfileEditEvent<T> {
const EquipmentProfileSaveEvent();
}
class EquipmentProfileCopyEvent extends EquipmentProfileEditEvent {
class EquipmentProfileCopyEvent<T extends IEquipmentProfile> extends IEquipmentProfileEditEvent<T> {
const EquipmentProfileCopyEvent();
}
class EquipmentProfileDeleteEvent extends EquipmentProfileEditEvent {
class EquipmentProfileDeleteEvent<T extends IEquipmentProfile> extends IEquipmentProfileEditEvent<T> {
const EquipmentProfileDeleteEvent();
}

View file

@ -7,11 +7,14 @@ import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
enum EquipmentProfileEditType { add, edit }
class EquipmentProfileEditArgs {
class EquipmentProfileEditArgs<T extends IEquipmentProfile> {
final EquipmentProfileEditType editType;
final EquipmentProfile? profile;
final T profile;
const EquipmentProfileEditArgs({required this.editType, this.profile});
const EquipmentProfileEditArgs({
required this.editType,
required this.profile,
});
}
class EquipmentProfileEditFlow extends StatelessWidget {
@ -25,13 +28,50 @@ class EquipmentProfileEditFlow extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => EquipmentProfileEditBloc(
EquipmentProfilesProvider.of(context),
profile: args.profile,
isEdit: _isEdit,
switch (args.profile) {
case final EquipmentProfile profile:
return _IEquipmentProfileBlocProvider<EquipmentProfile, EquipmentProfileEditBloc>(
create: (_) => EquipmentProfileEditBloc(
EquipmentProfilesProvider.of(context),
profile: profile,
isEdit: _isEdit,
),
isEdit: _isEdit,
);
case final PinholeEquipmentProfile profile:
return _IEquipmentProfileBlocProvider<PinholeEquipmentProfile, PinholeEquipmentProfileEditBloc>(
create: (_) => PinholeEquipmentProfileEditBloc(
EquipmentProfilesProvider.of(context),
profile: profile,
isEdit: _isEdit,
),
isEdit: _isEdit,
);
}
}
}
class _IEquipmentProfileBlocProvider<T extends IEquipmentProfile, V extends IEquipmentProfileEditBloc<T>>
extends StatelessWidget {
final V Function(BuildContext context) create;
final bool isEdit;
const _IEquipmentProfileBlocProvider({
required this.create,
required this.isEdit,
super.key,
});
@override
Widget build(BuildContext context) {
return BlocProvider<IEquipmentProfileEditBloc<T>>(
create: create,
child: Builder(
builder: (context) => BlocProvider<V>.value(
value: context.read<IEquipmentProfileEditBloc<T>>() as V,
child: EquipmentProfileEditScreen<T>(isEdit: isEdit),
),
),
child: EquipmentProfileEditScreen(isEdit: _isEdit),
);
}
}

View file

@ -17,7 +17,7 @@ import 'package:lightmeter/utils/double_to_zoom.dart';
import 'package:lightmeter/utils/to_string_signed.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class EquipmentProfileEditScreen extends StatefulWidget {
class EquipmentProfileEditScreen<T extends IEquipmentProfile> extends StatefulWidget {
final bool isEdit;
const EquipmentProfileEditScreen({
@ -26,13 +26,13 @@ class EquipmentProfileEditScreen extends StatefulWidget {
});
@override
State<EquipmentProfileEditScreen> createState() => _EquipmentProfileEditScreenState();
State<EquipmentProfileEditScreen<T>> createState() => _EquipmentProfileEditScreenState<T>();
}
class _EquipmentProfileEditScreenState extends State<EquipmentProfileEditScreen> {
class _EquipmentProfileEditScreenState<T extends IEquipmentProfile> extends State<EquipmentProfileEditScreen<T>> {
@override
Widget build(BuildContext context) {
return BlocConsumer<EquipmentProfileEditBloc, EquipmentProfileEditState>(
return BlocConsumer<IEquipmentProfileEditBloc<T>, EquipmentProfileEditState<T>>(
listenWhen: (previous, current) => previous.isLoading != current.isLoading,
listener: (context, state) {
if (state.isLoading) {
@ -41,9 +41,9 @@ class _EquipmentProfileEditScreenState extends State<EquipmentProfileEditScreen>
if (state.profileToCopy != null) {
Navigator.of(context).pushReplacementNamed(
NavigationRoutes.equipmentProfileEditScreen.name,
arguments: EquipmentProfileEditArgs(
arguments: EquipmentProfileEditArgs<T>(
editType: EquipmentProfileEditType.add,
profile: state.profileToCopy,
profile: state.profileToCopy!,
),
);
} else {
@ -57,25 +57,25 @@ class _EquipmentProfileEditScreenState extends State<EquipmentProfileEditScreen>
child: SliverScreen(
title: Text(widget.isEdit ? S.of(context).editEquipmentProfileTitle : S.of(context).addEquipmentProfileTitle),
appBarActions: [
BlocBuilder<EquipmentProfileEditBloc, EquipmentProfileEditState>(
BlocBuilder<IEquipmentProfileEditBloc<T>, EquipmentProfileEditState<T>>(
buildWhen: (previous, current) => previous.canSave != current.canSave,
builder: (context, state) => IconButton(
onPressed: state.canSave
? () {
context.read<EquipmentProfileEditBloc>().add(const EquipmentProfileSaveEvent());
context.read<IEquipmentProfileEditBloc<T>>().add(const EquipmentProfileSaveEvent());
}
: null,
icon: const Icon(Icons.save_outlined),
),
),
if (widget.isEdit)
BlocBuilder<EquipmentProfileEditBloc, EquipmentProfileEditState>(
BlocBuilder<IEquipmentProfileEditBloc<T>, EquipmentProfileEditState<T>>(
buildWhen: (previous, current) => previous.canSave != current.canSave,
builder: (context, state) => IconButton(
onPressed: state.canSave
? null
: () {
context.read<EquipmentProfileEditBloc>().add(const EquipmentProfileCopyEvent());
context.read<IEquipmentProfileEditBloc<T>>().add(const EquipmentProfileCopyEvent());
},
icon: const Icon(Icons.copy_outlined),
),
@ -83,7 +83,7 @@ class _EquipmentProfileEditScreenState extends State<EquipmentProfileEditScreen>
if (widget.isEdit)
IconButton(
onPressed: () {
context.read<EquipmentProfileEditBloc>().add(const EquipmentProfileDeleteEvent());
context.read<IEquipmentProfileEditBloc<T>>().add(const EquipmentProfileDeleteEvent());
},
icon: const Icon(Icons.delete_outlined),
),
@ -102,16 +102,25 @@ class _EquipmentProfileEditScreenState extends State<EquipmentProfileEditScreen>
padding: const EdgeInsets.symmetric(vertical: Dimens.paddingM),
child: Opacity(
opacity: state.isLoading ? Dimens.disabledOpacity : Dimens.enabledOpacity,
child: const Column(
children: [
_NameFieldBuilder(),
_IsoValuesListTileBuilder(),
_NdValuesListTileBuilder(),
_ApertureValuesListTileBuilder(),
_ShutterSpeedValuesListTileBuilder(),
_LensZoomListTileBuilder(),
_ExposureOffsetListTileBuilder(),
],
child: Column(
children: switch (state.profile) {
EquipmentProfile() => [
_NameFieldBuilder<T>(),
_ApertureValuesListTileBuilder(),
_ShutterSpeedValuesListTileBuilder(),
_IsoValuesListTileBuilder<T>(),
_NdValuesListTileBuilder(),
_LensZoomListTileBuilder<T>(),
_ExposureOffsetListTileBuilder<T>(),
],
PinholeEquipmentProfile() => [
_NameFieldBuilder<T>(),
// TODO: Add aperture value list tile for pinhole equipment profile
_IsoValuesListTileBuilder<T>(),
_LensZoomListTileBuilder<T>(),
_ExposureOffsetListTileBuilder<T>(),
],
},
),
),
),
@ -126,12 +135,13 @@ class _EquipmentProfileEditScreenState extends State<EquipmentProfileEditScreen>
}
}
class _NameFieldBuilder extends StatelessWidget {
class _NameFieldBuilder<T extends IEquipmentProfile> extends StatelessWidget {
const _NameFieldBuilder();
@override
Widget build(BuildContext context) {
return BlocBuilder<EquipmentProfileEditBloc, EquipmentProfileEditState>(
return BlocBuilder<IEquipmentProfileEditBloc<T>, EquipmentProfileEditState<T>>(
buildWhen: (previous, current) => previous.profile.name != current.profile.name,
builder: (context, state) => Padding(
padding: const EdgeInsets.only(
left: Dimens.paddingM,
@ -141,13 +151,13 @@ class _NameFieldBuilder extends StatelessWidget {
),
child: LightmeterTextField(
autofocus: true,
initialValue: state.name,
initialValue: state.profile.name,
maxLength: 48,
hintText: S.of(context).name,
style: Theme.of(context).listTileTheme.titleTextStyle,
leading: const Icon(Icons.edit_outlined),
onChanged: (value) {
context.read<EquipmentProfileEditBloc>().add(EquipmentProfileNameChangedEvent(value));
context.read<IEquipmentProfileEditBloc<T>>().add(EquipmentProfileNameChangedEvent<T>(value));
},
),
),
@ -155,20 +165,20 @@ class _NameFieldBuilder extends StatelessWidget {
}
}
class _IsoValuesListTileBuilder extends StatelessWidget {
class _IsoValuesListTileBuilder<T extends IEquipmentProfile> extends StatelessWidget {
const _IsoValuesListTileBuilder();
@override
Widget build(BuildContext context) {
return BlocBuilder<EquipmentProfileEditBloc, EquipmentProfileEditState>(
return BlocBuilder<IEquipmentProfileEditBloc<T>, EquipmentProfileEditState<T>>(
builder: (context, state) => FilterListTile<IsoValue>(
icon: Icons.iso_outlined,
title: S.of(context).isoValues,
description: S.of(context).isoValuesFilterDescription,
values: IsoValue.values,
selectedValues: state.isoValues,
selectedValues: state.profile.isoValues,
onChanged: (value) {
context.read<EquipmentProfileEditBloc>().add(EquipmentProfileIsoValuesChangedEvent(value));
context.read<IEquipmentProfileEditBloc<T>>().add(EquipmentProfileIsoValuesChangedEvent<T>(value));
},
),
);
@ -180,13 +190,13 @@ class _NdValuesListTileBuilder extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<EquipmentProfileEditBloc, EquipmentProfileEditState>(
return BlocBuilder<EquipmentProfileEditBloc, EquipmentProfileEditState<EquipmentProfile>>(
builder: (context, state) => FilterListTile<NdValue>(
icon: Icons.filter_b_and_w_outlined,
title: S.of(context).ndFilters,
description: S.of(context).ndFiltersFilterDescription,
values: NdValue.values,
selectedValues: state.ndValues,
selectedValues: state.profile.ndValues,
onChanged: (value) {
context.read<EquipmentProfileEditBloc>().add(EquipmentProfileNdValuesChangedEvent(value));
},
@ -200,13 +210,13 @@ class _ApertureValuesListTileBuilder extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<EquipmentProfileEditBloc, EquipmentProfileEditState>(
return BlocBuilder<EquipmentProfileEditBloc, EquipmentProfileEditState<EquipmentProfile>>(
builder: (context, state) => RangePickerListTile<ApertureValue>(
icon: Icons.camera_outlined,
title: S.of(context).apertureValues,
description: S.of(context).apertureValuesFilterDescription,
values: ApertureValue.values,
selectedValues: state.apertureValues,
selectedValues: state.profile.apertureValues,
onChanged: (value) {
context.read<EquipmentProfileEditBloc>().add(EquipmentProfileApertureValuesChangedEvent(value));
},
@ -220,13 +230,13 @@ class _ShutterSpeedValuesListTileBuilder extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<EquipmentProfileEditBloc, EquipmentProfileEditState>(
return BlocBuilder<EquipmentProfileEditBloc, EquipmentProfileEditState<EquipmentProfile>>(
builder: (context, state) => RangePickerListTile<ShutterSpeedValue>(
icon: Icons.shutter_speed_outlined,
title: S.of(context).shutterSpeedValues,
description: S.of(context).shutterSpeedValuesFilterDescription,
values: ShutterSpeedValue.values,
selectedValues: state.shutterSpeedValues,
selectedValues: state.profile.shutterSpeedValues,
trailingAdapter: (context, value) =>
value.value == 1 ? S.of(context).shutterSpeedManualShort : value.toString(),
dialogValueAdapter: (context, value) => value.value == 1 ? S.of(context).shutterSpeedManual : value.toString(),
@ -238,42 +248,44 @@ class _ShutterSpeedValuesListTileBuilder extends StatelessWidget {
}
}
class _LensZoomListTileBuilder extends StatelessWidget {
class _LensZoomListTileBuilder<T extends IEquipmentProfile> extends StatelessWidget {
const _LensZoomListTileBuilder();
@override
Widget build(BuildContext context) {
return BlocBuilder<EquipmentProfileEditBloc, EquipmentProfileEditState>(
return BlocBuilder<IEquipmentProfileEditBloc<T>, EquipmentProfileEditState<T>>(
buildWhen: (previous, current) => previous.profile.lensZoom != current.profile.lensZoom,
builder: (context, state) => SliderPickerListTile(
icon: Icons.zoom_in_outlined,
title: S.of(context).lensZoom,
description: S.of(context).lensZoomDescription,
value: state.lensZoom,
value: state.profile.lensZoom,
range: CameraContainerBloc.zoomMaxRange,
valueAdapter: (context, value) => value.toZoom(context),
onChanged: (value) {
context.read<EquipmentProfileEditBloc>().add(EquipmentProfileLensZoomChangedEvent(value));
context.read<IEquipmentProfileEditBloc<T>>().add(EquipmentProfileLensZoomChangedEvent<T>(value));
},
),
);
}
}
class _ExposureOffsetListTileBuilder extends StatelessWidget {
class _ExposureOffsetListTileBuilder<T extends IEquipmentProfile> extends StatelessWidget {
const _ExposureOffsetListTileBuilder();
@override
Widget build(BuildContext context) {
return BlocBuilder<EquipmentProfileEditBloc, EquipmentProfileEditState>(
return BlocBuilder<IEquipmentProfileEditBloc<T>, EquipmentProfileEditState<T>>(
buildWhen: (previous, current) => previous.profile.exposureOffset != current.profile.exposureOffset,
builder: (context, state) => SliderPickerListTile(
icon: Icons.light_mode_outlined,
title: S.of(context).exposureOffset,
description: S.of(context).exposureOffsetDescription,
value: state.exposureOffset,
value: state.profile.exposureOffset,
range: CameraContainerBloc.exposureMaxRange,
valueAdapter: (context, value) => S.of(context).evValue(value.toStringSignedAsFixed(1)),
onChanged: (value) {
context.read<EquipmentProfileEditBloc>().add(EquipmentProfileExposureOffsetChangedEvent(value));
context.read<IEquipmentProfileEditBloc<T>>().add(EquipmentProfileExposureOffsetChangedEvent<T>(value));
},
),
);

View file

@ -1,52 +1,28 @@
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class EquipmentProfileEditState {
final String name;
final List<ApertureValue> apertureValues;
final List<NdValue> ndValues;
final List<ShutterSpeedValue> shutterSpeedValues;
final List<IsoValue> isoValues;
final double lensZoom;
final double exposureOffset;
class EquipmentProfileEditState<T extends IEquipmentProfile> {
final T profile;
final T? profileToCopy;
final bool canSave;
final bool isLoading;
final EquipmentProfile? profileToCopy;
const EquipmentProfileEditState({
required this.name,
required this.apertureValues,
required this.ndValues,
required this.shutterSpeedValues,
required this.isoValues,
required this.lensZoom,
required this.exposureOffset,
required this.profile,
required this.canSave,
this.isLoading = false,
this.profileToCopy,
});
EquipmentProfileEditState copyWith({
String? name,
List<ApertureValue>? apertureValues,
List<NdValue>? ndValues,
List<ShutterSpeedValue>? shutterSpeedValues,
List<IsoValue>? isoValues,
double? lensZoom,
double? exposureOffset,
EquipmentProfileEditState<T> copyWith({
T? profile,
T? profileToCopy,
bool? canSave,
bool? isLoading,
EquipmentProfile? profileToCopy,
}) =>
EquipmentProfileEditState(
name: name ?? this.name,
apertureValues: apertureValues ?? this.apertureValues,
ndValues: ndValues ?? this.ndValues,
shutterSpeedValues: shutterSpeedValues ?? this.shutterSpeedValues,
isoValues: isoValues ?? this.isoValues,
lensZoom: lensZoom ?? this.lensZoom,
exposureOffset: exposureOffset ?? this.exposureOffset,
EquipmentProfileEditState<T>(
profile: profile ?? this.profile,
profileToCopy: profileToCopy ?? this.profileToCopy,
canSave: canSave ?? this.canSave,
isLoading: isLoading ?? this.isLoading,
profileToCopy: profileToCopy ?? this.profileToCopy,
);
}

View file

@ -4,11 +4,14 @@ import 'package:lightmeter/navigation/routes.dart';
import 'package:lightmeter/providers/equipment_profile_provider.dart';
import 'package:lightmeter/res/dimens.dart';
import 'package:lightmeter/screens/equipment_profile_edit/flow_equipment_profile_edit.dart';
import 'package:lightmeter/screens/settings/components/shared/dialog_picker/widget_dialog_picker.dart';
import 'package:lightmeter/screens/shared/sliver_placeholder/widget_sliver_placeholder.dart';
import 'package:lightmeter/screens/shared/sliver_screen/screen_sliver.dart';
import 'package:lightmeter/utils/guard_pro_tap.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
enum _EquipmentProfileType { regular, pinhole }
class EquipmentProfilesScreen extends StatefulWidget {
const EquipmentProfilesScreen({super.key});
@ -45,15 +48,45 @@ class _EquipmentProfilesScreenState extends State<EquipmentProfilesScreen> with
guardProTap(
context,
() {
Navigator.of(context).pushNamed(
NavigationRoutes.equipmentProfileEditScreen.name,
arguments: const EquipmentProfileEditArgs(editType: EquipmentProfileEditType.add),
);
showDialog<_EquipmentProfileType>(
context: context,
builder: (_) => DialogPicker<_EquipmentProfileType>(
icon: Icons.camera_alt_outlined,
title: S.of(context).equipmentProfileType,
selectedValue: _EquipmentProfileType.regular,
values: _EquipmentProfileType.values,
titleAdapter: (context, value) => switch (value) {
_EquipmentProfileType.regular => S.of(context).camera,
_EquipmentProfileType.pinhole => S.of(context).pinholeCamera,
},
),
).then((value) {
if (value != null && mounted) {
Navigator.of(context).pushNamed(
NavigationRoutes.equipmentProfileEditScreen.name,
arguments: switch (value) {
_EquipmentProfileType.regular => const EquipmentProfileEditArgs(
editType: EquipmentProfileEditType.add,
profile: EquipmentProfilesProvider.defaultProfile,
),
_EquipmentProfileType.pinhole => EquipmentProfileEditArgs(
editType: EquipmentProfileEditType.add,
profile: PinholeEquipmentProfile(
id: EquipmentProfilesProvider.defaultProfile.id,
name: EquipmentProfilesProvider.defaultProfile.name,
aperture: 22,
isoValues: EquipmentProfilesProvider.defaultProfile.isoValues,
),
),
},
);
}
});
},
);
}
void _editProfile(EquipmentProfile profile) {
void _editProfile(IEquipmentProfile profile) {
Navigator.of(context).pushNamed(
NavigationRoutes.equipmentProfileEditScreen.name,
arguments: EquipmentProfileEditArgs(
@ -65,9 +98,9 @@ class _EquipmentProfilesScreenState extends State<EquipmentProfilesScreen> with
}
class _EquipmentProfilesListBuilder extends StatelessWidget {
final List<EquipmentProfile> values;
final void Function(EquipmentProfile profile) onEdit;
final void Function(EquipmentProfile profile, bool value) onCheckbox;
final List<IEquipmentProfile> values;
final void Function(IEquipmentProfile profile) onEdit;
final void Function(String id, bool value) onCheckbox;
const _EquipmentProfilesListBuilder({
required this.values,
@ -102,7 +135,7 @@ class _EquipmentProfilesListBuilder extends StatelessWidget {
title: Text(values[index].name),
controlAffinity: ListTileControlAffinity.leading,
value: EquipmentProfiles.inUseOf(context).contains(values[index]),
onChanged: (value) => onCheckbox(values[index], value ?? false),
onChanged: (value) => onCheckbox(values[index].id, value ?? false),
secondary: IconButton(
onPressed: () => onEdit(values[index]),
icon: const Icon(Icons.edit_outlined),