feat(train): equipment by place and a plate calculator (ADR 0034)

The places a person trains at (home, a gym), each a sealed, synced
`training_place` row with its equipment: bars and handles with their own
weight, plates by weight and count, dumbbells and kettlebells, the rest
by count, the person's own by name. A new place starts from what a home
or a gym usually has. The place last chosen is remembered on the device,
never its location.

The plate calculator loads each weight both sides alike, the heaviest
load not above it with the fewest plates, heavier inside, and over a
series of sets picks the loads making the fewest plate moves, listing
what comes off and goes on. Equipment is in Settings; strength plans
are to build on it.

The migration only creates the table, its space index and its cascade
to the sync engine's spaces.

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-02 11:59:24 +01:00
commit 064b911aa2
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
48 changed files with 24759 additions and 7 deletions

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,936 @@
{
"__className__": "serverpod.DatabaseDefinition",
"schemaVersion": 2,
"moduleName": "halkyon_train",
"tables": [
{
"__className__": "serverpod.TableDefinition",
"name": "content_key",
"dartName": "ContentKey",
"module": "halkyon_train",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 7,
"isNullable": false,
"columnDefault": "random_v7",
"dartType": "UuidValue?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "spaceId",
"fieldName": "spaceId",
"columnType": 6,
"isNullable": true,
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "dataKeyId",
"fieldName": "dataKeyId",
"columnType": 0,
"isNullable": false,
"dartType": "String"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "sealedKey",
"fieldName": "sealedKey",
"columnType": 5,
"isNullable": false,
"dartType": "dart:typed_data:ByteData"
}
],
"foreignKeys": [
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "content_key_fk_0",
"columns": [
"spaceId"
],
"referenceTable": "offline_sync_spaces",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4
}
],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "content_key_space_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "spaceId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
}
],
"managed": true
},
{
"__className__": "serverpod.TableDefinition",
"name": "day",
"dartName": "Day",
"module": "halkyon_train",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 7,
"isNullable": false,
"columnDefault": "random_v7",
"dartType": "UuidValue?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "spaceId",
"fieldName": "spaceId",
"columnType": 6,
"isNullable": true,
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "keyId",
"fieldName": "keyId",
"columnType": 0,
"isNullable": true,
"dartType": "String?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "payload",
"fieldName": "payload",
"columnType": 5,
"isNullable": false,
"dartType": "dart:typed_data:ByteData"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "nonce",
"fieldName": "nonce",
"columnType": 5,
"isNullable": true,
"dartType": "dart:typed_data:ByteData?"
}
],
"foreignKeys": [
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "day_fk_0",
"columns": [
"spaceId"
],
"referenceTable": "offline_sync_spaces",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4
}
],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "day_space_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "spaceId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
}
],
"managed": true
},
{
"__className__": "serverpod.TableDefinition",
"name": "day_series",
"dartName": "DaySeries",
"module": "halkyon_train",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 7,
"isNullable": false,
"columnDefault": "random_v7",
"dartType": "UuidValue?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "spaceId",
"fieldName": "spaceId",
"columnType": 6,
"isNullable": true,
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "dayId",
"fieldName": "dayId",
"columnType": 7,
"isNullable": false,
"dartType": "UuidValue"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "keyId",
"fieldName": "keyId",
"columnType": 0,
"isNullable": true,
"dartType": "String?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "payload",
"fieldName": "payload",
"columnType": 5,
"isNullable": false,
"dartType": "dart:typed_data:ByteData"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "nonce",
"fieldName": "nonce",
"columnType": 5,
"isNullable": true,
"dartType": "dart:typed_data:ByteData?"
}
],
"foreignKeys": [
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "day_series_fk_0",
"columns": [
"spaceId"
],
"referenceTable": "offline_sync_spaces",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4
},
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "day_series_fk_1",
"columns": [
"dayId"
],
"referenceTable": "day",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4,
"deferrable": "initiallyDeferred"
}
],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "day_series_space_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "spaceId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
},
{
"__className__": "serverpod.IndexDefinition",
"indexName": "day_series_day_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "dayId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
}
],
"managed": true
},
{
"__className__": "serverpod.TableDefinition",
"name": "night",
"dartName": "Night",
"module": "halkyon_train",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 7,
"isNullable": false,
"columnDefault": "random_v7",
"dartType": "UuidValue?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "spaceId",
"fieldName": "spaceId",
"columnType": 6,
"isNullable": true,
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "keyId",
"fieldName": "keyId",
"columnType": 0,
"isNullable": true,
"dartType": "String?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "payload",
"fieldName": "payload",
"columnType": 5,
"isNullable": false,
"dartType": "dart:typed_data:ByteData"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "nonce",
"fieldName": "nonce",
"columnType": 5,
"isNullable": true,
"dartType": "dart:typed_data:ByteData?"
}
],
"foreignKeys": [
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "night_fk_0",
"columns": [
"spaceId"
],
"referenceTable": "offline_sync_spaces",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4
}
],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "night_space_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "spaceId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
}
],
"managed": true
},
{
"__className__": "serverpod.TableDefinition",
"name": "plan",
"dartName": "Plan",
"module": "halkyon_train",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 7,
"isNullable": false,
"columnDefault": "random_v7",
"dartType": "UuidValue?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "spaceId",
"fieldName": "spaceId",
"columnType": 6,
"isNullable": true,
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "keyId",
"fieldName": "keyId",
"columnType": 0,
"isNullable": true,
"dartType": "String?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "payload",
"fieldName": "payload",
"columnType": 5,
"isNullable": false,
"dartType": "dart:typed_data:ByteData"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "nonce",
"fieldName": "nonce",
"columnType": 5,
"isNullable": true,
"dartType": "dart:typed_data:ByteData?"
}
],
"foreignKeys": [
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "plan_fk_0",
"columns": [
"spaceId"
],
"referenceTable": "offline_sync_spaces",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4
}
],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "plan_space_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "spaceId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
}
],
"managed": true
},
{
"__className__": "serverpod.TableDefinition",
"name": "profile",
"dartName": "Profile",
"module": "halkyon_train",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 7,
"isNullable": false,
"columnDefault": "random_v7",
"dartType": "UuidValue?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "spaceId",
"fieldName": "spaceId",
"columnType": 6,
"isNullable": true,
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "keyId",
"fieldName": "keyId",
"columnType": 0,
"isNullable": true,
"dartType": "String?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "payload",
"fieldName": "payload",
"columnType": 5,
"isNullable": false,
"dartType": "dart:typed_data:ByteData"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "nonce",
"fieldName": "nonce",
"columnType": 5,
"isNullable": true,
"dartType": "dart:typed_data:ByteData?"
}
],
"foreignKeys": [
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "profile_fk_0",
"columns": [
"spaceId"
],
"referenceTable": "offline_sync_spaces",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4
}
],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "profile_space_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "spaceId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
}
],
"managed": true
},
{
"__className__": "serverpod.TableDefinition",
"name": "route",
"dartName": "SavedRoute",
"module": "halkyon_train",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 7,
"isNullable": false,
"columnDefault": "random_v7",
"dartType": "UuidValue?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "spaceId",
"fieldName": "spaceId",
"columnType": 6,
"isNullable": true,
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "keyId",
"fieldName": "keyId",
"columnType": 0,
"isNullable": true,
"dartType": "String?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "payload",
"fieldName": "payload",
"columnType": 5,
"isNullable": false,
"dartType": "dart:typed_data:ByteData"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "nonce",
"fieldName": "nonce",
"columnType": 5,
"isNullable": true,
"dartType": "dart:typed_data:ByteData?"
}
],
"foreignKeys": [
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "route_fk_0",
"columns": [
"spaceId"
],
"referenceTable": "offline_sync_spaces",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4
}
],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "route_space_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "spaceId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
}
],
"managed": true
},
{
"__className__": "serverpod.TableDefinition",
"name": "training_place",
"dartName": "TrainingPlace",
"module": "halkyon_train",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 7,
"isNullable": false,
"columnDefault": "random_v7",
"dartType": "UuidValue?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "spaceId",
"fieldName": "spaceId",
"columnType": 6,
"isNullable": true,
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "keyId",
"fieldName": "keyId",
"columnType": 0,
"isNullable": true,
"dartType": "String?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "payload",
"fieldName": "payload",
"columnType": 5,
"isNullable": false,
"dartType": "dart:typed_data:ByteData"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "nonce",
"fieldName": "nonce",
"columnType": 5,
"isNullable": true,
"dartType": "dart:typed_data:ByteData?"
}
],
"foreignKeys": [
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "training_place_fk_0",
"columns": [
"spaceId"
],
"referenceTable": "offline_sync_spaces",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4
}
],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "training_place_space_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "spaceId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
}
],
"managed": true
},
{
"__className__": "serverpod.TableDefinition",
"name": "workout",
"dartName": "Workout",
"module": "halkyon_train",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 7,
"isNullable": false,
"columnDefault": "random_v7",
"dartType": "UuidValue?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "spaceId",
"fieldName": "spaceId",
"columnType": 6,
"isNullable": true,
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "keyId",
"fieldName": "keyId",
"columnType": 0,
"isNullable": true,
"dartType": "String?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "payload",
"fieldName": "payload",
"columnType": 5,
"isNullable": false,
"dartType": "dart:typed_data:ByteData"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "nonce",
"fieldName": "nonce",
"columnType": 5,
"isNullable": true,
"dartType": "dart:typed_data:ByteData?"
}
],
"foreignKeys": [
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "workout_fk_0",
"columns": [
"spaceId"
],
"referenceTable": "offline_sync_spaces",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4
}
],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "workout_space_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "spaceId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
}
],
"managed": true
},
{
"__className__": "serverpod.TableDefinition",
"name": "workout_series",
"dartName": "WorkoutSeries",
"module": "halkyon_train",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 7,
"isNullable": false,
"columnDefault": "random_v7",
"dartType": "UuidValue?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "spaceId",
"fieldName": "spaceId",
"columnType": 6,
"isNullable": true,
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "workoutId",
"fieldName": "workoutId",
"columnType": 7,
"isNullable": false,
"dartType": "UuidValue"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "keyId",
"fieldName": "keyId",
"columnType": 0,
"isNullable": true,
"dartType": "String?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "payload",
"fieldName": "payload",
"columnType": 5,
"isNullable": false,
"dartType": "dart:typed_data:ByteData"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "nonce",
"fieldName": "nonce",
"columnType": 5,
"isNullable": true,
"dartType": "dart:typed_data:ByteData?"
}
],
"foreignKeys": [
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "workout_series_fk_0",
"columns": [
"spaceId"
],
"referenceTable": "offline_sync_spaces",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4
},
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "workout_series_fk_1",
"columns": [
"workoutId"
],
"referenceTable": "workout",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4,
"deferrable": "initiallyDeferred"
}
],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "workout_series_space_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "spaceId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
},
{
"__className__": "serverpod.IndexDefinition",
"indexName": "workout_series_workout_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "workoutId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
}
],
"managed": true
}
],
"installedModules": [
{
"__className__": "serverpod.DatabaseMigrationVersion",
"module": "halkyon_base",
"version": "20261026140329453-assistant-budgets"
},
{
"__className__": "serverpod.DatabaseMigrationVersion",
"module": "serverpod",
"version": "20260824182259319"
},
{
"__className__": "serverpod.DatabaseMigrationVersion",
"module": "serverpod_auth_idp",
"version": "20260924105404509"
},
{
"__className__": "serverpod.DatabaseMigrationVersion",
"module": "serverpod_offline_sync",
"version": "20260914143806119"
},
{
"__className__": "serverpod.DatabaseMigrationVersion",
"module": "halkyon_oidc",
"version": "20261023180644988"
},
{
"__className__": "serverpod.DatabaseMigrationVersion",
"module": "serverpod_auth_core",
"version": "20260924105232991"
}
],
"migrationApiVersion": 1
}

View file

@ -0,0 +1,94 @@
{
"__className__": "serverpod.DatabaseMigration",
"actions": [
{
"__className__": "serverpod.DatabaseMigrationAction",
"type": "createTable",
"createTable": {
"__className__": "serverpod.TableDefinition",
"name": "training_place",
"dartName": "TrainingPlace",
"module": "halkyon_train",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 7,
"isNullable": false,
"columnDefault": "random_v7",
"dartType": "UuidValue?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "spaceId",
"fieldName": "spaceId",
"columnType": 6,
"isNullable": true,
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "keyId",
"fieldName": "keyId",
"columnType": 0,
"isNullable": true,
"dartType": "String?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "payload",
"fieldName": "payload",
"columnType": 5,
"isNullable": false,
"dartType": "dart:typed_data:ByteData"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "nonce",
"fieldName": "nonce",
"columnType": 5,
"isNullable": true,
"dartType": "dart:typed_data:ByteData?"
}
],
"foreignKeys": [
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "training_place_fk_0",
"columns": [
"spaceId"
],
"referenceTable": "offline_sync_spaces",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4
}
],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "training_place_space_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "spaceId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
}
],
"managed": true
}
}
],
"warnings": [],
"migrationApiVersion": 1
}

View file

@ -9,6 +9,7 @@ part '20261028164640000-profile/migration.dart';
part '20261028181405893-days/migration.dart';
part '20261028211508606-plans/migration.dart';
part '20261028231042668-routes/migration.dart';
part '20261102103211357-training-places/migration.dart';
/// Migration registry for the client-side database.
class MigrationRegistry {
@ -20,6 +21,7 @@ class MigrationRegistry {
_Migration20261028181405893_days(),
_Migration20261028211508606_plans(),
_Migration20261028231042668_routes(),
_Migration20261102103211357_training_places(),
];
/// List of all client-side database migration versions.

View file

@ -0,0 +1,672 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:typed_data' as _idt;
import 'package:serverpod_client/serverpod_client.dart' as _isc;
import 'package:serverpod_database/serverpod_database.dart' as _isd;
/// A place the person trains at and its equipment (ADR 0034): its name and
/// items (bars and handles with their weight, plates and weights by weight
/// and count, benches, racks and the person's own), in its sealed payload
/// (ADR 0030, D7); the instance sees that it exists, its size and when it
/// changed. Changed when edited, the last writer winning. Synced.
abstract class TrainingPlace
implements _isd.TableRow<_isc.UuidValue?>, _isc.ProtocolSerialization {
TrainingPlace._({
this.id,
this.spaceId,
this.keyId,
required this.payload,
this.nonce,
});
factory TrainingPlace({
_isc.UuidValue? id,
int? spaceId,
String? keyId,
required _idt.ByteData payload,
_idt.ByteData? nonce,
}) = _TrainingPlaceImpl;
factory TrainingPlace.fromJson(Map<String, dynamic> jsonSerialization) {
return TrainingPlace(
id: jsonSerialization['id'] == null
? null
: _isc.UuidValueJsonExtension.fromJson(jsonSerialization['id']),
spaceId: jsonSerialization['spaceId'] as int?,
keyId: jsonSerialization['keyId'] as String?,
payload: _isc.ByteDataJsonExtension.fromJson(
jsonSerialization['payload'],
),
nonce: jsonSerialization['nonce'] == null
? null
: _isc.ByteDataJsonExtension.fromJson(jsonSerialization['nonce']),
);
}
static final t = TrainingPlaceTable();
static const db = TrainingPlaceRepository._();
@override
_isc.UuidValue? id;
/// The space owning this row. Maintained by the sync engine.
int? spaceId;
/// The key it is sealed with (ADR 0002's envelope); never null in Train.
String? keyId;
/// The content, sealed: UTF-8 JSON with a `schemaVersion` once opened.
_idt.ByteData payload;
/// The sealing's nonce (ADR 0002's envelope).
_idt.ByteData? nonce;
@override
_isd.Table<_isc.UuidValue?> get table => t;
/// Returns a shallow copy of this [TrainingPlace]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
TrainingPlace copyWith({
_isc.UuidValue? id,
int? spaceId,
String? keyId,
_idt.ByteData? payload,
_idt.ByteData? nonce,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'TrainingPlace',
if (id != null) 'id': id?.toJson(),
if (spaceId != null) 'spaceId': spaceId,
if (keyId != null) 'keyId': keyId,
'payload': payload.toJson(),
if (nonce != null) 'nonce': nonce?.toJson(),
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'TrainingPlace',
if (id != null) 'id': id?.toJson(),
if (spaceId != null) 'spaceId': spaceId,
if (keyId != null) 'keyId': keyId,
'payload': payload.toJson(),
if (nonce != null) 'nonce': nonce?.toJson(),
};
}
static TrainingPlaceInclude include() {
return TrainingPlaceInclude._();
}
static TrainingPlaceIncludeList includeList({
_isd.WhereExpressionBuilder<TrainingPlaceTable>? where,
int? limit,
int? offset,
_isd.OrderByBuilder<TrainingPlaceTable>? orderBy,
_isd.OrderByListBuilder<TrainingPlaceTable>? orderByList,
TrainingPlaceInclude? include,
}) {
return TrainingPlaceIncludeList._(
where: where,
limit: limit,
offset: offset,
orderBy: orderBy?.call(TrainingPlace.t),
orderByList: orderByList?.call(TrainingPlace.t),
include: include,
);
}
@override
String toString() {
return _isc.SerializationManager.encode(this);
}
}
class _Undefined {}
class _TrainingPlaceImpl extends TrainingPlace {
_TrainingPlaceImpl({
_isc.UuidValue? id,
int? spaceId,
String? keyId,
required _idt.ByteData payload,
_idt.ByteData? nonce,
}) : super._(
id: id,
spaceId: spaceId,
keyId: keyId,
payload: payload,
nonce: nonce,
);
/// Returns a shallow copy of this [TrainingPlace]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@override
TrainingPlace copyWith({
Object? id = _Undefined,
Object? spaceId = _Undefined,
Object? keyId = _Undefined,
_idt.ByteData? payload,
Object? nonce = _Undefined,
}) {
return TrainingPlace(
id: id is _isc.UuidValue? ? id : this.id,
spaceId: spaceId is int? ? spaceId : this.spaceId,
keyId: keyId is String? ? keyId : this.keyId,
payload: payload ?? this.payload.clone(),
nonce: nonce is _idt.ByteData? ? nonce : this.nonce?.clone(),
);
}
}
class TrainingPlaceUpdateTable extends _isd.UpdateTable<TrainingPlaceTable> {
TrainingPlaceUpdateTable(super.table);
_isd.ColumnValue<int, int> spaceId(int? value) => _isd.ColumnValue(
table.spaceId,
value,
);
_isd.ColumnValue<String, String> keyId(String? value) => _isd.ColumnValue(
table.keyId,
value,
);
_isd.ColumnValue<_idt.ByteData, _idt.ByteData> payload(_idt.ByteData value) =>
_isd.ColumnValue(
table.payload,
value,
);
_isd.ColumnValue<_idt.ByteData, _idt.ByteData> nonce(_idt.ByteData? value) =>
_isd.ColumnValue(
table.nonce,
value,
);
}
class TrainingPlaceTable extends _isd.Table<_isc.UuidValue?> {
TrainingPlaceTable({super.tableRelation})
: super(tableName: 'training_place') {
updateTable = TrainingPlaceUpdateTable(this);
spaceId = _isd.ColumnInt(
'spaceId',
this,
);
keyId = _isd.ColumnString(
'keyId',
this,
);
payload = _isd.ColumnByteData(
'payload',
this,
);
nonce = _isd.ColumnByteData(
'nonce',
this,
);
}
late final TrainingPlaceUpdateTable updateTable;
/// The space owning this row. Maintained by the sync engine.
late final _isd.ColumnInt spaceId;
/// The key it is sealed with (ADR 0002's envelope); never null in Train.
late final _isd.ColumnString keyId;
/// The content, sealed: UTF-8 JSON with a `schemaVersion` once opened.
late final _isd.ColumnByteData payload;
/// The sealing's nonce (ADR 0002's envelope).
late final _isd.ColumnByteData nonce;
@override
List<_isd.Column> get columns => [
id,
spaceId,
keyId,
payload,
nonce,
];
}
class TrainingPlaceInclude extends _isd.IncludeObject {
TrainingPlaceInclude._();
@override
Map<String, _isd.Include?> get includes => {};
@override
_isd.Table<_isc.UuidValue?> get table => TrainingPlace.t;
}
class TrainingPlaceIncludeList extends _isd.IncludeList {
TrainingPlaceIncludeList._({
_isd.WhereExpressionBuilder<TrainingPlaceTable>? where,
super.limit,
super.offset,
super.orderBy,
super.orderByList,
super.include,
}) {
super.where = where?.call(TrainingPlace.t);
}
@override
Map<String, _isd.Include?> get includes => include?.includes ?? {};
@override
_isd.Table<_isc.UuidValue?> get table => TrainingPlace.t;
}
class TrainingPlaceRepository {
const TrainingPlaceRepository._();
/// Returns a list of [TrainingPlace]s matching the given query parameters.
///
/// Use [where] to specify which items to include in the return value.
/// If none is specified, all items will be returned.
///
/// To specify the order of the items use [orderBy] or [orderByList]
/// when sorting by multiple columns.
///
/// The maximum number of items can be set by [limit]. If no limit is set,
/// all items matching the query will be returned.
///
/// [offset] defines how many items to skip, after which [limit] (or all)
/// items are read from the database.
///
/// ```dart
/// var persons = await Persons.db.find(
/// session,
/// where: (t) => t.lastName.equals('Jones'),
/// orderBy: (t) => t.firstName,
/// limit: 100,
/// );
/// ```
Future<List<TrainingPlace>> find(
_isd.DatabaseSession session, {
_isd.WhereExpressionBuilder<TrainingPlaceTable>? where,
int? limit,
int? offset,
_isd.OrderByBuilder<TrainingPlaceTable>? orderBy,
_isd.OrderByListBuilder<TrainingPlaceTable>? orderByList,
_isd.Transaction? transaction,
_isd.LockMode? lockMode,
_isd.LockBehavior? lockBehavior,
}) async {
return session.db.find<TrainingPlace>(
where: where?.call(TrainingPlace.t),
orderBy: orderBy?.call(TrainingPlace.t),
orderByList: orderByList?.call(TrainingPlace.t),
limit: limit,
offset: offset,
transaction: transaction,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Returns the first matching [TrainingPlace] matching the given query parameters.
///
/// Use [where] to specify which items to include in the return value.
/// If none is specified, all items will be returned.
///
/// To specify the order use [orderBy] or [orderByList]
/// when sorting by multiple columns.
///
/// [offset] defines how many items to skip, after which the next one will be picked.
///
/// ```dart
/// var youngestPerson = await Persons.db.findFirstRow(
/// session,
/// where: (t) => t.lastName.equals('Jones'),
/// orderBy: (t) => t.age,
/// );
/// ```
Future<TrainingPlace?> findFirstRow(
_isd.DatabaseSession session, {
_isd.WhereExpressionBuilder<TrainingPlaceTable>? where,
int? offset,
_isd.OrderByBuilder<TrainingPlaceTable>? orderBy,
_isd.OrderByListBuilder<TrainingPlaceTable>? orderByList,
_isd.Transaction? transaction,
_isd.LockMode? lockMode,
_isd.LockBehavior? lockBehavior,
}) async {
return session.db.findFirstRow<TrainingPlace>(
where: where?.call(TrainingPlace.t),
orderBy: orderBy?.call(TrainingPlace.t),
orderByList: orderByList?.call(TrainingPlace.t),
offset: offset,
transaction: transaction,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Finds a single [TrainingPlace] by its [id] or null if no such row exists.
Future<TrainingPlace?> findById(
_isd.DatabaseSession session,
_isc.UuidValue id, {
_isd.Transaction? transaction,
_isd.LockMode? lockMode,
_isd.LockBehavior? lockBehavior,
}) async {
return session.db.findById<TrainingPlace>(
id,
transaction: transaction,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Inserts all [TrainingPlace]s in the list and returns the inserted rows.
///
/// The returned [TrainingPlace]s will have their `id` fields set.
///
/// This is an atomic operation, meaning that if one of the rows fails to
/// insert, none of the rows will be inserted.
///
/// If [ignoreConflicts] is set to `true`, rows that conflict with existing
/// rows are silently skipped, and only the successfully inserted rows are
/// returned.
///
/// If [noReturn] is set to `true`, the inserted rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<TrainingPlace>> insert(
_isd.DatabaseSession session,
List<TrainingPlace> rows, {
_isd.Transaction? transaction,
bool ignoreConflicts = false,
bool noReturn = false,
}) async {
return session.db.insert<TrainingPlace>(
rows,
transaction: transaction,
ignoreConflicts: ignoreConflicts,
noReturn: noReturn,
);
}
/// Inserts a single [TrainingPlace] and returns the inserted row.
///
/// The returned [TrainingPlace] will have its `id` field set.
Future<TrainingPlace> insertRow(
_isd.DatabaseSession session,
TrainingPlace row, {
_isd.Transaction? transaction,
}) async {
return session.db.insertRow<TrainingPlace>(
row,
transaction: transaction,
);
}
/// Upserts all [TrainingPlace]s in the list and returns the resulting rows.
///
/// If a row conflicts on the given [conflictColumns], the existing row is
/// updated with the new values. Otherwise, a new row is inserted.
///
/// If [updateColumns] is provided, only those columns will be updated on
/// conflict. If null, all non-conflict, non-id columns are updated.
///
/// If [updateWhere] is provided, the update only applies to rows matching the
/// given expression. Conflicting rows that don't match are skipped and not
/// returned, so the resulting list may be shorter than [rows].
///
/// The returned [TrainingPlace]s will have their `id` fields set.
///
/// This is an atomic operation, meaning that if one of the rows fails,
/// none of the rows will be affected.
///
/// If [noReturn] is set to `true`, the resulting rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<TrainingPlace>> upsert(
_isd.DatabaseSession session,
List<TrainingPlace> rows, {
required _isd.ColumnSelections<TrainingPlaceTable> conflictColumns,
_isd.ColumnSelections<TrainingPlaceTable>? updateColumns,
_isd.WhereExpressionBuilder<TrainingPlaceTable>? updateWhere,
_isd.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.upsert<TrainingPlace>(
rows,
conflictColumns: conflictColumns(TrainingPlace.t),
updateColumns: updateColumns?.call(TrainingPlace.t),
updateWhere: updateWhere?.call(TrainingPlace.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Upserts a single [TrainingPlace] and returns the resulting row.
///
/// If the row conflicts on the given [conflictColumns], the existing row is
/// updated. Otherwise, a new row is inserted.
///
/// If [updateColumns] is provided, only those columns will be updated on
/// conflict. If null, all non-conflict, non-id columns are updated.
///
/// If [updateWhere] is provided, the update only applies when the existing
/// row matches the expression. Returns `null` if no row was affected — for
/// example when [updateWhere] does not match the conflicting row.
///
/// The returned [TrainingPlace] will have its `id` field set.
Future<TrainingPlace?> upsertRow(
_isd.DatabaseSession session,
TrainingPlace row, {
required _isd.ColumnSelections<TrainingPlaceTable> conflictColumns,
_isd.ColumnSelections<TrainingPlaceTable>? updateColumns,
_isd.WhereExpressionBuilder<TrainingPlaceTable>? updateWhere,
_isd.Transaction? transaction,
}) async {
return session.db.upsertRow<TrainingPlace>(
row,
conflictColumns: conflictColumns(TrainingPlace.t),
updateColumns: updateColumns?.call(TrainingPlace.t),
updateWhere: updateWhere?.call(TrainingPlace.t),
transaction: transaction,
);
}
/// Updates all [TrainingPlace]s in the list and returns the updated rows. If
/// [columns] is provided, only those columns will be updated. Defaults to
/// all columns.
/// This is an atomic operation, meaning that if one of the rows fails to
/// update, none of the rows will be updated.
///
/// If [noReturn] is set to `true`, the updated rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<TrainingPlace>> update(
_isd.DatabaseSession session,
List<TrainingPlace> rows, {
_isd.ColumnSelections<TrainingPlaceTable>? columns,
_isd.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.update<TrainingPlace>(
rows,
columns: columns?.call(TrainingPlace.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Updates a single [TrainingPlace]. The row needs to have its id set.
/// Optionally, a list of [columns] can be provided to only update those
/// columns. Defaults to all columns.
Future<TrainingPlace> updateRow(
_isd.DatabaseSession session,
TrainingPlace row, {
_isd.ColumnSelections<TrainingPlaceTable>? columns,
_isd.Transaction? transaction,
}) async {
return session.db.updateRow<TrainingPlace>(
row,
columns: columns?.call(TrainingPlace.t),
transaction: transaction,
);
}
/// Updates a single [TrainingPlace] by its [id] with the specified [columnValues].
/// Returns the updated row or null if no row with the given id exists.
Future<TrainingPlace?> updateById(
_isd.DatabaseSession session,
_isc.UuidValue id, {
required _isd.ColumnValueListBuilder<TrainingPlaceUpdateTable> columnValues,
_isd.Transaction? transaction,
}) async {
return session.db.updateById<TrainingPlace>(
id,
columnValues: columnValues(TrainingPlace.t.updateTable),
transaction: transaction,
);
}
/// Updates all [TrainingPlace]s matching the [where] expression with the specified [columnValues].
/// Returns the list of updated rows.
///
/// If [noReturn] is set to `true`, the updated rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<TrainingPlace>> updateWhere(
_isd.DatabaseSession session, {
required _isd.ColumnValueListBuilder<TrainingPlaceUpdateTable> columnValues,
required _isd.WhereExpressionBuilder<TrainingPlaceTable> where,
int? limit,
int? offset,
_isd.OrderByBuilder<TrainingPlaceTable>? orderBy,
_isd.OrderByListBuilder<TrainingPlaceTable>? orderByList,
_isd.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.updateWhere<TrainingPlace>(
columnValues: columnValues(TrainingPlace.t.updateTable),
where: where(TrainingPlace.t),
limit: limit,
offset: offset,
orderBy: orderBy?.call(TrainingPlace.t),
orderByList: orderByList?.call(TrainingPlace.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Deletes all [TrainingPlace]s in the list and returns the deleted rows.
///
/// To specify the order of the returned rows use [orderBy] or [orderByList]
/// when sorting by multiple columns.
///
/// This is an atomic operation, meaning that if one of the rows fail to
/// be deleted, none of the rows will be deleted.
///
/// If [noReturn] is set to `true`, the deleted rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<TrainingPlace>> delete(
_isd.DatabaseSession session,
List<TrainingPlace> rows, {
_isd.OrderByBuilder<TrainingPlaceTable>? orderBy,
_isd.OrderByListBuilder<TrainingPlaceTable>? orderByList,
_isd.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.delete<TrainingPlace>(
rows,
orderBy: orderBy?.call(TrainingPlace.t),
orderByList: orderByList?.call(TrainingPlace.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Deletes a single [TrainingPlace].
Future<TrainingPlace> deleteRow(
_isd.DatabaseSession session,
TrainingPlace row, {
_isd.Transaction? transaction,
}) async {
return session.db.deleteRow<TrainingPlace>(
row,
transaction: transaction,
);
}
/// Deletes all rows matching the [where] expression.
///
/// To specify the order of the returned rows use [orderBy] or [orderByList]
/// when sorting by multiple columns.
///
/// If [noReturn] is set to `true`, the deleted rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<TrainingPlace>> deleteWhere(
_isd.DatabaseSession session, {
required _isd.WhereExpressionBuilder<TrainingPlaceTable> where,
_isd.OrderByBuilder<TrainingPlaceTable>? orderBy,
_isd.OrderByListBuilder<TrainingPlaceTable>? orderByList,
_isd.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.deleteWhere<TrainingPlace>(
where: where(TrainingPlace.t),
orderBy: orderBy?.call(TrainingPlace.t),
orderByList: orderByList?.call(TrainingPlace.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Counts the number of rows matching the [where] expression. If omitted,
/// will return the count of all rows in the table.
Future<int> count(
_isd.DatabaseSession session, {
_isd.WhereExpressionBuilder<TrainingPlaceTable>? where,
int? limit,
_isd.Transaction? transaction,
}) async {
return session.db.count<TrainingPlace>(
where: where?.call(TrainingPlace.t),
limit: limit,
transaction: transaction,
);
}
/// Acquires row-level locks on [TrainingPlace] rows matching the [where] expression.
Future<void> lockRows(
_isd.DatabaseSession session, {
required _isd.WhereExpressionBuilder<TrainingPlaceTable> where,
required _isd.LockMode lockMode,
required _isd.Transaction transaction,
_isd.LockBehavior lockBehavior = _isd.LockBehavior.wait,
}) async {
return session.db.lockRows<TrainingPlace>(
where: where(TrainingPlace.t),
lockMode: lockMode,
lockBehavior: lockBehavior,
transaction: transaction,
);
}
}

View file

@ -28,6 +28,7 @@ import 'package:serverpod_offline_sync_client/serverpod_offline_sync_client.dart
import 'day/day.dart' as _ikmv9f3y;
import 'day/day_series.dart' as _ir1ofny6;
import 'day/night.dart' as _i17cgxdf;
import 'equipment/training_place.dart' as _icbb9j80;
import 'firmware/loop_firmware.dart' as _i4qollgq;
import 'firmware/loop_firmware_refused.dart' as _ipcwdy0l;
import 'map/map_source.dart' as _ikw0qg8x;
@ -48,6 +49,7 @@ import 'workout/workout_series.dart' as _i92mefto;
export 'day/day.dart';
export 'day/day_series.dart';
export 'day/night.dart';
export 'equipment/training_place.dart';
export 'firmware/loop_firmware.dart';
export 'firmware/loop_firmware_refused.dart';
export 'map/map_source.dart';
@ -569,6 +571,73 @@ class Protocol extends _isd.DatabaseSerializationManager {
],
managed: true,
),
_isd.TableDefinition(
name: 'training_place',
dartName: 'TrainingPlace',
schema: 'public',
module: 'halkyon_train',
columns: [
_isd.ColumnDefinition(
name: 'id',
columnType: _isd.ColumnType.uuid,
isNullable: false,
dartType: 'UuidValue?',
columnDefault: 'random_v7',
),
_isd.ColumnDefinition(
name: 'spaceId',
columnType: _isd.ColumnType.bigint,
isNullable: true,
dartType: 'int?',
),
_isd.ColumnDefinition(
name: 'keyId',
columnType: _isd.ColumnType.text,
isNullable: true,
dartType: 'String?',
),
_isd.ColumnDefinition(
name: 'payload',
columnType: _isd.ColumnType.bytea,
isNullable: false,
dartType: 'dart:typed_data:ByteData',
),
_isd.ColumnDefinition(
name: 'nonce',
columnType: _isd.ColumnType.bytea,
isNullable: true,
dartType: 'dart:typed_data:ByteData?',
),
],
foreignKeys: [
_isd.ForeignKeyDefinition(
constraintName: 'training_place_fk_0',
columns: ['spaceId'],
referenceTable: 'offline_sync_spaces',
referenceTableSchema: 'public',
referenceColumns: ['id'],
onUpdate: _isd.ForeignKeyAction.noAction,
onDelete: _isd.ForeignKeyAction.cascade,
matchType: null,
),
],
indexes: [
_isd.IndexDefinition(
indexName: 'training_place_space_idx',
tableSpace: null,
elements: [
_isd.IndexElementDefinition(
type: _isd.IndexElementDefinitionType.column,
definition: 'spaceId',
),
],
type: 'btree',
isUnique: false,
isPrimary: false,
),
],
managed: true,
),
_isd.TableDefinition(
name: 'workout',
dartName: 'Workout',
@ -791,6 +860,9 @@ class Protocol extends _isd.DatabaseSerializationManager {
if (t == _i17cgxdf.Night) {
return _i17cgxdf.Night.fromJson(data) as T;
}
if (t == _icbb9j80.TrainingPlace) {
return _icbb9j80.TrainingPlace.fromJson(data) as T;
}
if (t == _i4qollgq.LoopFirmware) {
return _i4qollgq.LoopFirmware.fromJson(data) as T;
}
@ -851,6 +923,10 @@ class Protocol extends _isd.DatabaseSerializationManager {
if (t == _isc.getType<_i17cgxdf.Night?>()) {
return (data != null ? _i17cgxdf.Night.fromJson(data) : null) as T;
}
if (t == _isc.getType<_icbb9j80.TrainingPlace?>()) {
return (data != null ? _icbb9j80.TrainingPlace.fromJson(data) : null)
as T;
}
if (t == _isc.getType<_i4qollgq.LoopFirmware?>()) {
return (data != null ? _i4qollgq.LoopFirmware.fromJson(data) : null) as T;
}
@ -961,6 +1037,7 @@ class Protocol extends _isd.DatabaseSerializationManager {
_ikmv9f3y.Day => 'Day',
_ir1ofny6.DaySeries => 'DaySeries',
_i17cgxdf.Night => 'Night',
_icbb9j80.TrainingPlace => 'TrainingPlace',
_i4qollgq.LoopFirmware => 'LoopFirmware',
_ipcwdy0l.LoopFirmwareRefused => 'LoopFirmwareRefused',
_ikw0qg8x.MapSource => 'MapSource',
@ -1001,6 +1078,8 @@ class Protocol extends _isd.DatabaseSerializationManager {
return 'DaySeries';
case _i17cgxdf.Night():
return 'Night';
case _icbb9j80.TrainingPlace():
return 'TrainingPlace';
case _i4qollgq.LoopFirmware():
return 'LoopFirmware';
case _ipcwdy0l.LoopFirmwareRefused():
@ -1080,6 +1159,9 @@ class Protocol extends _isd.DatabaseSerializationManager {
if (dataClassName == 'Night') {
return deserialize<_i17cgxdf.Night>(data['data']);
}
if (dataClassName == 'TrainingPlace') {
return deserialize<_icbb9j80.TrainingPlace>(data['data']);
}
if (dataClassName == 'LoopFirmware') {
return deserialize<_i4qollgq.LoopFirmware>(data['data']);
}
@ -1216,6 +1298,8 @@ class Protocol extends _isd.DatabaseSerializationManager {
return _ir1ofny6.DaySeries.t;
case _i17cgxdf.Night:
return _i17cgxdf.Night.t;
case _icbb9j80.TrainingPlace:
return _icbb9j80.TrainingPlace.t;
case _i45prw73.Plan:
return _i45prw73.Plan.t;
case _imed3x5d.ContentKey:

View file

@ -14,6 +14,7 @@ import 'package:serverpod_database/serverpod_database.dart' as _isd;
import 'day/day.dart' as _ikmv9f3y;
import 'day/day_series.dart' as _ir1ofny6;
import 'day/night.dart' as _i17cgxdf;
import 'equipment/training_place.dart' as _icbb9j80;
import 'plan/plan.dart' as _i45prw73;
import 'privacy/content_key.dart' as _imed3x5d;
import 'profile/profile.dart' as _ia5j8fch;
@ -32,6 +33,7 @@ final List<_isd.Table> syncTables = [
_i45prw73.Plan.t,
_ia5j8fch.Profile.t,
_i0mgeh8t.SavedRoute.t,
_icbb9j80.TrainingPlace.t,
_i5b9b17z.Workout.t,
_i92mefto.WorkoutSeries.t,
];

View file

@ -580,5 +580,59 @@
"bodyWeight": "{kg} kg",
"bodyResting": "Ruhepuls {bpm}",
"bodyMax": "Maximalpuls {bpm}",
"homeToday": "Heute"
"homeToday": "Heute",
"equipmentTitle": "Ausrüstung",
"equipmentHint": "Wo du trainierst und was es dort gibt",
"equipmentEmptyTitle": "Noch keine Orte",
"equipmentEmptyHint": "Füge die Orte hinzu, an denen du trainierst, etwa dein Zuhause und dein Studio, mit dem, was es dort gibt. Beginne mit dem, was ein Zuhause oder ein Studio meist hat, oder mit nichts.",
"equipmentAddPlace": "Ort hinzufügen",
"equipmentItems": "{count, plural, =0{Noch nichts} =1{1 Ausrüstungsstück} other{{count} Ausrüstungsstücke}}",
"equipmentChosen": "Hier trainierst du gerade",
"equipmentChoose": "Hier trainieren",
"equipmentLocked": "Lässt sich auf diesem Gerät nicht öffnen",
"placeNewTitle": "Neuer Ort",
"placeName": "Name",
"placeStartFrom": "Beginnen mit",
"placeStartHome": "Zuhause",
"placeStartGym": "Studio",
"placeStartNothing": "Nichts",
"placeDefaultHome": "Zuhause",
"placeDefaultGym": "Studio",
"placeAdd": "Hinzufügen",
"placeRename": "Umbenennen",
"placeSave": "Speichern",
"placeDelete": "Ort löschen",
"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}}",
"itemName": "Was es ist",
"itemHowMany": "Wie viele",
"itemOwnWeight": "Eigengewicht",
"itemWeights": "Gewichte",
"itemAddWeight": "Gewicht hinzufügen",
"itemDone": "Fertig",
"itemRemove": "Entfernen",
"itemWeightInvalid": "Ein Gewicht in Kilogramm, über 0",
"weightKg": "{weight} kg",
"weightCount": "{count} × {weight} kg",
"plateCalculatorTitle": "Scheibenrechner",
"plateCalculatorHint": "Was du auflegst und was sich zwischen den Sätzen ändert",
"platePlace": "Ort",
"plateBar": "Stange",
"plateDumbbellPair": "Zwei Kurzhantelgriffe",
"plateTargets": "Gewichte, kg",
"plateTargetsHelper": "Eins für jeden Satz, etwa 60, 80, 100",
"plateNoPlace": "Füge zuerst einen Ort hinzu, an dem du trainierst, mit seinen Scheiben.",
"plateNoBar": "An diesem Ort gibt es nichts, worauf Scheiben passen. Füge eine Langhantel oder Kurzhantelgriffe hinzu.",
"plateBelowBar": "Unter den {weight} kg der Stange",
"plateShort": "{weight} kg unter deinem Ziel: Näher kommen die Scheiben nicht",
"plateSide": "Jede Seite, von innen: {plates}",
"plateBarAlone": "Nur die Stange",
"plateOff": "Nimm ab: {plates}",
"plateOn": "Leg auf: {plates}",
"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}"
}

View file

@ -734,5 +734,72 @@
"@bodyResting": {"placeholders": {"bpm": {"type": "int"}}},
"bodyMax": "max {bpm} bpm",
"@bodyMax": {"placeholders": {"bpm": {"type": "int"}}},
"homeToday": "Today"
"homeToday": "Today",
"equipmentTitle": "Equipment",
"@equipmentTitle": {"description": "Settings' entry and the screen of the places the person trains at, with what each has (ADR 0034)."},
"equipmentHint": "Where you train, and what's there",
"equipmentEmptyTitle": "No places yet",
"equipmentEmptyHint": "Add the places you train at, such as your home and your gym, with what each has. Start from what a home or a gym usually has, or from nothing.",
"equipmentAddPlace": "Add a place",
"equipmentItems": "{count, plural, =0{Nothing yet} =1{1 piece of equipment} other{{count} pieces of equipment}}",
"@equipmentItems": {"placeholders": {"count": {"type": "int"}}},
"equipmentChosen": "Where you train now",
"@equipmentChosen": {"description": "Marks the place chosen, which the plate calculator and plans use (ADR 0034, D4)."},
"equipmentChoose": "Train here",
"equipmentLocked": "Can't be opened on this device",
"placeNewTitle": "New place",
"placeName": "Name",
"placeStartFrom": "Start from",
"placeStartHome": "A home",
"placeStartGym": "A gym",
"placeStartNothing": "Nothing",
"placeDefaultHome": "Home",
"placeDefaultGym": "Gym",
"placeAdd": "Add",
"placeRename": "Rename",
"placeSave": "Save",
"placeDelete": "Delete place",
"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": {"placeholders": {"kind": {"type": "String"}}},
"itemName": "What it is",
"itemHowMany": "How many",
"itemOwnWeight": "Its own weight",
"itemWeights": "Weights",
"itemAddWeight": "Add a weight",
"itemDone": "Done",
"itemRemove": "Remove",
"itemWeightInvalid": "A weight in kilograms, above 0",
"weightKg": "{weight} kg",
"@weightKg": {"placeholders": {"weight": {"type": "String"}}},
"weightCount": "{count} × {weight} kg",
"@weightCount": {"placeholders": {"count": {"type": "int"}, "weight": {"type": "String"}}},
"plateCalculatorTitle": "Plate calculator",
"plateCalculatorHint": "What to load, and what changes between sets",
"platePlace": "Place",
"plateBar": "Bar",
"plateDumbbellPair": "Two dumbbell handles",
"plateTargets": "Weights, kg",
"plateTargetsHelper": "One for each set, such as 60, 80, 100",
"plateNoPlace": "Add a place you train at, with its plates, first.",
"plateNoBar": "This place has nothing that takes plates. Add a barbell or dumbbell handles to it.",
"plateBelowBar": "Below the bar's {weight} kg",
"@plateBelowBar": {"placeholders": {"weight": {"type": "String"}}},
"plateShort": "{weight} kg below what you asked for: the plates make no closer weight",
"@plateShort": {"placeholders": {"weight": {"type": "String"}}},
"plateSide": "Each side, from the inside: {plates}",
"@plateSide": {"placeholders": {"plates": {"type": "String"}}},
"plateBarAlone": "The bar alone",
"plateOff": "Take off {plates}",
"@plateOff": {"placeholders": {"plates": {"type": "String"}}},
"plateOn": "Put on {plates}",
"@plateOn": {"placeholders": {"plates": {"type": "String"}}},
"plateNoChange": "Nothing changes",
"plateMoves": "{count, plural, =0{No plate moves} =1{1 plate moves on each side} other{{count} plates move on each side}}",
"@plateMoves": {"placeholders": {"count": {"type": "int"}}},
"plateSet": "Set {number}",
"@plateSet": {"placeholders": {"number": {"type": "int"}}}
}

View file

@ -3507,6 +3507,324 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'Today'**
String get homeToday;
/// Settings' entry and the screen of the places the person trains at, with what each has (ADR 0034).
///
/// In en, this message translates to:
/// **'Equipment'**
String get equipmentTitle;
/// No description provided for @equipmentHint.
///
/// In en, this message translates to:
/// **'Where you train, and what\'s there'**
String get equipmentHint;
/// No description provided for @equipmentEmptyTitle.
///
/// In en, this message translates to:
/// **'No places yet'**
String get equipmentEmptyTitle;
/// No description provided for @equipmentEmptyHint.
///
/// In en, this message translates to:
/// **'Add the places you train at, such as your home and your gym, with what each has. Start from what a home or a gym usually has, or from nothing.'**
String get equipmentEmptyHint;
/// No description provided for @equipmentAddPlace.
///
/// In en, this message translates to:
/// **'Add a place'**
String get equipmentAddPlace;
/// No description provided for @equipmentItems.
///
/// In en, this message translates to:
/// **'{count, plural, =0{Nothing yet} =1{1 piece of equipment} other{{count} pieces of equipment}}'**
String equipmentItems(int count);
/// Marks the place chosen, which the plate calculator and plans use (ADR 0034, D4).
///
/// In en, this message translates to:
/// **'Where you train now'**
String get equipmentChosen;
/// No description provided for @equipmentChoose.
///
/// In en, this message translates to:
/// **'Train here'**
String get equipmentChoose;
/// No description provided for @equipmentLocked.
///
/// In en, this message translates to:
/// **'Can\'t be opened on this device'**
String get equipmentLocked;
/// No description provided for @placeNewTitle.
///
/// In en, this message translates to:
/// **'New place'**
String get placeNewTitle;
/// No description provided for @placeName.
///
/// In en, this message translates to:
/// **'Name'**
String get placeName;
/// No description provided for @placeStartFrom.
///
/// In en, this message translates to:
/// **'Start from'**
String get placeStartFrom;
/// No description provided for @placeStartHome.
///
/// In en, this message translates to:
/// **'A home'**
String get placeStartHome;
/// No description provided for @placeStartGym.
///
/// In en, this message translates to:
/// **'A gym'**
String get placeStartGym;
/// No description provided for @placeStartNothing.
///
/// In en, this message translates to:
/// **'Nothing'**
String get placeStartNothing;
/// No description provided for @placeDefaultHome.
///
/// In en, this message translates to:
/// **'Home'**
String get placeDefaultHome;
/// No description provided for @placeDefaultGym.
///
/// In en, this message translates to:
/// **'Gym'**
String get placeDefaultGym;
/// No description provided for @placeAdd.
///
/// In en, this message translates to:
/// **'Add'**
String get placeAdd;
/// No description provided for @placeRename.
///
/// In en, this message translates to:
/// **'Rename'**
String get placeRename;
/// No description provided for @placeSave.
///
/// In en, this message translates to:
/// **'Save'**
String get placeSave;
/// No description provided for @placeDelete.
///
/// In en, this message translates to:
/// **'Delete place'**
String get placeDelete;
/// No description provided for @placeDeleteMessage.
///
/// In en, this message translates to:
/// **'The place and its equipment are deleted on all your devices.'**
String get placeDeleteMessage;
/// No description provided for @placeAddEquipment.
///
/// In en, this message translates to:
/// **'Add equipment'**
String get placeAddEquipment;
/// No description provided for @placeEmptyHint.
///
/// In en, this message translates to:
/// **'Nothing here yet. Add the equipment this place has.'**
String get placeEmptyHint;
/// 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}}'**
String equipmentKind(String kind);
/// No description provided for @itemName.
///
/// In en, this message translates to:
/// **'What it is'**
String get itemName;
/// No description provided for @itemHowMany.
///
/// In en, this message translates to:
/// **'How many'**
String get itemHowMany;
/// No description provided for @itemOwnWeight.
///
/// In en, this message translates to:
/// **'Its own weight'**
String get itemOwnWeight;
/// No description provided for @itemWeights.
///
/// In en, this message translates to:
/// **'Weights'**
String get itemWeights;
/// No description provided for @itemAddWeight.
///
/// In en, this message translates to:
/// **'Add a weight'**
String get itemAddWeight;
/// No description provided for @itemDone.
///
/// In en, this message translates to:
/// **'Done'**
String get itemDone;
/// No description provided for @itemRemove.
///
/// In en, this message translates to:
/// **'Remove'**
String get itemRemove;
/// No description provided for @itemWeightInvalid.
///
/// In en, this message translates to:
/// **'A weight in kilograms, above 0'**
String get itemWeightInvalid;
/// No description provided for @weightKg.
///
/// In en, this message translates to:
/// **'{weight} kg'**
String weightKg(String weight);
/// No description provided for @weightCount.
///
/// In en, this message translates to:
/// **'{count} × {weight} kg'**
String weightCount(int count, String weight);
/// No description provided for @plateCalculatorTitle.
///
/// In en, this message translates to:
/// **'Plate calculator'**
String get plateCalculatorTitle;
/// No description provided for @plateCalculatorHint.
///
/// In en, this message translates to:
/// **'What to load, and what changes between sets'**
String get plateCalculatorHint;
/// No description provided for @platePlace.
///
/// In en, this message translates to:
/// **'Place'**
String get platePlace;
/// No description provided for @plateBar.
///
/// In en, this message translates to:
/// **'Bar'**
String get plateBar;
/// No description provided for @plateDumbbellPair.
///
/// In en, this message translates to:
/// **'Two dumbbell handles'**
String get plateDumbbellPair;
/// No description provided for @plateTargets.
///
/// In en, this message translates to:
/// **'Weights, kg'**
String get plateTargets;
/// No description provided for @plateTargetsHelper.
///
/// In en, this message translates to:
/// **'One for each set, such as 60, 80, 100'**
String get plateTargetsHelper;
/// No description provided for @plateNoPlace.
///
/// In en, this message translates to:
/// **'Add a place you train at, with its plates, first.'**
String get plateNoPlace;
/// No description provided for @plateNoBar.
///
/// In en, this message translates to:
/// **'This place has nothing that takes plates. Add a barbell or dumbbell handles to it.'**
String get plateNoBar;
/// No description provided for @plateBelowBar.
///
/// In en, this message translates to:
/// **'Below the bar\'s {weight} kg'**
String plateBelowBar(String weight);
/// No description provided for @plateShort.
///
/// In en, this message translates to:
/// **'{weight} kg below what you asked for: the plates make no closer weight'**
String plateShort(String weight);
/// No description provided for @plateSide.
///
/// In en, this message translates to:
/// **'Each side, from the inside: {plates}'**
String plateSide(String plates);
/// No description provided for @plateBarAlone.
///
/// In en, this message translates to:
/// **'The bar alone'**
String get plateBarAlone;
/// No description provided for @plateOff.
///
/// In en, this message translates to:
/// **'Take off {plates}'**
String plateOff(String plates);
/// No description provided for @plateOn.
///
/// In en, this message translates to:
/// **'Put on {plates}'**
String plateOn(String plates);
/// No description provided for @plateNoChange.
///
/// In en, this message translates to:
/// **'Nothing changes'**
String get plateNoChange;
/// No description provided for @plateMoves.
///
/// In en, this message translates to:
/// **'{count, plural, =0{No plate moves} =1{1 plate moves on each side} other{{count} plates move on each side}}'**
String plateMoves(int count);
/// No description provided for @plateSet.
///
/// In en, this message translates to:
/// **'Set {number}'**
String plateSet(int number);
}
class _AppLocalizationsDelegate

View file

@ -2112,4 +2112,224 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get homeToday => 'Heute';
@override
String get equipmentTitle => 'Ausrüstung';
@override
String get equipmentHint => 'Wo du trainierst und was es dort gibt';
@override
String get equipmentEmptyTitle => 'Noch keine Orte';
@override
String get equipmentEmptyHint =>
'Füge die Orte hinzu, an denen du trainierst, etwa dein Zuhause und dein Studio, mit dem, was es dort gibt. Beginne mit dem, was ein Zuhause oder ein Studio meist hat, oder mit nichts.';
@override
String get equipmentAddPlace => 'Ort hinzufügen';
@override
String equipmentItems(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count Ausrüstungsstücke',
one: '1 Ausrüstungsstück',
zero: 'Noch nichts',
);
return '$_temp0';
}
@override
String get equipmentChosen => 'Hier trainierst du gerade';
@override
String get equipmentChoose => 'Hier trainieren';
@override
String get equipmentLocked => 'Lässt sich auf diesem Gerät nicht öffnen';
@override
String get placeNewTitle => 'Neuer Ort';
@override
String get placeName => 'Name';
@override
String get placeStartFrom => 'Beginnen mit';
@override
String get placeStartHome => 'Zuhause';
@override
String get placeStartGym => 'Studio';
@override
String get placeStartNothing => 'Nichts';
@override
String get placeDefaultHome => 'Zuhause';
@override
String get placeDefaultGym => 'Studio';
@override
String get placeAdd => 'Hinzufügen';
@override
String get placeRename => 'Umbenennen';
@override
String get placeSave => 'Speichern';
@override
String get placeDelete => 'Ort löschen';
@override
String get placeDeleteMessage =>
'Der Ort und seine Ausrüstung werden auf all deinen Geräten gelöscht.';
@override
String get placeAddEquipment => 'Ausrüstung hinzufügen';
@override
String get placeEmptyHint =>
'Hier ist noch nichts. Füge die Ausrüstung hinzu, die es an diesem Ort gibt.';
@override
String equipmentKind(String kind) {
String _temp0 = intl.Intl.selectLogic(
kind,
{
'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',
},
);
return '$_temp0';
}
@override
String get itemName => 'Was es ist';
@override
String get itemHowMany => 'Wie viele';
@override
String get itemOwnWeight => 'Eigengewicht';
@override
String get itemWeights => 'Gewichte';
@override
String get itemAddWeight => 'Gewicht hinzufügen';
@override
String get itemDone => 'Fertig';
@override
String get itemRemove => 'Entfernen';
@override
String get itemWeightInvalid => 'Ein Gewicht in Kilogramm, über 0';
@override
String weightKg(String weight) {
return '$weight kg';
}
@override
String weightCount(int count, String weight) {
return '$count × $weight kg';
}
@override
String get plateCalculatorTitle => 'Scheibenrechner';
@override
String get plateCalculatorHint =>
'Was du auflegst und was sich zwischen den Sätzen ändert';
@override
String get platePlace => 'Ort';
@override
String get plateBar => 'Stange';
@override
String get plateDumbbellPair => 'Zwei Kurzhantelgriffe';
@override
String get plateTargets => 'Gewichte, kg';
@override
String get plateTargetsHelper => 'Eins für jeden Satz, etwa 60, 80, 100';
@override
String get plateNoPlace =>
'Füge zuerst einen Ort hinzu, an dem du trainierst, mit seinen Scheiben.';
@override
String get plateNoBar =>
'An diesem Ort gibt es nichts, worauf Scheiben passen. Füge eine Langhantel oder Kurzhantelgriffe hinzu.';
@override
String plateBelowBar(String weight) {
return 'Unter den $weight kg der Stange';
}
@override
String plateShort(String weight) {
return '$weight kg unter deinem Ziel: Näher kommen die Scheiben nicht';
}
@override
String plateSide(String plates) {
return 'Jede Seite, von innen: $plates';
}
@override
String get plateBarAlone => 'Nur die Stange';
@override
String plateOff(String plates) {
return 'Nimm ab: $plates';
}
@override
String plateOn(String plates) {
return 'Leg auf: $plates';
}
@override
String get plateNoChange => 'Nichts ändert sich';
@override
String plateMoves(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count Scheiben je Seite bewegen sich',
one: '1 Scheibe je Seite bewegt sich',
zero: 'Keine Scheibe bewegt sich',
);
return '$_temp0';
}
@override
String plateSet(int number) {
return 'Satz $number';
}
}

View file

@ -2100,6 +2100,226 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get homeToday => 'Today';
@override
String get equipmentTitle => 'Equipment';
@override
String get equipmentHint => 'Where you train, and what\'s there';
@override
String get equipmentEmptyTitle => 'No places yet';
@override
String get equipmentEmptyHint =>
'Add the places you train at, such as your home and your gym, with what each has. Start from what a home or a gym usually has, or from nothing.';
@override
String get equipmentAddPlace => 'Add a place';
@override
String equipmentItems(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count pieces of equipment',
one: '1 piece of equipment',
zero: 'Nothing yet',
);
return '$_temp0';
}
@override
String get equipmentChosen => 'Where you train now';
@override
String get equipmentChoose => 'Train here';
@override
String get equipmentLocked => 'Can\'t be opened on this device';
@override
String get placeNewTitle => 'New place';
@override
String get placeName => 'Name';
@override
String get placeStartFrom => 'Start from';
@override
String get placeStartHome => 'A home';
@override
String get placeStartGym => 'A gym';
@override
String get placeStartNothing => 'Nothing';
@override
String get placeDefaultHome => 'Home';
@override
String get placeDefaultGym => 'Gym';
@override
String get placeAdd => 'Add';
@override
String get placeRename => 'Rename';
@override
String get placeSave => 'Save';
@override
String get placeDelete => 'Delete place';
@override
String get placeDeleteMessage =>
'The place and its equipment are deleted on all your devices.';
@override
String get placeAddEquipment => 'Add equipment';
@override
String get placeEmptyHint =>
'Nothing here yet. Add the equipment this place has.';
@override
String equipmentKind(String kind) {
String _temp0 = intl.Intl.selectLogic(
kind,
{
'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',
},
);
return '$_temp0';
}
@override
String get itemName => 'What it is';
@override
String get itemHowMany => 'How many';
@override
String get itemOwnWeight => 'Its own weight';
@override
String get itemWeights => 'Weights';
@override
String get itemAddWeight => 'Add a weight';
@override
String get itemDone => 'Done';
@override
String get itemRemove => 'Remove';
@override
String get itemWeightInvalid => 'A weight in kilograms, above 0';
@override
String weightKg(String weight) {
return '$weight kg';
}
@override
String weightCount(int count, String weight) {
return '$count × $weight kg';
}
@override
String get plateCalculatorTitle => 'Plate calculator';
@override
String get plateCalculatorHint =>
'What to load, and what changes between sets';
@override
String get platePlace => 'Place';
@override
String get plateBar => 'Bar';
@override
String get plateDumbbellPair => 'Two dumbbell handles';
@override
String get plateTargets => 'Weights, kg';
@override
String get plateTargetsHelper => 'One for each set, such as 60, 80, 100';
@override
String get plateNoPlace =>
'Add a place you train at, with its plates, first.';
@override
String get plateNoBar =>
'This place has nothing that takes plates. Add a barbell or dumbbell handles to it.';
@override
String plateBelowBar(String weight) {
return 'Below the bar\'s $weight kg';
}
@override
String plateShort(String weight) {
return '$weight kg below what you asked for: the plates make no closer weight';
}
@override
String plateSide(String plates) {
return 'Each side, from the inside: $plates';
}
@override
String get plateBarAlone => 'The bar alone';
@override
String plateOff(String plates) {
return 'Take off $plates';
}
@override
String plateOn(String plates) {
return 'Put on $plates';
}
@override
String get plateNoChange => 'Nothing changes';
@override
String plateMoves(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count plates move on each side',
one: '1 plate moves on each side',
zero: 'No plate moves',
);
return '$_temp0';
}
@override
String plateSet(int number) {
return 'Set $number';
}
}
/// The translations for English, as used in the United Kingdom (`en_GB`).

View file

@ -10,6 +10,9 @@ import 'package:halkyon_train_flutter/src/core/privacy/sync_content_key_gateway.
import 'package:halkyon_train_flutter/src/core/privacy/train_keys.dart';
import 'package:halkyon_train_flutter/src/feature/day/data/day_repository.dart';
import 'package:halkyon_train_flutter/src/feature/day/data/sync_day_gateway.dart';
import 'package:halkyon_train_flutter/src/feature/equipment/data/chosen_place_store.dart';
import 'package:halkyon_train_flutter/src/feature/equipment/data/place_repository.dart';
import 'package:halkyon_train_flutter/src/feature/equipment/data/sync_place_gateway.dart';
import 'package:halkyon_train_flutter/src/feature/health/data/android_health_platform.dart';
import 'package:halkyon_train_flutter/src/feature/health/data/health_connect.dart';
import 'package:halkyon_train_flutter/src/feature/health/data/health_store.dart';
@ -82,6 +85,13 @@ class TrainPerson(
/// Their routes (D44), sealed and synced.
required final RouteRepository routes,
/// The places they train at with their equipment (ADR 0034), sealed and
/// synced.
required final PlaceRepository places,
/// The place last chosen on this device (ADR 0034, D4).
required final ChosenPlaceStore chosenPlace,
/// The instance's planning of routes (D44).
required final RoutingGateway routing,
@ -403,6 +413,7 @@ Future<TrainPerson> _openPersonData(
final days = DayRepository(SyncDayGateway(db, sync), keys);
final plans = PlanRepository(SyncPlanGateway(db, sync), keys);
final routes = RouteRepository(SyncRouteGateway(db, sync), keys);
final places = PlaceRepository(SyncPlaceGateway(db, sync), keys);
final maps = MapSourceRepository(
ServerpodMapSourceGateway(client),
kept: File(p.join(folder.path, 'map_source.json')),
@ -460,6 +471,8 @@ Future<TrainPerson> _openPersonData(
days: days,
plans: plans,
routes: routes,
places: places,
chosenPlace: ChosenPlaceStore(File(p.join(folder.path, 'place.json'))),
routing: ServerpodRoutingGateway(client),
placeSearch: ServerpodPlaceSearchGateway(client),
maps: maps,
@ -496,6 +509,7 @@ Future<TrainPerson> _openPersonData(
await days.dispose();
await plans.dispose();
await routes.dispose();
await places.dispose();
await sync.dispose();
await db.close();
} finally {

View file

@ -0,0 +1,101 @@
import 'dart:async';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:halkyon_train_flutter/src/feature/equipment/data/chosen_place_store.dart';
import 'package:halkyon_train_flutter/src/feature/equipment/data/place_repository.dart';
import 'package:halkyon_train_flutter/src/feature/equipment/model/equipment.dart';
/// The places the person trains at (ADR 0034), by name, those this device
/// can't open last, read again whenever they change, here or on another
/// device; and the one chosen, remembered on this device (D4): the one last
/// chosen while it is there, else the first.
class PlacesCubit(
final PlaceRepository _places,
final ChosenPlaceStore _chosen,
) extends Cubit<PlacesState> {
this : super(const PlacesState());
StreamSubscription<void>? _changes;
Future<void> open() async {
_changes = _places.changes.listen((_) => unawaited(_read()));
await _read();
}
/// Keeps a new place, chosen from then on, and returns its id.
Future<String> add(PlaceContent content) async {
final id = await _places.save(content);
await choose(id);
return id;
}
Future<void> update(String id, PlaceContent content) =>
_places.update(id, content);
Future<void> delete(String id) => _places.delete(id);
Future<void> choose(String id) async {
await _chosen.write(id);
if (!isClosed) {
emit(state._with(chosen: id));
}
}
Future<void> _read() async {
final all = await _places.all();
final opened =
[
for (final place in all)
if (place.content != null) place,
]..sort(
(a, b) => a.content!.name.toLowerCase().compareTo(
b.content!.name.toLowerCase(),
),
);
if (!isClosed) {
emit(
state._with(
all: [
...opened,
for (final place in all)
if (place.content == null) place,
],
chosen: state._chosen ?? _chosen.read(),
),
);
}
}
@override
Future<void> close() async {
await _changes?.cancel();
await super.close();
}
}
final class const PlacesState({
final bool loaded = false,
final List<StoredPlace> all = const [],
final String? _chosen,
}) with Equatable {
/// The place chosen: the one last chosen while it opens here, else the
/// first that does; null while none does.
StoredPlace? get chosen {
final open = [
for (final place in all)
if (place.content != null) place,
];
return open.where((place) => place.id == _chosen).firstOrNull ??
open.firstOrNull;
}
PlacesState _with({List<StoredPlace>? all, String? chosen}) => PlacesState(
loaded: loaded || all != null,
all: all ?? this.all,
chosen: chosen ?? _chosen,
);
@override
List<Object?> get props => [loaded, all, _chosen];
}

View file

@ -0,0 +1,34 @@
import 'dart:convert';
import 'dart:io';
/// The place last chosen on this device (ADR 0034, D4), in the person's
/// `place.json`, never synced: where a phone is is the phone's. Without a
/// file (widget tests), kept in memory.
class ChosenPlaceStore([final File? _file]) {
String? _memory;
String? read() {
final file = _file;
if (file == null) {
return _memory;
}
try {
return switch (jsonDecode(file.readAsStringSync())) {
{'id': final String id} => id,
_ => null,
};
} on Object {
// None yet, or unreadable: as if none was chosen.
return null;
}
}
Future<void> write(String id) async {
final file = _file;
if (file == null) {
_memory = id;
return;
}
await file.writeAsString(jsonEncode({'id': id}), flush: true);
}
}

View file

@ -0,0 +1,23 @@
import 'package:halkyon_app/halkyon_app.dart';
/// A `training_place` row as kept: its id and its envelope, sealed (ADR 0030,
/// D7).
typedef PlaceRow = ({String id, Envelope envelope});
/// Where the person's training places are kept: their synced
/// `training_place` rows (ADR 0034). The repository seals and opens;
/// this only keeps.
abstract interface class PlaceGateway() {
Future<List<PlaceRow>> places();
Future<void> insert(PlaceRow place);
/// Replaces the place [id]'s envelope (an edit); nothing for one gone.
Future<void> update(String id, Envelope envelope);
/// Deletes the place [id], its payload blanked first (ADR 0002, rule 2).
Future<void> delete(String id);
/// Fires when a sync brought changes from the person's other devices.
Stream<void> get remoteChanges;
}

View file

@ -0,0 +1,83 @@
import 'dart:convert';
import 'dart:developer';
import 'dart:typed_data';
import 'package:halkyon_train_flutter/src/feature/equipment/model/equipment.dart';
/// The schema of a place's payload this app writes and reads.
const placeSchemaVersion = 1;
/// [content] as its row's payload, before sealing (ADR 0034): UTF-8 JSON,
/// every weight in grams, what is counted by weight as `[grams, count]`
/// pairs, the heaviest first.
ByteData encodePlace(PlaceContent content) => ByteData.sublistView(
Uint8List.fromList(
utf8.encode(
jsonEncode({
'schemaVersion': placeSchemaVersion,
'name': content.name,
'items': [
for (final item in content.items)
{
'kind': item.kind.name,
'name': ?item.name,
'count': item.count,
'grams': ?item.grams,
if (item.weights.isNotEmpty)
'weights': [
for (final grams
in item.weights.keys.toList()
..sort((a, b) => b.compareTo(a)))
[grams, item.weights[grams]],
],
},
],
}),
),
),
);
/// The place [payload] holds, opened; null for one of a newer schema or
/// one it can't read. An item of a kind it doesn't know is left out, as is
/// a weight that isn't one.
PlaceContent? decodePlace(ByteData payload) {
try {
final json = jsonDecode(
utf8.decode(Uint8List.sublistView(payload)),
) as Map<String, Object?>;
final version = json['schemaVersion']! as int;
if (version > placeSchemaVersion) {
log('A place of schema $version skipped', name: 'Place');
return null;
}
return PlaceContent(
name: json['name']! as String,
items: [
for (final item in json['items']! as List<Object?>)
if (item case final Map<String, Object?> fields
when EquipmentKind.values.asNameMap()[fields['kind']] != null)
EquipmentItem(
kind: EquipmentKind.values.byName(fields['kind']! as String),
name: fields['name'] as String?,
count: switch (fields['count']) {
final int count when count > 0 => count,
_ => 1,
},
grams: switch (fields['grams']) {
final int grams when grams > 0 => grams,
_ => null,
},
weights: {
for (final pair in fields['weights'] as List<Object?>? ?? [])
if (pair case [final int grams, final int count]
when grams > 0 && count > 0)
grams: count,
},
),
],
);
} on Object catch (error) {
log('A place not read: ${error.runtimeType}', name: 'Place');
return null;
}
}

View file

@ -0,0 +1,82 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:halkyon_app/halkyon_app.dart';
import 'package:halkyon_train_client/halkyon_train_client.dart' show Uuid;
import 'package:halkyon_train_flutter/src/core/privacy/train_keys.dart';
import 'package:halkyon_train_flutter/src/feature/equipment/data/place_gateway.dart';
import 'package:halkyon_train_flutter/src/feature/equipment/data/place_payload.dart';
import 'package:halkyon_train_flutter/src/feature/equipment/model/equipment.dart';
/// The places the person trains at (ADR 0034), sealed with their key for
/// their row as workouts are (D7); a readable row is one the instance made
/// up, and shows locked.
class PlaceRepository(final PlaceGateway _gateway, final TrainKeys _keys) {
StreamSubscription<void>? _remote;
/// This device's changes, and those a sync brings while anyone listens.
late final StreamController<void> _changes = StreamController.broadcast(
onListen: () => _remote = _gateway.remoteChanges.listen(_changes.add),
onCancel: () => _remote?.cancel(),
);
late final _envelope = PayloadEnvelope(_keys.cipher);
/// Fires when the places changed, here or on the person's other devices.
Stream<void> get changes => _changes.stream;
/// Every place, in the order they were made; one that doesn't open here
/// with its content null.
Future<List<StoredPlace>> all() async {
await _keys.refresh();
final rows = await _gateway.places()
..sort((a, b) => a.id.compareTo(b.id));
return [
for (final row in rows)
StoredPlace(row.id, switch (_open(row.envelope, row.id)) {
final payload? => decodePlace(payload),
null => null,
}),
];
}
/// Keeps a new place and returns its id; throws [KeysMissing] where this
/// device can't seal.
Future<String> save(PlaceContent content) async {
final id = const Uuid().v7();
await _gateway.insert((id: id, envelope: await _sealed(id, content)));
_changes.add(null);
return id;
}
Future<void> update(String id, PlaceContent content) async {
await _gateway.update(id, await _sealed(id, content));
_changes.add(null);
}
Future<void> delete(String id) async {
await _gateway.delete(id);
_changes.add(null);
}
/// Closes what it holds open.
Future<void> dispose() => _changes.close();
Future<Envelope> _sealed(String id, PlaceContent content) async =>
_envelope.seal(
encodePlace(content),
keyId: await _keys.keyToWrite(),
table: 'training_place',
id: id,
);
ByteData? _open(Envelope envelope, String id) {
if (envelope.keyId == null) {
return null;
}
return switch (_envelope.open(envelope, table: 'training_place', id: id)) {
Readable(:final payload) => payload,
Locked() => null,
};
}
}

Some files were not shown because too many files have changed in this diff Show more