Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 32 additions & 4 deletions lib/core/widgets/datetime_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -67,18 +67,30 @@ class _TimeInputWidgetState extends State<TimeInputWidget> {
_value = widget.value;
}

@override
void didChangeDependencies() {
super.didChangeDependencies();
// Format depends on MaterialLocalizations, so (re)sync once dependencies
// are available rather than during build (which would notify the parent
// Form mid-build and trigger setState-during-build).
_syncText();
}

@override
void didUpdateWidget(TimeInputWidget oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.value != oldWidget.value) {
_value = widget.value;
_syncText();
}
}

/// Rewrites the read-only display text to match [_value]. Must be called
/// outside of [build] because it notifies the controller's listeners.
void _syncText() {}

@override
Widget build(BuildContext context) {
// Keyed initialValue, not a controller: a controller notifies the enclosing
// Form on assignment, which crashes if that happens during a build.
return TextFormField(
key: ValueKey(_value),
readOnly: true,
Expand All @@ -92,6 +104,7 @@ class _TimeInputWidgetState extends State<TimeInputWidget> {
icon: const Icon(Icons.clear),
onPressed: () {
setState(() => _value = null);
_syncText();
widget.onCleared!();
},
)
Expand All @@ -107,6 +120,7 @@ class _TimeInputWidgetState extends State<TimeInputWidget> {
);
if (picked != null && context.mounted) {
setState(() => _value = picked);
_syncText();
widget.onChanged(picked);
}
},
Expand Down Expand Up @@ -172,19 +186,31 @@ class _DateInputWidgetState extends State<DateInputWidget> {
_value = widget.value;
}

@override
void didChangeDependencies() {
super.didChangeDependencies();
// Format depends on the locale, so (re)sync once dependencies are
// available rather than during build (which would notify the parent Form
// mid-build and trigger setState-during-build).
_syncText();
}

@override
void didUpdateWidget(DateInputWidget oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.value != oldWidget.value) {
_value = widget.value;
_syncText();
}
}

/// Rewrites the read-only display text to match [_value]. Must be called
/// outside of [build] because it notifies the controller's listeners.
void _syncText() {}

@override
Widget build(BuildContext context) {
final dateFormat = localizedDate(context);
// Keyed initialValue, not a controller: a controller notifies the enclosing
// Form on assignment, which crashes if that happens during a build.
return TextFormField(
key: ValueKey(_value),
readOnly: true,
Expand All @@ -199,6 +225,7 @@ class _DateInputWidgetState extends State<DateInputWidget> {
icon: const Icon(Icons.clear),
onPressed: () {
setState(() => _value = null);
_syncText();
widget.onCleared!();
},
)
Expand All @@ -216,6 +243,7 @@ class _DateInputWidgetState extends State<DateInputWidget> {
);
if (picked != null && context.mounted) {
setState(() => _value = picked);
_syncText();
widget.onChanged(picked);
}
},
Expand Down
23 changes: 16 additions & 7 deletions lib/features/routines/models/log.dart
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,13 @@ class Log {
sessionId = null;

slotEntryId = setConfig.slotEntryId;
exercise = setConfig.exercise;
// The exercise may not be hydrated yet; keep the id so the log is still
// valid to submit rather than throwing and taking the gym page down.
exerciseId = setConfig.exerciseId;
final configExercise = setConfig.exerciseOrNull;
if (configExercise != null) {
exerciseObj = configExercise;
}

weight = setConfig.weight;
weightTarget = setConfig.weight;
Expand Down Expand Up @@ -228,18 +234,21 @@ class Log {
// is no weight defined so that we don't just output something like "8" but
// rather "8 repetitions". If there is weight we want to output "8 x 50kg",
// since the repetitions are implied. If other units are used, we always
// print them
if (repetitionsUnitObj != null && repetitionsUnitObj!.id != REP_UNIT_REPETITIONS_ID ||
weight == 0 ||
weight == null) {
out.add(getServerStringTranslation(repetitionsUnitObj!.name, context));
// print them. The unit object may be missing (not yet hydrated), in which
// case we simply omit the label rather than crash.
final repUnit = repetitionsUnitObj;
final isNonDefaultRepUnit = repUnit != null && repUnit.id != REP_UNIT_REPETITIONS_ID;
if ((isNonDefaultRepUnit || weight == 0 || weight == null) && repUnit != null) {
out.add(getServerStringTranslation(repUnit.name, context));
}
}

if (weight != null && weight != 0) {
out.add('×');
out.add(formatNum(weight!).toString());
out.add(weightUnitObj!.name);
if (weightUnitObj != null) {
out.add(weightUnitObj!.name);
}
}

if (rir != null) {
Expand Down
21 changes: 19 additions & 2 deletions lib/features/routines/models/set_config_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,25 @@ class SetConfigData {
@JsonKey(required: true, name: 'exercise')
late int exerciseId;

/// The hydrated exercise. Only set once `hydrateSetConfigs` has found it, so
/// it stays null for configs whose exercise is missing locally. Read it
/// through [exerciseOrNull] anywhere a missing exercise must not take the
/// whole screen down — an unguarded [exercise] read throws and Flutter
/// replaces the surrounding widget with a blank ErrorWidget.
Exercise? _exercise;

@JsonKey(includeFromJson: false, includeToJson: false)
late Exercise exercise;
Exercise get exercise {
if (_exercise == null) {
throw StateError('SetConfigData has no hydrated exercise (exercise ID $exerciseId)');
}
return _exercise!;
}

set exercise(Exercise value) => _exercise = value;

/// Like [exercise] but returns null instead of throwing
Exercise? get exerciseOrNull => _exercise;

@JsonKey(required: true, name: 'slot_entry_id')
late int slotEntryId;
Expand Down Expand Up @@ -181,7 +198,7 @@ class SetConfigData {
restTime: restTime ?? this.restTime,
maxRestTime: maxRestTime ?? this.maxRestTime,
comment: comment ?? this.comment,
exercise: exercise ?? this.exercise,
exercise: exercise ?? _exercise,
weightUnit: weightUnit ?? this.weightUnit,
repetitionsUnit: repetitionsUnit ?? this.repetitionsUnit,
);
Expand Down
52 changes: 28 additions & 24 deletions lib/features/routines/models/set_config_data.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 29 additions & 0 deletions lib/features/routines/models/weight_unit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,35 @@
*/

import 'package:flutter/foundation.dart';
import 'package:wger/core/consts.dart';

const _KG_PER_LB = 0.45359237;

/// Converts [value] between [WEIGHT_UNIT_KG] and [WEIGHT_UNIT_LB].
///
/// Anything else (the server also knows custom units such as plates) is
/// returned untouched — there is no sensible factor for those.
///
/// The result is snapped to [rounding] when given (the routine's
/// `weightRounding`), otherwise to the nearest half unit, so switching units
/// mid-session yields a number that exists on a rack rather than 176.3696.
num convertWeight(num value, {required int from, required int to, num? rounding}) {
if (from == to) {
return value;
}

final num converted;
if (from == WEIGHT_UNIT_KG && to == WEIGHT_UNIT_LB) {
converted = value / _KG_PER_LB;
} else if (from == WEIGHT_UNIT_LB && to == WEIGHT_UNIT_KG) {
converted = value * _KG_PER_LB;
} else {
return value;
}

final step = (rounding == null || rounding <= 0) ? 0.5 : rounding;
return (converted / step).round() * step;
}

@immutable
class WeightUnit {
Expand Down
69 changes: 0 additions & 69 deletions lib/features/routines/providers/gym_log_notifier.dart

This file was deleted.

Loading