fix(train): a day's page of its own, in cards, its night and Recharge drawn in place

The night's chart threw mid-paint where Polar's last phase starts at the
night's end or after it: its right edge was clamped between left + 1 and
the row's width, the lower bound then above the upper (an ArgumentError).
A throw mid-paint leaves the canvas moved, so the rows below and the charts
after them were drawn out of place, the skin temperature's among them. Each
phase now stays within its row, a pixel wide at least.

Nightly Recharge showed Polar's marks for a figure it has none of (-1 for
the recovery and the intervals, -100 for the nervous system's status, 0 for
a baseline not yet learnt) as the person's own. Recharge.known leaves them
out, on the page and in the days' list; the figures are rows, each with the
person's usual below it, and Polar's explanation of them (the status from
-10 to +10, what each measures) is behind halkyon_ui's question mark.

The page is a screen of its own over the tabs, without the bar and with the
way back at its top left, its parts in cards (the developer's review of
3 Nov 2026; D60 had it inside Profile).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-11-03 21:36:34 +01:00
commit ddca423b8a
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
5 changed files with 1768 additions and 360 deletions

View file

@ -1,19 +1,13 @@
{
"@@locale": "de",
"appTitle": "Halkyon Train",
"launchProduct": "Train",
"instanceFirstStartSubtitle": "Wähle die Instanz, auf der deine Trainings liegen sollen. Jede betreibt jemand; weitere kannst du später hinzufügen.",
"homeEmptyTitle": "Noch keine Trainings",
"homeEmptyHint": "Trainings, die du auf deinem Telefon aufzeichnest, erscheinen hier.",
"accountTitle": "Konto",
"accountDeleteMessage": "Dein Konto wird auf dem Server und auf all deinen Geräten gelöscht. Dies kann nicht rückgängig gemacht werden.",
"aboutTitle": "Über Halkyon Train",
"homeRecordHint": "Zeichne eins im Tab Aufzeichnen auf.",
"recordBack": "Zurück zur Aufzeichnung",
"activityRunning": "Laufen",
@ -60,7 +54,6 @@
"recordingCutOffMessage": "Eine Aufzeichnung von {start} wurde abgebrochen. Bis {end} speichern oder verwerfen?",
"recordingNotificationChannel": "Aufzeichnung",
"recordingNotificationPaused": "{activity} · pausiert",
"workoutName": "{when, select, morning_strength{Krafttraining am Morgen} afternoon_strength{Krafttraining am Nachmittag} evening_strength{Krafttraining am Abend} night_strength{Krafttraining in der Nacht} morning_running{Morgenlauf} morning_walking{Morgenspaziergang} morning_hiking{Morgenwanderung} morning_cycling{Morgenradfahrt} morning_indoorRowing{Rudern am Morgen} afternoon_running{Nachmittagslauf} afternoon_walking{Nachmittagsspaziergang} afternoon_hiking{Nachmittagswanderung} afternoon_cycling{Nachmittagsradfahrt} afternoon_indoorRowing{Rudern am Nachmittag} evening_running{Abendlauf} evening_walking{Abendspaziergang} evening_hiking{Abendwanderung} evening_cycling{Abendradfahrt} evening_indoorRowing{Rudern am Abend} night_running{Nachtlauf} night_walking{Nachtspaziergang} night_hiking{Nachtwanderung} night_cycling{Nachtradfahrt} night_indoorRowing{Rudern in der Nacht} other{Training}}",
"workoutLocked": "Gesperrtes Training",
"workoutLockedHint": "Dieses Gerät hat den Schlüssel nicht, mit dem es versiegelt ist.",
@ -95,7 +88,6 @@
"workoutMapSourceAbout": "Was {name} speichert",
"workoutMapUnavailable": "Die Karte kann gerade nicht geladen werden.",
"workoutNotFound": "Dieses Training gibt es nicht mehr.",
"recordHeartRate": "Herzfrequenz",
"recordNoStrap": "Kein Brustgurt",
"recordHeartLoop": "Vom Polar Loop",
@ -115,7 +107,6 @@
"workoutMaxHeartRate": "höchste Herzfrequenz",
"workoutBpmValue": "{bpm} bpm",
"workoutHeartRateChart": "Herzfrequenz",
"recordRower": "Rudergerät",
"recordNoRower": "Wähle dein Rudergerät",
"recordRowerRefused": "Train braucht Geräte in der Nähe, um von deinem Rudergerät aufzuzeichnen. Du kannst das in den Systemeinstellungen erlauben.",
@ -144,7 +135,6 @@
"workoutPowerChart": "Leistung",
"recordAutoPauseRowingHint": "Wenn du aufhörst zu rudern, weiter mit dem nächsten Schlag",
"recordingMetres": "Meter",
"healthTitle": "Health Connect",
"healthHint": "Trainings mit anderen Apps auf diesem Telefon teilen",
"healthUnavailable": "Health Connect ist auf diesem Telefon nicht da. Ab Android 14 ist es eingebaut, davor eine eigene App.",
@ -159,7 +149,6 @@
"healthRationaleWrite": "Train schreibt deine Trainings erst in Health Connect, wenn du es einschaltest: jedes mit Strecke, Herzfrequenz, Tempo, Leistung, Distanz und Anstieg, damit andere Apps auf diesem Telefon sie nutzen können. Bearbeitest oder löschst du ein Training in Train, geschieht das dort auch.",
"healthRationaleRead": "Train liest die Trainings anderer Apps erst, wenn du es einschaltest, und nur von den Apps, die du wählst: Ihre Herzfrequenz, Distanz und Leistung kommen als Trainings in Train, mit deinem Schlüssel versiegelt, bevor sie mit deiner Instanz abgeglichen werden, die sie nicht lesen kann.",
"healthRationaleNothingElse": "Was Train dort liest, wird dir gezeigt und niemandem sonst, und für nichts anderes genutzt. Schaltest du einen Schalter aus oder nimmst eine Erlaubnis in Health Connect zurück, hört es auf.",
"healthRead": "Von anderen Apps lesen",
"healthReadHint": "Ihre Trainings kommen in Train, von den Apps, die du unten wählst",
"healthApps": "Apps",
@ -168,7 +157,6 @@
"workoutFrom": "Von {app}, über Health Connect",
"workoutBringTrack": "Strecke holen",
"workoutNoTrack": "Health Connect hat keine Strecke dazu gegeben.",
"profileTitle": "Dein Körper",
"profileHint": "Woraus dein Armband und deine Herzfrequenzzonen rechnen",
"profileIntro": "Jede Angabe ist freiwillig und mit deinem Schlüssel versiegelt, wie deine Trainings; deine Instanz kann sie nicht lesen.",
@ -203,7 +191,6 @@
"profileSave": "Speichern",
"profileSaved": "Gespeichert",
"profileInvalid": "Prüfe die markierten Angaben.",
"profileNeeded": "Braucht dein Band",
"loopTitle": "Polar Loop",
"loopHint": "Dein Band: Einrichtung, Akku und Uhrzeit",
@ -370,11 +357,15 @@
"dayRatingSomewhatWell": "Du hast eher gut geschlafen",
"dayRatingWell": "Du hast gut geschlafen",
"dayRecharge": "Nightly Recharge",
"dayRechargeLevel": "Erholung {level} von 6",
"dayAnsStatus": "Autonomes Nervensystem {value}, von −10 bis +10",
"dayNightHeart": "Herzfrequenz {bpm} S/min, sonst {usual} S/min",
"dayHrv": "Herzfrequenzvariabilität {ms} ms, sonst {usual} ms",
"dayBreathing": "{rate} Atemzüge pro Minute",
"dayRecovery": "Erholung",
"dayRecoveryOf": "{level} von 6",
"dayAns": "Autonomes Nervensystem",
"dayHrvName": "Herzfrequenzvariabilität",
"dayBreathingName": "Atmung",
"dayPerMinute": "{rate} pro Minute",
"dayMs": "{ms} ms",
"dayUsual": "sonst {value}",
"dayRechargeHelp": "Nightly Recharge ist Polars Maß dafür, wie sich dein Körper in den ersten Stunden des Schlafs erholt hat, gemessen an deinem eigenen Üblichen. Die Erholung reicht von 1 bis 6. Der Status des autonomen Nervensystems reicht von −10 bis +10: über 0 hat es sich besser beruhigt als sonst. Die Herzfrequenzvariabilität ist, wie sehr die Zeit zwischen den Herzschlägen schwankt; höher als sonst spricht meist für eine bessere Erholung. Die Atmung zählt deine Atemzüge pro Minute. Was das Band noch nicht kennt, fehlt.",
"daySkin": "Hauttemperatur",
"dayCelsius": "{value} °C",
"daySkinDeviation": "{value} °C gegenüber deinen Nächten sonst",
@ -582,7 +573,6 @@
"bodyResting": "Ruhepuls {bpm}",
"bodyMax": "Maximalpuls {bpm}",
"homeToday": "Heute",
"equipmentTitle": "Ausrüstung",
"equipmentHint": "Wo du trainierst und was es dort gibt",
"equipmentEmptyTitle": "Noch keine Orte",
@ -636,7 +626,6 @@
"plateNoChange": "Nichts ändert sich",
"plateMoves": "{count, plural, =0{Keine Scheibe bewegt sich} =1{1 Scheibe je Seite bewegt sich} other{{count} Scheiben je Seite bewegen sich}}",
"plateSet": "Satz {number}",
"exerciseName": "{id, select, backSquat{Kniebeuge} frontSquat{Frontkniebeuge} gobletSquat{Goblet Squat} kettlebellGobletSquat{Goblet Squat mit Kettlebell} bodyweightSquat{Kniebeuge ohne Gewicht} lunge{Ausfallschritt} bulgarianSplitSquat{Bulgarische Kniebeuge} bodyweightLunge{Ausfallschritt ohne Gewicht} deadlift{Kreuzheben} romanianDeadlift{Rumänisches Kreuzheben} trapBarDeadlift{Kreuzheben mit Trap Bar} dumbbellRomanianDeadlift{Rumänisches Kreuzheben mit Kurzhanteln} kettlebellSwing{Kettlebell Swing} hipThrust{Hip Thrust} gluteBridge{Glute Bridge} benchPress{Bankdrücken} inclineBenchPress{Schrägbankdrücken} dumbbellBenchPress{Kurzhantel-Bankdrücken} pushUp{Liegestütz} dip{Dip} overheadPress{Schulterdrücken} dumbbellShoulderPress{Kurzhantel-Schulterdrücken} pikePushUp{Pike-Liegestütz} barbellRow{Langhantelrudern} dumbbellRow{Einarmiges Kurzhantelrudern} cableRow{Rudern am Kabelzug} invertedRow{Umgekehrtes Rudern} bandPullApart{Band Pull-Apart} facePull{Face Pull} pullUp{Klimmzug} chinUp{Klimmzug im Untergriff} latPulldown{Latzug} bandPulldown{Latzug mit Band} barbellCurl{Langhantel-Curl} ezBarCurl{SZ-Curl} dumbbellCurl{Kurzhantel-Curl} hammerCurl{Hammer-Curl} tricepsPushdown{Trizepsdrücken am Kabel} skullCrusher{French Press} overheadTricepsExtension{Trizepsstrecken über Kopf} benchDip{Dip an der Bank} lateralRaise{Seitheben} cableLateralRaise{Seitheben am Kabel} calfRaise{Wadenheben} plank{Unterarmstütz} sidePlank{Seitstütz} hangingLegRaise{Beinheben im Hang} crunch{Crunch} farmersCarry{Farmer’s Walk} other{Übung}}",
"exerciseNeed": "{need, select, barbell{eine Langhantel} ezBar{eine SZ-Stange} trapBar{eine Trap Bar} dumbbells{Kurzhanteln} kettlebell{eine Kettlebell} bench{eine Bank} rack{ein Rack} pullUpBar{eine Klimmzugstange} dipBars{eine Dipstation} cableMachine{einen Kabelzug} bands{Widerstandsbänder} other{etwas}}",
"exercisePattern": "{pattern, select, squat{Kniebeugen} lunge{Ausfallschritte} hinge{Hüftstreckung} horizontalPush{Drücken} verticalPush{Drücken über Kopf} horizontalPull{Rudern} verticalPull{Klimmzüge und Latzug} curl{Curls} extension{Trizeps} raise{Heben} calf{Waden} core{Rumpf} carry{Tragen} other{Andere}}",

File diff suppressed because it is too large Load diff

View file

@ -2242,35 +2242,59 @@ abstract class AppLocalizations {
/// **'Nightly Recharge'**
String get dayRecharge;
/// No description provided for @dayRechargeLevel.
/// No description provided for @dayRecovery.
///
/// In en, this message translates to:
/// **'Recovery {level} of 6'**
String dayRechargeLevel(int level);
/// **'Recovery'**
String get dayRecovery;
/// No description provided for @dayAnsStatus.
/// No description provided for @dayRecoveryOf.
///
/// In en, this message translates to:
/// **'Autonomic nervous system {value}, from −10 to +10'**
String dayAnsStatus(String value);
/// **'{level} of 6'**
String dayRecoveryOf(int level);
/// No description provided for @dayNightHeart.
/// No description provided for @dayAns.
///
/// In en, this message translates to:
/// **'Heart rate {bpm} bpm, your usual {usual} bpm'**
String dayNightHeart(int bpm, int usual);
/// **'Autonomic nervous system'**
String get dayAns;
/// No description provided for @dayHrv.
/// No description provided for @dayHrvName.
///
/// In en, this message translates to:
/// **'Heart rate variability {ms} ms, your usual {usual} ms'**
String dayHrv(int ms, int usual);
/// **'Heart rate variability'**
String get dayHrvName;
/// No description provided for @dayBreathing.
/// No description provided for @dayBreathingName.
///
/// In en, this message translates to:
/// **'Breathing {rate} a minute'**
String dayBreathing(String rate);
/// **'Breathing'**
String get dayBreathingName;
/// No description provided for @dayPerMinute.
///
/// In en, this message translates to:
/// **'{rate} a minute'**
String dayPerMinute(String rate);
/// No description provided for @dayMs.
///
/// In en, this message translates to:
/// **'{ms} ms'**
String dayMs(int ms);
/// Below a figure of the night: the person's own usual, as Polar learnt it.
///
/// In en, this message translates to:
/// **'usually {value}'**
String dayUsual(String value);
/// No description provided for @dayRechargeHelp.
///
/// In en, this message translates to:
/// **'Nightly Recharge is Polar\'s measure of how your body recovered in the first hours of sleep, against your own usual. Recovery runs from 1 to 6. The autonomic nervous system\'s status runs from −10 to +10: above 0, it settled better than usual. Heart rate variability is how much the time between heartbeats varies; higher than usual tends to mean a better recovery. Breathing counts your breaths a minute. A figure the band has none of yet is left out.'**
String get dayRechargeHelp;
/// No description provided for @daySkin.
///

View file

@ -1340,29 +1340,40 @@ class AppLocalizationsDe extends AppLocalizations {
String get dayRecharge => 'Nightly Recharge';
@override
String dayRechargeLevel(int level) {
return 'Erholung $level von 6';
String get dayRecovery => 'Erholung';
@override
String dayRecoveryOf(int level) {
return '$level von 6';
}
@override
String dayAnsStatus(String value) {
return 'Autonomes Nervensystem $value, von −10 bis +10';
String get dayAns => 'Autonomes Nervensystem';
@override
String get dayHrvName => 'Herzfrequenzvariabilität';
@override
String get dayBreathingName => 'Atmung';
@override
String dayPerMinute(String rate) {
return '$rate pro Minute';
}
@override
String dayNightHeart(int bpm, int usual) {
return 'Herzfrequenz $bpm S/min, sonst $usual S/min';
String dayMs(int ms) {
return '$ms ms';
}
@override
String dayHrv(int ms, int usual) {
return 'Herzfrequenzvariabilität $ms ms, sonst $usual ms';
String dayUsual(String value) {
return 'sonst $value';
}
@override
String dayBreathing(String rate) {
return '$rate Atemzüge pro Minute';
}
String get dayRechargeHelp =>
'Nightly Recharge ist Polars Maß dafür, wie sich dein Körper in den ersten Stunden des Schlafs erholt hat, gemessen an deinem eigenen Üblichen. Die Erholung reicht von 1 bis 6. Der Status des autonomen Nervensystems reicht von −10 bis +10: über 0 hat es sich besser beruhigt als sonst. Die Herzfrequenzvariabilität ist, wie sehr die Zeit zwischen den Herzschlägen schwankt; höher als sonst spricht meist für eine bessere Erholung. Die Atmung zählt deine Atemzüge pro Minute. Was das Band noch nicht kennt, fehlt.';
@override
String get daySkin => 'Hauttemperatur';

View file

@ -1333,29 +1333,40 @@ class AppLocalizationsEn extends AppLocalizations {
String get dayRecharge => 'Nightly Recharge';
@override
String dayRechargeLevel(int level) {
return 'Recovery $level of 6';
String get dayRecovery => 'Recovery';
@override
String dayRecoveryOf(int level) {
return '$level of 6';
}
@override
String dayAnsStatus(String value) {
return 'Autonomic nervous system $value, from −10 to +10';
String get dayAns => 'Autonomic nervous system';
@override
String get dayHrvName => 'Heart rate variability';
@override
String get dayBreathingName => 'Breathing';
@override
String dayPerMinute(String rate) {
return '$rate a minute';
}
@override
String dayNightHeart(int bpm, int usual) {
return 'Heart rate $bpm bpm, your usual $usual bpm';
String dayMs(int ms) {
return '$ms ms';
}
@override
String dayHrv(int ms, int usual) {
return 'Heart rate variability $ms ms, your usual $usual ms';
String dayUsual(String value) {
return 'usually $value';
}
@override
String dayBreathing(String rate) {
return 'Breathing $rate a minute';
}
String get dayRechargeHelp =>
'Nightly Recharge is Polar\'s measure of how your body recovered in the first hours of sleep, against your own usual. Recovery runs from 1 to 6. The autonomic nervous system\'s status runs from −10 to +10: above 0, it settled better than usual. Heart rate variability is how much the time between heartbeats varies; higher than usual tends to mean a better recovery. Breathing counts your breaths a minute. A figure the band has none of yet is left out.';
@override
String get daySkin => 'Skin temperature';