main
1import 'package:blood_pressure_app/features/input/forms/form_base.dart';
2import 'package:blood_pressure_app/model/storage/settings_store.dart';
3import 'package:flutter/material.dart';
4import 'package:flutter/services.dart';
5import 'package:blood_pressure_app/l10n/app_localizations.dart';
6import 'package:provider/provider.dart';
7
8/// Form to enter freeform text and select color.
9class BloodPressureForm extends FormBase<({int? sys, int? dia, int? pul})> {
10 /// Create form to enter freeform text and select color.
11 const BloodPressureForm({super.key,
12 super.initialValue,
13 });
14
15 @override
16 BloodPressureFormState createState() => BloodPressureFormState();
17}
18
19/// State of form to enter freeform text and select color.
20class BloodPressureFormState extends FormStateBase<({int? sys, int? dia, int? pul}), BloodPressureForm> {
21 final _formKey = GlobalKey<FormState>();
22
23 final _sysFocusNode = FocusNode();
24 final _diaFocusNode = FocusNode();
25 final _pulFocusNode = FocusNode();
26
27 late final TextEditingController _sysController;
28 late final TextEditingController _diaController;
29 late final TextEditingController _pulController;
30
31 @override
32 void initState() {
33 super.initState();
34 _sysController = TextEditingController(text: widget.initialValue?.sys?.toString() ?? '');
35 _diaController = TextEditingController(text: widget.initialValue?.dia?.toString() ?? '');
36 _pulController = TextEditingController(text: widget.initialValue?.pul?.toString() ?? '');
37 _sysFocusNode.requestFocus();
38 }
39
40 @override
41 void dispose() {
42 _sysFocusNode.dispose();
43 _diaFocusNode.dispose();
44 _pulFocusNode.dispose();
45 _sysController.dispose();
46 _diaController.dispose();
47 _pulController.dispose();
48 super.dispose();
49 }
50
51 @override
52 bool validate() {
53 if (_sysController.text.isEmpty
54 && _diaController.text.isEmpty
55 && _pulController.text.isEmpty) {
56 return true;
57 }
58 return _formKey.currentState?.validate() ?? false;
59 }
60
61 @override
62 ({int? sys, int? dia, int? pul})? save() {
63 if (!validate()
64 || (int.tryParse(_sysController.text) == null
65 && int.tryParse(_diaController.text) == null
66 && int.tryParse(_pulController.text) == null)) {
67 return null;
68 }
69 return (
70 sys: int.tryParse(_sysController.text),
71 dia: int.tryParse(_diaController.text),
72 pul: int.tryParse(_pulController.text),
73 );
74 }
75
76 @override
77 bool isEmptyInputFocused() => (_diaFocusNode.hasFocus && _diaController.text.isEmpty)
78 || (_pulFocusNode.hasFocus && _pulController.text.isEmpty);
79
80 @override
81 void fillForm(({int? dia, int? pul, int? sys})? value) => setState(() {
82 if (value == null) {
83 _sysController.text = '';
84 _diaController.text = '';
85 _pulController.text = '';
86 } else {
87 if (value.dia != null) _diaController.text = value.dia.toString();
88 if (value.pul != null) _pulController.text = value.pul.toString();
89 if (value.sys != null) _sysController.text = value.sys.toString();
90 }
91 });
92
93 Widget _buildValueInput({
94 String? labelText,
95 FocusNode? focusNode,
96 TextEditingController? controller,
97 String? Function(String?)? validator,
98 }) => Expanded(
99 child: TextFormField(
100 focusNode: focusNode,
101 controller: controller,
102 keyboardType: TextInputType.number,
103 inputFormatters: [FilteringTextInputFormatter.digitsOnly],
104 onChanged: (String value) {
105 if (value.isNotEmpty
106 && (int.tryParse(value) ?? -1) > 40) {
107 FocusScope.of(context).nextFocus();
108 }
109 },
110 validator: (String? value) {
111 final settings = context.read<Settings>();
112 if (!settings.allowMissingValues
113 && (value == null
114 || value.isEmpty
115 || int.tryParse(value) == null)) {
116 return AppLocalizations.of(context)!.errNaN;
117 } else if (settings.validateInputs
118 && (int.tryParse(value ?? '') ?? -1) <= 30) {
119 return AppLocalizations.of(context)!.errLt30;
120 } else if (settings.validateInputs
121 && (int.tryParse(value ?? '') ?? 0) >= 400) {
122 // https://pubmed.ncbi.nlm.nih.gov/7741618/
123 return AppLocalizations.of(context)!.errUnrealistic;
124 }
125 return validator?.call(value);
126 },
127 decoration: InputDecoration(
128 labelText: labelText,
129 ),
130 style: Theme.of(context).textTheme.bodyLarge,
131 ),
132 );
133
134 @override
135 Widget build(BuildContext context) => Form(
136 key: _formKey,
137 child: Row(
138 mainAxisSize: MainAxisSize.min,
139 children: [
140 _buildValueInput(
141 focusNode: _sysFocusNode,
142 controller: _sysController,
143 labelText: AppLocalizations.of(context)!.sysLong,
144 ),
145 const SizedBox(width: 8,),
146 _buildValueInput(
147 labelText: AppLocalizations.of(context)!.diaLong,
148 controller: _diaController,
149 focusNode: _diaFocusNode,
150 validator: (value) {
151 if (context.read<Settings>().validateInputs
152 && (int.tryParse(value ?? '') ?? 0)
153 >= (int.tryParse(_sysController.text) ?? 1)) {
154 return AppLocalizations.of(context)?.errDiaGtSys;
155 }
156 return null;
157 },
158 ),
159 const SizedBox(width: 8,),
160 _buildValueInput(
161 controller: _pulController,
162 focusNode: _pulFocusNode,
163 labelText: AppLocalizations.of(context)!.pulLong,
164 ),
165 ],
166 ),
167 );
168}