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.",
|
"placeDeleteMessage": "Der Ort und seine Ausrüstung werden auf all deinen Geräten gelöscht.",
|
||||||
"placeAddEquipment": "Ausrüstung hinzufügen",
|
"placeAddEquipment": "Ausrüstung hinzufügen",
|
||||||
"placeEmptyHint": "Hier ist noch nichts. Füge die Ausrüstung hinzu, die es an diesem Ort gibt.",
|
"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",
|
"itemName": "Was es ist",
|
||||||
"itemHowMany": "Wie viele",
|
"itemHowMany": "Wie viele",
|
||||||
"itemOwnWeight": "Eigengewicht",
|
"itemOwnWeight": "Eigengewicht",
|
||||||
|
|
@ -626,6 +626,12 @@
|
||||||
"itemDone": "Fertig",
|
"itemDone": "Fertig",
|
||||||
"itemRemove": "Entfernen",
|
"itemRemove": "Entfernen",
|
||||||
"itemWeightInvalid": "Ein Gewicht in Kilogramm, über 0",
|
"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",
|
"weightKg": "{weight} kg",
|
||||||
"weightCount": "{count} × {weight} kg",
|
"weightCount": "{count} × {weight} kg",
|
||||||
"plateCalculatorTitle": "Scheibenrechner",
|
"plateCalculatorTitle": "Scheibenrechner",
|
||||||
|
|
|
||||||
|
|
@ -1702,7 +1702,7 @@
|
||||||
"placeDeleteMessage": "The place and its equipment are deleted on all your devices.",
|
"placeDeleteMessage": "The place and its equipment are deleted on all your devices.",
|
||||||
"placeAddEquipment": "Add equipment",
|
"placeAddEquipment": "Add equipment",
|
||||||
"placeEmptyHint": "Nothing here yet. Add the equipment this place has.",
|
"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": {
|
"@equipmentKind": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"kind": {
|
"kind": {
|
||||||
|
|
@ -1718,6 +1718,19 @@
|
||||||
"itemDone": "Done",
|
"itemDone": "Done",
|
||||||
"itemRemove": "Remove",
|
"itemRemove": "Remove",
|
||||||
"itemWeightInvalid": "A weight in kilograms, above 0",
|
"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": "{weight} kg",
|
||||||
"@weightKg": {
|
"@weightKg": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
|
|
|
||||||
|
|
@ -3805,7 +3805,7 @@ abstract class AppLocalizations {
|
||||||
/// No description provided for @equipmentKind.
|
/// No description provided for @equipmentKind.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// 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);
|
String equipmentKind(String kind);
|
||||||
|
|
||||||
/// No description provided for @itemName.
|
/// No description provided for @itemName.
|
||||||
|
|
@ -3856,6 +3856,42 @@ abstract class AppLocalizations {
|
||||||
/// **'A weight in kilograms, above 0'**
|
/// **'A weight in kilograms, above 0'**
|
||||||
String get itemWeightInvalid;
|
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.
|
/// No description provided for @weightKg.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|
|
||||||
|
|
@ -2298,6 +2298,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
'plates': 'Hantelscheiben',
|
'plates': 'Hantelscheiben',
|
||||||
'fixedDumbbells': 'Kurzhanteln',
|
'fixedDumbbells': 'Kurzhanteln',
|
||||||
'kettlebells': 'Kettlebells',
|
'kettlebells': 'Kettlebells',
|
||||||
|
'weightVest': 'Gewichtsweste',
|
||||||
'bench': 'Bank',
|
'bench': 'Bank',
|
||||||
'rack': 'Rack',
|
'rack': 'Rack',
|
||||||
'pullUpBar': 'Klimmzugstange',
|
'pullUpBar': 'Klimmzugstange',
|
||||||
|
|
@ -2334,6 +2335,28 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get itemWeightInvalid => 'Ein Gewicht in Kilogramm, über 0';
|
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
|
@override
|
||||||
String weightKg(String weight) {
|
String weightKg(String weight) {
|
||||||
return '$weight kg';
|
return '$weight kg';
|
||||||
|
|
|
||||||
|
|
@ -2286,6 +2286,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
'plates': 'Weight plates',
|
'plates': 'Weight plates',
|
||||||
'fixedDumbbells': 'Dumbbells',
|
'fixedDumbbells': 'Dumbbells',
|
||||||
'kettlebells': 'Kettlebells',
|
'kettlebells': 'Kettlebells',
|
||||||
|
'weightVest': 'Weight vest',
|
||||||
'bench': 'Bench',
|
'bench': 'Bench',
|
||||||
'rack': 'Rack',
|
'rack': 'Rack',
|
||||||
'pullUpBar': 'Pull-up bar',
|
'pullUpBar': 'Pull-up bar',
|
||||||
|
|
@ -2322,6 +2323,28 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get itemWeightInvalid => 'A weight in kilograms, above 0';
|
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
|
@override
|
||||||
String weightKg(String weight) {
|
String weightKg(String weight) {
|
||||||
return '$weight kg';
|
return '$weight kg';
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ ByteData encodePlace(PlaceContent content) => ByteData.sublistView(
|
||||||
'name': ?item.name,
|
'name': ?item.name,
|
||||||
'count': item.count,
|
'count': item.count,
|
||||||
'grams': ?item.grams,
|
'grams': ?item.grams,
|
||||||
|
'gramsUpTo': ?item.gramsUpTo,
|
||||||
if (item.weights.isNotEmpty)
|
if (item.weights.isNotEmpty)
|
||||||
'weights': [
|
'weights': [
|
||||||
for (final grams
|
for (final grams
|
||||||
|
|
@ -67,6 +68,10 @@ PlaceContent? decodePlace(ByteData payload) {
|
||||||
final int grams when grams > 0 => grams,
|
final int grams when grams > 0 => grams,
|
||||||
_ => null,
|
_ => null,
|
||||||
},
|
},
|
||||||
|
gramsUpTo: switch ((fields['grams'], fields['gramsUpTo'])) {
|
||||||
|
(final int from, final int upTo) when upTo > from => upTo,
|
||||||
|
_ => null,
|
||||||
|
},
|
||||||
weights: {
|
weights: {
|
||||||
for (final pair in fields['weights'] as List<Object?>? ?? [])
|
for (final pair in fields['weights'] as List<Object?>? ?? [])
|
||||||
if (pair case [final int grams, final int count]
|
if (pair case [final int grams, final int count]
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,9 @@ enum EquipmentKind() {
|
||||||
/// Kettlebells, by weight and count.
|
/// Kettlebells, by weight and count.
|
||||||
kettlebells,
|
kettlebells,
|
||||||
|
|
||||||
|
/// A weight vest, of one weight or adjustable (ADR 0034, D7).
|
||||||
|
weightVest,
|
||||||
|
|
||||||
/// A bench.
|
/// A bench.
|
||||||
bench,
|
bench,
|
||||||
|
|
||||||
|
|
@ -58,8 +61,12 @@ enum EquipmentKind() {
|
||||||
_ => false,
|
_ => 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
|
/// 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(
|
EquipmentItem get usual => EquipmentItem(
|
||||||
kind: this,
|
kind: this,
|
||||||
grams: switch (this) {
|
grams: switch (this) {
|
||||||
|
|
@ -67,6 +74,7 @@ enum EquipmentKind() {
|
||||||
ezBar => 10000,
|
ezBar => 10000,
|
||||||
trapBar => 25000,
|
trapBar => 25000,
|
||||||
dumbbellHandle => 2000,
|
dumbbellHandle => 2000,
|
||||||
|
weightVest => 10000,
|
||||||
_ => null,
|
_ => null,
|
||||||
},
|
},
|
||||||
count: this == dumbbellHandle ? 2 : 1,
|
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
|
/// 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
|
/// 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({
|
final class const EquipmentItem({
|
||||||
required final EquipmentKind kind,
|
required final EquipmentKind kind,
|
||||||
final String? name,
|
final String? name,
|
||||||
final int count = 1,
|
final int count = 1,
|
||||||
final int? grams,
|
final int? grams,
|
||||||
|
final int? gramsUpTo,
|
||||||
final Map<int, int> weights = const {},
|
final Map<int, int> weights = const {},
|
||||||
}) with Equatable {
|
}) with Equatable {
|
||||||
EquipmentItem copyWith({
|
EquipmentItem copyWith({
|
||||||
String? name,
|
String? name,
|
||||||
int? count,
|
int? count,
|
||||||
int? grams,
|
int? grams,
|
||||||
|
int? gramsUpTo,
|
||||||
Map<int, int>? weights,
|
Map<int, int>? weights,
|
||||||
}) => EquipmentItem(
|
}) => EquipmentItem(
|
||||||
kind: kind,
|
kind: kind,
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
count: count ?? this.count,
|
count: count ?? this.count,
|
||||||
grams: grams ?? this.grams,
|
grams: grams ?? this.grams,
|
||||||
|
gramsUpTo: gramsUpTo ?? this.gramsUpTo,
|
||||||
weights: weights ?? this.weights,
|
weights: weights ?? this.weights,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@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].
|
/// 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))
|
weightCount(item.weights[grams]!, kilograms(grams, locale))
|
||||||
else ...[
|
else ...[
|
||||||
if (item.count > 1) '${item.count} ×',
|
if (item.count > 1) '${item.count} ×',
|
||||||
if (item.grams case final grams? when item.kind.loadable)
|
if (item.grams case final grams? when item.kind.weighs)
|
||||||
weightKg(kilograms(grams, locale)),
|
switch (item.gramsUpTo) {
|
||||||
|
final upTo? => weightRangeKg(
|
||||||
|
kilograms(grams, locale),
|
||||||
|
kilograms(upTo, locale),
|
||||||
|
),
|
||||||
|
null => weightKg(kilograms(grams, locale)),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
].join(item.kind.byWeight ? ', ' : ' ');
|
].join(item.kind.byWeight ? ', ' : ' ');
|
||||||
}
|
}
|
||||||
|
|
@ -83,6 +89,7 @@ IconData equipmentIcon(EquipmentKind kind) => switch (kind) {
|
||||||
EquipmentKind.fixedDumbbells => LucideIcons.dumbbell,
|
EquipmentKind.fixedDumbbells => LucideIcons.dumbbell,
|
||||||
EquipmentKind.plates => LucideIcons.disc,
|
EquipmentKind.plates => LucideIcons.disc,
|
||||||
EquipmentKind.kettlebells => LucideIcons.weight,
|
EquipmentKind.kettlebells => LucideIcons.weight,
|
||||||
|
EquipmentKind.weightVest => LucideIcons.shirt,
|
||||||
EquipmentKind.bench => LucideIcons.armchair,
|
EquipmentKind.bench => LucideIcons.armchair,
|
||||||
EquipmentKind.rack => LucideIcons.columns3,
|
EquipmentKind.rack => LucideIcons.columns3,
|
||||||
EquipmentKind.pullUpBar || EquipmentKind.dipBars => LucideIcons.grip,
|
EquipmentKind.pullUpBar || EquipmentKind.dipBars => LucideIcons.grip,
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,8 @@ final class const ItemEdited(final EquipmentItem item)
|
||||||
final class const ItemRemoved() implements ItemSheetResult;
|
final class const ItemRemoved() implements ItemSheetResult;
|
||||||
|
|
||||||
/// Changes [item] on a sheet (ADR 0034, D2): the person's own's name; how
|
/// 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
|
/// many; a loadable one's own weight; a weight vest's, up to how much it
|
||||||
/// weight, each with how many. Null where it was left; [added] for an item
|
/// 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.
|
/// not on the place yet, which has nothing to remove.
|
||||||
Future<ItemSheetResult?> showItemSheet(
|
Future<ItemSheetResult?> showItemSheet(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
|
|
@ -45,6 +45,7 @@ class _ItemSheetState() extends State<_ItemSheet> {
|
||||||
late var _count = widget.item.count;
|
late var _count = widget.item.count;
|
||||||
late final _name = TextEditingController(text: widget.item.name ?? '');
|
late final _name = TextEditingController(text: widget.item.name ?? '');
|
||||||
late final _grams = TextEditingController();
|
late final _grams = TextEditingController();
|
||||||
|
late final _gramsUpTo = TextEditingController();
|
||||||
final _weights = <_Weight>[];
|
final _weights = <_Weight>[];
|
||||||
var _started = false;
|
var _started = false;
|
||||||
|
|
||||||
|
|
@ -59,6 +60,9 @@ class _ItemSheetState() extends State<_ItemSheet> {
|
||||||
if (widget.item.grams case final grams?) {
|
if (widget.item.grams case final grams?) {
|
||||||
_grams.text = kilograms(grams, locale);
|
_grams.text = kilograms(grams, locale);
|
||||||
}
|
}
|
||||||
|
if (widget.item.gramsUpTo case final upTo?) {
|
||||||
|
_gramsUpTo.text = kilograms(upTo, locale);
|
||||||
|
}
|
||||||
final weights = widget.item.weights.keys.toList()
|
final weights = widget.item.weights.keys.toList()
|
||||||
..sort((a, b) => b.compareTo(a));
|
..sort((a, b) => b.compareTo(a));
|
||||||
_weights.addAll([
|
_weights.addAll([
|
||||||
|
|
@ -71,6 +75,7 @@ class _ItemSheetState() extends State<_ItemSheet> {
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_name.dispose();
|
_name.dispose();
|
||||||
_grams.dispose();
|
_grams.dispose();
|
||||||
|
_gramsUpTo.dispose();
|
||||||
for (final weight in _weights) {
|
for (final weight in _weights) {
|
||||||
weight.field.dispose();
|
weight.field.dispose();
|
||||||
}
|
}
|
||||||
|
|
@ -79,10 +84,22 @@ class _ItemSheetState() extends State<_ItemSheet> {
|
||||||
|
|
||||||
EquipmentKind get _kind => widget.item.kind;
|
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.
|
/// The item as written; null while something isn't valid.
|
||||||
EquipmentItem? get _item {
|
EquipmentItem? get _item {
|
||||||
final grams = _kind.loadable ? gramsOf(_grams.text) : null;
|
final grams = _kind.weighs ? gramsOf(_grams.text) : null;
|
||||||
if (_kind.loadable && grams == null) {
|
if (_kind.weighs && grams == null || _vest && !_upToValid) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final weights = <int, int>{};
|
final weights = <int, int>{};
|
||||||
|
|
@ -102,6 +119,7 @@ class _ItemSheetState() extends State<_ItemSheet> {
|
||||||
name: _kind == EquipmentKind.own ? name : null,
|
name: _kind == EquipmentKind.own ? name : null,
|
||||||
count: _kind.byWeight ? 1 : _count,
|
count: _kind.byWeight ? 1 : _count,
|
||||||
grams: grams,
|
grams: grams,
|
||||||
|
gramsUpTo: _vest ? gramsOf(_gramsUpTo.text) : null,
|
||||||
weights: weights,
|
weights: weights,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -133,7 +151,7 @@ class _ItemSheetState() extends State<_ItemSheet> {
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
],
|
],
|
||||||
if (!_kind.byWeight)
|
if (!_kind.byWeight && !_vest)
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: Text(l10n.itemHowMany)),
|
Expanded(child: Text(l10n.itemHowMany)),
|
||||||
|
|
@ -150,7 +168,7 @@ class _ItemSheetState() extends State<_ItemSheet> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (_kind.loadable) ...[
|
if (_kind.weighs) ...[
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _grams,
|
controller: _grams,
|
||||||
|
|
@ -159,7 +177,7 @@ class _ItemSheetState() extends State<_ItemSheet> {
|
||||||
),
|
),
|
||||||
inputFormatters: [kgFormatter],
|
inputFormatters: [kgFormatter],
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: l10n.itemOwnWeight,
|
labelText: _vest ? l10n.itemVestWeight : l10n.itemOwnWeight,
|
||||||
suffixText: 'kg',
|
suffixText: 'kg',
|
||||||
errorText: gramsOf(_grams.text) == null
|
errorText: gramsOf(_grams.text) == null
|
||||||
? l10n.itemWeightInvalid
|
? l10n.itemWeightInvalid
|
||||||
|
|
@ -168,6 +186,23 @@ class _ItemSheetState() extends State<_ItemSheet> {
|
||||||
onChanged: (_) => setState(() {}),
|
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) ...[
|
if (_kind.byWeight) ...[
|
||||||
Text(
|
Text(
|
||||||
l10n.itemWeights,
|
l10n.itemWeights,
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,12 @@ final class const TypedWeight(
|
||||||
List<Object?> get props => [grams, madeUp];
|
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
|
/// The bar or handles [load] takes plates on at [place], with the
|
||||||
/// implements at once; null where it has none.
|
/// implements at once; null where it has none.
|
||||||
({int bar, int implements})? _barFor(ExerciseLoad load, PlaceContent place) {
|
({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/strength_session.dart';
|
||||||
import 'package:halkyon_train_flutter/src/feature/strength/model/weight_up.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/strength_texts.dart';
|
||||||
|
import 'package:halkyon_train_flutter/src/feature/strength/widget/vest_chip.dart';
|
||||||
import 'package:halkyon_ui/halkyon_ui.dart';
|
import 'package:halkyon_ui/halkyon_ui.dart';
|
||||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||||
import 'package:material_ui/material_ui.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),
|
const SizedBox(height: 16),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
onPressed: () {
|
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(_bytes([1, 2])), isNull);
|
||||||
expect(decodePlace(ByteData(0)), 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(series.last.change, const PlateChange(off: [], on: [10000]));
|
||||||
expect(plateSeries(ExerciseLoad.dumbbellPair, [7000], home), isNull);
|
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
|
place: a target, the bar or handle, the plates per side, and for several targets the
|
||||||
moves between them.
|
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
|
## Consequences
|
||||||
|
|
||||||
- Strength plans (exercises, sets, repetitions, weights, rest) are the next step: a plan
|
- Strength plans (exercises, sets, repetitions, weights, rest) are the next step: a plan
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue