feat(train): weight, entered on Progress and read from Health Connect, sealed and synced (ADR 0030, D63)
A person's weights are each a sealed `body_weight` row, as the days and
the places: the weight in grams, when it was measured (UTC), where it
came from and, for Health Connect's, the record's id and app are the
payload; the server sees the space and the ciphertext alone.
Progress gets a Weight card after Your body: the latest and its day,
its line over time (held, each weight and its day), the plus to enter
one (a comma taken as a point, 20 to 400 kg, when measured, today by
default) and All, the list to delete one. The latest weight is also
Your body's, which the band's and the zones' figures work from. In
kilograms alone: Train is metric throughout and has no units setting
yet.
Health Connect's weight records are read in when switched on in its
screen (READ_WEIGHT, asked as the others): each record once, its id kept
in health_connect.json also after it is deleted here, never Train's own,
and none within 10 minutes and 0.1 kg of a weight entered in Train.
The migration (20261103204323683-body-weights):
- creates (or replaces, unchanged) `gen_random_uuid_v7()`, the
default of every UUID id, as each of Serverpod's migrations does;
- CREATE TABLE "body_weight": "id" uuid primary key defaulting to a
UUID v7, "spaceId" bigint (the sync space), "keyId" text (the key
that sealed it), "payload" bytea NOT NULL (the sealed content),
"nonce" bytea;
- CREATE INDEX "body_weight_space_idx" on "spaceId", for a space's
rows as the sync reads them;
- a foreign key from "spaceId" to "offline_sync_spaces"("id"),
ON DELETE CASCADE, so that an account's deletion takes its weights
with its space;
- the version rows of Train's and every module's migrations.
Nothing existing changes, so nothing is lost.
Tests: the row written and read back in its owner's space, and the
denials: another person reads none of them, and writing one into
another's space throws OfflineSyncSpaceMembershipException and leaves
it empty; an account's deletion takes them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
984527a632
commit
b975659690
5 changed files with 23591 additions and 9 deletions
|
|
@ -151,6 +151,9 @@
|
|||
"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",
|
||||
"healthReadWeight": "Mein Gewicht lesen",
|
||||
"healthReadWeightHint": "Was andere Apps an Gewicht schrieben, etwa die einer Waage, kommt in Train",
|
||||
"healthRationaleWeight": "Train liest das Gewicht, das andere Apps schrieben, erst, wenn du es einschaltest: Jeder Wert kommt einmal in Train, mit deinem Schlüssel versiegelt, bevor er mit deiner Instanz synchronisiert wird, die ihn nicht lesen kann.",
|
||||
"healthApps": "Apps",
|
||||
"healthAppsEmpty": "Noch keine App hat Trainings in Health Connect geschrieben.",
|
||||
"workoutFromList": "von {app}",
|
||||
|
|
@ -570,6 +573,16 @@
|
|||
"profileWorkouts": "{count, plural, =1{1 Training} other{{count} Trainings}}",
|
||||
"profileYearEmpty": "Dieses Jahr noch keine Trainings.",
|
||||
"bodyWeight": "{kg} kg",
|
||||
"weightTitle": "Gewicht",
|
||||
"weightAdd": "Gewicht eintragen",
|
||||
"weightAll": "Alle",
|
||||
"weightEmpty": "Trag dein Gewicht ein, um es hier zu verfolgen; das neueste ist auch das Gewicht deines Körpers.",
|
||||
"weightOverTime": "Im Verlauf",
|
||||
"weightField": "Gewicht",
|
||||
"weightInvalid": "Ein Gewicht zwischen 20 und 400 kg",
|
||||
"weightWhen": "Gemessen",
|
||||
"weightDelete": "Löschen",
|
||||
"weightFromHealth": "von {app}",
|
||||
"bodyResting": "Ruhepuls {bpm}",
|
||||
"bodyMax": "Maximalpuls {bpm}",
|
||||
"homeToday": "Heute",
|
||||
|
|
|
|||
|
|
@ -434,6 +434,9 @@
|
|||
"healthRationaleNothingElse": "What Train reads there is shown to you and nobody else, and isn't used for anything else. Turning a switch off, or taking a permission back in Health Connect, stops it.",
|
||||
"healthRead": "Read from other apps",
|
||||
"healthReadHint": "Their workouts come into Train, from the apps you choose below",
|
||||
"healthReadWeight": "Read my weight",
|
||||
"healthReadWeightHint": "Weights other apps wrote, a scale's say, come into Train",
|
||||
"healthRationaleWeight": "Train reads the weights other apps wrote only once you turn it on: each comes into Train once, sealed with your key before it's synced to your instance, which can't read it.",
|
||||
"healthApps": "Apps",
|
||||
"@healthApps": {
|
||||
"description": "Over the apps that wrote workouts to Health Connect, each with its switch."
|
||||
|
|
@ -1597,6 +1600,25 @@
|
|||
},
|
||||
"profileYearEmpty": "No workouts this year yet.",
|
||||
"bodyWeight": "{kg} kg",
|
||||
"weightTitle": "Weight",
|
||||
"weightAdd": "Add weight",
|
||||
"weightAll": "All",
|
||||
"weightEmpty": "Add your weight to follow it here; the latest is also your body's weight.",
|
||||
"weightOverTime": "Over time",
|
||||
"weightField": "Weight",
|
||||
"weightInvalid": "A weight between 20 and 400 kg",
|
||||
"weightWhen": "Measured",
|
||||
"weightDelete": "Delete",
|
||||
"weightFromHealth": "from {app}",
|
||||
"@weightFromHealth": {
|
||||
"description": "A weight read from Health Connect: the package of the app that wrote it.",
|
||||
"placeholders": {
|
||||
"app": {
|
||||
"type": "String",
|
||||
"example": "com.example.scale"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@bodyWeight": {
|
||||
"placeholders": {
|
||||
"kg": {
|
||||
|
|
|
|||
|
|
@ -1006,6 +1006,24 @@ abstract class AppLocalizations {
|
|||
/// **'Their workouts come into Train, from the apps you choose below'**
|
||||
String get healthReadHint;
|
||||
|
||||
/// No description provided for @healthReadWeight.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Read my weight'**
|
||||
String get healthReadWeight;
|
||||
|
||||
/// No description provided for @healthReadWeightHint.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Weights other apps wrote, a scale\'s say, come into Train'**
|
||||
String get healthReadWeightHint;
|
||||
|
||||
/// No description provided for @healthRationaleWeight.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Train reads the weights other apps wrote only once you turn it on: each comes into Train once, sealed with your key before it\'s synced to your instance, which can\'t read it.'**
|
||||
String get healthRationaleWeight;
|
||||
|
||||
/// Over the apps that wrote workouts to Health Connect, each with its switch.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -3520,6 +3538,66 @@ abstract class AppLocalizations {
|
|||
/// **'{kg} kg'**
|
||||
String bodyWeight(String kg);
|
||||
|
||||
/// No description provided for @weightTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Weight'**
|
||||
String get weightTitle;
|
||||
|
||||
/// No description provided for @weightAdd.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Add weight'**
|
||||
String get weightAdd;
|
||||
|
||||
/// No description provided for @weightAll.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'All'**
|
||||
String get weightAll;
|
||||
|
||||
/// No description provided for @weightEmpty.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Add your weight to follow it here; the latest is also your body\'s weight.'**
|
||||
String get weightEmpty;
|
||||
|
||||
/// No description provided for @weightOverTime.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Over time'**
|
||||
String get weightOverTime;
|
||||
|
||||
/// No description provided for @weightField.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Weight'**
|
||||
String get weightField;
|
||||
|
||||
/// No description provided for @weightInvalid.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'A weight between 20 and 400 kg'**
|
||||
String get weightInvalid;
|
||||
|
||||
/// No description provided for @weightWhen.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Measured'**
|
||||
String get weightWhen;
|
||||
|
||||
/// No description provided for @weightDelete.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Delete'**
|
||||
String get weightDelete;
|
||||
|
||||
/// A weight read from Health Connect: the package of the app that wrote it.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'from {app}'**
|
||||
String weightFromHealth(String app);
|
||||
|
||||
/// No description provided for @bodyResting.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
|
|||
|
|
@ -565,6 +565,17 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String get healthReadHint =>
|
||||
'Ihre Trainings kommen in Train, von den Apps, die du unten wählst';
|
||||
|
||||
@override
|
||||
String get healthReadWeight => 'Mein Gewicht lesen';
|
||||
|
||||
@override
|
||||
String get healthReadWeightHint =>
|
||||
'Was andere Apps an Gewicht schrieben, etwa die einer Waage, kommt in Train';
|
||||
|
||||
@override
|
||||
String get healthRationaleWeight =>
|
||||
'Train liest das Gewicht, das andere Apps schrieben, erst, wenn du es einschaltest: Jeder Wert kommt einmal in Train, mit deinem Schlüssel versiegelt, bevor er mit deiner Instanz synchronisiert wird, die ihn nicht lesen kann.';
|
||||
|
||||
@override
|
||||
String get healthApps => 'Apps';
|
||||
|
||||
|
|
@ -2118,6 +2129,39 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
return '$kg kg';
|
||||
}
|
||||
|
||||
@override
|
||||
String get weightTitle => 'Gewicht';
|
||||
|
||||
@override
|
||||
String get weightAdd => 'Gewicht eintragen';
|
||||
|
||||
@override
|
||||
String get weightAll => 'Alle';
|
||||
|
||||
@override
|
||||
String get weightEmpty =>
|
||||
'Trag dein Gewicht ein, um es hier zu verfolgen; das neueste ist auch das Gewicht deines Körpers.';
|
||||
|
||||
@override
|
||||
String get weightOverTime => 'Im Verlauf';
|
||||
|
||||
@override
|
||||
String get weightField => 'Gewicht';
|
||||
|
||||
@override
|
||||
String get weightInvalid => 'Ein Gewicht zwischen 20 und 400 kg';
|
||||
|
||||
@override
|
||||
String get weightWhen => 'Gemessen';
|
||||
|
||||
@override
|
||||
String get weightDelete => 'Löschen';
|
||||
|
||||
@override
|
||||
String weightFromHealth(String app) {
|
||||
return 'von $app';
|
||||
}
|
||||
|
||||
@override
|
||||
String bodyResting(int bpm) {
|
||||
return 'Ruhepuls $bpm';
|
||||
|
|
|
|||
|
|
@ -561,6 +561,17 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String get healthReadHint =>
|
||||
'Their workouts come into Train, from the apps you choose below';
|
||||
|
||||
@override
|
||||
String get healthReadWeight => 'Read my weight';
|
||||
|
||||
@override
|
||||
String get healthReadWeightHint =>
|
||||
'Weights other apps wrote, a scale\'s say, come into Train';
|
||||
|
||||
@override
|
||||
String get healthRationaleWeight =>
|
||||
'Train reads the weights other apps wrote only once you turn it on: each comes into Train once, sealed with your key before it\'s synced to your instance, which can\'t read it.';
|
||||
|
||||
@override
|
||||
String get healthApps => 'Apps';
|
||||
|
||||
|
|
@ -2106,6 +2117,39 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
return '$kg kg';
|
||||
}
|
||||
|
||||
@override
|
||||
String get weightTitle => 'Weight';
|
||||
|
||||
@override
|
||||
String get weightAdd => 'Add weight';
|
||||
|
||||
@override
|
||||
String get weightAll => 'All';
|
||||
|
||||
@override
|
||||
String get weightEmpty =>
|
||||
'Add your weight to follow it here; the latest is also your body\'s weight.';
|
||||
|
||||
@override
|
||||
String get weightOverTime => 'Over time';
|
||||
|
||||
@override
|
||||
String get weightField => 'Weight';
|
||||
|
||||
@override
|
||||
String get weightInvalid => 'A weight between 20 and 400 kg';
|
||||
|
||||
@override
|
||||
String get weightWhen => 'Measured';
|
||||
|
||||
@override
|
||||
String get weightDelete => 'Delete';
|
||||
|
||||
@override
|
||||
String weightFromHealth(String app) {
|
||||
return 'from $app';
|
||||
}
|
||||
|
||||
@override
|
||||
String bodyResting(int bpm) {
|
||||
return 'resting $bpm bpm';
|
||||
|
|
|
|||
Loading…
Reference in a new issue