feat(train): routes kept as sealed rows, planned on the map (ADR 0030, D44)
Routes are synced `route` rows (the model `SavedRoute`, as Flutter has a `Route`), sealed as plans are; the migration `routes` only creates the table, its space index and its cascade to the sync engine's spaces. A route's payload holds its name, activity, profile, waypoints and the way planned, points and elevations delta-coded and gzipped. Home gets a fourth tab, Routes, and the planner: waypoints tapped on the map, held and dragged, the last undone, a new route's first where the phone is (GPS through the recorder's channel), the way planned by the instance's BRouter after each change, an earlier answer dropped; its distance, climb, descent, elevation and surfaces under the map. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
a8f7c26e69
commit
aea99c68ca
52 changed files with 24035 additions and 12 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,853 @@
|
|||
{
|
||||
"__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": "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
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,94 @@
|
|||
{
|
||||
"__className__": "serverpod.DatabaseMigration",
|
||||
"actions": [
|
||||
{
|
||||
"__className__": "serverpod.DatabaseMigrationAction",
|
||||
"type": "createTable",
|
||||
"createTable": {
|
||||
"__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
|
||||
}
|
||||
}
|
||||
],
|
||||
"warnings": [],
|
||||
"migrationApiVersion": 1
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ part '20261028101012641-workouts/migration.dart';
|
|||
part '20261028164640000-profile/migration.dart';
|
||||
part '20261028181405893-days/migration.dart';
|
||||
part '20261028211508606-plans/migration.dart';
|
||||
part '20261028231042668-routes/migration.dart';
|
||||
|
||||
/// Migration registry for the client-side database.
|
||||
class MigrationRegistry {
|
||||
|
|
@ -18,6 +19,7 @@ class MigrationRegistry {
|
|||
_Migration20261028164640000_profile(),
|
||||
_Migration20261028181405893_days(),
|
||||
_Migration20261028211508606_plans(),
|
||||
_Migration20261028231042668_routes(),
|
||||
];
|
||||
|
||||
/// List of all client-side database migration versions.
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import 'map/map_source.dart' as _ikw0qg8x;
|
|||
import 'plan/plan.dart' as _i45prw73;
|
||||
import 'privacy/content_key.dart' as _imed3x5d;
|
||||
import 'profile/profile.dart' as _ia5j8fch;
|
||||
import 'route/route.dart' as _i0mgeh8t;
|
||||
import 'routing/planned_route.dart' as _ikjgxraq;
|
||||
import 'routing/route_hint.dart' as _if6974nb;
|
||||
import 'routing/route_point.dart' as _isqtfus6;
|
||||
|
|
@ -49,6 +50,7 @@ export 'map/map_source.dart';
|
|||
export 'plan/plan.dart';
|
||||
export 'privacy/content_key.dart';
|
||||
export 'profile/profile.dart';
|
||||
export 'route/route.dart';
|
||||
export 'routing/planned_route.dart';
|
||||
export 'routing/route_hint.dart';
|
||||
export 'routing/route_point.dart';
|
||||
|
|
@ -494,6 +496,73 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
],
|
||||
managed: true,
|
||||
),
|
||||
_isd.TableDefinition(
|
||||
name: 'route',
|
||||
dartName: 'SavedRoute',
|
||||
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: 'route_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: 'route_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',
|
||||
|
|
@ -734,6 +803,9 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
if (t == _ia5j8fch.Profile) {
|
||||
return _ia5j8fch.Profile.fromJson(data) as T;
|
||||
}
|
||||
if (t == _i0mgeh8t.SavedRoute) {
|
||||
return _i0mgeh8t.SavedRoute.fromJson(data) as T;
|
||||
}
|
||||
if (t == _ikjgxraq.PlannedRoute) {
|
||||
return _ikjgxraq.PlannedRoute.fromJson(data) as T;
|
||||
}
|
||||
|
|
@ -788,6 +860,9 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
if (t == _isc.getType<_ia5j8fch.Profile?>()) {
|
||||
return (data != null ? _ia5j8fch.Profile.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _isc.getType<_i0mgeh8t.SavedRoute?>()) {
|
||||
return (data != null ? _i0mgeh8t.SavedRoute.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _isc.getType<_ikjgxraq.PlannedRoute?>()) {
|
||||
return (data != null ? _ikjgxraq.PlannedRoute.fromJson(data) : null) as T;
|
||||
}
|
||||
|
|
@ -869,6 +944,7 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
_i45prw73.Plan => 'Plan',
|
||||
_imed3x5d.ContentKey => 'ContentKey',
|
||||
_ia5j8fch.Profile => 'Profile',
|
||||
_i0mgeh8t.SavedRoute => 'SavedRoute',
|
||||
_ikjgxraq.PlannedRoute => 'PlannedRoute',
|
||||
_if6974nb.RouteHint => 'RouteHint',
|
||||
_isqtfus6.RoutePoint => 'RoutePoint',
|
||||
|
|
@ -912,6 +988,8 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
return 'ContentKey';
|
||||
case _ia5j8fch.Profile():
|
||||
return 'Profile';
|
||||
case _i0mgeh8t.SavedRoute():
|
||||
return 'SavedRoute';
|
||||
case _ikjgxraq.PlannedRoute():
|
||||
return 'PlannedRoute';
|
||||
case _if6974nb.RouteHint():
|
||||
|
|
@ -991,6 +1069,9 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
if (dataClassName == 'Profile') {
|
||||
return deserialize<_ia5j8fch.Profile>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'SavedRoute') {
|
||||
return deserialize<_i0mgeh8t.SavedRoute>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'PlannedRoute') {
|
||||
return deserialize<_ikjgxraq.PlannedRoute>(data['data']);
|
||||
}
|
||||
|
|
@ -1106,6 +1187,8 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
return _imed3x5d.ContentKey.t;
|
||||
case _ia5j8fch.Profile:
|
||||
return _ia5j8fch.Profile.t;
|
||||
case _i0mgeh8t.SavedRoute:
|
||||
return _i0mgeh8t.SavedRoute.t;
|
||||
case _i5b9b17z.Workout:
|
||||
return _i5b9b17z.Workout.t;
|
||||
case _i92mefto.WorkoutSeries:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,670 @@
|
|||
/* 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 planned route (ADR 0030, D44): its name, activity, profile, waypoints
|
||||
/// and the route BRouter planned through them, in its sealed payload (D7);
|
||||
/// the instance sees that it exists, its size and when it changed. Changed
|
||||
/// when edited, the last writer winning. Synced.
|
||||
abstract class SavedRoute
|
||||
implements _isd.TableRow<_isc.UuidValue?>, _isc.ProtocolSerialization {
|
||||
SavedRoute._({
|
||||
this.id,
|
||||
this.spaceId,
|
||||
this.keyId,
|
||||
required this.payload,
|
||||
this.nonce,
|
||||
});
|
||||
|
||||
factory SavedRoute({
|
||||
_isc.UuidValue? id,
|
||||
int? spaceId,
|
||||
String? keyId,
|
||||
required _idt.ByteData payload,
|
||||
_idt.ByteData? nonce,
|
||||
}) = _SavedRouteImpl;
|
||||
|
||||
factory SavedRoute.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return SavedRoute(
|
||||
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 = SavedRouteTable();
|
||||
|
||||
static const db = SavedRouteRepository._();
|
||||
|
||||
@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 [SavedRoute]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
SavedRoute copyWith({
|
||||
_isc.UuidValue? id,
|
||||
int? spaceId,
|
||||
String? keyId,
|
||||
_idt.ByteData? payload,
|
||||
_idt.ByteData? nonce,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'SavedRoute',
|
||||
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__': 'SavedRoute',
|
||||
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 SavedRouteInclude include() {
|
||||
return SavedRouteInclude._();
|
||||
}
|
||||
|
||||
static SavedRouteIncludeList includeList({
|
||||
_isd.WhereExpressionBuilder<SavedRouteTable>? where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_isd.OrderByBuilder<SavedRouteTable>? orderBy,
|
||||
_isd.OrderByListBuilder<SavedRouteTable>? orderByList,
|
||||
SavedRouteInclude? include,
|
||||
}) {
|
||||
return SavedRouteIncludeList._(
|
||||
where: where,
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
orderBy: orderBy?.call(SavedRoute.t),
|
||||
orderByList: orderByList?.call(SavedRoute.t),
|
||||
include: include,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _isc.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _Undefined {}
|
||||
|
||||
class _SavedRouteImpl extends SavedRoute {
|
||||
_SavedRouteImpl({
|
||||
_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 [SavedRoute]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
@override
|
||||
SavedRoute copyWith({
|
||||
Object? id = _Undefined,
|
||||
Object? spaceId = _Undefined,
|
||||
Object? keyId = _Undefined,
|
||||
_idt.ByteData? payload,
|
||||
Object? nonce = _Undefined,
|
||||
}) {
|
||||
return SavedRoute(
|
||||
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 SavedRouteUpdateTable extends _isd.UpdateTable<SavedRouteTable> {
|
||||
SavedRouteUpdateTable(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 SavedRouteTable extends _isd.Table<_isc.UuidValue?> {
|
||||
SavedRouteTable({super.tableRelation}) : super(tableName: 'route') {
|
||||
updateTable = SavedRouteUpdateTable(this);
|
||||
spaceId = _isd.ColumnInt(
|
||||
'spaceId',
|
||||
this,
|
||||
);
|
||||
keyId = _isd.ColumnString(
|
||||
'keyId',
|
||||
this,
|
||||
);
|
||||
payload = _isd.ColumnByteData(
|
||||
'payload',
|
||||
this,
|
||||
);
|
||||
nonce = _isd.ColumnByteData(
|
||||
'nonce',
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
late final SavedRouteUpdateTable 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 SavedRouteInclude extends _isd.IncludeObject {
|
||||
SavedRouteInclude._();
|
||||
|
||||
@override
|
||||
Map<String, _isd.Include?> get includes => {};
|
||||
|
||||
@override
|
||||
_isd.Table<_isc.UuidValue?> get table => SavedRoute.t;
|
||||
}
|
||||
|
||||
class SavedRouteIncludeList extends _isd.IncludeList {
|
||||
SavedRouteIncludeList._({
|
||||
_isd.WhereExpressionBuilder<SavedRouteTable>? where,
|
||||
super.limit,
|
||||
super.offset,
|
||||
super.orderBy,
|
||||
super.orderByList,
|
||||
super.include,
|
||||
}) {
|
||||
super.where = where?.call(SavedRoute.t);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, _isd.Include?> get includes => include?.includes ?? {};
|
||||
|
||||
@override
|
||||
_isd.Table<_isc.UuidValue?> get table => SavedRoute.t;
|
||||
}
|
||||
|
||||
class SavedRouteRepository {
|
||||
const SavedRouteRepository._();
|
||||
|
||||
/// Returns a list of [SavedRoute]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<SavedRoute>> find(
|
||||
_isd.DatabaseSession session, {
|
||||
_isd.WhereExpressionBuilder<SavedRouteTable>? where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_isd.OrderByBuilder<SavedRouteTable>? orderBy,
|
||||
_isd.OrderByListBuilder<SavedRouteTable>? orderByList,
|
||||
_isd.Transaction? transaction,
|
||||
_isd.LockMode? lockMode,
|
||||
_isd.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.find<SavedRoute>(
|
||||
where: where?.call(SavedRoute.t),
|
||||
orderBy: orderBy?.call(SavedRoute.t),
|
||||
orderByList: orderByList?.call(SavedRoute.t),
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns the first matching [SavedRoute] 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<SavedRoute?> findFirstRow(
|
||||
_isd.DatabaseSession session, {
|
||||
_isd.WhereExpressionBuilder<SavedRouteTable>? where,
|
||||
int? offset,
|
||||
_isd.OrderByBuilder<SavedRouteTable>? orderBy,
|
||||
_isd.OrderByListBuilder<SavedRouteTable>? orderByList,
|
||||
_isd.Transaction? transaction,
|
||||
_isd.LockMode? lockMode,
|
||||
_isd.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.findFirstRow<SavedRoute>(
|
||||
where: where?.call(SavedRoute.t),
|
||||
orderBy: orderBy?.call(SavedRoute.t),
|
||||
orderByList: orderByList?.call(SavedRoute.t),
|
||||
offset: offset,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Finds a single [SavedRoute] by its [id] or null if no such row exists.
|
||||
Future<SavedRoute?> findById(
|
||||
_isd.DatabaseSession session,
|
||||
_isc.UuidValue id, {
|
||||
_isd.Transaction? transaction,
|
||||
_isd.LockMode? lockMode,
|
||||
_isd.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.findById<SavedRoute>(
|
||||
id,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Inserts all [SavedRoute]s in the list and returns the inserted rows.
|
||||
///
|
||||
/// The returned [SavedRoute]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<SavedRoute>> insert(
|
||||
_isd.DatabaseSession session,
|
||||
List<SavedRoute> rows, {
|
||||
_isd.Transaction? transaction,
|
||||
bool ignoreConflicts = false,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.insert<SavedRoute>(
|
||||
rows,
|
||||
transaction: transaction,
|
||||
ignoreConflicts: ignoreConflicts,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Inserts a single [SavedRoute] and returns the inserted row.
|
||||
///
|
||||
/// The returned [SavedRoute] will have its `id` field set.
|
||||
Future<SavedRoute> insertRow(
|
||||
_isd.DatabaseSession session,
|
||||
SavedRoute row, {
|
||||
_isd.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.insertRow<SavedRoute>(
|
||||
row,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Upserts all [SavedRoute]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 [SavedRoute]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<SavedRoute>> upsert(
|
||||
_isd.DatabaseSession session,
|
||||
List<SavedRoute> rows, {
|
||||
required _isd.ColumnSelections<SavedRouteTable> conflictColumns,
|
||||
_isd.ColumnSelections<SavedRouteTable>? updateColumns,
|
||||
_isd.WhereExpressionBuilder<SavedRouteTable>? updateWhere,
|
||||
_isd.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.upsert<SavedRoute>(
|
||||
rows,
|
||||
conflictColumns: conflictColumns(SavedRoute.t),
|
||||
updateColumns: updateColumns?.call(SavedRoute.t),
|
||||
updateWhere: updateWhere?.call(SavedRoute.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Upserts a single [SavedRoute] 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 [SavedRoute] will have its `id` field set.
|
||||
Future<SavedRoute?> upsertRow(
|
||||
_isd.DatabaseSession session,
|
||||
SavedRoute row, {
|
||||
required _isd.ColumnSelections<SavedRouteTable> conflictColumns,
|
||||
_isd.ColumnSelections<SavedRouteTable>? updateColumns,
|
||||
_isd.WhereExpressionBuilder<SavedRouteTable>? updateWhere,
|
||||
_isd.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.upsertRow<SavedRoute>(
|
||||
row,
|
||||
conflictColumns: conflictColumns(SavedRoute.t),
|
||||
updateColumns: updateColumns?.call(SavedRoute.t),
|
||||
updateWhere: updateWhere?.call(SavedRoute.t),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates all [SavedRoute]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<SavedRoute>> update(
|
||||
_isd.DatabaseSession session,
|
||||
List<SavedRoute> rows, {
|
||||
_isd.ColumnSelections<SavedRouteTable>? columns,
|
||||
_isd.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.update<SavedRoute>(
|
||||
rows,
|
||||
columns: columns?.call(SavedRoute.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates a single [SavedRoute]. 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<SavedRoute> updateRow(
|
||||
_isd.DatabaseSession session,
|
||||
SavedRoute row, {
|
||||
_isd.ColumnSelections<SavedRouteTable>? columns,
|
||||
_isd.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.updateRow<SavedRoute>(
|
||||
row,
|
||||
columns: columns?.call(SavedRoute.t),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates a single [SavedRoute] by its [id] with the specified [columnValues].
|
||||
/// Returns the updated row or null if no row with the given id exists.
|
||||
Future<SavedRoute?> updateById(
|
||||
_isd.DatabaseSession session,
|
||||
_isc.UuidValue id, {
|
||||
required _isd.ColumnValueListBuilder<SavedRouteUpdateTable> columnValues,
|
||||
_isd.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.updateById<SavedRoute>(
|
||||
id,
|
||||
columnValues: columnValues(SavedRoute.t.updateTable),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates all [SavedRoute]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<SavedRoute>> updateWhere(
|
||||
_isd.DatabaseSession session, {
|
||||
required _isd.ColumnValueListBuilder<SavedRouteUpdateTable> columnValues,
|
||||
required _isd.WhereExpressionBuilder<SavedRouteTable> where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_isd.OrderByBuilder<SavedRouteTable>? orderBy,
|
||||
_isd.OrderByListBuilder<SavedRouteTable>? orderByList,
|
||||
_isd.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.updateWhere<SavedRoute>(
|
||||
columnValues: columnValues(SavedRoute.t.updateTable),
|
||||
where: where(SavedRoute.t),
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
orderBy: orderBy?.call(SavedRoute.t),
|
||||
orderByList: orderByList?.call(SavedRoute.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes all [SavedRoute]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<SavedRoute>> delete(
|
||||
_isd.DatabaseSession session,
|
||||
List<SavedRoute> rows, {
|
||||
_isd.OrderByBuilder<SavedRouteTable>? orderBy,
|
||||
_isd.OrderByListBuilder<SavedRouteTable>? orderByList,
|
||||
_isd.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.delete<SavedRoute>(
|
||||
rows,
|
||||
orderBy: orderBy?.call(SavedRoute.t),
|
||||
orderByList: orderByList?.call(SavedRoute.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes a single [SavedRoute].
|
||||
Future<SavedRoute> deleteRow(
|
||||
_isd.DatabaseSession session,
|
||||
SavedRoute row, {
|
||||
_isd.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.deleteRow<SavedRoute>(
|
||||
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<SavedRoute>> deleteWhere(
|
||||
_isd.DatabaseSession session, {
|
||||
required _isd.WhereExpressionBuilder<SavedRouteTable> where,
|
||||
_isd.OrderByBuilder<SavedRouteTable>? orderBy,
|
||||
_isd.OrderByListBuilder<SavedRouteTable>? orderByList,
|
||||
_isd.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.deleteWhere<SavedRoute>(
|
||||
where: where(SavedRoute.t),
|
||||
orderBy: orderBy?.call(SavedRoute.t),
|
||||
orderByList: orderByList?.call(SavedRoute.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<SavedRouteTable>? where,
|
||||
int? limit,
|
||||
_isd.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.count<SavedRoute>(
|
||||
where: where?.call(SavedRoute.t),
|
||||
limit: limit,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Acquires row-level locks on [SavedRoute] rows matching the [where] expression.
|
||||
Future<void> lockRows(
|
||||
_isd.DatabaseSession session, {
|
||||
required _isd.WhereExpressionBuilder<SavedRouteTable> where,
|
||||
required _isd.LockMode lockMode,
|
||||
required _isd.Transaction transaction,
|
||||
_isd.LockBehavior lockBehavior = _isd.LockBehavior.wait,
|
||||
}) async {
|
||||
return session.db.lockRows<SavedRoute>(
|
||||
where: where(SavedRoute.t),
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ import 'day/night.dart' as _i17cgxdf;
|
|||
import 'plan/plan.dart' as _i45prw73;
|
||||
import 'privacy/content_key.dart' as _imed3x5d;
|
||||
import 'profile/profile.dart' as _ia5j8fch;
|
||||
import 'route/route.dart' as _i0mgeh8t;
|
||||
import 'workout/workout.dart' as _i5b9b17z;
|
||||
import 'workout/workout_series.dart' as _i92mefto;
|
||||
|
||||
|
|
@ -30,6 +31,7 @@ final List<_isd.Table> syncTables = [
|
|||
_i17cgxdf.Night.t,
|
||||
_i45prw73.Plan.t,
|
||||
_ia5j8fch.Profile.t,
|
||||
_i0mgeh8t.SavedRoute.t,
|
||||
_i5b9b17z.Workout.t,
|
||||
_i92mefto.WorkoutSeries.t,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -3,8 +3,14 @@ package app.halkyon.train
|
|||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.location.Location
|
||||
import android.location.LocationListener
|
||||
import android.location.LocationManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.CancellationSignal
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.EventChannel
|
||||
|
|
@ -67,6 +73,7 @@ class MainActivity : FlutterActivity() {
|
|||
"stop" -> result.success(RecorderService.stop().let { null })
|
||||
"show" -> result.success(RecorderService.show(call.arguments as String).let { null })
|
||||
"isRecording" -> result.success(RecorderService.isRecording())
|
||||
"position" -> position(result)
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
|
@ -166,7 +173,59 @@ class MainActivity : FlutterActivity() {
|
|||
else -> emptyList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Where the phone is, for a route's first waypoint (ADR 0030, D44): GPS's
|
||||
* last fix where it is at most two minutes old, else a new one, waited
|
||||
* for 15 s at most; null without the location permission or a fix.
|
||||
*/
|
||||
@Suppress("MissingPermission") // Checked first.
|
||||
private fun position(result: MethodChannel.Result) {
|
||||
if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
||||
result.success(null)
|
||||
return
|
||||
}
|
||||
val manager = getSystemService(LocationManager::class.java)
|
||||
var answered = false
|
||||
fun answer(location: Location?) {
|
||||
if (answered) return
|
||||
answered = true
|
||||
result.success(location?.let { mapOf("lat" to it.latitude, "lon" to it.longitude) })
|
||||
}
|
||||
val last = manager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
|
||||
if (last != null && System.currentTimeMillis() - last.time <= FRESH_MS) {
|
||||
answer(last)
|
||||
return
|
||||
}
|
||||
if (!manager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
|
||||
answer(last)
|
||||
return
|
||||
}
|
||||
val handler = Handler(Looper.getMainLooper())
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
val cancel = CancellationSignal()
|
||||
handler.postDelayed({
|
||||
cancel.cancel()
|
||||
answer(last)
|
||||
}, WAIT_MS)
|
||||
manager.getCurrentLocation(LocationManager.GPS_PROVIDER, cancel, mainExecutor) { answer(it ?: last) }
|
||||
} else {
|
||||
val listener = object : LocationListener {
|
||||
override fun onLocationChanged(location: Location) {
|
||||
manager.removeUpdates(this)
|
||||
answer(location)
|
||||
}
|
||||
}
|
||||
handler.postDelayed({
|
||||
manager.removeUpdates(listener)
|
||||
answer(last)
|
||||
}, WAIT_MS)
|
||||
manager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0L, 0f, listener, Looper.getMainLooper())
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val RECORDER_REQUEST = 1
|
||||
const val FRESH_MS = 120_000L
|
||||
const val WAIT_MS = 15_000L
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -376,5 +376,45 @@
|
|||
"dayBreathing": "{rate} Atemzüge pro Minute",
|
||||
"daySkin": "Hauttemperatur",
|
||||
"dayCelsius": "{value} °C",
|
||||
"daySkinDeviation": "{value} °C gegenüber deinen Nächten sonst"
|
||||
"daySkinDeviation": "{value} °C gegenüber deinen Nächten sonst",
|
||||
"homeRoutes": "Routen",
|
||||
"routesNew": "Neue Route",
|
||||
"routesEmptyTitle": "Noch keine Routen",
|
||||
"routesEmptyHint": "Tipp auf die Karte, um Wegpunkte zu setzen, und deine Instanz plant den Weg dazwischen über Wege und Straßen, mit Anstieg und Untergrund.",
|
||||
"routeLocked": "Eine Route, die sich hier nicht öffnet",
|
||||
"routeTitle": "Route",
|
||||
"routeNewTitle": "Neue Route",
|
||||
"routeName": "Name",
|
||||
"routeActivity": "Für",
|
||||
"routeActivityAny": "Jede Aktivität",
|
||||
"routeProfile": "Geplant für",
|
||||
"routeProfileRun": "Laufen",
|
||||
"routeProfileHike": "Wandern",
|
||||
"routeProfileRoadBike": "Rennrad",
|
||||
"routeProfileTrekkingBike": "Trekkingrad",
|
||||
"routeProfileGravel": "Gravelbike",
|
||||
"routeProfileMtb": "Mountainbike",
|
||||
"routeTapHint": "Tipp auf die Karte, um einen Wegpunkt hinzuzufügen; halt einen gedrückt und zieh ihn, um ihn zu verschieben.",
|
||||
"routeUndo": "Letzten Wegpunkt entfernen",
|
||||
"routePlanning": "Wird geplant …",
|
||||
"routeRefusedInvalid": "Ein Wegpunkt zu viel, oder einer außerhalb der Karte.",
|
||||
"routeRefusedTooOften": "Zu oft geplant: versuch es in einer Minute wieder.",
|
||||
"routeRefusedNoRoute": "Dort wurde kein Weg gefunden: Ein Wegpunkt liegt vielleicht abseits der Wege oder außerhalb des Gebiets, in dem deine Instanz plant.",
|
||||
"routeRefusedUnreachable": "Deine Instanz ist nicht erreichbar, um den Weg zu planen.",
|
||||
"routeNotOffered": "Deine Instanz plant keine Routen.",
|
||||
"routeAscent": "{metres} m bergauf",
|
||||
"routeDescent": "{metres} m bergab",
|
||||
"routeSummary": "{distance} · {ascent}",
|
||||
"routeSurfaces": "Untergrund",
|
||||
"surfacePaved": "Befestigt",
|
||||
"surfaceCompacted": "Verdichtet",
|
||||
"surfaceGravel": "Schotter",
|
||||
"surfaceNatural": "Naturboden",
|
||||
"surfaceUnknown": "Unbekannt",
|
||||
"routeSave": "Speichern",
|
||||
"routeNotSaved": "Die Route wurde nicht gespeichert.",
|
||||
"routeDelete": "Löschen",
|
||||
"routeDeleteTitle": "Diese Route löschen?",
|
||||
"routeDeleteMessage": "Sie wird auf all deinen Geräten gelöscht.",
|
||||
"routeDeleted": "Route gelöscht"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -481,5 +481,48 @@
|
|||
"dayCelsius": "{value} °C",
|
||||
"@dayCelsius": {"placeholders": {"value": {"type": "String", "example": "33.4"}}},
|
||||
"daySkinDeviation": "{value} °C from your usual at night",
|
||||
"@daySkinDeviation": {"placeholders": {"value": {"type": "String", "example": "−0.2"}}}
|
||||
"@daySkinDeviation": {"placeholders": {"value": {"type": "String", "example": "−0.2"}}},
|
||||
"homeRoutes": "Routes",
|
||||
"routesNew": "New route",
|
||||
"routesEmptyTitle": "No routes yet",
|
||||
"routesEmptyHint": "Tap the map to set waypoints, and your instance plans the way between them on paths and roads, with its climb and surfaces.",
|
||||
"routeLocked": "A route that doesn't open here",
|
||||
"routeTitle": "Route",
|
||||
"routeNewTitle": "New route",
|
||||
"routeName": "Name",
|
||||
"routeActivity": "For",
|
||||
"routeActivityAny": "Any activity",
|
||||
"routeProfile": "Planned for",
|
||||
"routeProfileRun": "Running",
|
||||
"routeProfileHike": "Hiking",
|
||||
"routeProfileRoadBike": "Road bike",
|
||||
"routeProfileTrekkingBike": "Trekking bike",
|
||||
"routeProfileGravel": "Gravel bike",
|
||||
"routeProfileMtb": "Mountain bike",
|
||||
"routeTapHint": "Tap the map to add a waypoint; hold one and drag it to move it.",
|
||||
"routeUndo": "Take the last waypoint off",
|
||||
"routePlanning": "Planning…",
|
||||
"routeRefusedInvalid": "A waypoint too many, or one off the map.",
|
||||
"routeRefusedTooOften": "Planned too often: try again in a minute.",
|
||||
"routeRefusedNoRoute": "No way found there: a waypoint may be off the paths, or outside the region your instance plans in.",
|
||||
"routeRefusedUnreachable": "Your instance can't be reached to plan the way.",
|
||||
"routeNotOffered": "Your instance doesn't plan routes.",
|
||||
"routeAscent": "{metres} m up",
|
||||
"@routeAscent": {"placeholders": {"metres": {"type": "int"}}},
|
||||
"routeDescent": "{metres} m down",
|
||||
"@routeDescent": {"placeholders": {"metres": {"type": "int"}}},
|
||||
"routeSummary": "{distance} · {ascent}",
|
||||
"@routeSummary": {"description": "A route in the list: its distance and climb.", "placeholders": {"distance": {"type": "String", "example": "12.3 km"}, "ascent": {"type": "String", "example": "120 m up"}}},
|
||||
"routeSurfaces": "Surfaces",
|
||||
"surfacePaved": "Paved",
|
||||
"surfaceCompacted": "Compacted",
|
||||
"surfaceGravel": "Gravel",
|
||||
"surfaceNatural": "Natural ground",
|
||||
"surfaceUnknown": "Not known",
|
||||
"routeSave": "Save",
|
||||
"routeNotSaved": "The route wasn't saved.",
|
||||
"routeDelete": "Delete",
|
||||
"routeDeleteTitle": "Delete this route?",
|
||||
"routeDeleteMessage": "It's deleted on all your devices.",
|
||||
"routeDeleted": "Route deleted"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2283,6 +2283,246 @@ abstract class AppLocalizations {
|
|||
/// In en, this message translates to:
|
||||
/// **'{value} °C from your usual at night'**
|
||||
String daySkinDeviation(String value);
|
||||
|
||||
/// No description provided for @homeRoutes.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Routes'**
|
||||
String get homeRoutes;
|
||||
|
||||
/// No description provided for @routesNew.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'New route'**
|
||||
String get routesNew;
|
||||
|
||||
/// No description provided for @routesEmptyTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No routes yet'**
|
||||
String get routesEmptyTitle;
|
||||
|
||||
/// No description provided for @routesEmptyHint.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Tap the map to set waypoints, and your instance plans the way between them on paths and roads, with its climb and surfaces.'**
|
||||
String get routesEmptyHint;
|
||||
|
||||
/// No description provided for @routeLocked.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'A route that doesn\'t open here'**
|
||||
String get routeLocked;
|
||||
|
||||
/// No description provided for @routeTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Route'**
|
||||
String get routeTitle;
|
||||
|
||||
/// No description provided for @routeNewTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'New route'**
|
||||
String get routeNewTitle;
|
||||
|
||||
/// No description provided for @routeName.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Name'**
|
||||
String get routeName;
|
||||
|
||||
/// No description provided for @routeActivity.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'For'**
|
||||
String get routeActivity;
|
||||
|
||||
/// No description provided for @routeActivityAny.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Any activity'**
|
||||
String get routeActivityAny;
|
||||
|
||||
/// No description provided for @routeProfile.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Planned for'**
|
||||
String get routeProfile;
|
||||
|
||||
/// No description provided for @routeProfileRun.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Running'**
|
||||
String get routeProfileRun;
|
||||
|
||||
/// No description provided for @routeProfileHike.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Hiking'**
|
||||
String get routeProfileHike;
|
||||
|
||||
/// No description provided for @routeProfileRoadBike.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Road bike'**
|
||||
String get routeProfileRoadBike;
|
||||
|
||||
/// No description provided for @routeProfileTrekkingBike.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Trekking bike'**
|
||||
String get routeProfileTrekkingBike;
|
||||
|
||||
/// No description provided for @routeProfileGravel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Gravel bike'**
|
||||
String get routeProfileGravel;
|
||||
|
||||
/// No description provided for @routeProfileMtb.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Mountain bike'**
|
||||
String get routeProfileMtb;
|
||||
|
||||
/// No description provided for @routeTapHint.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Tap the map to add a waypoint; hold one and drag it to move it.'**
|
||||
String get routeTapHint;
|
||||
|
||||
/// No description provided for @routeUndo.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Take the last waypoint off'**
|
||||
String get routeUndo;
|
||||
|
||||
/// No description provided for @routePlanning.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Planning…'**
|
||||
String get routePlanning;
|
||||
|
||||
/// No description provided for @routeRefusedInvalid.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'A waypoint too many, or one off the map.'**
|
||||
String get routeRefusedInvalid;
|
||||
|
||||
/// No description provided for @routeRefusedTooOften.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Planned too often: try again in a minute.'**
|
||||
String get routeRefusedTooOften;
|
||||
|
||||
/// No description provided for @routeRefusedNoRoute.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No way found there: a waypoint may be off the paths, or outside the region your instance plans in.'**
|
||||
String get routeRefusedNoRoute;
|
||||
|
||||
/// No description provided for @routeRefusedUnreachable.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Your instance can\'t be reached to plan the way.'**
|
||||
String get routeRefusedUnreachable;
|
||||
|
||||
/// No description provided for @routeNotOffered.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Your instance doesn\'t plan routes.'**
|
||||
String get routeNotOffered;
|
||||
|
||||
/// No description provided for @routeAscent.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'{metres} m up'**
|
||||
String routeAscent(int metres);
|
||||
|
||||
/// No description provided for @routeDescent.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'{metres} m down'**
|
||||
String routeDescent(int metres);
|
||||
|
||||
/// A route in the list: its distance and climb.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'{distance} · {ascent}'**
|
||||
String routeSummary(String distance, String ascent);
|
||||
|
||||
/// No description provided for @routeSurfaces.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Surfaces'**
|
||||
String get routeSurfaces;
|
||||
|
||||
/// No description provided for @surfacePaved.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Paved'**
|
||||
String get surfacePaved;
|
||||
|
||||
/// No description provided for @surfaceCompacted.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Compacted'**
|
||||
String get surfaceCompacted;
|
||||
|
||||
/// No description provided for @surfaceGravel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Gravel'**
|
||||
String get surfaceGravel;
|
||||
|
||||
/// No description provided for @surfaceNatural.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Natural ground'**
|
||||
String get surfaceNatural;
|
||||
|
||||
/// No description provided for @surfaceUnknown.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Not known'**
|
||||
String get surfaceUnknown;
|
||||
|
||||
/// No description provided for @routeSave.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Save'**
|
||||
String get routeSave;
|
||||
|
||||
/// No description provided for @routeNotSaved.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'The route wasn\'t saved.'**
|
||||
String get routeNotSaved;
|
||||
|
||||
/// No description provided for @routeDelete.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Delete'**
|
||||
String get routeDelete;
|
||||
|
||||
/// No description provided for @routeDeleteTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Delete this route?'**
|
||||
String get routeDeleteTitle;
|
||||
|
||||
/// No description provided for @routeDeleteMessage.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'It\'s deleted on all your devices.'**
|
||||
String get routeDeleteMessage;
|
||||
|
||||
/// No description provided for @routeDeleted.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Route deleted'**
|
||||
String get routeDeleted;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
|
|
|||
|
|
@ -1367,4 +1367,136 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String daySkinDeviation(String value) {
|
||||
return '$value °C gegenüber deinen Nächten sonst';
|
||||
}
|
||||
|
||||
@override
|
||||
String get homeRoutes => 'Routen';
|
||||
|
||||
@override
|
||||
String get routesNew => 'Neue Route';
|
||||
|
||||
@override
|
||||
String get routesEmptyTitle => 'Noch keine Routen';
|
||||
|
||||
@override
|
||||
String get routesEmptyHint =>
|
||||
'Tipp auf die Karte, um Wegpunkte zu setzen, und deine Instanz plant den Weg dazwischen über Wege und Straßen, mit Anstieg und Untergrund.';
|
||||
|
||||
@override
|
||||
String get routeLocked => 'Eine Route, die sich hier nicht öffnet';
|
||||
|
||||
@override
|
||||
String get routeTitle => 'Route';
|
||||
|
||||
@override
|
||||
String get routeNewTitle => 'Neue Route';
|
||||
|
||||
@override
|
||||
String get routeName => 'Name';
|
||||
|
||||
@override
|
||||
String get routeActivity => 'Für';
|
||||
|
||||
@override
|
||||
String get routeActivityAny => 'Jede Aktivität';
|
||||
|
||||
@override
|
||||
String get routeProfile => 'Geplant für';
|
||||
|
||||
@override
|
||||
String get routeProfileRun => 'Laufen';
|
||||
|
||||
@override
|
||||
String get routeProfileHike => 'Wandern';
|
||||
|
||||
@override
|
||||
String get routeProfileRoadBike => 'Rennrad';
|
||||
|
||||
@override
|
||||
String get routeProfileTrekkingBike => 'Trekkingrad';
|
||||
|
||||
@override
|
||||
String get routeProfileGravel => 'Gravelbike';
|
||||
|
||||
@override
|
||||
String get routeProfileMtb => 'Mountainbike';
|
||||
|
||||
@override
|
||||
String get routeTapHint =>
|
||||
'Tipp auf die Karte, um einen Wegpunkt hinzuzufügen; halt einen gedrückt und zieh ihn, um ihn zu verschieben.';
|
||||
|
||||
@override
|
||||
String get routeUndo => 'Letzten Wegpunkt entfernen';
|
||||
|
||||
@override
|
||||
String get routePlanning => 'Wird geplant …';
|
||||
|
||||
@override
|
||||
String get routeRefusedInvalid =>
|
||||
'Ein Wegpunkt zu viel, oder einer außerhalb der Karte.';
|
||||
|
||||
@override
|
||||
String get routeRefusedTooOften =>
|
||||
'Zu oft geplant: versuch es in einer Minute wieder.';
|
||||
|
||||
@override
|
||||
String get routeRefusedNoRoute =>
|
||||
'Dort wurde kein Weg gefunden: Ein Wegpunkt liegt vielleicht abseits der Wege oder außerhalb des Gebiets, in dem deine Instanz plant.';
|
||||
|
||||
@override
|
||||
String get routeRefusedUnreachable =>
|
||||
'Deine Instanz ist nicht erreichbar, um den Weg zu planen.';
|
||||
|
||||
@override
|
||||
String get routeNotOffered => 'Deine Instanz plant keine Routen.';
|
||||
|
||||
@override
|
||||
String routeAscent(int metres) {
|
||||
return '$metres m bergauf';
|
||||
}
|
||||
|
||||
@override
|
||||
String routeDescent(int metres) {
|
||||
return '$metres m bergab';
|
||||
}
|
||||
|
||||
@override
|
||||
String routeSummary(String distance, String ascent) {
|
||||
return '$distance · $ascent';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routeSurfaces => 'Untergrund';
|
||||
|
||||
@override
|
||||
String get surfacePaved => 'Befestigt';
|
||||
|
||||
@override
|
||||
String get surfaceCompacted => 'Verdichtet';
|
||||
|
||||
@override
|
||||
String get surfaceGravel => 'Schotter';
|
||||
|
||||
@override
|
||||
String get surfaceNatural => 'Naturboden';
|
||||
|
||||
@override
|
||||
String get surfaceUnknown => 'Unbekannt';
|
||||
|
||||
@override
|
||||
String get routeSave => 'Speichern';
|
||||
|
||||
@override
|
||||
String get routeNotSaved => 'Die Route wurde nicht gespeichert.';
|
||||
|
||||
@override
|
||||
String get routeDelete => 'Löschen';
|
||||
|
||||
@override
|
||||
String get routeDeleteTitle => 'Diese Route löschen?';
|
||||
|
||||
@override
|
||||
String get routeDeleteMessage => 'Sie wird auf all deinen Geräten gelöscht.';
|
||||
|
||||
@override
|
||||
String get routeDeleted => 'Route gelöscht';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1360,6 +1360,137 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String daySkinDeviation(String value) {
|
||||
return '$value °C from your usual at night';
|
||||
}
|
||||
|
||||
@override
|
||||
String get homeRoutes => 'Routes';
|
||||
|
||||
@override
|
||||
String get routesNew => 'New route';
|
||||
|
||||
@override
|
||||
String get routesEmptyTitle => 'No routes yet';
|
||||
|
||||
@override
|
||||
String get routesEmptyHint =>
|
||||
'Tap the map to set waypoints, and your instance plans the way between them on paths and roads, with its climb and surfaces.';
|
||||
|
||||
@override
|
||||
String get routeLocked => 'A route that doesn\'t open here';
|
||||
|
||||
@override
|
||||
String get routeTitle => 'Route';
|
||||
|
||||
@override
|
||||
String get routeNewTitle => 'New route';
|
||||
|
||||
@override
|
||||
String get routeName => 'Name';
|
||||
|
||||
@override
|
||||
String get routeActivity => 'For';
|
||||
|
||||
@override
|
||||
String get routeActivityAny => 'Any activity';
|
||||
|
||||
@override
|
||||
String get routeProfile => 'Planned for';
|
||||
|
||||
@override
|
||||
String get routeProfileRun => 'Running';
|
||||
|
||||
@override
|
||||
String get routeProfileHike => 'Hiking';
|
||||
|
||||
@override
|
||||
String get routeProfileRoadBike => 'Road bike';
|
||||
|
||||
@override
|
||||
String get routeProfileTrekkingBike => 'Trekking bike';
|
||||
|
||||
@override
|
||||
String get routeProfileGravel => 'Gravel bike';
|
||||
|
||||
@override
|
||||
String get routeProfileMtb => 'Mountain bike';
|
||||
|
||||
@override
|
||||
String get routeTapHint =>
|
||||
'Tap the map to add a waypoint; hold one and drag it to move it.';
|
||||
|
||||
@override
|
||||
String get routeUndo => 'Take the last waypoint off';
|
||||
|
||||
@override
|
||||
String get routePlanning => 'Planning…';
|
||||
|
||||
@override
|
||||
String get routeRefusedInvalid => 'A waypoint too many, or one off the map.';
|
||||
|
||||
@override
|
||||
String get routeRefusedTooOften =>
|
||||
'Planned too often: try again in a minute.';
|
||||
|
||||
@override
|
||||
String get routeRefusedNoRoute =>
|
||||
'No way found there: a waypoint may be off the paths, or outside the region your instance plans in.';
|
||||
|
||||
@override
|
||||
String get routeRefusedUnreachable =>
|
||||
'Your instance can\'t be reached to plan the way.';
|
||||
|
||||
@override
|
||||
String get routeNotOffered => 'Your instance doesn\'t plan routes.';
|
||||
|
||||
@override
|
||||
String routeAscent(int metres) {
|
||||
return '$metres m up';
|
||||
}
|
||||
|
||||
@override
|
||||
String routeDescent(int metres) {
|
||||
return '$metres m down';
|
||||
}
|
||||
|
||||
@override
|
||||
String routeSummary(String distance, String ascent) {
|
||||
return '$distance · $ascent';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routeSurfaces => 'Surfaces';
|
||||
|
||||
@override
|
||||
String get surfacePaved => 'Paved';
|
||||
|
||||
@override
|
||||
String get surfaceCompacted => 'Compacted';
|
||||
|
||||
@override
|
||||
String get surfaceGravel => 'Gravel';
|
||||
|
||||
@override
|
||||
String get surfaceNatural => 'Natural ground';
|
||||
|
||||
@override
|
||||
String get surfaceUnknown => 'Not known';
|
||||
|
||||
@override
|
||||
String get routeSave => 'Save';
|
||||
|
||||
@override
|
||||
String get routeNotSaved => 'The route wasn\'t saved.';
|
||||
|
||||
@override
|
||||
String get routeDelete => 'Delete';
|
||||
|
||||
@override
|
||||
String get routeDeleteTitle => 'Delete this route?';
|
||||
|
||||
@override
|
||||
String get routeDeleteMessage => 'It\'s deleted on all your devices.';
|
||||
|
||||
@override
|
||||
String get routeDeleted => 'Route deleted';
|
||||
}
|
||||
|
||||
/// The translations for English, as used in the United Kingdom (`en_GB`).
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import 'package:halkyon_train_flutter/src/feature/plan/bloc/plans_cubit.dart';
|
|||
import 'package:halkyon_train_flutter/src/feature/plan/widget/plan_speech.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/recording/bloc/recording_bloc.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/recording/widget/recording_texts.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/route/bloc/routes_cubit.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/workout/bloc/workouts_cubit.dart';
|
||||
import 'package:halkyon_train_flutter/src/home/home_screen.dart';
|
||||
import 'package:halkyon_ui/halkyon_ui.dart';
|
||||
|
|
@ -100,6 +101,12 @@ class const TrainPersonApp({
|
|||
return PlansCubit(train.plans, train.planDeletions)..open();
|
||||
},
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) {
|
||||
final train = PersonScope.of<TrainPerson>(context);
|
||||
return RoutesCubit(train.routes, train.routeDeletions)..open();
|
||||
},
|
||||
),
|
||||
],
|
||||
child: _Recording(child: child!),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@ import 'package:halkyon_train_flutter/src/feature/profile/data/sync_profile_gate
|
|||
import 'package:halkyon_train_flutter/src/feature/recording/data/android_recorder_platform.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/recording/data/device_store.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/recording/data/recorder_platform.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/route/data/route_repository.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/route/data/routing_gateway.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/route/data/sync_route_gateway.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/workout/data/sync_workout_gateway.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/workout/data/workout_repository.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
|
@ -67,6 +70,12 @@ class TrainPerson(
|
|||
/// Their interval plans (D42), sealed and synced.
|
||||
required final PlanRepository plans,
|
||||
|
||||
/// Their routes (D44), sealed and synced.
|
||||
required final RouteRepository routes,
|
||||
|
||||
/// The instance's planning of routes (D44).
|
||||
required final RoutingGateway routing,
|
||||
|
||||
/// The instance's map source (D22).
|
||||
required final MapSourceRepository maps,
|
||||
|
||||
|
|
@ -93,11 +102,18 @@ class TrainPerson(
|
|||
/// Plans deleted, to be undone for a moment, as workouts are.
|
||||
late final planDeletions = DelayedDeletions(plans.delete, logName: 'Plans');
|
||||
|
||||
/// Routes deleted, to be undone for a moment, as workouts are.
|
||||
late final routeDeletions = DelayedDeletions(
|
||||
routes.delete,
|
||||
logName: 'Routes',
|
||||
);
|
||||
|
||||
/// Closes what was opened for the person.
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
await deletions.dispose();
|
||||
await planDeletions.dispose();
|
||||
await routeDeletions.dispose();
|
||||
await health?.dispose();
|
||||
await loop?.dispose();
|
||||
await _onDispose?.call();
|
||||
|
|
@ -340,6 +356,7 @@ Future<TrainPerson> _openPersonData(
|
|||
person: person.authUserId,
|
||||
);
|
||||
final plans = PlanRepository(SyncPlanGateway(db, sync), keys);
|
||||
final routes = RouteRepository(SyncRouteGateway(db, sync), keys);
|
||||
// Writes what changed while the app was closed, as it opens (D31).
|
||||
final health = Platform.isAndroid
|
||||
? (HealthConnect(
|
||||
|
|
@ -367,6 +384,8 @@ Future<TrainPerson> _openPersonData(
|
|||
profile: profile,
|
||||
days: days,
|
||||
plans: plans,
|
||||
routes: routes,
|
||||
routing: ServerpodRoutingGateway(client),
|
||||
maps: MapSourceRepository(ServerpodMapSourceGateway(client)),
|
||||
recording: recorder != null
|
||||
? (
|
||||
|
|
@ -384,6 +403,7 @@ Future<TrainPerson> _openPersonData(
|
|||
await profile.dispose();
|
||||
await days.dispose();
|
||||
await plans.dispose();
|
||||
await routes.dispose();
|
||||
await sync.dispose();
|
||||
await db.close();
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -69,6 +69,13 @@ class AndroidRecorderPlatform() implements RecorderPlatform {
|
|||
Future<bool> isRecording() async =>
|
||||
await _channel.invokeMethod<bool>('isRecording') ?? false;
|
||||
|
||||
@override
|
||||
Future<({double lat, double lon})?> position() async =>
|
||||
switch (await _channel.invokeMapMethod<String, double>('position')) {
|
||||
{'lat': final lat, 'lon': final lon} => (lat: lat, lon: lon),
|
||||
_ => null,
|
||||
};
|
||||
|
||||
@override
|
||||
Stream<String> lines() => _lines.receiveBroadcastStream().cast<String>();
|
||||
|
||||
|
|
|
|||
|
|
@ -124,6 +124,10 @@ abstract interface class RecorderPlatform() {
|
|||
/// Whether a recording runs, as after the app was gone and came back.
|
||||
Future<bool> isRecording();
|
||||
|
||||
/// Where the phone is, for a route's first waypoint (D44); null without
|
||||
/// the location permission ([ask] first) or a fix within 15 s.
|
||||
Future<({double lat, double lon})?> position();
|
||||
|
||||
/// Every line of the file written so far, then each new one as it is
|
||||
/// written, while a recording runs.
|
||||
Stream<String> lines();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,188 @@
|
|||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:halkyon_train_flutter/src/core/privacy/train_keys.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/route/data/route_repository.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/route/data/routing_gateway.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/route/model/route.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/workout/model/workout.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
/// A route being planned or changed (ADR 0030, D44): waypoints tapped,
|
||||
/// dragged and the last undone, a new route's first where the phone is
|
||||
/// (`here`, null where the device can't tell); the instance plans the way
|
||||
/// through them after each change, an answer to an earlier change dropped
|
||||
/// once a later one is asked. Kept once named and planned; [id] null for a
|
||||
/// new one.
|
||||
class RouteEditorCubit(
|
||||
final RouteRepository _routes,
|
||||
final RoutingGateway _routing, {
|
||||
required final RouteContent route,
|
||||
final String? id,
|
||||
final Future<LatLng?> Function()? _here,
|
||||
}) extends Cubit<RouteEditorState> {
|
||||
this : super(RouteEditorState(route: route, id: id));
|
||||
|
||||
/// Counts the plans asked, so that only the latest's answer counts.
|
||||
var _asked = 0;
|
||||
|
||||
/// Asks whether the instance plans routes, and starts a new route where
|
||||
/// the phone is.
|
||||
Future<void> open() async {
|
||||
final offered = await _routing.offered();
|
||||
if (isClosed) {
|
||||
return;
|
||||
}
|
||||
emit(state._with(route: state.route, offered: offered));
|
||||
if (!offered || state.id != null || state.route.waypoints.isNotEmpty) {
|
||||
return;
|
||||
}
|
||||
final here = await _here?.call();
|
||||
if (here != null && !isClosed && state.route.waypoints.isEmpty) {
|
||||
emit(state._with(route: _route(waypoints: [here])));
|
||||
}
|
||||
}
|
||||
|
||||
void rename(String name) => emit(state._with(route: _route(name: name)));
|
||||
|
||||
/// Sets what the route is for, and the profile that goes with it.
|
||||
Future<void> setActivity(Activity? activity) => _replan(
|
||||
RouteContent(
|
||||
name: state.route.name,
|
||||
activity: activity,
|
||||
profile: RouteProfile.of(activity),
|
||||
waypoints: state.route.waypoints,
|
||||
),
|
||||
);
|
||||
|
||||
Future<void> setProfile(RouteProfile profile) =>
|
||||
_replan(_route(profile: profile));
|
||||
|
||||
/// Adds a waypoint at the end.
|
||||
Future<void> add(LatLng point) =>
|
||||
_replan(_route(waypoints: [...state.route.waypoints, point]));
|
||||
|
||||
/// Moves waypoint [index] to [point] (dragged).
|
||||
Future<void> move(int index, LatLng point) =>
|
||||
_replan(_route(waypoints: [...state.route.waypoints]..[index] = point));
|
||||
|
||||
/// Takes the last waypoint off.
|
||||
Future<void> undo() async {
|
||||
final waypoints = state.route.waypoints;
|
||||
if (waypoints.isNotEmpty) {
|
||||
await _replan(
|
||||
_route(waypoints: waypoints.sublist(0, waypoints.length - 1)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Keeps the route; false where it can't be (no name, no way planned, or
|
||||
/// this device can't seal).
|
||||
Future<bool> save() async {
|
||||
if (!state.canSave) {
|
||||
return false;
|
||||
}
|
||||
final named = _route(name: state.route.name.trim(), keepPlanned: true);
|
||||
try {
|
||||
if (state.id case final id?) {
|
||||
await _routes.update(id, named);
|
||||
} else {
|
||||
final id = await _routes.save(named);
|
||||
if (!isClosed) {
|
||||
emit(RouteEditorState(route: named, id: id, offered: state.offered));
|
||||
}
|
||||
}
|
||||
} on KeysMissing {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// [route], its way planned anew where it has two waypoints at least.
|
||||
Future<void> _replan(RouteContent route) async {
|
||||
final asked = ++_asked;
|
||||
if (route.waypoints.length < 2) {
|
||||
emit(state._with(route: route, planning: false));
|
||||
return;
|
||||
}
|
||||
emit(state._with(route: route, planning: true));
|
||||
RouteGeometry? planned;
|
||||
RoutingRefusal? refusal;
|
||||
try {
|
||||
planned = await _routing.plan(route.waypoints, route.profile);
|
||||
} on RoutingRefused catch (refused) {
|
||||
refusal = refused.reason;
|
||||
}
|
||||
if (asked != _asked || isClosed) {
|
||||
return;
|
||||
}
|
||||
emit(
|
||||
RouteEditorState(
|
||||
route: RouteContent(
|
||||
name: state.route.name,
|
||||
activity: route.activity,
|
||||
profile: route.profile,
|
||||
waypoints: route.waypoints,
|
||||
planned: planned,
|
||||
),
|
||||
id: state.id,
|
||||
offered: state.offered,
|
||||
refusal: refusal,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// The route with what is given changed; its way planned dropped unless
|
||||
/// [keepPlanned] or only the name changes, as it is planned again.
|
||||
RouteContent _route({
|
||||
String? name,
|
||||
RouteProfile? profile,
|
||||
List<LatLng>? waypoints,
|
||||
bool keepPlanned = false,
|
||||
}) {
|
||||
final route = state.route;
|
||||
return RouteContent(
|
||||
name: name ?? route.name,
|
||||
activity: route.activity,
|
||||
profile: profile ?? route.profile,
|
||||
waypoints: waypoints ?? route.waypoints,
|
||||
planned: keepPlanned || (profile == null && waypoints == null)
|
||||
? route.planned
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final class const RouteEditorState({
|
||||
required final RouteContent route,
|
||||
final String? id,
|
||||
|
||||
/// Whether the instance plans routes; null until asked.
|
||||
final bool? offered,
|
||||
|
||||
/// Whether the instance is planning the way after a change.
|
||||
final bool planning = false,
|
||||
|
||||
/// Why the instance planned no way after the last change; null: it did,
|
||||
/// or nothing was asked.
|
||||
final RoutingRefusal? refusal,
|
||||
}) with Equatable {
|
||||
/// Whether it has what a route needs: a name and a way planned.
|
||||
bool get canSave =>
|
||||
route.name.trim().isNotEmpty && route.planned != null && !planning;
|
||||
|
||||
/// A refusal stays only until the next change is planned.
|
||||
RouteEditorState _with({
|
||||
required RouteContent route,
|
||||
bool? planning,
|
||||
bool? offered,
|
||||
}) => RouteEditorState(
|
||||
route: route,
|
||||
id: id,
|
||||
offered: offered ?? this.offered,
|
||||
planning: planning ?? this.planning,
|
||||
refusal: (planning ?? false) || route.waypoints.length < 2 ? null : refusal,
|
||||
);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [route, id, offered, planning, refusal];
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue