From 59be88f0e620add25408fbacc8dba0194f6d7238 Mon Sep 17 00:00:00 2001 From: Vadim <44135514+vodemn@users.noreply.github.com> Date: Thu, 4 Sep 2025 10:22:14 +0200 Subject: [PATCH] input custom aperture value --- .../bloc_equipment_profile_edit.dart | 26 +++++-- ...dget_input_aperture_equipment_profile.dart | 68 +++++++++++++++++++ .../event_equipment_profile_edit.dart | 6 ++ .../screen_equipment_profile_edit.dart | 34 +++++++--- .../state_equipment_profile_edit.dart | 12 ++-- .../shared/text_field/widget_text_field.dart | 4 ++ 6 files changed, 132 insertions(+), 18 deletions(-) create mode 100644 lib/screens/equipment_profile_edit/components/aperture_input/widget_input_aperture_equipment_profile.dart diff --git a/lib/screens/equipment_profile_edit/bloc_equipment_profile_edit.dart b/lib/screens/equipment_profile_edit/bloc_equipment_profile_edit.dart index 37b4594..1db00c3 100644 --- a/lib/screens/equipment_profile_edit/bloc_equipment_profile_edit.dart +++ b/lib/screens/equipment_profile_edit/bloc_equipment_profile_edit.dart @@ -23,7 +23,8 @@ sealed class IEquipmentProfileEditBloc super( EquipmentProfileEditState( profile: profile, - canSave: false, + hasChanges: false, + isValid: true, ), ) { on>(mapEventToState); @@ -51,7 +52,8 @@ sealed class IEquipmentProfileEditBloc emit( state.copyWith( profile: profile, - canSave: _canSave(profile), + hasChanges: _hasChanges(profile), + isValid: _isValid(profile), ), ); } @@ -76,7 +78,9 @@ sealed class IEquipmentProfileEditBloc emit(state.copyWith(isLoading: false)); } - bool _canSave(T profile) => profile != originalEquipmentProfile; + bool _hasChanges(T profile) => profile != originalEquipmentProfile; + + bool _isValid(T profile) => profile.name.isNotEmpty; } class EquipmentProfileEditBloc extends IEquipmentProfileEditBloc { @@ -163,7 +167,7 @@ class PinholeEquipmentProfileEditBloc extends IEquipmentProfileEditBloc _onApertureValuesChanged(EquipmentProfileApertureValuesChangedEvent event, Emitter emit) async { - //emitProfile(state.profile.copyWith(apertureValues: event.apertureValues), emit); + Future _onApertureValuesChanged(EquipmentProfileApertureValueChangedEvent event, Emitter emit) async { + emitProfile(state.profile.copyWith(aperture: event.aperture ?? 0), emit); + + final profile = state.profile.copyWith(aperture: event.aperture); + emit( + state.copyWith( + profile: profile, + hasChanges: _hasChanges(profile), + isValid: _isValid(profile) && event.aperture != null, + ), + ); } + Future _onIsoValuesChanged(EquipmentProfileIsoValuesChangedEvent event, Emitter emit) async { emitProfile(state.profile.copyWith(isoValues: event.isoValues), emit); } diff --git a/lib/screens/equipment_profile_edit/components/aperture_input/widget_input_aperture_equipment_profile.dart b/lib/screens/equipment_profile_edit/components/aperture_input/widget_input_aperture_equipment_profile.dart new file mode 100644 index 0000000..17234c4 --- /dev/null +++ b/lib/screens/equipment_profile_edit/components/aperture_input/widget_input_aperture_equipment_profile.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:lightmeter/generated/l10n.dart'; +import 'package:lightmeter/res/dimens.dart'; +import 'package:lightmeter/screens/shared/text_field/widget_text_field.dart'; + +class EquipmentProfileApertureInput extends StatefulWidget { + final double? value; + final ValueChanged onChanged; + + const EquipmentProfileApertureInput({ + super.key, + required this.value, + required this.onChanged, + }); + + @override + State createState() => _EquipmentProfileApertureInputState(); +} + +class _EquipmentProfileApertureInputState extends State { + TextStyle get style => + Theme.of(context).textTheme.bodyMedium!.copyWith(color: Theme.of(context).listTileTheme.textColor); + + @override + Widget build(BuildContext context) { + return ListTile( + leading: const Icon(Icons.camera), + title: Text( + S.of(context).apertureValue, + style: Theme.of(context).listTileTheme.titleTextStyle, + ), + trailing: SizedBox( + width: _textInputWidth(context), + child: LightmeterTextField( + initialValue: widget.value?.toString() ?? '', + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp("[0-9.]")), + LengthLimitingTextInputFormatter(6), + ], + onChanged: (value) { + final parsed = double.tryParse(value); + widget.onChanged(parsed != null && parsed > 0 ? parsed : null); + }, + validator: (value) { + final parsed = double.tryParse(value); + if (parsed != null && parsed > 0) { + return null; + } else { + return ''; + } + }, + textAlign: TextAlign.end, + style: style, + ), + ), + ); + } + + double _textInputWidth(BuildContext context) { + final textPainter = TextPainter( + text: TextSpan(text: widget.value.toString(), style: style), + maxLines: 1, + textDirection: TextDirection.ltr, + )..layout(); + return textPainter.maxIntrinsicWidth + Dimens.grid4; + } +} diff --git a/lib/screens/equipment_profile_edit/event_equipment_profile_edit.dart b/lib/screens/equipment_profile_edit/event_equipment_profile_edit.dart index 83fc685..56fa719 100644 --- a/lib/screens/equipment_profile_edit/event_equipment_profile_edit.dart +++ b/lib/screens/equipment_profile_edit/event_equipment_profile_edit.dart @@ -28,6 +28,12 @@ class EquipmentProfileApertureValuesChangedEvent extends IEquipmentProfileEditEv const EquipmentProfileApertureValuesChangedEvent(this.apertureValues); } +class EquipmentProfileApertureValueChangedEvent extends IEquipmentProfileEditEvent { + final double? aperture; + + const EquipmentProfileApertureValueChangedEvent(this.aperture); +} + class EquipmentProfileShutterSpeedValuesChangedEvent extends IEquipmentProfileEditEvent { final List shutterSpeedValues; diff --git a/lib/screens/equipment_profile_edit/screen_equipment_profile_edit.dart b/lib/screens/equipment_profile_edit/screen_equipment_profile_edit.dart index ea4e5c7..40fd380 100644 --- a/lib/screens/equipment_profile_edit/screen_equipment_profile_edit.dart +++ b/lib/screens/equipment_profile_edit/screen_equipment_profile_edit.dart @@ -4,6 +4,7 @@ import 'package:lightmeter/generated/l10n.dart'; import 'package:lightmeter/navigation/routes.dart'; import 'package:lightmeter/res/dimens.dart'; import 'package:lightmeter/screens/equipment_profile_edit/bloc_equipment_profile_edit.dart'; +import 'package:lightmeter/screens/equipment_profile_edit/components/aperture_input/widget_input_aperture_equipment_profile.dart'; import 'package:lightmeter/screens/equipment_profile_edit/components/filter_list_tile/widget_list_tile_filter.dart'; import 'package:lightmeter/screens/equipment_profile_edit/components/range_picker_list_tile/widget_list_tile_range_picker.dart'; import 'package:lightmeter/screens/equipment_profile_edit/components/slider_picker_list_tile/widget_list_tile_slider_picker.dart'; @@ -58,9 +59,10 @@ class _EquipmentProfileEditScreenState extends Stat title: Text(widget.isEdit ? S.of(context).editEquipmentProfileTitle : S.of(context).addEquipmentProfileTitle), appBarActions: [ BlocBuilder, EquipmentProfileEditState>( - buildWhen: (previous, current) => previous.canSave != current.canSave, + buildWhen: (previous, current) => + previous.hasChanges != current.hasChanges || previous.isValid != current.isValid, builder: (context, state) => IconButton( - onPressed: state.canSave + onPressed: state.hasChanges && state.isValid ? () { context.read>().add(const EquipmentProfileSaveEvent()); } @@ -70,13 +72,13 @@ class _EquipmentProfileEditScreenState extends Stat ), if (widget.isEdit) BlocBuilder, EquipmentProfileEditState>( - buildWhen: (previous, current) => previous.canSave != current.canSave, + buildWhen: (previous, current) => previous.isValid != current.isValid, builder: (context, state) => IconButton( - onPressed: state.canSave - ? null - : () { + onPressed: state.isValid + ? () { context.read>().add(const EquipmentProfileCopyEvent()); - }, + } + : null, icon: const Icon(Icons.copy_outlined), ), ), @@ -106,7 +108,7 @@ class _EquipmentProfileEditScreenState extends Stat children: [ _NameFieldBuilder(), if (state.profile is PinholeEquipmentProfile) - const SizedBox.shrink() + const _ApertureValueListTileBuilder() else ...[ const _ApertureValuesListTileBuilder(), const _ShutterSpeedValuesListTileBuilder(), @@ -220,6 +222,22 @@ class _ApertureValuesListTileBuilder extends StatelessWidget { } } +class _ApertureValueListTileBuilder extends StatelessWidget { + const _ApertureValueListTileBuilder(); + + @override + Widget build(BuildContext context) { + return BlocBuilder>( + builder: (context, state) => EquipmentProfileApertureInput( + value: state.profile.aperture, + onChanged: (value) { + context.read().add(EquipmentProfileApertureValueChangedEvent(value)); + }, + ), + ); + } +} + class _ShutterSpeedValuesListTileBuilder extends StatelessWidget { const _ShutterSpeedValuesListTileBuilder(); diff --git a/lib/screens/equipment_profile_edit/state_equipment_profile_edit.dart b/lib/screens/equipment_profile_edit/state_equipment_profile_edit.dart index 2018ec1..e095ee3 100644 --- a/lib/screens/equipment_profile_edit/state_equipment_profile_edit.dart +++ b/lib/screens/equipment_profile_edit/state_equipment_profile_edit.dart @@ -3,12 +3,14 @@ import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; class EquipmentProfileEditState { final T profile; final T? profileToCopy; - final bool canSave; + final bool hasChanges; + final bool isValid; final bool isLoading; const EquipmentProfileEditState({ required this.profile, - required this.canSave, + required this.hasChanges, + required this.isValid, this.isLoading = false, this.profileToCopy, }); @@ -16,13 +18,15 @@ class EquipmentProfileEditState { EquipmentProfileEditState copyWith({ T? profile, T? profileToCopy, - bool? canSave, + bool? isValid, + bool? hasChanges, bool? isLoading, }) => EquipmentProfileEditState( profile: profile ?? this.profile, profileToCopy: profileToCopy ?? this.profileToCopy, - canSave: canSave ?? this.canSave, + isValid: isValid ?? this.isValid, + hasChanges: hasChanges ?? this.hasChanges, isLoading: isLoading ?? this.isLoading, ); } diff --git a/lib/screens/shared/text_field/widget_text_field.dart b/lib/screens/shared/text_field/widget_text_field.dart index f801845..dd9bc8c 100644 --- a/lib/screens/shared/text_field/widget_text_field.dart +++ b/lib/screens/shared/text_field/widget_text_field.dart @@ -8,6 +8,7 @@ class LightmeterTextField extends StatefulWidget { this.hintText, this.initialValue, this.inputFormatters, + this.validator, this.leading, this.maxLength, this.maxLines = 1, @@ -21,6 +22,7 @@ class LightmeterTextField extends StatefulWidget { final String? hintText; final String? initialValue; final List? inputFormatters; + final String? Function(String)? validator; final Widget? leading; final int? maxLength; final int? maxLines; @@ -62,6 +64,8 @@ class _LightmeterTextFieldState extends State { validator: (value) { if (value == null || value.isEmpty) { return ''; + } else if (widget.validator != null) { + return widget.validator!(value); } else { return null; }