feat(train): a weight vest among the equipment (ADR 0034, D7)
A vest keeps its weight and, if adjustable, how far it goes. At a place that has one, a set of bodyweight work offers it beside the weight, a tap writing its weight in. Whats-New: Add a weight vest to your equipment, and put its weight on a bodyweight set with a tap. Whats-New-De: Trage eine Gewichtsweste bei deiner Ausrüstung ein und übernimm ihr Gewicht mit einem Tipp in einen Satz mit Körpergewicht. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
3bd49920db
commit
011b144ba1
16 changed files with 332 additions and 15 deletions
|
|
@ -617,7 +617,7 @@
|
|||
"placeDeleteMessage": "Der Ort und seine Ausrüstung werden auf all deinen Geräten gelöscht.",
|
||||
"placeAddEquipment": "Ausrüstung hinzufügen",
|
||||
"placeEmptyHint": "Hier ist noch nichts. Füge die Ausrüstung hinzu, die es an diesem Ort gibt.",
|
||||
"equipmentKind": "{kind, select, barbell{Langhantel} ezBar{SZ-Stange} trapBar{Trap Bar} dumbbellHandle{Kurzhantelgriffe} plates{Hantelscheiben} fixedDumbbells{Kurzhanteln} kettlebells{Kettlebells} bench{Bank} rack{Rack} pullUpBar{Klimmzugstange} dipBars{Dipstation} cableMachine{Kabelzug} bands{Widerstandsbänder} other{Eigenes}}",
|
||||
"equipmentKind": "{kind, select, barbell{Langhantel} ezBar{SZ-Stange} trapBar{Trap Bar} dumbbellHandle{Kurzhantelgriffe} plates{Hantelscheiben} fixedDumbbells{Kurzhanteln} kettlebells{Kettlebells} weightVest{Gewichtsweste} bench{Bank} rack{Rack} pullUpBar{Klimmzugstange} dipBars{Dipstation} cableMachine{Kabelzug} bands{Widerstandsbänder} other{Eigenes}}",
|
||||
"itemName": "Was es ist",
|
||||
"itemHowMany": "Wie viele",
|
||||
"itemOwnWeight": "Eigengewicht",
|
||||
|
|
@ -626,6 +626,12 @@
|
|||
"itemDone": "Fertig",
|
||||
"itemRemove": "Entfernen",
|
||||
"itemWeightInvalid": "Ein Gewicht in Kilogramm, über 0",
|
||||
"weightRangeKg": "{from}–{to} kg",
|
||||
"itemVestWeight": "Gewicht, oder das geringste",
|
||||
"itemVestUpTo": "Bis",
|
||||
"itemVestUpToHint": "Nur, wenn sie verstellbar ist",
|
||||
"itemVestUpToInvalid": "Mehr als das geringste Gewicht",
|
||||
"strengthWearVest": "{name}, {weight}",
|
||||
"weightKg": "{weight} kg",
|
||||
"weightCount": "{count} × {weight} kg",
|
||||
"plateCalculatorTitle": "Scheibenrechner",
|
||||
|
|
|
|||
|
|
@ -1702,7 +1702,7 @@
|
|||
"placeDeleteMessage": "The place and its equipment are deleted on all your devices.",
|
||||
"placeAddEquipment": "Add equipment",
|
||||
"placeEmptyHint": "Nothing here yet. Add the equipment this place has.",
|
||||
"equipmentKind": "{kind, select, barbell{Barbell} ezBar{EZ bar} trapBar{Trap bar} dumbbellHandle{Dumbbell handles} plates{Weight plates} fixedDumbbells{Dumbbells} kettlebells{Kettlebells} bench{Bench} rack{Rack} pullUpBar{Pull-up bar} dipBars{Dip bars} cableMachine{Cable machine} bands{Resistance bands} other{Your own}}",
|
||||
"equipmentKind": "{kind, select, barbell{Barbell} ezBar{EZ bar} trapBar{Trap bar} dumbbellHandle{Dumbbell handles} plates{Weight plates} fixedDumbbells{Dumbbells} kettlebells{Kettlebells} weightVest{Weight vest} bench{Bench} rack{Rack} pullUpBar{Pull-up bar} dipBars{Dip bars} cableMachine{Cable machine} bands{Resistance bands} other{Your own}}",
|
||||
"@equipmentKind": {
|
||||
"placeholders": {
|
||||
"kind": {
|
||||
|
|
@ -1718,6 +1718,19 @@
|
|||
"itemDone": "Done",
|
||||
"itemRemove": "Remove",
|
||||
"itemWeightInvalid": "A weight in kilograms, above 0",
|
||||
"weightRangeKg": "{from}–{to} kg",
|
||||
"@weightRangeKg": {
|
||||
"placeholders": { "from": { "type": "String" }, "to": { "type": "String" } }
|
||||
},
|
||||
"itemVestWeight": "Weight, or its lightest",
|
||||
"itemVestUpTo": "Up to",
|
||||
"itemVestUpToHint": "Only if it's adjustable",
|
||||
"itemVestUpToInvalid": "More than its lightest weight",
|
||||
"strengthWearVest": "{name}, {weight}",
|
||||
"@strengthWearVest": {
|
||||
"description": "A chip at a set of bodyweight work offering the place's weight vest: its name, and its weight or range.",
|
||||
"placeholders": { "name": { "type": "String" }, "weight": { "type": "String" } }
|
||||
},
|
||||
"weightKg": "{weight} kg",
|
||||
"@weightKg": {
|
||||
"placeholders": {
|
||||
|
|
|
|||
|
|
@ -3805,7 +3805,7 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @equipmentKind.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'{kind, select, barbell{Barbell} ezBar{EZ bar} trapBar{Trap bar} dumbbellHandle{Dumbbell handles} plates{Weight plates} fixedDumbbells{Dumbbells} kettlebells{Kettlebells} bench{Bench} rack{Rack} pullUpBar{Pull-up bar} dipBars{Dip bars} cableMachine{Cable machine} bands{Resistance bands} other{Your own}}'**
|
||||
/// **'{kind, select, barbell{Barbell} ezBar{EZ bar} trapBar{Trap bar} dumbbellHandle{Dumbbell handles} plates{Weight plates} fixedDumbbells{Dumbbells} kettlebells{Kettlebells} weightVest{Weight vest} bench{Bench} rack{Rack} pullUpBar{Pull-up bar} dipBars{Dip bars} cableMachine{Cable machine} bands{Resistance bands} other{Your own}}'**
|
||||
String equipmentKind(String kind);
|
||||
|
||||
/// No description provided for @itemName.
|
||||
|
|
@ -3856,6 +3856,42 @@ abstract class AppLocalizations {
|
|||
/// **'A weight in kilograms, above 0'**
|
||||
String get itemWeightInvalid;
|
||||
|
||||
/// No description provided for @weightRangeKg.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'{from}–{to} kg'**
|
||||
String weightRangeKg(String from, String to);
|
||||
|
||||
/// No description provided for @itemVestWeight.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Weight, or its lightest'**
|
||||
String get itemVestWeight;
|
||||
|
||||
/// No description provided for @itemVestUpTo.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Up to'**
|
||||
String get itemVestUpTo;
|
||||
|
||||
/// No description provided for @itemVestUpToHint.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Only if it\'s adjustable'**
|
||||
String get itemVestUpToHint;
|
||||
|
||||
/// No description provided for @itemVestUpToInvalid.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'More than its lightest weight'**
|
||||
String get itemVestUpToInvalid;
|
||||
|
||||
/// A chip at a set of bodyweight work offering the place's weight vest: its name, and its weight or range.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'{name}, {weight}'**
|
||||
String strengthWearVest(String name, String weight);
|
||||
|
||||
/// No description provided for @weightKg.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
|
|||
|
|
@ -2298,6 +2298,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
'plates': 'Hantelscheiben',
|
||||
'fixedDumbbells': 'Kurzhanteln',
|
||||
'kettlebells': 'Kettlebells',
|
||||
'weightVest': 'Gewichtsweste',
|
||||
'bench': 'Bank',
|
||||
'rack': 'Rack',
|
||||
'pullUpBar': 'Klimmzugstange',
|
||||
|
|
@ -2334,6 +2335,28 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get itemWeightInvalid => 'Ein Gewicht in Kilogramm, über 0';
|
||||
|
||||
@override
|
||||
String weightRangeKg(String from, String to) {
|
||||
return '$from–$to kg';
|
||||
}
|
||||
|
||||
@override
|
||||
String get itemVestWeight => 'Gewicht, oder das geringste';
|
||||
|
||||
@override
|
||||
String get itemVestUpTo => 'Bis';
|
||||
|
||||
@override
|
||||
String get itemVestUpToHint => 'Nur, wenn sie verstellbar ist';
|
||||
|
||||
@override
|
||||
String get itemVestUpToInvalid => 'Mehr als das geringste Gewicht';
|
||||
|
||||
@override
|
||||
String strengthWearVest(String name, String weight) {
|
||||
return '$name, $weight';
|
||||
}
|
||||
|
||||
@override
|
||||
String weightKg(String weight) {
|
||||
return '$weight kg';
|
||||
|
|
|
|||
|
|
@ -2286,6 +2286,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
'plates': 'Weight plates',
|
||||
'fixedDumbbells': 'Dumbbells',
|
||||
'kettlebells': 'Kettlebells',
|
||||
'weightVest': 'Weight vest',
|
||||
'bench': 'Bench',
|
||||
'rack': 'Rack',
|
||||
'pullUpBar': 'Pull-up bar',
|
||||
|
|
@ -2322,6 +2323,28 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get itemWeightInvalid => 'A weight in kilograms, above 0';
|
||||
|
||||
@override
|
||||
String weightRangeKg(String from, String to) {
|
||||
return '$from–$to kg';
|
||||
}
|
||||
|
||||
@override
|
||||
String get itemVestWeight => 'Weight, or its lightest';
|
||||
|
||||
@override
|
||||
String get itemVestUpTo => 'Up to';
|
||||
|
||||
@override
|
||||
String get itemVestUpToHint => 'Only if it\'s adjustable';
|
||||
|
||||
@override
|
||||
String get itemVestUpToInvalid => 'More than its lightest weight';
|
||||
|
||||
@override
|
||||
String strengthWearVest(String name, String weight) {
|
||||
return '$name, $weight';
|
||||
}
|
||||
|
||||
@override
|
||||
String weightKg(String weight) {
|
||||
return '$weight kg';
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ ByteData encodePlace(PlaceContent content) => ByteData.sublistView(
|
|||
'name': ?item.name,
|
||||
'count': item.count,
|
||||
'grams': ?item.grams,
|
||||
'gramsUpTo': ?item.gramsUpTo,
|
||||
if (item.weights.isNotEmpty)
|
||||
'weights': [
|
||||
for (final grams
|
||||
|
|
@ -67,6 +68,10 @@ PlaceContent? decodePlace(ByteData payload) {
|
|||
final int grams when grams > 0 => grams,
|
||||
_ => null,
|
||||
},
|
||||
gramsUpTo: switch ((fields['grams'], fields['gramsUpTo'])) {
|
||||
(final int from, final int upTo) when upTo > from => upTo,
|
||||
_ => null,
|
||||
},
|
||||
weights: {
|
||||
for (final pair in fields['weights'] as List<Object?>? ?? [])
|
||||
if (pair case [final int grams, final int count]
|
||||
|
|
|
|||
|
|
@ -25,6 +25,9 @@ enum EquipmentKind() {
|
|||
/// Kettlebells, by weight and count.
|
||||
kettlebells,
|
||||
|
||||
/// A weight vest, of one weight or adjustable (ADR 0034, D7).
|
||||
weightVest,
|
||||
|
||||
/// A bench.
|
||||
bench,
|
||||
|
||||
|
|
@ -58,8 +61,12 @@ enum EquipmentKind() {
|
|||
_ => false,
|
||||
};
|
||||
|
||||
/// Whether a single weight of its own is written for it.
|
||||
bool get weighs => loadable || this == weightVest;
|
||||
|
||||
/// A new item of this kind, as a place usually has it: a barbell of
|
||||
/// 20 kg, a handle of 2 kg, an EZ bar of 10 kg, a trap bar of 25 kg.
|
||||
/// 20 kg, a handle of 2 kg, an EZ bar of 10 kg, a trap bar of 25 kg, a
|
||||
/// weight vest of 10 kg.
|
||||
EquipmentItem get usual => EquipmentItem(
|
||||
kind: this,
|
||||
grams: switch (this) {
|
||||
|
|
@ -67,6 +74,7 @@ enum EquipmentKind() {
|
|||
ezBar => 10000,
|
||||
trapBar => 25000,
|
||||
dumbbellHandle => 2000,
|
||||
weightVest => 10000,
|
||||
_ => null,
|
||||
},
|
||||
count: this == dumbbellHandle ? 2 : 1,
|
||||
|
|
@ -75,29 +83,33 @@ enum EquipmentKind() {
|
|||
|
||||
/// A piece of equipment at a place: its [kind], its [name] (the person's
|
||||
/// own's), how many there are ([count]), a loadable one's own weight
|
||||
/// ([grams]), and what is counted by weight ([weights]: grams by count).
|
||||
/// ([grams]; an adjustable vest's lightest, [gramsUpTo] its heaviest), and
|
||||
/// what is counted by weight ([weights]: grams by count).
|
||||
final class const EquipmentItem({
|
||||
required final EquipmentKind kind,
|
||||
final String? name,
|
||||
final int count = 1,
|
||||
final int? grams,
|
||||
final int? gramsUpTo,
|
||||
final Map<int, int> weights = const {},
|
||||
}) with Equatable {
|
||||
EquipmentItem copyWith({
|
||||
String? name,
|
||||
int? count,
|
||||
int? grams,
|
||||
int? gramsUpTo,
|
||||
Map<int, int>? weights,
|
||||
}) => EquipmentItem(
|
||||
kind: kind,
|
||||
name: name ?? this.name,
|
||||
count: count ?? this.count,
|
||||
grams: grams ?? this.grams,
|
||||
gramsUpTo: gramsUpTo ?? this.gramsUpTo,
|
||||
weights: weights ?? this.weights,
|
||||
);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [kind, name, count, grams, weights];
|
||||
List<Object?> get props => [kind, name, count, grams, gramsUpTo, weights];
|
||||
}
|
||||
|
||||
/// A place the person trains at (ADR 0034): its [name] and its [items].
|
||||
|
|
|
|||
|
|
@ -68,8 +68,14 @@ extension EquipmentTexts on AppLocalizations {
|
|||
weightCount(item.weights[grams]!, kilograms(grams, locale))
|
||||
else ...[
|
||||
if (item.count > 1) '${item.count} ×',
|
||||
if (item.grams case final grams? when item.kind.loadable)
|
||||
weightKg(kilograms(grams, locale)),
|
||||
if (item.grams case final grams? when item.kind.weighs)
|
||||
switch (item.gramsUpTo) {
|
||||
final upTo? => weightRangeKg(
|
||||
kilograms(grams, locale),
|
||||
kilograms(upTo, locale),
|
||||
),
|
||||
null => weightKg(kilograms(grams, locale)),
|
||||
},
|
||||
],
|
||||
].join(item.kind.byWeight ? ', ' : ' ');
|
||||
}
|
||||
|
|
@ -83,6 +89,7 @@ IconData equipmentIcon(EquipmentKind kind) => switch (kind) {
|
|||
EquipmentKind.fixedDumbbells => LucideIcons.dumbbell,
|
||||
EquipmentKind.plates => LucideIcons.disc,
|
||||
EquipmentKind.kettlebells => LucideIcons.weight,
|
||||
EquipmentKind.weightVest => LucideIcons.shirt,
|
||||
EquipmentKind.bench => LucideIcons.armchair,
|
||||
EquipmentKind.rack => LucideIcons.columns3,
|
||||
EquipmentKind.pullUpBar || EquipmentKind.dipBars => LucideIcons.grip,
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ final class const ItemEdited(final EquipmentItem item)
|
|||
final class const ItemRemoved() implements ItemSheetResult;
|
||||
|
||||
/// Changes [item] on a sheet (ADR 0034, D2): the person's own's name; how
|
||||
/// many; a loadable one's own weight; the weights of what is counted by
|
||||
/// weight, each with how many. Null where it was left; [added] for an item
|
||||
/// many; a loadable one's own weight; a weight vest's, up to how much it
|
||||
/// adjusts; the weights of what is counted by weight, each with how many. Null where it was left; [added] for an item
|
||||
/// not on the place yet, which has nothing to remove.
|
||||
Future<ItemSheetResult?> showItemSheet(
|
||||
BuildContext context,
|
||||
|
|
@ -45,6 +45,7 @@ class _ItemSheetState() extends State<_ItemSheet> {
|
|||
late var _count = widget.item.count;
|
||||
late final _name = TextEditingController(text: widget.item.name ?? '');
|
||||
late final _grams = TextEditingController();
|
||||
late final _gramsUpTo = TextEditingController();
|
||||
final _weights = <_Weight>[];
|
||||
var _started = false;
|
||||
|
||||
|
|
@ -59,6 +60,9 @@ class _ItemSheetState() extends State<_ItemSheet> {
|
|||
if (widget.item.grams case final grams?) {
|
||||
_grams.text = kilograms(grams, locale);
|
||||
}
|
||||
if (widget.item.gramsUpTo case final upTo?) {
|
||||
_gramsUpTo.text = kilograms(upTo, locale);
|
||||
}
|
||||
final weights = widget.item.weights.keys.toList()
|
||||
..sort((a, b) => b.compareTo(a));
|
||||
_weights.addAll([
|
||||
|
|
@ -71,6 +75,7 @@ class _ItemSheetState() extends State<_ItemSheet> {
|
|||
void dispose() {
|
||||
_name.dispose();
|
||||
_grams.dispose();
|
||||
_gramsUpTo.dispose();
|
||||
for (final weight in _weights) {
|
||||
weight.field.dispose();
|
||||
}
|
||||
|
|
@ -79,10 +84,22 @@ class _ItemSheetState() extends State<_ItemSheet> {
|
|||
|
||||
EquipmentKind get _kind => widget.item.kind;
|
||||
|
||||
bool get _vest => _kind == EquipmentKind.weightVest;
|
||||
|
||||
/// Whether the vest's upper weight is empty (one weight) or above its
|
||||
/// weight.
|
||||
bool get _upToValid {
|
||||
if (_gramsUpTo.text.trim().isEmpty) {
|
||||
return true;
|
||||
}
|
||||
final (from, upTo) = (gramsOf(_grams.text), gramsOf(_gramsUpTo.text));
|
||||
return upTo != null && (from == null || upTo > from);
|
||||
}
|
||||
|
||||
/// The item as written; null while something isn't valid.
|
||||
EquipmentItem? get _item {
|
||||
final grams = _kind.loadable ? gramsOf(_grams.text) : null;
|
||||
if (_kind.loadable && grams == null) {
|
||||
final grams = _kind.weighs ? gramsOf(_grams.text) : null;
|
||||
if (_kind.weighs && grams == null || _vest && !_upToValid) {
|
||||
return null;
|
||||
}
|
||||
final weights = <int, int>{};
|
||||
|
|
@ -102,6 +119,7 @@ class _ItemSheetState() extends State<_ItemSheet> {
|
|||
name: _kind == EquipmentKind.own ? name : null,
|
||||
count: _kind.byWeight ? 1 : _count,
|
||||
grams: grams,
|
||||
gramsUpTo: _vest ? gramsOf(_gramsUpTo.text) : null,
|
||||
weights: weights,
|
||||
);
|
||||
}
|
||||
|
|
@ -133,7 +151,7 @@ class _ItemSheetState() extends State<_ItemSheet> {
|
|||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
if (!_kind.byWeight)
|
||||
if (!_kind.byWeight && !_vest)
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: Text(l10n.itemHowMany)),
|
||||
|
|
@ -150,7 +168,7 @@ class _ItemSheetState() extends State<_ItemSheet> {
|
|||
),
|
||||
],
|
||||
),
|
||||
if (_kind.loadable) ...[
|
||||
if (_kind.weighs) ...[
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: _grams,
|
||||
|
|
@ -159,7 +177,7 @@ class _ItemSheetState() extends State<_ItemSheet> {
|
|||
),
|
||||
inputFormatters: [kgFormatter],
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.itemOwnWeight,
|
||||
labelText: _vest ? l10n.itemVestWeight : l10n.itemOwnWeight,
|
||||
suffixText: 'kg',
|
||||
errorText: gramsOf(_grams.text) == null
|
||||
? l10n.itemWeightInvalid
|
||||
|
|
@ -168,6 +186,23 @@ class _ItemSheetState() extends State<_ItemSheet> {
|
|||
onChanged: (_) => setState(() {}),
|
||||
),
|
||||
],
|
||||
if (_vest) ...[
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: _gramsUpTo,
|
||||
keyboardType: const TextInputType.numberWithOptions(
|
||||
decimal: true,
|
||||
),
|
||||
inputFormatters: [kgFormatter],
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.itemVestUpTo,
|
||||
helperText: l10n.itemVestUpToHint,
|
||||
suffixText: 'kg',
|
||||
errorText: _upToValid ? null : l10n.itemVestUpToInvalid,
|
||||
),
|
||||
onChanged: (_) => setState(() {}),
|
||||
),
|
||||
],
|
||||
if (_kind.byWeight) ...[
|
||||
Text(
|
||||
l10n.itemWeights,
|
||||
|
|
|
|||
|
|
@ -43,6 +43,12 @@ final class const TypedWeight(
|
|||
List<Object?> get props => [grams, madeUp];
|
||||
}
|
||||
|
||||
/// The weight vest at [place], which what the body lifts can add
|
||||
/// (ADR 0034, D7); null without one.
|
||||
EquipmentItem? vestAt(PlaceContent? place) => place?.items
|
||||
.where((item) => item.kind == EquipmentKind.weightVest)
|
||||
.firstOrNull;
|
||||
|
||||
/// The bar or handles [load] takes plates on at [place], with the
|
||||
/// implements at once; null where it has none.
|
||||
({int bar, int implements})? _barFor(ExerciseLoad load, PlaceContent place) {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import 'package:halkyon_train_flutter/src/feature/strength/model/strength_plan.d
|
|||
import 'package:halkyon_train_flutter/src/feature/strength/model/strength_session.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/strength/model/weight_up.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/strength/widget/strength_texts.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/strength/widget/vest_chip.dart';
|
||||
import 'package:halkyon_ui/halkyon_ui.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
|
|
@ -447,6 +448,17 @@ class _NextSetState() extends State<_NextSet> {
|
|||
],
|
||||
],
|
||||
),
|
||||
if (vestAt(place) case final vest?
|
||||
when _exercise.load == ExerciseLoad.body) ...[
|
||||
const SizedBox(height: 8),
|
||||
VestChip(
|
||||
vest: vest,
|
||||
onWear: (grams) => _kg.text = kilograms(
|
||||
grams,
|
||||
Localizations.localeOf(context).toString(),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
import 'package:halkyon_train_flutter/l10n/app_localizations.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/equipment/model/equipment.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/equipment/widget/equipment_texts.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
|
||||
/// Offers [vest] at a set of the body (ADR 0034, D7): tapped, [onWear]
|
||||
/// gets its weight, an adjustable one's lightest, to change if it's set
|
||||
/// heavier.
|
||||
class const VestChip({
|
||||
required final EquipmentItem vest,
|
||||
required final void Function(int grams) onWear,
|
||||
super.key,
|
||||
}) extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final locale = Localizations.localeOf(context).toString();
|
||||
return Align(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: ActionChip(
|
||||
avatar: const Icon(LucideIcons.shirt, size: 18),
|
||||
label: Text(
|
||||
l10n.strengthWearVest(
|
||||
l10n.itemTitle(vest),
|
||||
l10n.itemSummary(vest, locale),
|
||||
),
|
||||
),
|
||||
onPressed: switch (vest.grams) {
|
||||
final grams? => () => onWear(grams),
|
||||
null => null,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -97,4 +97,33 @@ void main() {
|
|||
expect(decodePlace(_bytes([1, 2])), isNull);
|
||||
expect(decodePlace(ByteData(0)), isNull);
|
||||
});
|
||||
|
||||
test('a weight vest keeps its weight, and how far an adjustable one goes '
|
||||
'(ADR 0034, D7)', () {
|
||||
const place = PlaceContent(
|
||||
name: 'Home',
|
||||
items: [
|
||||
EquipmentItem(
|
||||
kind: EquipmentKind.weightVest,
|
||||
grams: 6000,
|
||||
gramsUpTo: 10000,
|
||||
),
|
||||
EquipmentItem(kind: EquipmentKind.weightVest, grams: 8000),
|
||||
],
|
||||
);
|
||||
expect(decodePlace(encodePlace(place)), place);
|
||||
});
|
||||
|
||||
test("an upper weight that isn't above the vest's weight is left out", () {
|
||||
final place = decodePlace(
|
||||
_bytes({
|
||||
'schemaVersion': 1,
|
||||
'name': 'Home',
|
||||
'items': [
|
||||
{'kind': 'weightVest', 'count': 1, 'grams': 8000, 'gramsUpTo': 8000},
|
||||
],
|
||||
}),
|
||||
);
|
||||
expect(place!.items.single.gramsUpTo, isNull);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:halkyon_train_flutter/l10n/app_localizations.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/equipment/model/equipment.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/strength/widget/vest_chip.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
|
||||
/// The vest offered at a set of the body (ADR 0034, D7).
|
||||
void main() {
|
||||
Future<void> pump(
|
||||
WidgetTester tester,
|
||||
EquipmentItem vest,
|
||||
void Function(int) onWear,
|
||||
) => tester.pumpWidget(
|
||||
MaterialApp(
|
||||
locale: const Locale('en'),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: Scaffold(
|
||||
body: VestChip(vest: vest, onWear: onWear),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
testWidgets('an adjustable vest shows its range and gives its lightest', (
|
||||
tester,
|
||||
) async {
|
||||
int? worn;
|
||||
await pump(
|
||||
tester,
|
||||
const EquipmentItem(
|
||||
kind: EquipmentKind.weightVest,
|
||||
grams: 6000,
|
||||
gramsUpTo: 10000,
|
||||
),
|
||||
(grams) => worn = grams,
|
||||
);
|
||||
|
||||
expect(find.text('Weight vest, 6–10 kg'), findsOneWidget);
|
||||
await tester.tap(find.byType(ActionChip));
|
||||
expect(worn, 6000);
|
||||
});
|
||||
|
||||
testWidgets('a vest of one weight shows that weight', (tester) async {
|
||||
await pump(
|
||||
tester,
|
||||
const EquipmentItem(kind: EquipmentKind.weightVest, grams: 8000),
|
||||
(_) {},
|
||||
);
|
||||
|
||||
expect(find.text('Weight vest, 8 kg'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
|
|
@ -74,4 +74,18 @@ void main() {
|
|||
expect(series.last.change, const PlateChange(off: [], on: [10000]));
|
||||
expect(plateSeries(ExerciseLoad.dumbbellPair, [7000], home), isNull);
|
||||
});
|
||||
|
||||
test(
|
||||
'a place offers its weight vest to what the body lifts (ADR 0034, D7)',
|
||||
() {
|
||||
const vest = EquipmentItem(
|
||||
kind: EquipmentKind.weightVest,
|
||||
grams: 6000,
|
||||
gramsUpTo: 10000,
|
||||
);
|
||||
expect(vestAt(const PlaceContent(name: 'Home', items: [vest])), vest);
|
||||
expect(vestAt(home), isNull);
|
||||
expect(vestAt(null), isNull);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,6 +76,14 @@ No model holds an exercise, a set, a repetition or a weight.
|
|||
place: a target, the bar or handle, the plates per side, and for several targets the
|
||||
moves between them.
|
||||
|
||||
7. **A weight vest is a kind of its own** (the developer's, 5 Nov 2026; the details the
|
||||
agent's): `weightVest`, its weight and, for an adjustable one, how far it goes
|
||||
(`gramsUpTo`, added to the payload without a new schema: an older app leaves the
|
||||
kind out). What the body lifts takes weight as typed, so a vest at the place chosen
|
||||
shows as a chip by a set's weight (`VestChip`), tapped writing its weight, an
|
||||
adjustable one's lightest; a plan doesn't plan with it. A hand grip trainer stays
|
||||
the person's own, by name, as it would serve one exercise, itself their own.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Strength plans (exercises, sets, repetitions, weights, rest) are the next step: a plan
|
||||
|
|
|
|||
Loading…
Reference in a new issue