[FilmEditScreen] added validation

This commit is contained in:
Vadim 2024-10-22 18:34:23 +02:00
parent b7f0305055
commit 1eebc7008e
8 changed files with 229 additions and 41 deletions

View file

@ -150,5 +150,9 @@
} }
} }
}, },
"close": "Close" "close": "Close",
"filmFormula": "Formula",
"filmFormulaExponential": "T=t^Rf",
"filmFormulaExponentialRf": "Rf",
"name": "Name"
} }

View file

@ -67,7 +67,8 @@ ThemeData themeFrom(Color primaryColor, Brightness brightness) {
style: ListTileStyle.list, style: ListTileStyle.list,
iconColor: scheme.onSurface, iconColor: scheme.onSurface,
textColor: scheme.onSurface, textColor: scheme.onSurface,
subtitleTextStyle: theme.textTheme.bodyMedium!.copyWith(color: scheme.onSurfaceVariant), subtitleTextStyle: theme.textTheme.bodyMedium,
leadingAndTrailingTextStyle: theme.textTheme.bodyMedium,
), ),
); );
} }

View file

@ -4,11 +4,18 @@ import 'package:lightmeter/screens/film_edit/state_film_edit.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class FilmEditBloc extends Bloc<FilmEditEvent, FilmEditState> { class FilmEditBloc extends Bloc<FilmEditEvent, FilmEditState> {
final FilmExponential _originalFilm;
FilmExponential _newFilm;
FilmEditBloc(FilmExponential film) FilmEditBloc(FilmExponential film)
: super( : _originalFilm = film,
_newFilm = film,
super(
FilmEditState( FilmEditState(
film, name: film.name,
IsoValue.values.firstWhere((element) => element.value == film.iso), isoValue: IsoValue.values.firstWhere((element) => element.value == film.iso),
exponent: film.exponent,
canSave: false,
), ),
) { ) {
on<FilmEditEvent>( on<FilmEditEvent>(
@ -27,18 +34,47 @@ class FilmEditBloc extends Bloc<FilmEditEvent, FilmEditState> {
); );
} }
Future<void> _onNameChanged(FilmEditNameChangedEvent event, Emitter emit) async {} Future<void> _onNameChanged(FilmEditNameChangedEvent event, Emitter emit) async {
_newFilm = _newFilm.copyWith(name: event.name);
Future<void> _onIsoChanged(FilmEditIsoChangedEvent event, Emitter emit) async {
emit( emit(
FilmEditState( FilmEditState(
state.film.copyWith(iso: event.iso.value), name: event.name,
event.iso, isoValue: state.isoValue,
exponent: state.exponent,
canSave: _canSave(event.name, state.exponent),
), ),
); );
} }
Future<void> _onExpChanged(FilmEditExpChangedEvent event, Emitter emit) async {} Future<void> _onIsoChanged(FilmEditIsoChangedEvent event, Emitter emit) async {
_newFilm = _newFilm.copyWith(iso: event.iso.value);
emit(
FilmEditState(
name: state.name,
isoValue: event.iso,
exponent: state.exponent,
canSave: _canSave(state.name, state.exponent),
),
);
}
Future<void> _onExpChanged(FilmEditExpChangedEvent event, Emitter emit) async {
if (event.exponent != null) {
_newFilm = _newFilm.copyWith(exponent: event.exponent);
}
emit(
FilmEditState(
name: state.name,
isoValue: state.isoValue,
exponent: event.exponent,
canSave: _canSave(state.name, event.exponent),
),
);
}
Future<void> _onSave(FilmEditSaveEvent _, Emitter emit) async {} Future<void> _onSave(FilmEditSaveEvent _, Emitter emit) async {}
bool _canSave(String name, double? exponent) {
return name.isNotEmpty && exponent != null && _newFilm != _originalFilm;
}
} }

View file

@ -0,0 +1,65 @@
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 ExponentialFormulaInput extends StatefulWidget {
final double? value;
final ValueChanged<double?> onChanged;
const ExponentialFormulaInput({
super.key,
required this.value,
required this.onChanged,
});
@override
State<ExponentialFormulaInput> createState() => _ExponentialFormulaInputState();
}
class _ExponentialFormulaInputState extends State<ExponentialFormulaInput> {
TextStyle get style =>
Theme.of(context).textTheme.bodyMedium!.copyWith(color: Theme.of(context).listTileTheme.textColor);
@override
Widget build(BuildContext context) {
return Column(
children: [
ListTile(
leading: const Icon(Icons.show_chart),
title: Text(S.of(context).filmFormula),
trailing: Text(S.of(context).filmFormulaExponential),
),
ListTile(
leading: const SizedBox(),
title: Text(
S.of(context).filmFormulaExponentialRf,
style: Theme.of(context).listTileTheme.titleTextStyle,
),
trailing: SizedBox(
width: _textInputWidth(context),
child: LightmeterTextField(
initialValue: widget.value?.toString() ?? '',
inputFormatters: [FilteringTextInputFormatter.allow(RegExp("[0-9.]"))],
onChanged: (value) {
widget.onChanged(double.tryParse(value));
},
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;
}
}

View file

@ -17,9 +17,9 @@ class FilmEditIsoChangedEvent extends FilmEditEvent {
} }
class FilmEditExpChangedEvent extends FilmEditEvent { class FilmEditExpChangedEvent extends FilmEditEvent {
final double exp; final double? exponent;
const FilmEditExpChangedEvent(this.exp); const FilmEditExpChangedEvent(this.exponent);
} }
class FilmEditSaveEvent extends FilmEditEvent { class FilmEditSaveEvent extends FilmEditEvent {

View file

@ -1,13 +1,15 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:lightmeter/generated/l10n.dart'; import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/res/dimens.dart';
import 'package:lightmeter/screens/film_edit/bloc_film_edit.dart'; import 'package:lightmeter/screens/film_edit/bloc_film_edit.dart';
import 'package:lightmeter/screens/film_edit/components/exponential_formula_input/widget_input_exponential_formula.dart';
import 'package:lightmeter/screens/film_edit/event_film_edit.dart'; import 'package:lightmeter/screens/film_edit/event_film_edit.dart';
import 'package:lightmeter/screens/film_edit/state_film_edit.dart'; import 'package:lightmeter/screens/film_edit/state_film_edit.dart';
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/components/dialog_picker/widget_picker_dialog.dart'; import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/components/dialog_picker/widget_picker_dialog.dart';
import 'package:lightmeter/screens/settings/components/shared/dialog_filter/widget_dialog_filter.dart';
import 'package:lightmeter/screens/settings/components/shared/expandable_section_list/widget_expandable_section_list.dart';
import 'package:lightmeter/screens/shared/sliver_screen/screen_sliver.dart'; import 'package:lightmeter/screens/shared/sliver_screen/screen_sliver.dart';
import 'package:lightmeter/screens/shared/text_field/widget_text_field.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class FilmEditScreen extends StatefulWidget { class FilmEditScreen extends StatefulWidget {
@ -22,37 +24,54 @@ class _FilmEditScreenState extends State<FilmEditScreen> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SliverScreen( return SliverScreen(
title: BlocBuilder<FilmEditBloc, FilmEditState>( title: BlocBuilder<FilmEditBloc, FilmEditState>(
buildWhen: (previous, current) => previous.film.name != current.film.name, buildWhen: (previous, current) => previous.name != current.name,
builder: (context, state) => TextFormField( builder: (_, state) => Text(state.name),
initialValue: state.film.name,
onChanged: (value) {},
),
), ),
appBarActions: [ appBarActions: [
IconButton( BlocBuilder<FilmEditBloc, FilmEditState>(
onPressed: () {}, buildWhen: (previous, current) => previous.canSave != current.canSave,
icon: const Icon(Icons.save), builder: (context, state) => IconButton(
onPressed: state.canSave ? () {} : null,
icon: const Icon(Icons.save),
),
), ),
], ],
slivers: [ slivers: [
SliverToBoxAdapter( SliverToBoxAdapter(
child: BlocBuilder<FilmEditBloc, FilmEditState>( child: Padding(
buildWhen: (previous, current) => previous.isoValue != current.isoValue, padding: const EdgeInsets.fromLTRB(
builder: (context, state) => _IsoPickerListTile( Dimens.paddingM,
selected: state.isoValue, 0,
onChanged: (value) { Dimens.paddingM,
context.read<FilmEditBloc>().add(FilmEditIsoChangedEvent(value)); Dimens.paddingM,
},
), ),
), child: Card(
), child: Padding(
SliverToBoxAdapter( padding: const EdgeInsets.symmetric(vertical: Dimens.paddingM),
child: BlocBuilder<FilmEditBloc, FilmEditState>( child: Column(
buildWhen: (previous, current) => previous.film.exponent != current.film.exponent, children: [
builder: (context, state) => ListTile( _NameFieldBuilder(),
leading: const Icon(Icons.equalizer), BlocBuilder<FilmEditBloc, FilmEditState>(
title: Text('Formula'), buildWhen: (previous, current) => previous.isoValue != current.isoValue,
trailing: Text(state.film.exponent.toString()), builder: (context, state) => _IsoPickerListTile(
selected: state.isoValue,
onChanged: (value) {
context.read<FilmEditBloc>().add(FilmEditIsoChangedEvent(value));
},
),
),
BlocBuilder<FilmEditBloc, FilmEditState>(
buildWhen: (previous, current) => previous.exponent != current.exponent,
builder: (context, state) => ExponentialFormulaInput(
value: state.exponent,
onChanged: (value) {
context.read<FilmEditBloc>().add(FilmEditExpChangedEvent(value));
},
),
),
],
),
),
), ),
), ),
), ),
@ -62,6 +81,33 @@ class _FilmEditScreenState extends State<FilmEditScreen> {
} }
} }
class _NameFieldBuilder extends StatelessWidget {
const _NameFieldBuilder();
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(
left: Dimens.paddingM,
top: Dimens.paddingS / 2,
right: Dimens.paddingL,
bottom: Dimens.paddingS / 2,
),
child: BlocBuilder<FilmEditBloc, FilmEditState>(
buildWhen: (previous, current) => previous.name != current.name,
builder: (context, state) => LightmeterTextField(
initialValue: state.name,
onChanged: (value) {
context.read<FilmEditBloc>().add(FilmEditNameChangedEvent(value));
},
style: Theme.of(context).listTileTheme.titleTextStyle,
leading: const Icon(Icons.edit_outlined),
),
),
);
}
}
class _IsoPickerListTile extends StatelessWidget { class _IsoPickerListTile extends StatelessWidget {
final IsoValue selected; final IsoValue selected;
final ValueChanged<IsoValue> onChanged; final ValueChanged<IsoValue> onChanged;

View file

@ -1,8 +1,15 @@
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class FilmEditState { class FilmEditState {
final FilmExponential film; final String name;
final IsoValue isoValue; final IsoValue isoValue;
final double? exponent;
final bool canSave;
const FilmEditState(this.film, this.isoValue); const FilmEditState({
required this.name,
required this.isoValue,
required this.exponent,
required this.canSave,
});
} }

View file

@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
class LightmeterTextField extends TextFormField {
LightmeterTextField({
super.controller,
super.focusNode,
super.initialValue,
super.inputFormatters,
super.onChanged,
super.style,
super.textAlign,
Widget? leading,
}) : super(
autovalidateMode: AutovalidateMode.onUserInteraction,
maxLines: 1,
decoration: InputDecoration(
contentPadding: EdgeInsets.zero,
errorStyle: const TextStyle(fontSize: 0),
icon: leading,
),
validator: (value) {
if (value == null || value.isEmpty) {
return '';
} else {
return null;
}
},
);
}