diff --git a/lib/enums/map_layer_type.dart b/lib/enums/map_layer_type.dart new file mode 100644 index 0000000..d557560 --- /dev/null +++ b/lib/enums/map_layer_type.dart @@ -0,0 +1 @@ +enum LayerType { geojson, shapefile, dxf, csv, postgis, wms, track, gnss } diff --git a/lib/enums/map_layer_visibility.dart b/lib/enums/map_layer_visibility.dart new file mode 100644 index 0000000..db1aae7 --- /dev/null +++ b/lib/enums/map_layer_visibility.dart @@ -0,0 +1 @@ +enum LayerVisibility { visible, hidden } diff --git a/lib/map_layers/map_dxf_layer.dart b/lib/map_layers/map_dxf_layer.dart new file mode 100644 index 0000000..3b63a75 --- /dev/null +++ b/lib/map_layers/map_dxf_layer.dart @@ -0,0 +1,11 @@ +// import 'package:flutter/material.dart'; +// import 'package:terepi_seged/map_layers/map_layer.dart'; + +// import 'map_geojson_layer.dart'; + +// class DxfLayer extends MapLayer { +// // DXF parsing — nincs Flutter csomag, szerver oldali konverzió kell +// // Supabase Edge Function: DXF → GeoJSON (ogr2ogr alapú) +// @override +// Widget buildFlutterMapLayer() => GeoJsonLayer.fromFeatures(_features); +// } diff --git a/lib/map_layers/map_geojson_layer.dart b/lib/map_layers/map_geojson_layer.dart new file mode 100644 index 0000000..9413dff --- /dev/null +++ b/lib/map_layers/map_geojson_layer.dart @@ -0,0 +1,17 @@ +// import 'package:flutter/material.dart'; +// import 'package:flutter_map/flutter_map.dart'; + +// import 'map_layer.dart'; + +// class GeoJsonLayer extends MapLayer { +// final String sourceUrl; // Supabase Storage URL +// final String? localPath; // offline cache +// FeatureCollection? _data; + +// @override +// Widget buildFlutterMapLayer() { +// if (_data == null) return const SizedBox.shrink(); +// return PolygonLayer(polygons: _buildPolygons()); +// // vagy PolylineLayer / MarkerLayer a geometry type-tól függően +// } +// } diff --git a/lib/map_layers/map_layer.dart b/lib/map_layers/map_layer.dart new file mode 100644 index 0000000..073ea95 --- /dev/null +++ b/lib/map_layers/map_layer.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:terepi_seged/enums/map_layer_type.dart'; +import 'package:terepi_seged/enums/map_layer_visibility.dart'; + +abstract class MapLayer { + late final String id; + late final String name; + late final LayerType type; + late LayerVisibility visibility; + late double opacity; + late int zIndex; + //LayerStyle style; + + // Minden réteg tudja magát flutter_map widget-té alakítani + Widget buildFlutterMapLayer(); + + // Offline cache-elhetőség + Future prefetch(); + bool get isCached; +} diff --git a/lib/map_layers/map_postgis_layer.dart b/lib/map_layers/map_postgis_layer.dart new file mode 100644 index 0000000..ab2193a --- /dev/null +++ b/lib/map_layers/map_postgis_layer.dart @@ -0,0 +1,23 @@ +// import 'package:flutter/material.dart'; +// import 'package:flutter_map/flutter_map.dart'; +// import 'package:supabase_flutter/supabase_flutter.dart'; + +// import 'map_layer.dart'; + +// class PostGisLayer extends MapLayer { +// final String table; // Supabase tábla neve +// final String geomColumn; // geometry oszlop +// final String? filter; // pl. "project_id=eq.123" +// final BoundingBox? bbox; // csak látható terület lekérése + +// @override +// Widget buildFlutterMapLayer() { +// // flutter_map_tile_caching + PostGIS tile endpoint +// return TileLayer(urlTemplate: _buildMvtUrl()); +// } + +// // MapBox Vector Tiles (MVT) Supabase Edge Function-ből +// String _buildMvtUrl() => +// '${Supabase.instance.client.supabaseUrl}/functions/v1/tiles' +// '/$table/{z}/{x}/{y}?geom=$geomColumn${filter != null ? "&filter=$filter" : ""}'; +// } diff --git a/lib/map_layers/map_shapefile_layer.dart b/lib/map_layers/map_shapefile_layer.dart new file mode 100644 index 0000000..76a881f --- /dev/null +++ b/lib/map_layers/map_shapefile_layer.dart @@ -0,0 +1,11 @@ +// import 'package:flutter/material.dart'; + +// import 'map_geojson_layer.dart'; +// import 'map_layer.dart'; + +// class ShapefileLayer extends MapLayer { +// // SHP → GeoJSON konverzió device-on +// // shapefile_dart csomag +// @override +// Widget buildFlutterMapLayer() => GeoJsonLayer.fromFeatures(_features); +// } diff --git a/lib/models/track.dart b/lib/models/track.dart index 7ca48e5..d24eaa1 100644 --- a/lib/models/track.dart +++ b/lib/models/track.dart @@ -54,6 +54,9 @@ class TrackPoint { enum TrackStatus { recording, paused, finished } +enum TrackSyncMode { localOnly, online } + +enum TrackSyncStatus { pending, synced, error } // ─── Track ─────────────────────────────────────────────────────────────────── class Track { @@ -68,41 +71,50 @@ class Track { // 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, - }); + 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, - }) => + 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, - ); + 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 { @@ -132,24 +144,34 @@ class Track { '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 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, - ); + 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 ─────────────────────────────────────── diff --git a/lib/pages/tracking/presentation/controllers/tracking_controller.dart b/lib/pages/tracking/presentation/controllers/tracking_controller.dart index 71d2222..3d12c51 100644 --- a/lib/pages/tracking/presentation/controllers/tracking_controller.dart +++ b/lib/pages/tracking/presentation/controllers/tracking_controller.dart @@ -5,6 +5,7 @@ import 'package:get/get.dart'; import 'package:intl/intl.dart'; import 'package:latlong2/latlong.dart'; import 'package:share_plus/share_plus.dart'; +import 'package:terepi_seged/services/project_service.dart'; import '../../../../services/location_source.dart'; import '../../../../services/phone_gps_source.dart'; @@ -116,19 +117,23 @@ class TrackingController extends GetxController { // ── Publikus API ─────────────────────────────────────────────────────────── /// Elindítja a rögzítést a megadott forrással (alapértelmezett: telefon GPS). - Future startRecording({LocationSource? source}) async { + Future startRecording( + {LocationSource? source, + String? name, + TrackSyncMode syncMode = TrackSyncMode.online}) async { if (isRecording.value) return; _source = source ?? PhoneGpsSource(intervalMs: 1000, distanceFilter: 2.0); // Track létrehozása az adatbázisban final now = DateTime.now(); - final name = DateFormat('yyyy-MM-dd HH:mm').format(now); + final trackName = name ?? DateFormat('yyyy-MM-dd HH:mm').format(now); final trackId = await _db.insertTrack(Track( - name: name, - startTime: now, - source: _source!.displayName, - )); + name: trackName, + startTime: now, + source: _source!.displayName, + projectId: ProjectService.to.activeProjectId, + syncMode: syncMode)); currentTrack.value = await _db.getTrack(trackId); // Állapot reset @@ -141,7 +146,7 @@ class TrackingController extends GetxController { // Foreground service indítása (háttér-működéshez) await FlutterForegroundTask.startService( notificationTitle: 'Track rögzítése', - notificationText: name, + notificationText: trackName, callback: startTrackingCallback, ); diff --git a/lib/services/app_database.dart b/lib/services/app_database.dart index bd35948..a8d086c 100644 --- a/lib/services/app_database.dart +++ b/lib/services/app_database.dart @@ -87,7 +87,10 @@ class AppDatabase { status TEXT NOT NULL DEFAULT 'recording', source TEXT NOT NULL DEFAULT 'Telefon GPS', distance_m REAL NOT NULL DEFAULT 0, - point_count INTEGER NOT NULL DEFAULT 0 + point_count INTEGER NOT NULL DEFAULT 0, + is_local_only INTEGER NOT NULL DEFAULT 0, + sync_status TEXT NOT NULL DEFAULT 'pending', + supabase_id TEXT ) '''); await db.execute('CREATE INDEX idx_tracks_project ON tracks(project_id)');