Skip to content

Commit 7e7b441

Browse files
committed
Properly handle localized inputs in further forms
1 parent 814c1cb commit 7e7b441

3 files changed

Lines changed: 69 additions & 8 deletions

File tree

lib/widgets/nutrition/forms.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ class IngredientFormState extends State<IngredientForm> {
322322
keyboardType: textInputTypeDecimal,
323323
onChanged: (value) {
324324
setState(() {
325-
final v = double.tryParse(value);
325+
final v = numberFormat.tryParse(value);
326326
if (v != null) {
327327
_mealItem.amount = v;
328328
}
@@ -336,7 +336,7 @@ class IngredientFormState extends State<IngredientForm> {
336336
if (text.isEmpty) {
337337
return i18n.enterValue;
338338
}
339-
final parsed = double.tryParse(text);
339+
final parsed = numberFormat.tryParse(text);
340340
if (parsed == null) {
341341
return i18n.enterValidNumber;
342342
}

lib/widgets/routines/forms/slot_entry.dart

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,18 +67,33 @@ class _SlotEntryFormState extends State<SlotEntryForm> {
6767

6868
var _edit = false;
6969

70+
bool _controllersInitialized = false;
71+
7072
@override
7173
void initState() {
7274
super.initState();
7375
if (widget.entry.nrOfSetsConfigs.isNotEmpty) {
7476
setsSliderValue = widget.entry.nrOfSetsConfigs.first.value.toDouble();
7577
}
78+
}
79+
80+
@override
81+
void didChangeDependencies() {
82+
super.didChangeDependencies();
83+
if (_controllersInitialized) {
84+
return;
85+
}
86+
_controllersInitialized = true;
87+
88+
// Weights can be fractional, so they are rendered with the active locale's
89+
// decimal separator to round-trip through numberFormat when the form is saved.
90+
final numberFormat = NumberFormat.decimalPattern(Localizations.localeOf(context).toString());
7691

7792
if (widget.entry.weightConfigs.isNotEmpty) {
78-
weightController.text = widget.entry.weightConfigs.first.value.toString();
93+
weightController.text = numberFormat.format(widget.entry.weightConfigs.first.value);
7994
}
8095
if (widget.entry.maxWeightConfigs.isNotEmpty) {
81-
maxWeightController.text = widget.entry.maxWeightConfigs.first.value.toString();
96+
maxWeightController.text = numberFormat.format(widget.entry.maxWeightConfigs.first.value);
8297
}
8398

8499
if (widget.entry.repetitionsConfigs.isNotEmpty) {
@@ -98,7 +113,8 @@ class _SlotEntryFormState extends State<SlotEntryForm> {
98113
}
99114

100115
if (widget.entry.rirConfigs.isNotEmpty) {
101-
rirController.text = widget.entry.rirConfigs.first.value.round().toString();
116+
// RiR uses 0.5 steps, so the fractional part must be kept
117+
rirController.text = widget.entry.rirConfigs.first.value.toString();
102118
}
103119
}
104120

@@ -389,7 +405,8 @@ class _SlotEntryFormState extends State<SlotEntryForm> {
389405
),
390406
provider.handleConfig(
391407
widget.entry,
392-
numberFormat.tryParse(rirController.text),
408+
// RiR is slider-driven and held as an invariant string
409+
num.tryParse(rirController.text),
393410
ConfigType.rir,
394411
),
395412
]);

test/routine/slot_entry_form_test.dart

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import 'package:mockito/mockito.dart';
2323
import 'package:provider/provider.dart';
2424
import 'package:wger/helpers/consts.dart';
2525
import 'package:wger/l10n/generated/app_localizations.dart';
26+
import 'package:wger/models/workouts/base_config.dart';
2627
import 'package:wger/models/workouts/slot_entry.dart';
2728
import 'package:wger/providers/routines.dart';
2829
import 'package:wger/widgets/routines/forms/repetitions.dart';
@@ -47,7 +48,7 @@ void main() {
4748
when(mockRoutinesProvider.findRepetitionUnitById(any)).thenReturn(testRepetitionUnit1);
4849
});
4950

50-
Widget renderWidget({simpleMode = true, locale = 'en'}) {
51+
Widget renderWidget({simpleMode = true, locale = 'en', SlotEntry? entry}) {
5152
final key = GlobalKey<NavigatorState>();
5253

5354
return ChangeNotifierProvider<RoutinesProvider>(
@@ -57,7 +58,7 @@ void main() {
5758
localizationsDelegates: AppLocalizations.localizationsDelegates,
5859
supportedLocales: AppLocalizations.supportedLocales,
5960
navigatorKey: key,
60-
home: Scaffold(body: SlotEntryForm(slotEntry, 1, simpleMode: simpleMode)),
61+
home: Scaffold(body: SlotEntryForm(entry ?? slotEntry, 1, simpleMode: simpleMode)),
6162
),
6263
);
6364
}
@@ -156,4 +157,47 @@ void main() {
156157
expect(capturedArgs[(6 * 3) + 1], 100);
157158
expect(capturedArgs[(6 * 3) + 2], ConfigType.maxRest);
158159
});
160+
161+
testWidgets('Fractional weight survives a no-op save in a comma-decimal locale', (
162+
WidgetTester tester,
163+
) async {
164+
final entry = getTestRoutine().days[0].slots[0].entries[0];
165+
entry.weightConfigs.first.value = 2.5;
166+
167+
await tester.pumpWidget(renderWidget(simpleMode: false, locale: 'de', entry: entry));
168+
await tester.pumpAndSettle();
169+
170+
// The field shows the locale-formatted value, not the invariant "2.5"
171+
expect(find.text('2,5'), findsOneWidget);
172+
173+
// Save without touching anything
174+
await tester.tap(find.byKey(const ValueKey(SUBMIT_BUTTON_KEY_NAME)));
175+
await tester.pumpAndSettle();
176+
177+
final capturedArgs = verify(
178+
mockRoutinesProvider.handleConfig(captureAny, captureAny, captureAny),
179+
).captured;
180+
181+
expect(capturedArgs[(1 * 3) + 2], ConfigType.weight);
182+
expect(capturedArgs[(1 * 3) + 1], 2.5);
183+
});
184+
185+
testWidgets('Fractional RiR survives a no-op save', (WidgetTester tester) async {
186+
final entry = getTestRoutine().days[0].slots[0].entries[0];
187+
entry.rirConfigs = [BaseConfig.firstIteration(2.5, 1)];
188+
189+
await tester.pumpWidget(renderWidget(simpleMode: false, entry: entry));
190+
await tester.pumpAndSettle();
191+
192+
// Save without touching anything
193+
await tester.tap(find.byKey(const ValueKey(SUBMIT_BUTTON_KEY_NAME)));
194+
await tester.pumpAndSettle();
195+
196+
final capturedArgs = verify(
197+
mockRoutinesProvider.handleConfig(captureAny, captureAny, captureAny),
198+
).captured;
199+
200+
expect(capturedArgs[(7 * 3) + 2], ConfigType.rir);
201+
expect(capturedArgs[(7 * 3) + 1], 2.5);
202+
});
159203
}

0 commit comments

Comments
 (0)