feat(train): Days, the Loop's days and nights kept sealed (ADR 0030, D37, D38)

What the band read brings, from the bridge or the background job's
file (`LoopLines`), is kept by `DayRepository`: one sealed `day` and
`night` a date and the day's heart rate in chunks of three hours, every
ID made from the person and the date, a later read replacing what it
brought and keeping the rest, nothing rewritten unchanged; versioned,
gzipped JSON (`day_payload.dart`). `PolarLoop` reads the band as each
connection is ready (every half hour at most, or when asked), takes in
the background file as the app opens, and remembers up to which day
the days are sealed, which the next read then deletes on the band;
it keeps the background job on the band and from the right day, and
`Let the band go` stops it.

Home gains a Days tab, each day with its steps, active time, sleep and
Nightly Recharge, pulled to read the band; a day's page shows its
figures, its time in each class, its heart rate, the night's phases
and rating, Nightly Recharge and skin temperature.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-28 19:53:12 +01:00
commit 5250f32329
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
5 changed files with 4079 additions and 28 deletions

View file

@ -220,5 +220,47 @@
"loopSetUpDone": "Das Band ist eingerichtet.",
"loopFormTitle": "Für dein Band",
"loopFormIntro": "Dein Band braucht diese Angaben. Sie bleiben in „Dein Körper“, mit deinem Schlüssel versiegelt. Für einen Ruhe- oder Maximalpuls oder eine VO2max, die du nicht angegeben hast, nimmt das Band Polars Standardwerte, und „Dein Körper“ bleibt, wie er ist.",
"loopFormSave": "Band einrichten"
"loopFormSave": "Band einrichten",
"homeWorkouts": "Workouts",
"homeDays": "Tage",
"daysEmptyTitle": "Noch keine Tage",
"daysEmptyHint": "Die Tage und Nächte, die dein Polar Loop zählt, erscheinen hier.",
"daysSteps": "{steps, plural, =1{1 Schritt} other{{steps} Schritte}}",
"daysActive": "{time} aktiv",
"daysAsleep": "{time} geschlafen",
"daysRecharge": "Erholung {level} von 6",
"dayHoursMinutes": "{hours} h {minutes} min",
"dayMinutes": "{minutes} min",
"dayCalories": "{calories} kcal",
"dayGoal": "{percent} % des Tagesziels",
"dayNothing": "Das Band hat für diesen Tag noch nichts geliefert.",
"dayTimes": "Die Zeit des Tages",
"dayTimeNonWear": "Nicht getragen",
"dayTimeSleep": "Geschlafen",
"dayTimeSedentary": "Still gesessen",
"dayTimeLight": "Leichte Aktivität",
"dayTimeModerate": "Mittlere Aktivität",
"dayTimeVigorous": "Intensive Aktivität",
"dayHeartRate": "Herzfrequenz",
"dayBpm": "{bpm} S/min",
"dayNight": "Die Nacht",
"dayNightSpan": "{from} – {to}",
"daySleepWake": "Wach",
"daySleepRem": "REM",
"daySleepLight": "Leicht",
"daySleepDeep": "Tief",
"dayRatingPoorly": "Du hast schlecht geschlafen",
"dayRatingSomewhatPoorly": "Du hast eher schlecht geschlafen",
"dayRatingNeither": "Du hast weder schlecht noch gut geschlafen",
"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",
"daySkin": "Hauttemperatur",
"dayCelsius": "{value} °C",
"daySkinDeviation": "{value} °C gegenüber deinen Nächten sonst"
}

View file

@ -276,5 +276,64 @@
"loopSetUpDone": "The band is set up.",
"loopFormTitle": "For your band",
"loopFormIntro": "Your band needs these. They're kept in Your body, sealed with your key. For a resting or highest heart rate or a VO2max you haven't given, the band takes Polar's defaults, and Your body stays as it is.",
"loopFormSave": "Set up the band"
"loopFormSave": "Set up the band",
"homeWorkouts": "Workouts",
"homeDays": "Days",
"daysEmptyTitle": "No days yet",
"daysEmptyHint": "The days and nights your Polar Loop counts show here.",
"daysSteps": "{steps, plural, =1{1 step} other{{steps} steps}}",
"@daysSteps": {"placeholders": {"steps": {"type": "int"}}},
"daysActive": "{time} active",
"@daysActive": {"placeholders": {"time": {"type": "String", "example": "1 h 20 min"}}},
"daysAsleep": "{time} asleep",
"@daysAsleep": {"placeholders": {"time": {"type": "String", "example": "7 h 10 min"}}},
"daysRecharge": "Recharge {level} of 6",
"@daysRecharge": {"placeholders": {"level": {"type": "int"}}},
"dayHoursMinutes": "{hours} h {minutes} min",
"@dayHoursMinutes": {"placeholders": {"hours": {"type": "int"}, "minutes": {"type": "int"}}},
"dayMinutes": "{minutes} min",
"@dayMinutes": {"placeholders": {"minutes": {"type": "int"}}},
"dayCalories": "{calories} kcal",
"@dayCalories": {"placeholders": {"calories": {"type": "int"}}},
"dayGoal": "{percent} % of the day's goal",
"@dayGoal": {"placeholders": {"percent": {"type": "int"}}},
"dayNothing": "The band has given nothing for this day yet.",
"dayTimes": "The day's time",
"dayTimeNonWear": "Not worn",
"dayTimeSleep": "Asleep",
"dayTimeSedentary": "Sitting still",
"dayTimeLight": "Light activity",
"dayTimeModerate": "Moderate activity",
"dayTimeVigorous": "Vigorous activity",
"dayHeartRate": "Heart rate",
"dayBpm": "{bpm} bpm",
"@dayBpm": {"placeholders": {"bpm": {"type": "int"}}},
"dayNight": "The night",
"dayNightSpan": "{from} – {to}",
"@dayNightSpan": {"placeholders": {"from": {"type": "String", "example": "23:15"}, "to": {"type": "String", "example": "07:05"}}},
"daySleepWake": "Awake",
"daySleepRem": "REM",
"daySleepLight": "Light",
"daySleepDeep": "Deep",
"dayRatingPoorly": "You slept poorly",
"dayRatingSomewhatPoorly": "You slept somewhat poorly",
"dayRatingNeither": "You slept neither poorly nor well",
"dayRatingSomewhatWell": "You slept somewhat well",
"dayRatingWell": "You slept well",
"dayRecharge": "Nightly Recharge",
"dayRechargeLevel": "Recovery {level} of 6",
"@dayRechargeLevel": {"placeholders": {"level": {"type": "int"}}},
"dayAnsStatus": "Autonomic nervous system {value}, from −10 to +10",
"@dayAnsStatus": {"placeholders": {"value": {"type": "String", "example": "+2.5"}}},
"dayNightHeart": "Heart rate {bpm} bpm, your usual {usual} bpm",
"@dayNightHeart": {"placeholders": {"bpm": {"type": "int"}, "usual": {"type": "int"}}},
"dayHrv": "Heart rate variability {ms} ms, your usual {usual} ms",
"@dayHrv": {"placeholders": {"ms": {"type": "int"}, "usual": {"type": "int"}}},
"dayBreathing": "Breathing {rate} a minute",
"@dayBreathing": {"placeholders": {"rate": {"type": "String", "example": "14.5"}}},
"daySkin": "Skin temperature",
"dayCelsius": "{value} °C",
"@dayCelsius": {"placeholders": {"value": {"type": "String", "example": "33.4"}}},
"daySkinDeviation": "{value} °C from your usual at night",
"@daySkinDeviation": {"placeholders": {"value": {"type": "String", "example": "−0.2"}}}
}

View file

@ -1347,6 +1347,258 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'Set up the band'**
String get loopFormSave;
/// No description provided for @homeWorkouts.
///
/// In en, this message translates to:
/// **'Workouts'**
String get homeWorkouts;
/// No description provided for @homeDays.
///
/// In en, this message translates to:
/// **'Days'**
String get homeDays;
/// No description provided for @daysEmptyTitle.
///
/// In en, this message translates to:
/// **'No days yet'**
String get daysEmptyTitle;
/// No description provided for @daysEmptyHint.
///
/// In en, this message translates to:
/// **'The days and nights your Polar Loop counts show here.'**
String get daysEmptyHint;
/// No description provided for @daysSteps.
///
/// In en, this message translates to:
/// **'{steps, plural, =1{1 step} other{{steps} steps}}'**
String daysSteps(int steps);
/// No description provided for @daysActive.
///
/// In en, this message translates to:
/// **'{time} active'**
String daysActive(String time);
/// No description provided for @daysAsleep.
///
/// In en, this message translates to:
/// **'{time} asleep'**
String daysAsleep(String time);
/// No description provided for @daysRecharge.
///
/// In en, this message translates to:
/// **'Recharge {level} of 6'**
String daysRecharge(int level);
/// No description provided for @dayHoursMinutes.
///
/// In en, this message translates to:
/// **'{hours} h {minutes} min'**
String dayHoursMinutes(int hours, int minutes);
/// No description provided for @dayMinutes.
///
/// In en, this message translates to:
/// **'{minutes} min'**
String dayMinutes(int minutes);
/// No description provided for @dayCalories.
///
/// In en, this message translates to:
/// **'{calories} kcal'**
String dayCalories(int calories);
/// No description provided for @dayGoal.
///
/// In en, this message translates to:
/// **'{percent} % of the day\'s goal'**
String dayGoal(int percent);
/// No description provided for @dayNothing.
///
/// In en, this message translates to:
/// **'The band has given nothing for this day yet.'**
String get dayNothing;
/// No description provided for @dayTimes.
///
/// In en, this message translates to:
/// **'The day\'s time'**
String get dayTimes;
/// No description provided for @dayTimeNonWear.
///
/// In en, this message translates to:
/// **'Not worn'**
String get dayTimeNonWear;
/// No description provided for @dayTimeSleep.
///
/// In en, this message translates to:
/// **'Asleep'**
String get dayTimeSleep;
/// No description provided for @dayTimeSedentary.
///
/// In en, this message translates to:
/// **'Sitting still'**
String get dayTimeSedentary;
/// No description provided for @dayTimeLight.
///
/// In en, this message translates to:
/// **'Light activity'**
String get dayTimeLight;
/// No description provided for @dayTimeModerate.
///
/// In en, this message translates to:
/// **'Moderate activity'**
String get dayTimeModerate;
/// No description provided for @dayTimeVigorous.
///
/// In en, this message translates to:
/// **'Vigorous activity'**
String get dayTimeVigorous;
/// No description provided for @dayHeartRate.
///
/// In en, this message translates to:
/// **'Heart rate'**
String get dayHeartRate;
/// No description provided for @dayBpm.
///
/// In en, this message translates to:
/// **'{bpm} bpm'**
String dayBpm(int bpm);
/// No description provided for @dayNight.
///
/// In en, this message translates to:
/// **'The night'**
String get dayNight;
/// No description provided for @dayNightSpan.
///
/// In en, this message translates to:
/// **'{from} – {to}'**
String dayNightSpan(String from, String to);
/// No description provided for @daySleepWake.
///
/// In en, this message translates to:
/// **'Awake'**
String get daySleepWake;
/// No description provided for @daySleepRem.
///
/// In en, this message translates to:
/// **'REM'**
String get daySleepRem;
/// No description provided for @daySleepLight.
///
/// In en, this message translates to:
/// **'Light'**
String get daySleepLight;
/// No description provided for @daySleepDeep.
///
/// In en, this message translates to:
/// **'Deep'**
String get daySleepDeep;
/// No description provided for @dayRatingPoorly.
///
/// In en, this message translates to:
/// **'You slept poorly'**
String get dayRatingPoorly;
/// No description provided for @dayRatingSomewhatPoorly.
///
/// In en, this message translates to:
/// **'You slept somewhat poorly'**
String get dayRatingSomewhatPoorly;
/// No description provided for @dayRatingNeither.
///
/// In en, this message translates to:
/// **'You slept neither poorly nor well'**
String get dayRatingNeither;
/// No description provided for @dayRatingSomewhatWell.
///
/// In en, this message translates to:
/// **'You slept somewhat well'**
String get dayRatingSomewhatWell;
/// No description provided for @dayRatingWell.
///
/// In en, this message translates to:
/// **'You slept well'**
String get dayRatingWell;
/// No description provided for @dayRecharge.
///
/// In en, this message translates to:
/// **'Nightly Recharge'**
String get dayRecharge;
/// No description provided for @dayRechargeLevel.
///
/// In en, this message translates to:
/// **'Recovery {level} of 6'**
String dayRechargeLevel(int level);
/// No description provided for @dayAnsStatus.
///
/// In en, this message translates to:
/// **'Autonomic nervous system {value}, from −10 to +10'**
String dayAnsStatus(String value);
/// No description provided for @dayNightHeart.
///
/// In en, this message translates to:
/// **'Heart rate {bpm} bpm, your usual {usual} bpm'**
String dayNightHeart(int bpm, int usual);
/// No description provided for @dayHrv.
///
/// In en, this message translates to:
/// **'Heart rate variability {ms} ms, your usual {usual} ms'**
String dayHrv(int ms, int usual);
/// No description provided for @dayBreathing.
///
/// In en, this message translates to:
/// **'Breathing {rate} a minute'**
String dayBreathing(String rate);
/// No description provided for @daySkin.
///
/// In en, this message translates to:
/// **'Skin temperature'**
String get daySkin;
/// No description provided for @dayCelsius.
///
/// In en, this message translates to:
/// **'{value} °C'**
String dayCelsius(String value);
/// No description provided for @daySkinDeviation.
///
/// In en, this message translates to:
/// **'{value} °C from your usual at night'**
String daySkinDeviation(String value);
}
class _AppLocalizationsDelegate

View file

@ -747,4 +747,171 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get loopFormSave => 'Band einrichten';
@override
String get homeWorkouts => 'Workouts';
@override
String get homeDays => 'Tage';
@override
String get daysEmptyTitle => 'Noch keine Tage';
@override
String get daysEmptyHint =>
'Die Tage und Nächte, die dein Polar Loop zählt, erscheinen hier.';
@override
String daysSteps(int steps) {
String _temp0 = intl.Intl.pluralLogic(
steps,
locale: localeName,
other: '$steps Schritte',
one: '1 Schritt',
);
return '$_temp0';
}
@override
String daysActive(String time) {
return '$time aktiv';
}
@override
String daysAsleep(String time) {
return '$time geschlafen';
}
@override
String daysRecharge(int level) {
return 'Erholung $level von 6';
}
@override
String dayHoursMinutes(int hours, int minutes) {
return '$hours h $minutes min';
}
@override
String dayMinutes(int minutes) {
return '$minutes min';
}
@override
String dayCalories(int calories) {
return '$calories kcal';
}
@override
String dayGoal(int percent) {
return '$percent % des Tagesziels';
}
@override
String get dayNothing => 'Das Band hat für diesen Tag noch nichts geliefert.';
@override
String get dayTimes => 'Die Zeit des Tages';
@override
String get dayTimeNonWear => 'Nicht getragen';
@override
String get dayTimeSleep => 'Geschlafen';
@override
String get dayTimeSedentary => 'Still gesessen';
@override
String get dayTimeLight => 'Leichte Aktivität';
@override
String get dayTimeModerate => 'Mittlere Aktivität';
@override
String get dayTimeVigorous => 'Intensive Aktivität';
@override
String get dayHeartRate => 'Herzfrequenz';
@override
String dayBpm(int bpm) {
return '$bpm S/min';
}
@override
String get dayNight => 'Die Nacht';
@override
String dayNightSpan(String from, String to) {
return '$from – $to';
}
@override
String get daySleepWake => 'Wach';
@override
String get daySleepRem => 'REM';
@override
String get daySleepLight => 'Leicht';
@override
String get daySleepDeep => 'Tief';
@override
String get dayRatingPoorly => 'Du hast schlecht geschlafen';
@override
String get dayRatingSomewhatPoorly => 'Du hast eher schlecht geschlafen';
@override
String get dayRatingNeither => 'Du hast weder schlecht noch gut geschlafen';
@override
String get dayRatingSomewhatWell => 'Du hast eher gut geschlafen';
@override
String get dayRatingWell => 'Du hast gut geschlafen';
@override
String get dayRecharge => 'Nightly Recharge';
@override
String dayRechargeLevel(int level) {
return 'Erholung $level von 6';
}
@override
String dayAnsStatus(String value) {
return 'Autonomes Nervensystem $value, von −10 bis +10';
}
@override
String dayNightHeart(int bpm, int usual) {
return 'Herzfrequenz $bpm S/min, sonst $usual S/min';
}
@override
String dayHrv(int ms, int usual) {
return 'Herzfrequenzvariabilität $ms ms, sonst $usual ms';
}
@override
String dayBreathing(String rate) {
return '$rate Atemzüge pro Minute';
}
@override
String get daySkin => 'Hauttemperatur';
@override
String dayCelsius(String value) {
return '$value °C';
}
@override
String daySkinDeviation(String value) {
return '$value °C gegenüber deinen Nächten sonst';
}
}

View file

@ -741,6 +741,173 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get loopFormSave => 'Set up the band';
@override
String get homeWorkouts => 'Workouts';
@override
String get homeDays => 'Days';
@override
String get daysEmptyTitle => 'No days yet';
@override
String get daysEmptyHint =>
'The days and nights your Polar Loop counts show here.';
@override
String daysSteps(int steps) {
String _temp0 = intl.Intl.pluralLogic(
steps,
locale: localeName,
other: '$steps steps',
one: '1 step',
);
return '$_temp0';
}
@override
String daysActive(String time) {
return '$time active';
}
@override
String daysAsleep(String time) {
return '$time asleep';
}
@override
String daysRecharge(int level) {
return 'Recharge $level of 6';
}
@override
String dayHoursMinutes(int hours, int minutes) {
return '$hours h $minutes min';
}
@override
String dayMinutes(int minutes) {
return '$minutes min';
}
@override
String dayCalories(int calories) {
return '$calories kcal';
}
@override
String dayGoal(int percent) {
return '$percent % of the day\'s goal';
}
@override
String get dayNothing => 'The band has given nothing for this day yet.';
@override
String get dayTimes => 'The day\'s time';
@override
String get dayTimeNonWear => 'Not worn';
@override
String get dayTimeSleep => 'Asleep';
@override
String get dayTimeSedentary => 'Sitting still';
@override
String get dayTimeLight => 'Light activity';
@override
String get dayTimeModerate => 'Moderate activity';
@override
String get dayTimeVigorous => 'Vigorous activity';
@override
String get dayHeartRate => 'Heart rate';
@override
String dayBpm(int bpm) {
return '$bpm bpm';
}
@override
String get dayNight => 'The night';
@override
String dayNightSpan(String from, String to) {
return '$from – $to';
}
@override
String get daySleepWake => 'Awake';
@override
String get daySleepRem => 'REM';
@override
String get daySleepLight => 'Light';
@override
String get daySleepDeep => 'Deep';
@override
String get dayRatingPoorly => 'You slept poorly';
@override
String get dayRatingSomewhatPoorly => 'You slept somewhat poorly';
@override
String get dayRatingNeither => 'You slept neither poorly nor well';
@override
String get dayRatingSomewhatWell => 'You slept somewhat well';
@override
String get dayRatingWell => 'You slept well';
@override
String get dayRecharge => 'Nightly Recharge';
@override
String dayRechargeLevel(int level) {
return 'Recovery $level of 6';
}
@override
String dayAnsStatus(String value) {
return 'Autonomic nervous system $value, from −10 to +10';
}
@override
String dayNightHeart(int bpm, int usual) {
return 'Heart rate $bpm bpm, your usual $usual bpm';
}
@override
String dayHrv(int ms, int usual) {
return 'Heart rate variability $ms ms, your usual $usual ms';
}
@override
String dayBreathing(String rate) {
return 'Breathing $rate a minute';
}
@override
String get daySkin => 'Skin temperature';
@override
String dayCelsius(String value) {
return '$value °C';
}
@override
String daySkinDeviation(String value) {
return '$value °C from your usual at night';
}
}
/// The translations for English, as used in the United Kingdom (`en_GB`).