Persist the ground elevation profile with the mission
The Mini-Altitude-Profile terrain data only ever lived in TerrainNotifier's in-memory state (one slot, keyed by route). Every app restart, or every switch away from and back to a mission, forced a full re-fetch of all AWS Terrarium elevation tiles for the route - the noticeably slow load the user reported for some missions was this happening on every visit, not just once. Adds a nullable terrainProfileJson column to the missions table (schema v6->v7) and a saveTerrainProfile() repository method, kept separate from the regular waypoint upsert() so an ordinary autosave never clobbers an already-cached profile. TerrainNotifier persists a profile right after a successful fetch (fire-and-forget) and gains restore(), called from CurrentMissionMetaNotifier whenever a mission is loaded/ started so a previously fetched profile is available immediately - ensureFor() still validates its routeKey before use, so a stale restored profile is never shown for a route that has since changed. Also included in mission export/import (MissionExportData/ ParsedMissionImport) so sharing a mission carries its terrain cache along instead of forcing the recipient to refetch it. Sample-count/resolution stays as-is for now (still fixed 30m spacing, 10-2000 samples) - adapting the sampling density to terrain variance (e.g. coarser sampling over flat ground) is a separate follow-up. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
4e94102861
commit
35351140ae
@@ -24,6 +24,14 @@ class Missions extends Table {
|
||||
TextColumn get waypointsJson => text()();
|
||||
BoolColumn get isDirty => boolean().withDefault(const Constant(true))();
|
||||
TextColumn get serverId => text().nullable()();
|
||||
|
||||
/// Gecachtes Bodenhoehenprofil (Doku 3.9), an einen routeKeyForTerrain()
|
||||
/// gebunden (siehe TerrainProfile) - null, solange fuer die aktuelle Route
|
||||
/// noch keins abgerufen wurde. Persistiert statt nur im TerrainNotifier
|
||||
/// im Speicher gehalten, damit ein erneutes Oeffnen derselben Mission
|
||||
/// (oder ein Import auf einem anderen Geraet) nicht jedes Mal alle
|
||||
/// AWS-Terrarium-Kacheln neu laden muss.
|
||||
TextColumn get terrainProfileJson => text().nullable()();
|
||||
}
|
||||
|
||||
/// Drohnen-Profile (Doku 3.5/7.3) - FC-Typ bewusst nicht Teil des Schemas,
|
||||
@@ -102,7 +110,7 @@ class AppDatabase extends _$AppDatabase {
|
||||
AppDatabase.forTesting(super.executor);
|
||||
|
||||
@override
|
||||
int get schemaVersion => 6;
|
||||
int get schemaVersion => 7;
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration => MigrationStrategy(
|
||||
@@ -146,6 +154,9 @@ class AppDatabase extends _$AppDatabase {
|
||||
appSettingsTable.connectionWifiLastSsid,
|
||||
);
|
||||
}
|
||||
if (from < 7) {
|
||||
await m.addColumn(missions, missions.terrainProfileJson);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -102,6 +102,17 @@ class $MissionsTable extends Missions with TableInfo<$MissionsTable, Mission> {
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: false,
|
||||
);
|
||||
static const VerificationMeta _terrainProfileJsonMeta =
|
||||
const VerificationMeta('terrainProfileJson');
|
||||
@override
|
||||
late final GeneratedColumn<String> terrainProfileJson =
|
||||
GeneratedColumn<String>(
|
||||
'terrain_profile_json',
|
||||
aliasedName,
|
||||
true,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: false,
|
||||
);
|
||||
@override
|
||||
List<GeneratedColumn> get $columns => [
|
||||
id,
|
||||
@@ -112,6 +123,7 @@ class $MissionsTable extends Missions with TableInfo<$MissionsTable, Mission> {
|
||||
waypointsJson,
|
||||
isDirty,
|
||||
serverId,
|
||||
terrainProfileJson,
|
||||
];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@@ -177,6 +189,15 @@ class $MissionsTable extends Missions with TableInfo<$MissionsTable, Mission> {
|
||||
serverId.isAcceptableOrUnknown(data['server_id']!, _serverIdMeta),
|
||||
);
|
||||
}
|
||||
if (data.containsKey('terrain_profile_json')) {
|
||||
context.handle(
|
||||
_terrainProfileJsonMeta,
|
||||
terrainProfileJson.isAcceptableOrUnknown(
|
||||
data['terrain_profile_json']!,
|
||||
_terrainProfileJsonMeta,
|
||||
),
|
||||
);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
@@ -218,6 +239,10 @@ class $MissionsTable extends Missions with TableInfo<$MissionsTable, Mission> {
|
||||
DriftSqlType.string,
|
||||
data['${effectivePrefix}server_id'],
|
||||
),
|
||||
terrainProfileJson: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.string,
|
||||
data['${effectivePrefix}terrain_profile_json'],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -236,6 +261,14 @@ class Mission extends DataClass implements Insertable<Mission> {
|
||||
final String waypointsJson;
|
||||
final bool isDirty;
|
||||
final String? serverId;
|
||||
|
||||
/// Gecachtes Bodenhoehenprofil (Doku 3.9), an einen routeKeyForTerrain()
|
||||
/// gebunden (siehe TerrainProfile) - null, solange fuer die aktuelle Route
|
||||
/// noch keins abgerufen wurde. Persistiert statt nur im TerrainNotifier
|
||||
/// im Speicher gehalten, damit ein erneutes Oeffnen derselben Mission
|
||||
/// (oder ein Import auf einem anderen Geraet) nicht jedes Mal alle
|
||||
/// AWS-Terrarium-Kacheln neu laden muss.
|
||||
final String? terrainProfileJson;
|
||||
const Mission({
|
||||
required this.id,
|
||||
required this.name,
|
||||
@@ -245,6 +278,7 @@ class Mission extends DataClass implements Insertable<Mission> {
|
||||
required this.waypointsJson,
|
||||
required this.isDirty,
|
||||
this.serverId,
|
||||
this.terrainProfileJson,
|
||||
});
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
@@ -261,6 +295,9 @@ class Mission extends DataClass implements Insertable<Mission> {
|
||||
if (!nullToAbsent || serverId != null) {
|
||||
map['server_id'] = Variable<String>(serverId);
|
||||
}
|
||||
if (!nullToAbsent || terrainProfileJson != null) {
|
||||
map['terrain_profile_json'] = Variable<String>(terrainProfileJson);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@@ -278,6 +315,9 @@ class Mission extends DataClass implements Insertable<Mission> {
|
||||
serverId: serverId == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: Value(serverId),
|
||||
terrainProfileJson: terrainProfileJson == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: Value(terrainProfileJson),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -295,6 +335,9 @@ class Mission extends DataClass implements Insertable<Mission> {
|
||||
waypointsJson: serializer.fromJson<String>(json['waypointsJson']),
|
||||
isDirty: serializer.fromJson<bool>(json['isDirty']),
|
||||
serverId: serializer.fromJson<String?>(json['serverId']),
|
||||
terrainProfileJson: serializer.fromJson<String?>(
|
||||
json['terrainProfileJson'],
|
||||
),
|
||||
);
|
||||
}
|
||||
@override
|
||||
@@ -309,6 +352,7 @@ class Mission extends DataClass implements Insertable<Mission> {
|
||||
'waypointsJson': serializer.toJson<String>(waypointsJson),
|
||||
'isDirty': serializer.toJson<bool>(isDirty),
|
||||
'serverId': serializer.toJson<String?>(serverId),
|
||||
'terrainProfileJson': serializer.toJson<String?>(terrainProfileJson),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -321,6 +365,7 @@ class Mission extends DataClass implements Insertable<Mission> {
|
||||
String? waypointsJson,
|
||||
bool? isDirty,
|
||||
Value<String?> serverId = const Value.absent(),
|
||||
Value<String?> terrainProfileJson = const Value.absent(),
|
||||
}) => Mission(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
@@ -330,6 +375,9 @@ class Mission extends DataClass implements Insertable<Mission> {
|
||||
waypointsJson: waypointsJson ?? this.waypointsJson,
|
||||
isDirty: isDirty ?? this.isDirty,
|
||||
serverId: serverId.present ? serverId.value : this.serverId,
|
||||
terrainProfileJson: terrainProfileJson.present
|
||||
? terrainProfileJson.value
|
||||
: this.terrainProfileJson,
|
||||
);
|
||||
Mission copyWithCompanion(MissionsCompanion data) {
|
||||
return Mission(
|
||||
@@ -343,6 +391,9 @@ class Mission extends DataClass implements Insertable<Mission> {
|
||||
: this.waypointsJson,
|
||||
isDirty: data.isDirty.present ? data.isDirty.value : this.isDirty,
|
||||
serverId: data.serverId.present ? data.serverId.value : this.serverId,
|
||||
terrainProfileJson: data.terrainProfileJson.present
|
||||
? data.terrainProfileJson.value
|
||||
: this.terrainProfileJson,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -356,7 +407,8 @@ class Mission extends DataClass implements Insertable<Mission> {
|
||||
..write('updatedAt: $updatedAt, ')
|
||||
..write('waypointsJson: $waypointsJson, ')
|
||||
..write('isDirty: $isDirty, ')
|
||||
..write('serverId: $serverId')
|
||||
..write('serverId: $serverId, ')
|
||||
..write('terrainProfileJson: $terrainProfileJson')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
@@ -371,6 +423,7 @@ class Mission extends DataClass implements Insertable<Mission> {
|
||||
waypointsJson,
|
||||
isDirty,
|
||||
serverId,
|
||||
terrainProfileJson,
|
||||
);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
@@ -383,7 +436,8 @@ class Mission extends DataClass implements Insertable<Mission> {
|
||||
other.updatedAt == this.updatedAt &&
|
||||
other.waypointsJson == this.waypointsJson &&
|
||||
other.isDirty == this.isDirty &&
|
||||
other.serverId == this.serverId);
|
||||
other.serverId == this.serverId &&
|
||||
other.terrainProfileJson == this.terrainProfileJson);
|
||||
}
|
||||
|
||||
class MissionsCompanion extends UpdateCompanion<Mission> {
|
||||
@@ -395,6 +449,7 @@ class MissionsCompanion extends UpdateCompanion<Mission> {
|
||||
final Value<String> waypointsJson;
|
||||
final Value<bool> isDirty;
|
||||
final Value<String?> serverId;
|
||||
final Value<String?> terrainProfileJson;
|
||||
const MissionsCompanion({
|
||||
this.id = const Value.absent(),
|
||||
this.name = const Value.absent(),
|
||||
@@ -404,6 +459,7 @@ class MissionsCompanion extends UpdateCompanion<Mission> {
|
||||
this.waypointsJson = const Value.absent(),
|
||||
this.isDirty = const Value.absent(),
|
||||
this.serverId = const Value.absent(),
|
||||
this.terrainProfileJson = const Value.absent(),
|
||||
});
|
||||
MissionsCompanion.insert({
|
||||
this.id = const Value.absent(),
|
||||
@@ -414,6 +470,7 @@ class MissionsCompanion extends UpdateCompanion<Mission> {
|
||||
required String waypointsJson,
|
||||
this.isDirty = const Value.absent(),
|
||||
this.serverId = const Value.absent(),
|
||||
this.terrainProfileJson = const Value.absent(),
|
||||
}) : name = Value(name),
|
||||
waypointsJson = Value(waypointsJson);
|
||||
static Insertable<Mission> custom({
|
||||
@@ -425,6 +482,7 @@ class MissionsCompanion extends UpdateCompanion<Mission> {
|
||||
Expression<String>? waypointsJson,
|
||||
Expression<bool>? isDirty,
|
||||
Expression<String>? serverId,
|
||||
Expression<String>? terrainProfileJson,
|
||||
}) {
|
||||
return RawValuesInsertable({
|
||||
if (id != null) 'id': id,
|
||||
@@ -435,6 +493,8 @@ class MissionsCompanion extends UpdateCompanion<Mission> {
|
||||
if (waypointsJson != null) 'waypoints_json': waypointsJson,
|
||||
if (isDirty != null) 'is_dirty': isDirty,
|
||||
if (serverId != null) 'server_id': serverId,
|
||||
if (terrainProfileJson != null)
|
||||
'terrain_profile_json': terrainProfileJson,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -447,6 +507,7 @@ class MissionsCompanion extends UpdateCompanion<Mission> {
|
||||
Value<String>? waypointsJson,
|
||||
Value<bool>? isDirty,
|
||||
Value<String?>? serverId,
|
||||
Value<String?>? terrainProfileJson,
|
||||
}) {
|
||||
return MissionsCompanion(
|
||||
id: id ?? this.id,
|
||||
@@ -457,6 +518,7 @@ class MissionsCompanion extends UpdateCompanion<Mission> {
|
||||
waypointsJson: waypointsJson ?? this.waypointsJson,
|
||||
isDirty: isDirty ?? this.isDirty,
|
||||
serverId: serverId ?? this.serverId,
|
||||
terrainProfileJson: terrainProfileJson ?? this.terrainProfileJson,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -487,6 +549,9 @@ class MissionsCompanion extends UpdateCompanion<Mission> {
|
||||
if (serverId.present) {
|
||||
map['server_id'] = Variable<String>(serverId.value);
|
||||
}
|
||||
if (terrainProfileJson.present) {
|
||||
map['terrain_profile_json'] = Variable<String>(terrainProfileJson.value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@@ -500,7 +565,8 @@ class MissionsCompanion extends UpdateCompanion<Mission> {
|
||||
..write('updatedAt: $updatedAt, ')
|
||||
..write('waypointsJson: $waypointsJson, ')
|
||||
..write('isDirty: $isDirty, ')
|
||||
..write('serverId: $serverId')
|
||||
..write('serverId: $serverId, ')
|
||||
..write('terrainProfileJson: $terrainProfileJson')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
@@ -2036,6 +2102,7 @@ typedef $$MissionsTableCreateCompanionBuilder =
|
||||
required String waypointsJson,
|
||||
Value<bool> isDirty,
|
||||
Value<String?> serverId,
|
||||
Value<String?> terrainProfileJson,
|
||||
});
|
||||
typedef $$MissionsTableUpdateCompanionBuilder =
|
||||
MissionsCompanion Function({
|
||||
@@ -2047,6 +2114,7 @@ typedef $$MissionsTableUpdateCompanionBuilder =
|
||||
Value<String> waypointsJson,
|
||||
Value<bool> isDirty,
|
||||
Value<String?> serverId,
|
||||
Value<String?> terrainProfileJson,
|
||||
});
|
||||
|
||||
class $$MissionsTableFilterComposer
|
||||
@@ -2097,6 +2165,11 @@ class $$MissionsTableFilterComposer
|
||||
column: $table.serverId,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
ColumnFilters<String> get terrainProfileJson => $composableBuilder(
|
||||
column: $table.terrainProfileJson,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
}
|
||||
|
||||
class $$MissionsTableOrderingComposer
|
||||
@@ -2147,6 +2220,11 @@ class $$MissionsTableOrderingComposer
|
||||
column: $table.serverId,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
|
||||
ColumnOrderings<String> get terrainProfileJson => $composableBuilder(
|
||||
column: $table.terrainProfileJson,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
}
|
||||
|
||||
class $$MissionsTableAnnotationComposer
|
||||
@@ -2183,6 +2261,11 @@ class $$MissionsTableAnnotationComposer
|
||||
|
||||
GeneratedColumn<String> get serverId =>
|
||||
$composableBuilder(column: $table.serverId, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<String> get terrainProfileJson => $composableBuilder(
|
||||
column: $table.terrainProfileJson,
|
||||
builder: (column) => column,
|
||||
);
|
||||
}
|
||||
|
||||
class $$MissionsTableTableManager
|
||||
@@ -2221,6 +2304,7 @@ class $$MissionsTableTableManager
|
||||
Value<String> waypointsJson = const Value.absent(),
|
||||
Value<bool> isDirty = const Value.absent(),
|
||||
Value<String?> serverId = const Value.absent(),
|
||||
Value<String?> terrainProfileJson = const Value.absent(),
|
||||
}) => MissionsCompanion(
|
||||
id: id,
|
||||
name: name,
|
||||
@@ -2230,6 +2314,7 @@ class $$MissionsTableTableManager
|
||||
waypointsJson: waypointsJson,
|
||||
isDirty: isDirty,
|
||||
serverId: serverId,
|
||||
terrainProfileJson: terrainProfileJson,
|
||||
),
|
||||
createCompanionCallback:
|
||||
({
|
||||
@@ -2241,6 +2326,7 @@ class $$MissionsTableTableManager
|
||||
required String waypointsJson,
|
||||
Value<bool> isDirty = const Value.absent(),
|
||||
Value<String?> serverId = const Value.absent(),
|
||||
Value<String?> terrainProfileJson = const Value.absent(),
|
||||
}) => MissionsCompanion.insert(
|
||||
id: id,
|
||||
name: name,
|
||||
@@ -2250,6 +2336,7 @@ class $$MissionsTableTableManager
|
||||
waypointsJson: waypointsJson,
|
||||
isDirty: isDirty,
|
||||
serverId: serverId,
|
||||
terrainProfileJson: terrainProfileJson,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
.map((e) => (e.readTable(table), BaseReferences(db, table, e)))
|
||||
|
||||
@@ -2,8 +2,10 @@ import 'dart:convert';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
|
||||
import '../../domain/mission/terrain_math.dart';
|
||||
import '../../domain/waypoint/flat_waypoint_list.dart';
|
||||
import 'app_database.dart';
|
||||
import 'terrain_json_codec.dart';
|
||||
import 'waypoint_json_codec.dart';
|
||||
|
||||
/// Kopfzeilen-taugliche Zusammenfassung einer gespeicherten Mission, ohne
|
||||
@@ -31,12 +33,18 @@ class LoadedMission {
|
||||
required this.placeName,
|
||||
required this.createdAt,
|
||||
required this.waypoints,
|
||||
this.terrainProfile,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String? placeName;
|
||||
final DateTime createdAt;
|
||||
final List<Waypoint> waypoints;
|
||||
|
||||
/// Gecachtes Bodenhoehenprofil (Doku 3.9), falls fuer die zuletzt
|
||||
/// gespeicherte Route bereits eins abgerufen wurde - null, wenn noch
|
||||
/// keins vorliegt oder es (Route inzwischen anders) verworfen werden muss.
|
||||
final TerrainProfile? terrainProfile;
|
||||
}
|
||||
|
||||
/// Persistenz-Zugriff fuer Missionen (Doku 4.15): kapselt die Drift-Tabelle
|
||||
@@ -77,6 +85,9 @@ class MissionRepository {
|
||||
waypoints: [
|
||||
for (final entry in decoded) waypointFromJson(entry as Map<String, dynamic>),
|
||||
],
|
||||
terrainProfile: row.terrainProfileJson != null
|
||||
? decodeTerrainProfile(row.terrainProfileJson!)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -110,6 +121,16 @@ class MissionRepository {
|
||||
return id;
|
||||
}
|
||||
|
||||
/// Persistiert ein frisch abgerufenes Bodenhoehenprofil fuer eine bereits
|
||||
/// gespeicherte Mission (Doku 3.9) - separat von [upsert], damit ein
|
||||
/// gewoehnlicher Wegpunkt-Autosave (bei dem noch kein Profil vorliegt)
|
||||
/// nicht versehentlich ein zuvor gespeichertes Profil loescht.
|
||||
Future<void> saveTerrainProfile(int id, TerrainProfile profile) async {
|
||||
await (_db.update(_db.missions)..where((m) => m.id.equals(id))).write(
|
||||
MissionsCompanion(terrainProfileJson: Value(encodeTerrainProfile(profile))),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> rename(int id, String name) async {
|
||||
await (_db.update(_db.missions)..where((m) => m.id.equals(id))).write(
|
||||
MissionsCompanion(name: Value(name), updatedAt: Value(DateTime.now())),
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import '../../domain/mission/terrain_math.dart';
|
||||
|
||||
/// JSON-(De-)Serialisierung fuer [TerrainProfile] - gemeinsam genutzt von
|
||||
/// der Missions-Persistenz ([MissionRepository]) und dem Mission-Sharing
|
||||
/// ([MissionShareCodec]), analog waypoint_json_codec.dart.
|
||||
Map<String, dynamic> terrainProfileToJson(TerrainProfile profile) => {
|
||||
'routeKey': profile.routeKey,
|
||||
'points': [
|
||||
for (final p in profile.points)
|
||||
{'distanceM': p.distanceM, 'elevationRelM': p.elevationRelM},
|
||||
],
|
||||
};
|
||||
|
||||
TerrainProfile terrainProfileFromJson(Map<String, dynamic> json) => TerrainProfile(
|
||||
routeKey: json['routeKey'] as String,
|
||||
points: [
|
||||
for (final entry in json['points'] as List)
|
||||
TerrainSamplePoint(
|
||||
distanceM: (entry['distanceM'] as num).toDouble(),
|
||||
elevationRelM: (entry['elevationRelM'] as num?)?.toDouble(),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
String encodeTerrainProfile(TerrainProfile profile) =>
|
||||
jsonEncode(terrainProfileToJson(profile));
|
||||
|
||||
TerrainProfile decodeTerrainProfile(String json) =>
|
||||
terrainProfileFromJson(jsonDecode(json) as Map<String, dynamic>);
|
||||
@@ -1,7 +1,9 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import '../../app_info.dart';
|
||||
import '../../domain/mission/terrain_math.dart';
|
||||
import '../../domain/waypoint/flat_waypoint_list.dart';
|
||||
import '../database/terrain_json_codec.dart';
|
||||
import '../database/waypoint_json_codec.dart';
|
||||
|
||||
/// Missionsdaten fuer den Export - unabhaengig von der Drift-Zeilenklasse,
|
||||
@@ -12,12 +14,18 @@ class MissionExportData {
|
||||
this.placeName,
|
||||
required this.createdAt,
|
||||
required this.waypoints,
|
||||
this.terrainProfile,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String? placeName;
|
||||
final DateTime createdAt;
|
||||
final List<Waypoint> waypoints;
|
||||
|
||||
/// Gecachtes Bodenhoehenprofil (Doku 3.9), falls fuer die Mission bereits
|
||||
/// eins abgerufen wurde - wird mitexportiert, damit ein Import auf einem
|
||||
/// anderen Geraet nicht erneut alle Kacheln laden muss.
|
||||
final TerrainProfile? terrainProfile;
|
||||
}
|
||||
|
||||
/// Baut die Export-Datei fuer eine Mission (Doku 3.6, analog
|
||||
@@ -33,6 +41,8 @@ String buildMissionExportJson(MissionExportData mission) {
|
||||
'placeName': mission.placeName,
|
||||
'createdAt': mission.createdAt.toUtc().toIso8601String(),
|
||||
'waypoints': [for (final w in mission.waypoints) waypointToJson(w)],
|
||||
if (mission.terrainProfile != null)
|
||||
'terrainProfile': terrainProfileToJson(mission.terrainProfile!),
|
||||
},
|
||||
};
|
||||
return const JsonEncoder.withIndent(' ').convert(map);
|
||||
@@ -51,6 +61,7 @@ class ParsedMissionImport {
|
||||
this.placeName,
|
||||
required this.waypoints,
|
||||
this.versionMismatch,
|
||||
this.terrainProfile,
|
||||
});
|
||||
|
||||
final String name;
|
||||
@@ -61,6 +72,11 @@ class ParsedMissionImport {
|
||||
/// der Hauptversionsnummer unterscheidet - sonst null (analog der
|
||||
/// "Imported from a different app version"-Warnung im HTML-Demonstrator).
|
||||
final String? versionMismatch;
|
||||
|
||||
/// Mitexportiertes Bodenhoehenprofil (Doku 3.9), falls die Datei eins
|
||||
/// enthaelt - null bei Dateien aus einer Version ohne dieses Feld oder
|
||||
/// wenn die Quellmission noch keins hatte.
|
||||
final TerrainProfile? terrainProfile;
|
||||
}
|
||||
|
||||
/// Parst eine importierte Missionsdatei - wirft [FormatException] bei
|
||||
@@ -100,10 +116,16 @@ ParsedMissionImport parseMissionImportJson(String jsonText) {
|
||||
? appVersion
|
||||
: null;
|
||||
|
||||
final terrainProfileRaw = missionData['terrainProfile'];
|
||||
final terrainProfile = terrainProfileRaw is Map<String, dynamic>
|
||||
? terrainProfileFromJson(terrainProfileRaw)
|
||||
: null;
|
||||
|
||||
return ParsedMissionImport(
|
||||
name: name,
|
||||
placeName: placeName,
|
||||
waypoints: waypoints,
|
||||
versionMismatch: mismatch,
|
||||
terrainProfile: terrainProfile,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user