Terepbejárás geometriák szerkesztése: kontrollpontok és tulajdonságok.
This commit is contained in:
parent
1276ac0610
commit
0257beec38
1
lib/enums/note_type.dart
Normal file
1
lib/enums/note_type.dart
Normal file
@ -0,0 +1 @@
|
|||||||
|
enum NoteType { point, line, polygon }
|
||||||
183
lib/models/note_item.dart
Normal file
183
lib/models/note_item.dart
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
|
import 'package:latlong2/latlong.dart';
|
||||||
|
|
||||||
|
import '../enums/note_type.dart';
|
||||||
|
|
||||||
|
class NoteItem {
|
||||||
|
final int? id;
|
||||||
|
final int? projectId;
|
||||||
|
final NoteType type;
|
||||||
|
final List<LatLng> points; // ← szerkesztéshez mindig elérhető
|
||||||
|
final Color color;
|
||||||
|
final double opacity;
|
||||||
|
final double strokeWidth;
|
||||||
|
final Color strokeColor;
|
||||||
|
final String label;
|
||||||
|
final DateTime createdAt;
|
||||||
|
|
||||||
|
const NoteItem(
|
||||||
|
{this.id,
|
||||||
|
this.projectId,
|
||||||
|
required this.type,
|
||||||
|
required this.points,
|
||||||
|
required this.color,
|
||||||
|
this.opacity = 0.5,
|
||||||
|
this.strokeWidth = 3.0,
|
||||||
|
required this.strokeColor,
|
||||||
|
this.label = '',
|
||||||
|
required this.createdAt});
|
||||||
|
|
||||||
|
NoteItem copyWith({
|
||||||
|
List<LatLng>? points,
|
||||||
|
Color? color,
|
||||||
|
double? opacity,
|
||||||
|
double? strokeWidth,
|
||||||
|
Color? strokeColor,
|
||||||
|
String? label,
|
||||||
|
}) =>
|
||||||
|
NoteItem(
|
||||||
|
id: id,
|
||||||
|
projectId: projectId,
|
||||||
|
type: type,
|
||||||
|
points: points ?? this.points,
|
||||||
|
color: color ?? this.color,
|
||||||
|
opacity: opacity ?? this.opacity,
|
||||||
|
strokeWidth: strokeWidth ?? this.strokeWidth,
|
||||||
|
strokeColor: strokeColor ?? this.strokeColor,
|
||||||
|
label: label ?? this.label,
|
||||||
|
createdAt: createdAt,
|
||||||
|
);
|
||||||
|
// ── Koordináta ↔ GeoJSON ────────────────────────────────────────
|
||||||
|
|
||||||
|
static List<LatLng> _parsePoints(String json) {
|
||||||
|
final geom = jsonDecode(json) as Map<String, dynamic>;
|
||||||
|
final gType = geom['type'] as String;
|
||||||
|
final coords = geom['coordinates'];
|
||||||
|
|
||||||
|
switch (gType) {
|
||||||
|
case 'Point':
|
||||||
|
final c = coords as List;
|
||||||
|
return [
|
||||||
|
LatLng(
|
||||||
|
(c[1] as num).toDouble(),
|
||||||
|
(c[0] as num).toDouble(),
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
|
case 'LineString':
|
||||||
|
return (coords as List)
|
||||||
|
.map((c) => LatLng(
|
||||||
|
(c[1] as num).toDouble(),
|
||||||
|
(c[0] as num).toDouble(),
|
||||||
|
))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
case 'Polygon':
|
||||||
|
// Külső gyűrű (index 0)
|
||||||
|
return ((coords as List)[0] as List)
|
||||||
|
.map((c) => LatLng(
|
||||||
|
(c[1] as num).toDouble(),
|
||||||
|
(c[0] as num).toDouble(),
|
||||||
|
))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
default:
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _toPointsJson() {
|
||||||
|
List<dynamic> coords;
|
||||||
|
String gType;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case NoteType.point:
|
||||||
|
// GeoJSON: [lon, lat]
|
||||||
|
gType = 'Point';
|
||||||
|
coords = [points.first.longitude, points.first.latitude];
|
||||||
|
|
||||||
|
case NoteType.line:
|
||||||
|
gType = 'LineString';
|
||||||
|
coords = points.map((p) => [p.longitude, p.latitude]).toList();
|
||||||
|
|
||||||
|
case NoteType.polygon:
|
||||||
|
gType = 'Polygon';
|
||||||
|
final ring = points.map((p) => [p.longitude, p.latitude]).toList();
|
||||||
|
// Polygon zárt: első = utolsó pont
|
||||||
|
if (ring.isNotEmpty) {
|
||||||
|
final first = ring.first;
|
||||||
|
final last = ring.last;
|
||||||
|
final isClosed = first[0] == last[0] && first[1] == last[1];
|
||||||
|
|
||||||
|
if (!isClosed) ring.add(List.from(first));
|
||||||
|
}
|
||||||
|
coords = [ring];
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonEncode({'type': gType, 'coordinates': coords});
|
||||||
|
}
|
||||||
|
// ── SQLite ↔ Map ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
static Color _parseColor(String hex) {
|
||||||
|
final h = hex.replaceFirst('#', '');
|
||||||
|
return Color(int.parse(h.length == 6 ? 'FF$h' : h, radix: 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
static String _colorHex(Color c) =>
|
||||||
|
'#${c.value.toRadixString(16).padLeft(8, '0').substring(2).toUpperCase()}';
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() => {
|
||||||
|
if (id != null) 'id': id,
|
||||||
|
if (projectId != null) 'project_id': projectId,
|
||||||
|
'type': type.name,
|
||||||
|
'points_json': _toPointsJson(),
|
||||||
|
'color': _colorHex(color),
|
||||||
|
'opacity': opacity,
|
||||||
|
'stroke_width': strokeWidth,
|
||||||
|
'stroke_color': _colorHex(strokeColor),
|
||||||
|
'label': label,
|
||||||
|
'created_at': createdAt.toIso8601String(),
|
||||||
|
};
|
||||||
|
|
||||||
|
factory NoteItem.fromMap(Map<String, dynamic> m) => NoteItem(
|
||||||
|
id: m['id'] as int?,
|
||||||
|
projectId: m['project_id'] as int?,
|
||||||
|
type: NoteType.values.firstWhere((t) => t.name == (m['type'] as String),
|
||||||
|
orElse: () => NoteType.point),
|
||||||
|
points: _parsePoints(m['points_json'] as String),
|
||||||
|
color: _parseColor(m['color'] as String? ?? '#185FA5'),
|
||||||
|
opacity: (m['opacity'] as num?)?.toDouble() ?? 0.5,
|
||||||
|
strokeWidth: (m['stroke_width'] as num?)?.toDouble() ?? 3.0,
|
||||||
|
strokeColor: _parseColor(m['stroke_color'] as String? ?? '#FFD700'),
|
||||||
|
label: m['label'] as String? ?? '',
|
||||||
|
createdAt: DateTime.parse(m['created_at'] as String),
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Kitöltési szín az opacity-val alkalmazva.
|
||||||
|
Color get fillColor => Color.fromARGB(
|
||||||
|
(opacity * 255).round(),
|
||||||
|
color.red,
|
||||||
|
color.green,
|
||||||
|
color.blue,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Rendereléshez — a hitValue hordozza az id-t a tap detektáláshoz
|
||||||
|
Polyline<int> toPolyline() => Polyline(
|
||||||
|
points: points,
|
||||||
|
color: color,
|
||||||
|
strokeWidth: strokeWidth,
|
||||||
|
hitValue: id!,
|
||||||
|
);
|
||||||
|
|
||||||
|
Polygon<int> toPolygon() => Polygon(
|
||||||
|
points: points,
|
||||||
|
color: color.withOpacity(opacity),
|
||||||
|
borderColor: strokeColor,
|
||||||
|
borderStrokeWidth: strokeWidth,
|
||||||
|
label: label.isEmpty ? null : label,
|
||||||
|
hitValue: id!,
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -23,8 +23,10 @@ import 'package:terepi_seged/controls/geoid_grid.dart';
|
|||||||
import 'package:terepi_seged/controls/wgs84_coordinate_formatter.dart';
|
import 'package:terepi_seged/controls/wgs84_coordinate_formatter.dart';
|
||||||
import 'package:terepi_seged/enums/map_edit_tool.dart';
|
import 'package:terepi_seged/enums/map_edit_tool.dart';
|
||||||
import 'package:terepi_seged/enums/map_survey_mode.dart';
|
import 'package:terepi_seged/enums/map_survey_mode.dart';
|
||||||
|
import 'package:terepi_seged/enums/note_type.dart';
|
||||||
import 'package:terepi_seged/eov/convert_coordinate.dart';
|
import 'package:terepi_seged/eov/convert_coordinate.dart';
|
||||||
import 'package:terepi_seged/eov/eov.dart';
|
import 'package:terepi_seged/eov/eov.dart';
|
||||||
|
import 'package:terepi_seged/models/note_item.dart';
|
||||||
import 'package:terepi_seged/models/point_to_measure.dart';
|
import 'package:terepi_seged/models/point_to_measure.dart';
|
||||||
import 'package:terepi_seged/models/point_with_description_model.dart';
|
import 'package:terepi_seged/models/point_with_description_model.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
@ -32,11 +34,13 @@ import 'package:terepi_seged/pages/map_survey/presentations/views/measured_point
|
|||||||
import 'package:terepi_seged/pages/ntrip_settings/presentation/controllers/ntrip_settings_controller.dart';
|
import 'package:terepi_seged/pages/ntrip_settings/presentation/controllers/ntrip_settings_controller.dart';
|
||||||
import 'package:terepi_seged/pages/ntrip_settings/presentation/views/ntrip_settings_sheet.dart';
|
import 'package:terepi_seged/pages/ntrip_settings/presentation/views/ntrip_settings_sheet.dart';
|
||||||
import 'package:terepi_seged/pages/tracking/presentation/controllers/tracking_controller.dart';
|
import 'package:terepi_seged/pages/tracking/presentation/controllers/tracking_controller.dart';
|
||||||
|
import 'package:terepi_seged/services/app_database.dart';
|
||||||
import 'package:terepi_seged/services/coord_converter_service.dart';
|
import 'package:terepi_seged/services/coord_converter_service.dart';
|
||||||
import 'package:terepi_seged/services/gnss/gnss_connection.dart';
|
import 'package:terepi_seged/services/gnss/gnss_connection.dart';
|
||||||
import 'package:terepi_seged/services/gnss/gnss_device_service.dart';
|
import 'package:terepi_seged/services/gnss/gnss_device_service.dart';
|
||||||
import 'package:terepi_seged/services/gnss/gnss_service.dart';
|
import 'package:terepi_seged/services/gnss/gnss_service.dart';
|
||||||
import 'package:terepi_seged/services/ntrip_service.dart';
|
import 'package:terepi_seged/services/ntrip_service.dart';
|
||||||
|
import 'package:terepi_seged/services/project_service.dart';
|
||||||
import 'package:terepi_seged/widgets/map_edit_tools/map_feature_save_sheet.dart';
|
import 'package:terepi_seged/widgets/map_edit_tools/map_feature_save_sheet.dart';
|
||||||
|
|
||||||
class MapSurveyController extends GetxController {
|
class MapSurveyController extends GetxController {
|
||||||
@ -120,6 +124,9 @@ class MapSurveyController extends GetxController {
|
|||||||
|
|
||||||
final MapController mapController = MapController();
|
final MapController mapController = MapController();
|
||||||
|
|
||||||
|
final polylineHitNotifier = ValueNotifier<LayerHitResult<int>?>(null);
|
||||||
|
final polygonHitNotifier = ValueNotifier<LayerHitResult<int>?>(null);
|
||||||
|
|
||||||
final currentLocationMarker = <Marker>[].obs;
|
final currentLocationMarker = <Marker>[].obs;
|
||||||
final pointNotesMarker = <Marker>[].obs;
|
final pointNotesMarker = <Marker>[].obs;
|
||||||
final pointsToMeasureMarker = <Marker>[].obs;
|
final pointsToMeasureMarker = <Marker>[].obs;
|
||||||
@ -166,8 +173,8 @@ class MapSurveyController extends GetxController {
|
|||||||
final activeEditTool = MapEditTool.none.obs;
|
final activeEditTool = MapEditTool.none.obs;
|
||||||
final editorPointCount = 0.obs;
|
final editorPointCount = 0.obs;
|
||||||
final pointNotes = <Marker>[].obs;
|
final pointNotes = <Marker>[].obs;
|
||||||
final polylineNotes = <Polyline<Object>>[].obs;
|
final polylineNotes = <Polyline<int>>[].obs;
|
||||||
final polygonNotes = <Polygon<Object>>[].obs;
|
final polygonNotes = <Polygon<int>>[].obs;
|
||||||
|
|
||||||
late final PolygonEditorController polygonEditorController;
|
late final PolygonEditorController polygonEditorController;
|
||||||
|
|
||||||
@ -181,6 +188,13 @@ class MapSurveyController extends GetxController {
|
|||||||
const PolygonLabelPlacementCalculator.centroid();
|
const PolygonLabelPlacementCalculator.centroid();
|
||||||
|
|
||||||
bool get isMapEditing => activeEditTool.value != MapEditTool.none;
|
bool get isMapEditing => activeEditTool.value != MapEditTool.none;
|
||||||
|
final selectedNoteItemId = Rx<int?>(null);
|
||||||
|
final selectedNoteItemType = NoteType.line.obs;
|
||||||
|
int? _editingNoteItemId;
|
||||||
|
bool get isGeometryEditing => _editingNoteItemId != null;
|
||||||
|
|
||||||
|
// NoteItem? get selectedPoint =>
|
||||||
|
// pointNotes.firstWhereOrNull((n) => n.id == selectedNoteItemId.value);
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
@ -229,6 +243,10 @@ class MapSurveyController extends GetxController {
|
|||||||
await _initStorage();
|
await _initStorage();
|
||||||
|
|
||||||
gpsHeightController.text = '1.8';
|
gpsHeightController.text = '1.8';
|
||||||
|
|
||||||
|
ever(ProjectService.to.activeProject, (_) => _loadNoteItems());
|
||||||
|
|
||||||
|
await _loadNoteItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -962,6 +980,9 @@ class MapSurveyController extends GetxController {
|
|||||||
void cancelEditing() {
|
void cancelEditing() {
|
||||||
polygonEditorController.clear();
|
polygonEditorController.clear();
|
||||||
activeEditTool.value = MapEditTool.none;
|
activeEditTool.value = MapEditTool.none;
|
||||||
|
_editingNoteItemId = null;
|
||||||
|
selectedNoteItemId.value = null;
|
||||||
|
editorPointCount.value = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void openFeatureList() {
|
void openFeatureList() {
|
||||||
@ -1005,73 +1026,437 @@ class MapSurveyController extends GetxController {
|
|||||||
//draftPoints.clear();
|
//draftPoints.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> finishDraft() async {
|
Marker _markerFromNoteItem(NoteItem item) {
|
||||||
if (polygonEditorController.mode == PolygonEditorMode.line) {
|
return Marker(
|
||||||
print("Points number in line: ${polygonEditorController.points.length}");
|
key: ValueKey('note_point_${item.id}'),
|
||||||
print(
|
point: item.points.first,
|
||||||
"1. point coords: ${polygonEditorController.points[0].latitude} - ${polygonEditorController.points[0].longitude}");
|
width: 32.0,
|
||||||
if (polygonEditorController.points.length < 2) return;
|
height: 32.0,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => selectedNoteItem(item.id!),
|
||||||
|
child: Obx(() {
|
||||||
|
final isSelected = selectedNoteItemId.value == item.id;
|
||||||
|
return Center(
|
||||||
|
child: AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
width: isSelected ? 26.0 : 20.0,
|
||||||
|
height: isSelected ? 26.0 : 20.0,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: item.color,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(
|
||||||
|
width: isSelected ? 3.0 : 1.5,
|
||||||
|
color: isSelected ? Colors.white : item.strokeColor),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withValues(alpha: 0.3),
|
||||||
|
blurRadius: isSelected ? 8 : 3)
|
||||||
|
]),
|
||||||
|
));
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// ── SQLite mentés ─────────────────────────────────────────────────
|
||||||
|
|
||||||
Polyline polyline = Polyline(
|
Future<NoteItem?> _saveItem({
|
||||||
points: List.from(polygonEditorController.points),
|
required NoteType type,
|
||||||
|
required List<LatLng> points,
|
||||||
|
}) async {
|
||||||
|
final projectId = ProjectService.to.activeProject.value?.id;
|
||||||
|
|
||||||
|
final item = NoteItem(
|
||||||
|
projectId: projectId, // projekt nélkül is mentődik
|
||||||
|
type: type,
|
||||||
|
points: points,
|
||||||
color: activeEditColor.value,
|
color: activeEditColor.value,
|
||||||
|
opacity: activeEditOpacity.value,
|
||||||
strokeWidth: activeEditStrokeWidth.value,
|
strokeWidth: activeEditStrokeWidth.value,
|
||||||
// hitValue: (
|
strokeColor: activeEditStrokeColor.value,
|
||||||
// title: 'Purple Line',
|
|
||||||
// subtitle: 'Nothing really special here...',
|
|
||||||
// ),
|
|
||||||
);
|
|
||||||
polylineNotes.add(polyline);
|
|
||||||
// polylineNotes.refresh();
|
|
||||||
|
|
||||||
print("Points number in polylineNotes: ${polylineNotes.length}");
|
|
||||||
print(
|
|
||||||
"1. point coords of polyline: ${polyline.points[0].latitude} - ${polyline.points[0].longitude}");
|
|
||||||
|
|
||||||
polygonEditorController.clear();
|
|
||||||
activeEditTool.value = MapEditTool.none;
|
|
||||||
}
|
|
||||||
if (polygonEditorController.mode == PolygonEditorMode.polygon) {
|
|
||||||
print(
|
|
||||||
"Points number in polygon: ${polygonEditorController.points.length}");
|
|
||||||
|
|
||||||
Polygon polygon = Polygon(
|
|
||||||
points: List.from(polygonEditorController.points),
|
|
||||||
color: activeEditColor.value.withValues(alpha: activeEditOpacity.value),
|
|
||||||
borderColor: activeEditStrokeColor.value,
|
|
||||||
borderStrokeWidth: activeEditStrokeWidth.value,
|
|
||||||
label: activeEditLabel.value,
|
label: activeEditLabel.value,
|
||||||
labelPlacementCalculator: _labelPlacementCalculator,
|
createdAt: DateTime.now(),
|
||||||
// hitValue: (
|
|
||||||
// title: 'Basic Filled Polygon',
|
|
||||||
// subtitle: 'Nothing really special here...',
|
|
||||||
);
|
);
|
||||||
|
|
||||||
polygonNotes.add(polygon);
|
try {
|
||||||
//polygonNotes.refresh();
|
final id = await AppDatabase.instance.insertNoteItem(item);
|
||||||
//update();
|
// id-vel visszaadott NoteItem — a hitValue ehhez az id-hez kötődik
|
||||||
|
return NoteItem(
|
||||||
|
id: id,
|
||||||
|
projectId: item.projectId,
|
||||||
|
type: item.type,
|
||||||
|
points: item.points,
|
||||||
|
color: item.color,
|
||||||
|
opacity: item.opacity,
|
||||||
|
strokeWidth: item.strokeWidth,
|
||||||
|
strokeColor: item.strokeColor,
|
||||||
|
label: item.label,
|
||||||
|
createdAt: item.createdAt,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print('_saveItem hiba: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
print("Points number in polygonNotes: ${polygonNotes.length}");
|
// ── SQLite betöltés (onReady-ben hívandó) ─────────────────────────
|
||||||
|
|
||||||
polygonEditorController.clear();
|
Future<void> _loadNoteItems() async {
|
||||||
|
final projectId = ProjectService.to.activeProject.value?.id;
|
||||||
|
final items = await AppDatabase.instance.listNoteItems(projectId);
|
||||||
|
|
||||||
|
// Listák resetelése
|
||||||
|
pointNotes.clear();
|
||||||
|
polylineNotes.clear();
|
||||||
|
polygonNotes.clear();
|
||||||
|
|
||||||
|
for (final item in items) {
|
||||||
|
switch (item.type) {
|
||||||
|
case NoteType.point:
|
||||||
|
pointNotes.add(_markerFromNoteItem(item));
|
||||||
|
case NoteType.line:
|
||||||
|
polylineNotes.add(item.toPolyline());
|
||||||
|
case NoteType.polygon:
|
||||||
|
polygonNotes.add(item.toPolygon());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> finishDraft() async {
|
||||||
|
if (_editingNoteItemId != null) {
|
||||||
|
if (polygonEditorController.points.isEmpty) {
|
||||||
|
await _finishStyleUpdate();
|
||||||
|
} else {
|
||||||
|
// ── FRISSÍTÉSI MÓD ──────────────────────────────────────────
|
||||||
|
await _finishGeometryUpdate();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// ── LÉTREHOZÁSI MÓD (eredeti logika) ────────────────────────
|
||||||
|
await _finishCreate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _finishStyleUpdate() async {
|
||||||
|
final id = _editingNoteItemId!;
|
||||||
|
final existing = await AppDatabase.instance.getNoteItem(id);
|
||||||
|
if (existing == null) return;
|
||||||
|
|
||||||
|
final updated = existing.copyWith(
|
||||||
|
color: activeEditColor.value,
|
||||||
|
opacity: activeEditOpacity.value,
|
||||||
|
strokeWidth: activeEditStrokeWidth.value,
|
||||||
|
strokeColor: activeEditStrokeColor.value,
|
||||||
|
label: activeEditLabel.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
await updateNoteItem(updated);
|
||||||
|
_editingNoteItemId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteEditingItem() async {
|
||||||
|
final id = _editingNoteItemId;
|
||||||
|
if (id == null) return;
|
||||||
|
final item = await AppDatabase.instance.getNoteItem(id);
|
||||||
|
if (item != null) await deleteNoteItem(item);
|
||||||
|
_editingNoteItemId = null;
|
||||||
|
selectedNoteItemId.value = null;
|
||||||
activeEditTool.value = MapEditTool.none;
|
activeEditTool.value = MapEditTool.none;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// Future<void> finishDraft() async {
|
||||||
|
// if (polygonEditorController.mode == PolygonEditorMode.line) {
|
||||||
|
// print("Points number in line: ${polygonEditorController.points.length}");
|
||||||
|
// print(
|
||||||
|
// "1. point coords: ${polygonEditorController.points[0].latitude} - ${polygonEditorController.points[0].longitude}");
|
||||||
|
// if (polygonEditorController.points.length < 2) return;
|
||||||
|
|
||||||
|
// final saved = await _saveItem(
|
||||||
|
// type: NoteType.line,
|
||||||
|
// points: List.from(polygonEditorController.points));
|
||||||
|
|
||||||
|
// if (saved != null) {
|
||||||
|
// polylineNotes.add(saved.toPolyline());
|
||||||
|
// }
|
||||||
|
|
||||||
|
// polygonEditorController.clear();
|
||||||
|
// activeEditTool.value = MapEditTool.none;
|
||||||
|
// }
|
||||||
|
// if (polygonEditorController.mode == PolygonEditorMode.polygon) {
|
||||||
|
// if (polygonEditorController.points.length < 3) return;
|
||||||
|
|
||||||
|
// final saved = await _saveItem(
|
||||||
|
// type: NoteType.polygon,
|
||||||
|
// points: List.from(polygonEditorController.points));
|
||||||
|
|
||||||
|
// if (saved != null) {
|
||||||
|
// polygonNotes.add(saved.toPolygon());
|
||||||
|
// }
|
||||||
|
|
||||||
|
// polygonEditorController.clear();
|
||||||
|
// activeEditTool.value = MapEditTool.none;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
void saveEditedPoint({required LatLng point}) {
|
void saveEditedPoint({required LatLng point}) {
|
||||||
Marker marker = Marker(
|
if (_editingNoteItemId != null) {
|
||||||
point: point,
|
// ── PONT FRISSÍTÉSI MÓD ──────────────────────────────────────
|
||||||
width: 15.0,
|
final id = _editingNoteItemId!;
|
||||||
height: 15.0,
|
_editingNoteItemId = null;
|
||||||
child: Container(
|
activeEditTool.value = MapEditTool.none;
|
||||||
width: 15.0,
|
|
||||||
height: 15.0,
|
AppDatabase.instance.getNoteItem(id).then((existing) async {
|
||||||
decoration: BoxDecoration(
|
if (existing == null) return;
|
||||||
color: Colors.amber[700],
|
final updated = existing.copyWith(points: [point]);
|
||||||
shape: BoxShape.circle,
|
await updateNoteItem(updated);
|
||||||
border: Border.all(width: 1.0, color: Colors.black)),
|
});
|
||||||
));
|
} else {
|
||||||
pointNotes.add(marker);
|
// ── ÚJ PONT LÉTREHOZÁS (eredeti logika) ─────────────────────
|
||||||
|
_saveItem(
|
||||||
|
type: NoteType.point,
|
||||||
|
points: [point],
|
||||||
|
).then((saved) {
|
||||||
|
if (saved == null) return;
|
||||||
|
pointNotes.add(_markerFromNoteItem(saved));
|
||||||
|
});
|
||||||
activeEditTool.value = MapEditTool.none;
|
activeEditTool.value = MapEditTool.none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// void saveEditedPoint({required LatLng point}) {
|
||||||
|
// _saveItem(type: NoteType.point, points: [point]).then((saved) {
|
||||||
|
// if (saved == null) return;
|
||||||
|
// pointNotes.add(_markerFromNoteItem(saved));
|
||||||
|
// });
|
||||||
|
|
||||||
|
// // Marker marker = Marker(
|
||||||
|
// // point: point,
|
||||||
|
// // width: 15.0,
|
||||||
|
// // height: 15.0,
|
||||||
|
// // child: Container(
|
||||||
|
// // width: 15.0,
|
||||||
|
// // height: 15.0,
|
||||||
|
// // decoration: BoxDecoration(
|
||||||
|
// // color: Colors.amber[700],
|
||||||
|
// // shape: BoxShape.circle,
|
||||||
|
// // border: Border.all(width: 1.0, color: Colors.black)),
|
||||||
|
// // ));
|
||||||
|
// //pointNotes.add(marker);
|
||||||
|
// activeEditTool.value = MapEditTool.none;
|
||||||
|
// }
|
||||||
|
|
||||||
|
Future<void> selectedNoteItem(int id) async {
|
||||||
|
selectedNoteItemId.value = id;
|
||||||
|
|
||||||
|
final item = await AppDatabase.instance.getNoteItem(id);
|
||||||
|
if (item == null) return;
|
||||||
|
|
||||||
|
_editingNoteItemId = item.id;
|
||||||
|
activeEditColor.value = item.color;
|
||||||
|
activeEditOpacity.value = item.opacity;
|
||||||
|
activeEditStrokeWidth.value = item.strokeWidth;
|
||||||
|
activeEditStrokeColor.value = item.strokeColor;
|
||||||
|
activeEditLabel.value = item.label;
|
||||||
|
activeEditTool.value = switch (item.type) {
|
||||||
|
NoteType.point => MapEditTool.point,
|
||||||
|
NoteType.line => MapEditTool.line,
|
||||||
|
NoteType.polygon => MapEditTool.polygon
|
||||||
|
};
|
||||||
|
|
||||||
|
Get.bottomSheet(
|
||||||
|
DraggableScrollableSheet(
|
||||||
|
initialChildSize: 0.52,
|
||||||
|
minChildSize: 0.35,
|
||||||
|
maxChildSize: 0.85,
|
||||||
|
snap: true,
|
||||||
|
snapSizes: const [0.35, 0.52, 0.85],
|
||||||
|
expand: false,
|
||||||
|
builder: (_, scrollCtrl) =>
|
||||||
|
MapFeatureSaveSheet(ctrl: this, scrollCtrl: scrollCtrl)),
|
||||||
|
isScrollControlled: true,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
ignoreSafeArea: false)
|
||||||
|
.whenComplete(() {
|
||||||
|
selectedNoteItemId.value = null;
|
||||||
|
//_editingNoteItemId = null;
|
||||||
|
if (!isGeometryEditing) {
|
||||||
|
activeEditTool.value = MapEditTool.none;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearNoteItemSelection() {
|
||||||
|
selectedNoteItemId.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateNoteItem(NoteItem updated) async {
|
||||||
|
await AppDatabase.instance.updateNoteItem(updated);
|
||||||
|
|
||||||
|
switch (updated.type) {
|
||||||
|
case NoteType.line:
|
||||||
|
final idx = polylineNotes.indexWhere((p) => p.hitValue == updated.id);
|
||||||
|
if (idx >= 0) {
|
||||||
|
polylineNotes[idx] = updated.toPolyline();
|
||||||
|
polylineNotes.refresh();
|
||||||
|
}
|
||||||
|
case NoteType.polygon:
|
||||||
|
final idx = polygonNotes.indexWhere((p) => p.hitValue == updated.id);
|
||||||
|
if (idx >= 0) {
|
||||||
|
polygonNotes[idx] = updated.toPolygon();
|
||||||
|
polygonNotes.refresh();
|
||||||
|
}
|
||||||
|
case NoteType.point:
|
||||||
|
final idx = _findPointMarkerIndex(updated.id!);
|
||||||
|
if (idx >= 0) {
|
||||||
|
pointNotes[idx] = _markerFromNoteItem(updated);
|
||||||
|
pointNotes.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteNoteItem(NoteItem item) async {
|
||||||
|
await AppDatabase.instance.deleteNoteItem(item.id!);
|
||||||
|
|
||||||
|
switch (item.type) {
|
||||||
|
case NoteType.line:
|
||||||
|
polylineNotes.removeWhere((p) => p.hitValue == item.id);
|
||||||
|
case NoteType.polygon:
|
||||||
|
polygonNotes.removeWhere((p) => p.hitValue == item.id);
|
||||||
|
case NoteType.point:
|
||||||
|
// Pontot tag-elt Key alapján keresünk
|
||||||
|
final idx = _findPointMarkerIndex(item.id!);
|
||||||
|
if (idx >= 0) pointNotes.removeAt(idx);
|
||||||
|
}
|
||||||
|
selectedNoteItemId.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pont marker indexének megkeresése.
|
||||||
|
/// A marker Key-je hordozza a NoteItem id-t.
|
||||||
|
int _findPointMarkerIndex(int noteId) {
|
||||||
|
return pointNotes.indexWhere(
|
||||||
|
(m) => m.key == ValueKey('note_point_$noteId'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// ── Geometria szerkesztés indítása ────────────────────────────────────
|
||||||
|
|
||||||
|
Future<void> startGeometryEdit() async {
|
||||||
|
final id = _editingNoteItemId;
|
||||||
|
if (id == null) return;
|
||||||
|
|
||||||
|
final item = await AppDatabase.instance.getNoteItem(id);
|
||||||
|
if (item == null) return;
|
||||||
|
|
||||||
|
if (item.type == NoteType.point) {
|
||||||
|
// Pontnál: régi törlése, új elhelyezés vár
|
||||||
|
activeEditTool.value = MapEditTool.point;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Editor mód beállítása a típus szerint
|
||||||
|
final editorMode = item.type == NoteType.line
|
||||||
|
? PolygonEditorMode.line
|
||||||
|
: PolygonEditorMode.polygon;
|
||||||
|
|
||||||
|
// PolygonEditorController újraindítása a meglévő pontokkal
|
||||||
|
// Dispose → újra létrehozás szükséges ha a controller már él
|
||||||
|
polygonEditorController.clear();
|
||||||
|
polygonEditorController.setMode(editorMode);
|
||||||
|
|
||||||
|
// Meglévő pontok betöltése az editorba
|
||||||
|
for (final point in item.points) {
|
||||||
|
polygonEditorController.addPoint(point);
|
||||||
|
}
|
||||||
|
|
||||||
|
editorPointCount.value = item.points.length;
|
||||||
|
|
||||||
|
// Szerkesztési mód aktiválása
|
||||||
|
activeEditTool.value =
|
||||||
|
item.type == NoteType.line ? MapEditTool.line : MapEditTool.polygon;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Geometria szerkesztés megszakítása ────────────────────────────────
|
||||||
|
|
||||||
|
void cancelGeometryEdit() {
|
||||||
|
_editingNoteItemId = null;
|
||||||
|
polygonEditorController.clear();
|
||||||
|
activeEditTool.value = MapEditTool.none;
|
||||||
|
editorPointCount.value = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _finishGeometryUpdate() async {
|
||||||
|
final id = _editingNoteItemId!;
|
||||||
|
|
||||||
|
if (polygonEditorController.points.length < _minPoints) {
|
||||||
|
Get.snackbar(
|
||||||
|
'Figyelem',
|
||||||
|
'Nincs elég pont a geometriához.',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Meglévő elem lekérése az adatbázisból
|
||||||
|
final existing = await AppDatabase.instance.getNoteItem(id);
|
||||||
|
if (existing == null) return;
|
||||||
|
|
||||||
|
// Frissítés: csak a pontok változnak, a stílus marad
|
||||||
|
final updated = existing.copyWith(
|
||||||
|
points: List<LatLng>.from(polygonEditorController.points),
|
||||||
|
// Ha a stílus is változott a szerkesztés közben:
|
||||||
|
color: activeEditColor.value,
|
||||||
|
opacity: activeEditOpacity.value,
|
||||||
|
strokeWidth: activeEditStrokeWidth.value,
|
||||||
|
strokeColor: activeEditStrokeColor.value,
|
||||||
|
label: activeEditLabel.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
// SQLite + display lista frissítése
|
||||||
|
await updateNoteItem(updated);
|
||||||
|
|
||||||
|
// Reset
|
||||||
|
_editingNoteItemId = null;
|
||||||
|
polygonEditorController.clear();
|
||||||
|
activeEditTool.value = MapEditTool.none;
|
||||||
|
editorPointCount.value = 0;
|
||||||
|
|
||||||
|
Get.snackbar(
|
||||||
|
'Geometria frissítve',
|
||||||
|
updated.label.isNotEmpty ? updated.label : '',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
duration: const Duration(seconds: 2),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _finishCreate() async {
|
||||||
|
if (polygonEditorController.mode == PolygonEditorMode.line) {
|
||||||
|
if (polygonEditorController.points.length < 2) return;
|
||||||
|
|
||||||
|
final saved = await _saveItem(
|
||||||
|
type: NoteType.line,
|
||||||
|
points: List.from(polygonEditorController.points),
|
||||||
|
);
|
||||||
|
if (saved != null) {
|
||||||
|
polylineNotes.add(saved.toPolyline());
|
||||||
|
}
|
||||||
|
polygonEditorController.clear();
|
||||||
|
activeEditTool.value = MapEditTool.none;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (polygonEditorController.mode == PolygonEditorMode.polygon) {
|
||||||
|
if (polygonEditorController.points.length < 3) return;
|
||||||
|
|
||||||
|
final saved = await _saveItem(
|
||||||
|
type: NoteType.polygon,
|
||||||
|
points: List.from(polygonEditorController.points),
|
||||||
|
);
|
||||||
|
if (saved != null) {
|
||||||
|
polygonNotes.add(saved.toPolygon());
|
||||||
|
}
|
||||||
|
polygonEditorController.clear();
|
||||||
|
activeEditTool.value = MapEditTool.none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int get _minPoints {
|
||||||
|
if (polygonEditorController.mode == PolygonEditorMode.line) return 2;
|
||||||
|
return 3; // polygon
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -41,6 +41,23 @@ class MapSurveyView extends GetView<MapSurveyController> {
|
|||||||
controller.polygonEditorController.addPoint(point);
|
controller.polygonEditorController.addPoint(point);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onTap: (tapPosition, point) {
|
||||||
|
if (controller.mode.value != MapSurveyMode.fieldWalk) return;
|
||||||
|
if (controller.isMapEditing) return;
|
||||||
|
final polygonHit = controller.polygonHitNotifier.value;
|
||||||
|
if (polygonHit != null && polygonHit.hitValues.isNotEmpty) {
|
||||||
|
final id = polygonHit.hitValues.first;
|
||||||
|
controller.selectedNoteItem(id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final polylineHit = controller.polylineHitNotifier.value;
|
||||||
|
if (polylineHit != null && polylineHit.hitValues.isNotEmpty) {
|
||||||
|
final id = polylineHit.hitValues.first;
|
||||||
|
controller.selectedNoteItem(id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
controller.clearNoteItemSelection();
|
||||||
|
},
|
||||||
layers: [
|
layers: [
|
||||||
Obx(() =>
|
Obx(() =>
|
||||||
MarkerLayer(markers: controller.currentLocationMarker.toList())),
|
MarkerLayer(markers: controller.currentLocationMarker.toList())),
|
||||||
@ -55,14 +72,6 @@ class MapSurveyView extends GetView<MapSurveyController> {
|
|||||||
return _buildTrackLayer();
|
return _buildTrackLayer();
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
Obx(() {
|
|
||||||
if (controller.mode.value != MapSurveyMode.fieldWalk) {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
return PolygonEditor(
|
|
||||||
controller: controller.polygonEditorController,
|
|
||||||
throttleDuration: Duration.zero);
|
|
||||||
}),
|
|
||||||
Obx(() {
|
Obx(() {
|
||||||
if (controller.mode.value != MapSurveyMode.fieldWalk) {
|
if (controller.mode.value != MapSurveyMode.fieldWalk) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
@ -75,7 +84,9 @@ class MapSurveyView extends GetView<MapSurveyController> {
|
|||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
return PolylineLayer(polylines: [...controller.polylineNotes]);
|
return PolylineLayer(
|
||||||
|
hitNotifier: controller.polylineHitNotifier,
|
||||||
|
polylines: [...controller.polylineNotes]);
|
||||||
}),
|
}),
|
||||||
Obx(() {
|
Obx(() {
|
||||||
if (controller.mode.value != MapSurveyMode.fieldWalk) {
|
if (controller.mode.value != MapSurveyMode.fieldWalk) {
|
||||||
@ -83,8 +94,56 @@ class MapSurveyView extends GetView<MapSurveyController> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return PolygonLayer(
|
return PolygonLayer(
|
||||||
polygons: [...controller.polygonNotes], useAltRendering: true);
|
hitNotifier: controller.polygonHitNotifier,
|
||||||
})
|
polygons: [...controller.polygonNotes],
|
||||||
|
useAltRendering: true);
|
||||||
|
}),
|
||||||
|
Obx(() {
|
||||||
|
if (controller.mode.value != MapSurveyMode.fieldWalk) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
final selectedId = controller.selectedNoteItemId.value;
|
||||||
|
if (selectedId == null) return const SizedBox.shrink();
|
||||||
|
|
||||||
|
// Polygon kiemelés
|
||||||
|
final selectedPolygon = controller.polygonNotes
|
||||||
|
.where((p) => p.hitValue == selectedId)
|
||||||
|
.firstOrNull;
|
||||||
|
if (selectedPolygon != null) {
|
||||||
|
return PolygonLayer(polygons: [
|
||||||
|
Polygon(
|
||||||
|
points: selectedPolygon.points,
|
||||||
|
color: Colors.transparent,
|
||||||
|
borderColor: Colors.white,
|
||||||
|
borderStrokeWidth: selectedPolygon.borderStrokeWidth + 3,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyline kiemelés
|
||||||
|
final selectedPolyline = controller.polylineNotes
|
||||||
|
.where((p) => p.hitValue == selectedId)
|
||||||
|
.firstOrNull;
|
||||||
|
if (selectedPolyline != null) {
|
||||||
|
return PolylineLayer(polylines: [
|
||||||
|
Polyline(
|
||||||
|
points: selectedPolyline.points,
|
||||||
|
color: Colors.white.withOpacity(0.6),
|
||||||
|
strokeWidth: selectedPolyline.strokeWidth + 4,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}),
|
||||||
|
Obx(() {
|
||||||
|
if (controller.mode.value != MapSurveyMode.fieldWalk) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
return PolygonEditor(
|
||||||
|
controller: controller.polygonEditorController,
|
||||||
|
throttleDuration: Duration.zero);
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
|
|||||||
@ -3,6 +3,8 @@ import 'dart:io';
|
|||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:sqflite/sqflite.dart';
|
import 'package:sqflite/sqflite.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
|
import 'package:terepi_seged/enums/note_type.dart';
|
||||||
|
import 'package:terepi_seged/models/note_item.dart';
|
||||||
import 'package:terepi_seged/models/track.dart';
|
import 'package:terepi_seged/models/track.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
import '../models/project.dart';
|
import '../models/project.dart';
|
||||||
@ -116,7 +118,7 @@ class AppDatabase {
|
|||||||
await db.execute('''
|
await db.execute('''
|
||||||
CREATE TABLE IF NOT EXISTS note_items (
|
CREATE TABLE IF NOT EXISTS note_items (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
project_id INTEGER REFERENCES projects(id) ON DELETE CASCADE,
|
||||||
type TEXT NOT NULL,
|
type TEXT NOT NULL,
|
||||||
points_json TEXT NOT NULL,
|
points_json TEXT NOT NULL,
|
||||||
color TEXT NOT NULL DEFAULT '#185FA5',
|
color TEXT NOT NULL DEFAULT '#185FA5',
|
||||||
@ -381,4 +383,80 @@ class AppDatabase {
|
|||||||
whereArgs: ['synced'],
|
whereArgs: ['synced'],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------------- Terepbejárás pontok, vonalak, területek
|
||||||
|
|
||||||
|
/// Elem mentése - visszaadja a kapott AQLite id-t
|
||||||
|
Future<int> insertNoteItem(NoteItem item) async {
|
||||||
|
final db = await database;
|
||||||
|
return db.insert('note_items', item.toMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Elem frissítése (szín, label, koordináták módosítása után).
|
||||||
|
Future<void> updateNoteItem(NoteItem item) async {
|
||||||
|
final db = await database;
|
||||||
|
await db.update(
|
||||||
|
'note_items',
|
||||||
|
item.toMap(),
|
||||||
|
where: 'id = ?',
|
||||||
|
whereArgs: [item.id],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Egy elem törlése.
|
||||||
|
Future<void> deleteNoteItem(int id) async {
|
||||||
|
final db = await database;
|
||||||
|
await db.delete(
|
||||||
|
'note_items',
|
||||||
|
where: 'id = ?',
|
||||||
|
whereArgs: [id],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Projekt összes eleme — opcionálisan típus szerint szűrve.
|
||||||
|
Future<List<NoteItem>> listNoteItems(int? projectId, {NoteType? type}) async {
|
||||||
|
final db = await database;
|
||||||
|
|
||||||
|
String? where;
|
||||||
|
List<Object?> whereArgs = [];
|
||||||
|
|
||||||
|
if (projectId != null) {
|
||||||
|
where = type != null ? 'project_id = ? AND type = ?' : 'project_id = ?';
|
||||||
|
whereArgs = type != null ? [projectId, type.name] : [projectId];
|
||||||
|
} else {
|
||||||
|
// Projekt nélküli elemek
|
||||||
|
where = type != null ? 'type = ?' : null;
|
||||||
|
whereArgs = type != null ? [type.name] : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final rows = await db.query(
|
||||||
|
'note_items',
|
||||||
|
where: where,
|
||||||
|
whereArgs: whereArgs,
|
||||||
|
orderBy: 'created_at ASC',
|
||||||
|
);
|
||||||
|
return rows.map(NoteItem.fromMap).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Egyetlen elem lekérése id alapján.
|
||||||
|
Future<NoteItem?> getNoteItem(int id) async {
|
||||||
|
final db = await database;
|
||||||
|
final rows = await db.query(
|
||||||
|
'note_items',
|
||||||
|
where: 'id = ?',
|
||||||
|
whereArgs: [id],
|
||||||
|
limit: 1,
|
||||||
|
);
|
||||||
|
return rows.isEmpty ? null : NoteItem.fromMap(rows.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Projekt összes elemének törlése.
|
||||||
|
Future<void> deleteAllNoteItems(int projectId) async {
|
||||||
|
final db = await database;
|
||||||
|
await db.delete(
|
||||||
|
'note_items',
|
||||||
|
where: 'project_id = ?',
|
||||||
|
whereArgs: [projectId],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
import 'package:terepi_seged/enums/map_edit_tool.dart';
|
import 'package:terepi_seged/enums/map_edit_tool.dart';
|
||||||
import 'package:terepi_seged/pages/map_survey/presentations/controllers/map_survey_controller.dart';
|
import 'package:terepi_seged/pages/map_survey/presentations/controllers/map_survey_controller.dart';
|
||||||
|
|
||||||
@ -56,10 +57,14 @@ class MapFeatureSaveSheet extends StatelessWidget {
|
|||||||
const SizedBox(height: 18),
|
const SizedBox(height: 18),
|
||||||
OpacitySlider(ctrl: ctrl),
|
OpacitySlider(ctrl: ctrl),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
if (ctrl.activeEditTool.value != MapEditTool.point) ...[
|
Obx(() => ctrl.activeEditTool.value != MapEditTool.point
|
||||||
|
? Column(children: [
|
||||||
StrokeSlider(ctrl: ctrl),
|
StrokeSlider(ctrl: ctrl),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(
|
||||||
],
|
height: 1,
|
||||||
|
)
|
||||||
|
])
|
||||||
|
: const SizedBox.shrink()),
|
||||||
LabelField(ctrl: ctrl),
|
LabelField(ctrl: ctrl),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
SaveSheetActions(ctrl: ctrl),
|
SaveSheetActions(ctrl: ctrl),
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:terepi_seged/enums/map_edit_tool.dart';
|
||||||
import 'package:terepi_seged/pages/map_survey/presentations/controllers/map_survey_controller.dart';
|
import 'package:terepi_seged/pages/map_survey/presentations/controllers/map_survey_controller.dart';
|
||||||
|
|
||||||
class SaveSheetActions extends StatelessWidget {
|
class SaveSheetActions extends StatelessWidget {
|
||||||
@ -8,44 +9,142 @@ class SaveSheetActions extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Row(children: [
|
return Obx(() {
|
||||||
// ← Vissza — bezárja a sheet-et, folytatja a rajzolást
|
final isEditing = ctrl.isGeometryEditing;
|
||||||
|
final tool = ctrl.activeEditTool.value;
|
||||||
|
final isPoint = tool == MapEditTool.point;
|
||||||
|
|
||||||
|
return Column(mainAxisSize: MainAxisSize.min, children: [
|
||||||
|
Row(children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: OutlinedButton.icon(
|
child: OutlinedButton.icon(
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||||
shape:
|
shape: RoundedRectangleBorder(
|
||||||
RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
borderRadius: BorderRadius.circular(10))),
|
||||||
|
icon: Icon(
|
||||||
|
isEditing ? Icons.close : Icons.arrow_back,
|
||||||
|
size: 18,
|
||||||
),
|
),
|
||||||
icon: const Icon(Icons.arrow_back, size: 18),
|
label: Text(isEditing ? 'Mégse' : 'Vissza'),
|
||||||
label: const Text('Vissza'),
|
onPressed: () => Get.back(),
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
|
|
||||||
// Mentés — végleges mentés, mindkét sheet bezárása
|
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: Obx(() => FilledButton.icon(
|
child: FilledButton.icon(
|
||||||
style: FilledButton.styleFrom(
|
style: FilledButton.styleFrom(
|
||||||
backgroundColor: ctrl.activeEditColor.value,
|
backgroundColor: ctrl.activeEditColor.value,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(10)),
|
borderRadius: BorderRadius.circular(10))),
|
||||||
),
|
|
||||||
icon: const Icon(Icons.check, size: 18),
|
icon: const Icon(Icons.check, size: 18),
|
||||||
label: const Text(
|
label: const Text(
|
||||||
'Mentés',
|
'Mentés',
|
||||||
style: TextStyle(fontWeight: FontWeight.w600, fontSize: 15),
|
style: TextStyle(fontWeight: FontWeight.w600, fontSize: 15),
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
//Navigator.pop(context); // style sheet bezárás
|
|
||||||
Get.back();
|
Get.back();
|
||||||
await ctrl.finishDraft();
|
await ctrl.finishDraft();
|
||||||
},
|
},
|
||||||
)),
|
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
if (isEditing && !isPoint) ...[
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: OutlinedButton.icon(
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10)),
|
||||||
|
),
|
||||||
|
icon: const Icon(Icons.edit_outlined, size: 18),
|
||||||
|
label: const Text('Geometria szerkesztése'),
|
||||||
|
onPressed: () {
|
||||||
|
Get.back();
|
||||||
|
ctrl.startGeometryEdit();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
if (isEditing) ...[
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: OutlinedButton.icon(
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
foregroundColor: Colors.red,
|
||||||
|
side: const BorderSide(color: Colors.red),
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10))),
|
||||||
|
icon: const Icon(Icons.delete_outline, size: 18),
|
||||||
|
label: const Text('Törlés'),
|
||||||
|
onPressed: () => _confirmDelete(context, ctrl),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]
|
||||||
]);
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// return Row(children: [
|
||||||
|
// // ← Vissza — bezárja a sheet-et, folytatja a rajzolást
|
||||||
|
// Expanded(
|
||||||
|
// child: OutlinedButton.icon(
|
||||||
|
// style: OutlinedButton.styleFrom(
|
||||||
|
// padding: const EdgeInsets.symmetric(vertical: 14),
|
||||||
|
// shape: RoundedRectangleBorder(
|
||||||
|
// borderRadius: BorderRadius.circular(10)),
|
||||||
|
// ),
|
||||||
|
// icon: const Icon(Icons.arrow_back, size: 18),
|
||||||
|
// label: const Text('Vissza'),
|
||||||
|
// onPressed: () => Navigator.pop(context),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// const SizedBox(width: 12),
|
||||||
|
|
||||||
|
// // Mentés — végleges mentés, mindkét sheet bezárása
|
||||||
|
// Expanded(
|
||||||
|
// flex: 2,
|
||||||
|
// child: Obx(() => FilledButton.icon(
|
||||||
|
// style: FilledButton.styleFrom(
|
||||||
|
// backgroundColor: ctrl.activeEditColor.value,
|
||||||
|
// padding: const EdgeInsets.symmetric(vertical: 14),
|
||||||
|
// shape: RoundedRectangleBorder(
|
||||||
|
// borderRadius: BorderRadius.circular(10)),
|
||||||
|
// ),
|
||||||
|
// icon: const Icon(Icons.check, size: 18),
|
||||||
|
// label: const Text(
|
||||||
|
// 'Mentés',
|
||||||
|
// style: TextStyle(fontWeight: FontWeight.w600, fontSize: 15),
|
||||||
|
// ),
|
||||||
|
// onPressed: () async {
|
||||||
|
// //Navigator.pop(context); // style sheet bezárás
|
||||||
|
// Get.back();
|
||||||
|
// await ctrl.finishDraft();
|
||||||
|
// },
|
||||||
|
// )),
|
||||||
|
// ),
|
||||||
|
// ]);
|
||||||
|
void _confirmDelete(BuildContext context, MapSurveyController ctrl) {
|
||||||
|
Get.dialog(AlertDialog(
|
||||||
|
title: const Text('Elem törlése'),
|
||||||
|
content: const Text('Ez az elem véglegesen törlődik.'),
|
||||||
|
actions: [
|
||||||
|
TextButton(onPressed: Get.back, child: const Text('Mégse')),
|
||||||
|
FilledButton(
|
||||||
|
style: FilledButton.styleFrom(backgroundColor: Colors.red),
|
||||||
|
onPressed: () async {
|
||||||
|
Get.back(); // dialóg
|
||||||
|
Get.back(); // sheet
|
||||||
|
await ctrl.deleteEditingItem();
|
||||||
|
},
|
||||||
|
child: const Text('Törlés'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ class SharedMapWidget extends StatelessWidget {
|
|||||||
final MapController mapController;
|
final MapController mapController;
|
||||||
final List<Widget> layers;
|
final List<Widget> layers;
|
||||||
final void Function(TapPosition, LatLng)? onLongPress;
|
final void Function(TapPosition, LatLng)? onLongPress;
|
||||||
|
final void Function(TapPosition, LatLng)? onTap;
|
||||||
final void Function(MapCamera, bool)? onPositionChanged;
|
final void Function(MapCamera, bool)? onPositionChanged;
|
||||||
|
|
||||||
final MapControls controls;
|
final MapControls controls;
|
||||||
@ -33,6 +34,7 @@ class SharedMapWidget extends StatelessWidget {
|
|||||||
required this.mapController,
|
required this.mapController,
|
||||||
this.layers = const [],
|
this.layers = const [],
|
||||||
this.onLongPress,
|
this.onLongPress,
|
||||||
|
this.onTap,
|
||||||
this.onPositionChanged,
|
this.onPositionChanged,
|
||||||
this.controls = const MapControls(),
|
this.controls = const MapControls(),
|
||||||
this.initialCenter = const LatLng(47.5, 19.0),
|
this.initialCenter = const LatLng(47.5, 19.0),
|
||||||
@ -61,6 +63,7 @@ class SharedMapWidget extends StatelessWidget {
|
|||||||
minZoom: minZoom,
|
minZoom: minZoom,
|
||||||
maxZoom: maxZoom,
|
maxZoom: maxZoom,
|
||||||
onLongPress: onLongPress,
|
onLongPress: onLongPress,
|
||||||
|
onTap: onTap,
|
||||||
onPositionChanged: onPositionChanged,
|
onPositionChanged: onPositionChanged,
|
||||||
interactionOptions: const InteractionOptions(
|
interactionOptions: const InteractionOptions(
|
||||||
flags: InteractiveFlag.all,
|
flags: InteractiveFlag.all,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user