Skip to content

Commit c0a8865

Browse files
authored
Improve decimal input handling (#853)
We now use TextInputType.numberWithOptions(decimal: true) which seems to have a more consistent behaviour under android and iOS. Also, we now use NumberFormat to parse the inputs according to the user's locale.
2 parents 20ff983 + 8ae889a commit c0a8865

19 files changed

+93
-126
lines changed

lib/helpers/consts.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,6 @@ const LIBERAPAY_URL = 'https://liberapay.com/wger';
132132
const double CHART_MILLISECOND_FACTOR = 100000.0;
133133

134134
enum WeightUnitEnum { kg, lb }
135+
136+
/// TextInputType for decimal numbers
137+
const textInputTypeDecimal = TextInputType.numberWithOptions(decimal: true);

lib/providers/routines.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -568,10 +568,10 @@ class RoutinesProvider with ChangeNotifier {
568568
notifyListeners();
569569
}
570570

571-
Future<void> handleConfig(SlotEntry entry, String input, ConfigType type) async {
571+
Future<void> handleConfig(SlotEntry entry, num? value, ConfigType type) async {
572572
final configs = entry.getConfigsByType(type);
573573
final config = configs.isNotEmpty ? configs.first : null;
574-
final value = input.isNotEmpty ? num.parse(input) : null;
574+
// final value = input.isNotEmpty ? num.parse(input) : null;
575575

576576
if (value == null && config != null) {
577577
// Value removed, delete entry

lib/widgets/measurements/forms.dart

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
*/
1818

1919
import 'package:flutter/material.dart';
20+
import 'package:intl/intl.dart';
2021
import 'package:provider/provider.dart';
22+
import 'package:wger/helpers/consts.dart';
2123
import 'package:wger/helpers/json.dart';
2224
import 'package:wger/l10n/generated/app_localizations.dart';
2325
import 'package:wger/models/measurements/measurement_category.dart';
@@ -168,6 +170,8 @@ class MeasurementEntryForm extends StatelessWidget {
168170
(category) => category.id == _categoryId,
169171
);
170172

173+
final numberFormat = NumberFormat.decimalPattern(Localizations.localeOf(context).toString());
174+
171175
return Form(
172176
key: _form,
173177
child: Column(
@@ -218,20 +222,20 @@ class MeasurementEntryForm extends StatelessWidget {
218222
suffixIconConstraints: const BoxConstraints(minWidth: 0, minHeight: 0),
219223
),
220224
controller: _valueController,
221-
keyboardType: TextInputType.number,
225+
keyboardType: textInputTypeDecimal,
222226
validator: (value) {
223227
if (value!.isEmpty) {
224228
return AppLocalizations.of(context).enterValue;
225229
}
226230
try {
227-
double.parse(value);
231+
numberFormat.parse(value);
228232
} catch (error) {
229233
return AppLocalizations.of(context).enterValidNumber;
230234
}
231235
return null;
232236
},
233237
onSaved: (newValue) {
234-
_entryData['value'] = double.parse(newValue!);
238+
_entryData['value'] = numberFormat.parse(newValue!);
235239
},
236240
),
237241
// Value

lib/widgets/nutrition/forms.dart

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
*/
1818

1919
import 'package:flutter/material.dart';
20+
import 'package:intl/intl.dart';
2021
import 'package:provider/provider.dart';
2122
import 'package:wger/helpers/consts.dart';
2223
import 'package:wger/helpers/json.dart';
@@ -236,6 +237,8 @@ class IngredientFormState extends State<IngredientForm> {
236237
final queryLower = _searchQuery.toLowerCase();
237238
final suggestions =
238239
widget.recent.where((e) => e.ingredient.name.toLowerCase().contains(queryLower)).toList();
240+
final numberFormat = NumberFormat.decimalPattern(Localizations.localeOf(context).toString());
241+
239242
return Container(
240243
margin: const EdgeInsets.all(20),
241244
child: Form(
@@ -261,7 +264,7 @@ class IngredientFormState extends State<IngredientForm> {
261264
labelText: AppLocalizations.of(context).weight,
262265
),
263266
controller: _amountController,
264-
keyboardType: TextInputType.number,
267+
keyboardType: textInputTypeDecimal,
265268
onChanged: (value) {
266269
setState(() {
267270
final v = double.tryParse(value);
@@ -271,11 +274,11 @@ class IngredientFormState extends State<IngredientForm> {
271274
});
272275
},
273276
onSaved: (value) {
274-
_mealItem.amount = double.parse(value!);
277+
_mealItem.amount = numberFormat.parse(value!);
275278
},
276279
validator: (value) {
277280
try {
278-
double.parse(value!);
281+
numberFormat.parse(value!);
279282
} catch (error) {
280283
return AppLocalizations.of(context).enterValidNumber;
281284
}
@@ -703,22 +706,24 @@ class GoalMacros extends StatelessWidget {
703706

704707
@override
705708
Widget build(BuildContext context) {
709+
final numberFormat = NumberFormat.decimalPattern(Localizations.localeOf(context).toString());
710+
706711
return TextFormField(
707712
initialValue: val ?? '',
708713
decoration: InputDecoration(labelText: label, suffixText: suffix),
709-
keyboardType: TextInputType.number,
714+
keyboardType: textInputTypeDecimal,
710715
onSaved: (newValue) {
711716
if (newValue == null || newValue == '') {
712717
return;
713718
}
714-
onSave(double.parse(newValue));
719+
onSave(numberFormat.parse(newValue) as double);
715720
},
716721
validator: (value) {
717722
if (value == '') {
718723
return null;
719724
}
720725
try {
721-
double.parse(value!);
726+
numberFormat.parse(value!);
722727
} catch (error) {
723728
return AppLocalizations.of(context).enterValidNumber;
724729
}

lib/widgets/routines/forms/reps.dart

Lines changed: 0 additions & 58 deletions
This file was deleted.

lib/widgets/routines/forms/slot_entry.dart

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
*/
1818

1919
import 'package:flutter/material.dart';
20+
import 'package:intl/intl.dart';
2021
import 'package:provider/provider.dart';
2122
import 'package:wger/exceptions/http_exception.dart';
2223
import 'package:wger/helpers/consts.dart';
@@ -120,6 +121,7 @@ class _SlotEntryFormState extends State<SlotEntryForm> {
120121
Widget build(BuildContext context) {
121122
final i18n = AppLocalizations.of(context);
122123
final languageCode = Localizations.localeOf(context).languageCode;
124+
final numberFormat = NumberFormat.decimalPattern(Localizations.localeOf(context).toString());
123125

124126
final provider = context.read<RoutinesProvider>();
125127

@@ -206,10 +208,10 @@ class _SlotEntryFormState extends State<SlotEntryForm> {
206208
Flexible(
207209
child: TextFormField(
208210
controller: weightController,
209-
keyboardType: TextInputType.number,
211+
keyboardType: textInputTypeDecimal,
210212
decoration: InputDecoration(labelText: i18n.weight),
211213
validator: (value) {
212-
if (value != null && value != '' && double.tryParse(value) == null) {
214+
if (value != null && value != '' && numberFormat.tryParse(value) == null) {
213215
return i18n.enterValidNumber;
214216
}
215217
return null;
@@ -220,10 +222,10 @@ class _SlotEntryFormState extends State<SlotEntryForm> {
220222
Flexible(
221223
child: TextFormField(
222224
controller: maxWeightController,
223-
keyboardType: TextInputType.number,
225+
keyboardType: textInputTypeDecimal,
224226
decoration: InputDecoration(labelText: i18n.max),
225227
validator: (value) {
226-
if (value != null && value != '' && double.tryParse(value) == null) {
228+
if (value != null && value != '' && numberFormat.tryParse(value) == null) {
227229
return i18n.enterValidNumber;
228230
}
229231
return null;
@@ -245,10 +247,10 @@ class _SlotEntryFormState extends State<SlotEntryForm> {
245247
Flexible(
246248
child: TextFormField(
247249
controller: repetitionsController,
248-
keyboardType: TextInputType.number,
250+
keyboardType: textInputTypeDecimal,
249251
decoration: InputDecoration(labelText: i18n.repetitions),
250252
validator: (value) {
251-
if (value != null && value != '' && int.tryParse(value) == null) {
253+
if (value != null && value != '' && numberFormat.tryParse(value) == null) {
252254
return i18n.enterValidNumber;
253255
}
254256
return null;
@@ -259,10 +261,10 @@ class _SlotEntryFormState extends State<SlotEntryForm> {
259261
Flexible(
260262
child: TextFormField(
261263
controller: maxRepetitionsController,
262-
keyboardType: TextInputType.number,
264+
keyboardType: textInputTypeDecimal,
263265
decoration: InputDecoration(labelText: i18n.max),
264266
validator: (value) {
265-
if (value != null && value != '' && int.tryParse(value) == null) {
267+
if (value != null && value != '' && numberFormat.tryParse(value) == null) {
266268
return i18n.enterValidNumber;
267269
}
268270
return null;
@@ -325,42 +327,42 @@ class _SlotEntryFormState extends State<SlotEntryForm> {
325327
await Future.wait([
326328
provider.handleConfig(
327329
widget.entry,
328-
setsSliderValue == 0 ? '' : setsSliderValue.round().toString(),
330+
setsSliderValue == 0 ? null : setsSliderValue.round(),
329331
ConfigType.sets,
330332
),
331333
provider.handleConfig(
332334
widget.entry,
333-
weightController.text,
335+
numberFormat.tryParse(weightController.text),
334336
ConfigType.weight,
335337
),
336338
provider.handleConfig(
337339
widget.entry,
338-
maxWeightController.text,
340+
numberFormat.tryParse(maxWeightController.text),
339341
ConfigType.maxWeight,
340342
),
341343
provider.handleConfig(
342344
widget.entry,
343-
repetitionsController.text,
345+
numberFormat.tryParse(repetitionsController.text),
344346
ConfigType.repetitions,
345347
),
346348
provider.handleConfig(
347349
widget.entry,
348-
maxRepetitionsController.text,
350+
numberFormat.tryParse(maxRepetitionsController.text),
349351
ConfigType.maxRepetitions,
350352
),
351353
provider.handleConfig(
352354
widget.entry,
353-
restController.text,
355+
numberFormat.tryParse(restController.text),
354356
ConfigType.rest,
355357
),
356358
provider.handleConfig(
357359
widget.entry,
358-
maxRestController.text,
360+
numberFormat.tryParse(maxRestController.text),
359361
ConfigType.maxRest,
360362
),
361363
provider.handleConfig(
362364
widget.entry,
363-
rirController.text,
365+
numberFormat.tryParse(rirController.text),
364366
ConfigType.rir,
365367
),
366368
]);

0 commit comments

Comments
 (0)