192 lines
6.4 KiB
Dart
192 lines
6.4 KiB
Dart
import 'dart:math' as math;
|
|
|
|
// ─── TrackPoint ───────────────────────────────────────────────────────────────
|
|
|
|
class TrackPoint {
|
|
final int? id;
|
|
final int trackId;
|
|
final double latitude;
|
|
final double longitude;
|
|
final double? altitude;
|
|
final double? accuracy;
|
|
final double? speed; // m/s
|
|
final double? heading;
|
|
final DateTime timestamp;
|
|
|
|
const TrackPoint({
|
|
this.id,
|
|
required this.trackId,
|
|
required this.latitude,
|
|
required this.longitude,
|
|
this.altitude,
|
|
this.accuracy,
|
|
this.speed,
|
|
this.heading,
|
|
required this.timestamp,
|
|
});
|
|
|
|
Map<String, dynamic> toMap() => {
|
|
if (id != null) 'id': id,
|
|
'track_id': trackId,
|
|
'latitude': latitude,
|
|
'longitude': longitude,
|
|
'altitude': altitude,
|
|
'accuracy': accuracy,
|
|
'speed': speed,
|
|
'heading': heading,
|
|
'timestamp': timestamp.toIso8601String(),
|
|
};
|
|
|
|
factory TrackPoint.fromMap(Map<String, dynamic> m) => TrackPoint(
|
|
id: m['id'] as int?,
|
|
trackId: m['track_id'] as int,
|
|
latitude: m['latitude'] as double,
|
|
longitude: m['longitude'] as double,
|
|
altitude: m['altitude'] as double?,
|
|
accuracy: m['accuracy'] as double?,
|
|
speed: m['speed'] as double?,
|
|
heading: m['heading'] as double?,
|
|
timestamp: DateTime.parse(m['timestamp'] as String),
|
|
);
|
|
}
|
|
|
|
// ─── Track státusz ────────────────────────────────────────────────────────────
|
|
|
|
enum TrackStatus { recording, paused, finished }
|
|
|
|
enum TrackSyncMode { localOnly, online }
|
|
|
|
enum TrackSyncStatus { pending, synced, error }
|
|
// ─── Track ───────────────────────────────────────────────────────────────────
|
|
|
|
class Track {
|
|
final int? id;
|
|
final int? projectId;
|
|
final String name;
|
|
final DateTime startTime;
|
|
final DateTime? endTime;
|
|
final TrackStatus status;
|
|
final String source; // pl. "Telefon GPS", "BLE GNSS"
|
|
|
|
// Statisztikák — ezeket a DB is tárolja a gyors listázáshoz
|
|
final double distanceMeters;
|
|
final int pointCount;
|
|
final TrackSyncMode syncMode;
|
|
final TrackSyncStatus syncStatus;
|
|
final String? supabaseId;
|
|
|
|
const Track(
|
|
{this.id,
|
|
this.projectId,
|
|
required this.name,
|
|
required this.startTime,
|
|
this.endTime,
|
|
this.status = TrackStatus.recording,
|
|
this.source = 'Telefon GPS',
|
|
this.distanceMeters = 0,
|
|
this.pointCount = 0,
|
|
this.syncMode = TrackSyncMode.online,
|
|
this.syncStatus = TrackSyncStatus.pending,
|
|
this.supabaseId});
|
|
|
|
Track copyWith(
|
|
{int? id,
|
|
int? projectId,
|
|
String? name,
|
|
DateTime? startTime,
|
|
DateTime? endTime,
|
|
TrackStatus? status,
|
|
String? source,
|
|
double? distanceMeters,
|
|
int? pointCount,
|
|
TrackSyncMode? syncMode,
|
|
TrackSyncStatus? syncStatus,
|
|
String? supabaseId}) =>
|
|
Track(
|
|
id: id ?? this.id,
|
|
projectId: projectId ?? this.projectId,
|
|
name: name ?? this.name,
|
|
startTime: startTime ?? this.startTime,
|
|
endTime: endTime ?? this.endTime,
|
|
status: status ?? this.status,
|
|
source: source ?? this.source,
|
|
distanceMeters: distanceMeters ?? this.distanceMeters,
|
|
pointCount: pointCount ?? this.pointCount,
|
|
syncMode: syncMode ?? this.syncMode,
|
|
syncStatus: syncStatus ?? this.syncStatus,
|
|
supabaseId: supabaseId ?? this.supabaseId);
|
|
|
|
/// Formázott időtartam (óó:pp:mm)
|
|
String get durationFormatted {
|
|
final end = endTime ?? DateTime.now();
|
|
final d = end.difference(startTime);
|
|
final h = d.inHours.toString().padLeft(2, '0');
|
|
final m = (d.inMinutes % 60).toString().padLeft(2, '0');
|
|
final s = (d.inSeconds % 60).toString().padLeft(2, '0');
|
|
return '$h:$m:$s';
|
|
}
|
|
|
|
/// Formázott távolság
|
|
String get distanceFormatted {
|
|
if (distanceMeters < 1000) {
|
|
return '${distanceMeters.toStringAsFixed(0)} m';
|
|
}
|
|
return '${(distanceMeters / 1000).toStringAsFixed(2)} km';
|
|
}
|
|
|
|
Map<String, dynamic> toMap() => {
|
|
if (id != null) 'id': id,
|
|
if (projectId != null) 'project_id': projectId,
|
|
'name': name,
|
|
'start_time': startTime.toIso8601String(),
|
|
'end_time': endTime?.toIso8601String(),
|
|
'status': status.name,
|
|
'source': source,
|
|
'distance_m': distanceMeters,
|
|
'point_count': pointCount,
|
|
'is_local_only': syncMode == TrackSyncMode.localOnly ? 1 : 0,
|
|
'sync_status': syncStatus.name,
|
|
'supabase_id': supabaseId,
|
|
};
|
|
|
|
factory Track.fromMap(Map<String, dynamic> m) => Track(
|
|
id: m['id'] as int?,
|
|
projectId: m['project_id'] as int?,
|
|
name: m['name'] as String,
|
|
startTime: DateTime.parse(m['start_time'] as String),
|
|
endTime: m['end_time'] != null
|
|
? DateTime.parse(m['end_time'] as String)
|
|
: null,
|
|
status: TrackStatus.values.firstWhere(
|
|
(s) => s.name == (m['status'] as String?),
|
|
orElse: () => TrackStatus.finished,
|
|
),
|
|
source: m['source'] as String? ?? 'Telefon GPS',
|
|
distanceMeters: (m['distance_m'] as num?)?.toDouble() ?? 0,
|
|
pointCount: m['point_count'] as int? ?? 0,
|
|
syncMode: (m['is_local_only'] as int?) == 1
|
|
? TrackSyncMode.localOnly
|
|
: TrackSyncMode.online,
|
|
syncStatus: TrackSyncStatus.values
|
|
.byName(m['sync_status'] as String? ?? 'pending'),
|
|
supabaseId: m['supabase_id'] as String?);
|
|
|
|
bool get isLocalOnly => syncMode == TrackSyncMode.localOnly;
|
|
}
|
|
|
|
// ─── Segédszámítás: Haversine távolság ───────────────────────────────────────
|
|
|
|
double haversineMeters(double lat1, double lon1, double lat2, double lon2) {
|
|
const r = 6371000.0;
|
|
final dLat = _toRad(lat2 - lat1);
|
|
final dLon = _toRad(lon2 - lon1);
|
|
final a = math.sin(dLat / 2) * math.sin(dLat / 2) +
|
|
math.cos(_toRad(lat1)) *
|
|
math.cos(_toRad(lat2)) *
|
|
math.sin(dLon / 2) *
|
|
math.sin(dLon / 2);
|
|
return r * 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a));
|
|
}
|
|
|
|
double _toRad(double deg) => deg * math.pi / 180;
|