Kapcsolat hozzáadása koordinátával a terepbejárás térképen.
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 6s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 6s
This commit is contained in:
@@ -1,6 +1 @@
|
||||
enum MapEditTool {
|
||||
none,
|
||||
point,
|
||||
line,
|
||||
polygon,
|
||||
}
|
||||
enum MapEditTool { none, point, line, polygon, contact }
|
||||
|
||||
+38
-7
@@ -14,8 +14,17 @@ class Contact {
|
||||
final String email;
|
||||
final String note;
|
||||
final String? createdBy;
|
||||
|
||||
final DateTime? updatedAt;
|
||||
|
||||
/// Opcionális helyszín — WGS84 (lat/lon) és EOV (eovY/eovX) is tárolva,
|
||||
/// akárcsak a többi geometria-típusnál. Csak akkor van kitöltve, ha a
|
||||
/// térképi "Kapcsolat" eszközzel vették fel, vagy utólag hozzárendelték.
|
||||
final double? lat;
|
||||
final double? lon;
|
||||
final double? eovY;
|
||||
final double? eovX;
|
||||
|
||||
const Contact({
|
||||
this.id,
|
||||
required this.projectId,
|
||||
@@ -26,15 +35,25 @@ class Contact {
|
||||
this.note = '',
|
||||
this.createdBy,
|
||||
this.updatedAt,
|
||||
this.lat,
|
||||
this.lon,
|
||||
this.eovY,
|
||||
this.eovX,
|
||||
});
|
||||
|
||||
Contact copyWith({
|
||||
String? name,
|
||||
String? address,
|
||||
String? phone,
|
||||
String? email,
|
||||
String? note,
|
||||
}) =>
|
||||
bool get hasLocation => lat != null && lon != null;
|
||||
|
||||
Contact copyWith(
|
||||
{String? name,
|
||||
String? address,
|
||||
String? phone,
|
||||
String? email,
|
||||
String? note,
|
||||
double? lat,
|
||||
double? lon,
|
||||
double? eovY,
|
||||
double? eovX,
|
||||
bool clearLocation = false}) =>
|
||||
Contact(
|
||||
id: id,
|
||||
projectId: projectId,
|
||||
@@ -45,6 +64,10 @@ class Contact {
|
||||
note: note ?? this.note,
|
||||
createdBy: createdBy,
|
||||
updatedAt: updatedAt,
|
||||
lat: clearLocation ? null : (lat ?? this.lat),
|
||||
lon: clearLocation ? null : (lon ?? this.lon),
|
||||
eovX: clearLocation ? null : (eovX ?? this.eovX),
|
||||
eovY: clearLocation ? null : (eovY ?? this.eovY),
|
||||
);
|
||||
|
||||
/// Beszúráshoz/frissítéshez — az id-t csak akkor küldjük, ha van
|
||||
@@ -58,6 +81,10 @@ class Contact {
|
||||
'phone': phone.trim(),
|
||||
'email': email.trim(),
|
||||
'note': note.trim(),
|
||||
'lat': lat,
|
||||
'lon': lon,
|
||||
'eov_y': eovY,
|
||||
'eov_x': eovX,
|
||||
};
|
||||
|
||||
factory Contact.fromMap(Map<String, dynamic> m) => Contact(
|
||||
@@ -72,5 +99,9 @@ class Contact {
|
||||
updatedAt: m['updated_at'] != null
|
||||
? DateTime.tryParse(m['updated_at'] as String)
|
||||
: null,
|
||||
lat: (m['lat'] as num?)?.toDouble(),
|
||||
lon: (m['lon'] as num?)?.toDouble(),
|
||||
eovY: (m['eov_y'] as num?)?.toDouble(),
|
||||
eovX: (m['eov_x'] as num?)?.toDouble(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,10 @@ class _ContactEditViewState extends State<ContactEditView> {
|
||||
late final TextEditingController _note;
|
||||
|
||||
bool _saving = false;
|
||||
late double? _lat;
|
||||
late double? _lon;
|
||||
late double? _eovY;
|
||||
late double? _eovX;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -43,6 +47,10 @@ class _ContactEditViewState extends State<ContactEditView> {
|
||||
_phone = TextEditingController(text: _original?.phone ?? '');
|
||||
_email = TextEditingController(text: _original?.email ?? '');
|
||||
_note = TextEditingController(text: _original?.note ?? '');
|
||||
_lat = _original?.lat;
|
||||
_lon = _original?.lon;
|
||||
_eovY = _original?.eovY;
|
||||
_eovX = _original?.eovX;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -69,7 +77,13 @@ class _ContactEditViewState extends State<ContactEditView> {
|
||||
phone: _phone.text,
|
||||
email: _email.text,
|
||||
note: _note.text,
|
||||
lat: _lat,
|
||||
lon: _lon,
|
||||
eovY: _eovY,
|
||||
eovX: _eovX,
|
||||
clearLocation: _lat == null,
|
||||
);
|
||||
|
||||
final queued = await ContactService.to.save(contact);
|
||||
Get.back(result: queued); // a lista frissítéshez visszakapja
|
||||
if (Get.isRegistered<ContactsController>()) {
|
||||
@@ -182,6 +196,27 @@ class _ContactEditViewState extends State<ContactEditView> {
|
||||
alignLabelWithHint: true,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
if (_lat != null && _lon != null)
|
||||
Card(
|
||||
color: Colors.indigo.withOpacity(0.06),
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.location_on, color: Colors.indigo),
|
||||
title: Text(
|
||||
'${_lat!.toStringAsFixed(6)}, ${_lon!.toStringAsFixed(6)}'),
|
||||
subtitle: const Text('Helyszín rögzítve'),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
tooltip: 'Helyszín törlése',
|
||||
onPressed: () => setState(() {
|
||||
_lat = null;
|
||||
_lon = null;
|
||||
_eovY = null;
|
||||
_eovX = null;
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
FilledButton.icon(
|
||||
onPressed: _saving ? null : _save,
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'dart:math';
|
||||
//import 'dart:math' as math;
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
@@ -31,22 +32,26 @@ 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/eov.dart';
|
||||
import 'package:terepi_seged/models/contact.dart';
|
||||
import 'package:terepi_seged/models/measured_point.dart';
|
||||
import 'package:terepi_seged/models/note_item.dart';
|
||||
import 'package:terepi_seged/models/point_to_measure.dart';
|
||||
import 'package:terepi_seged/models/point_with_description_model.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:terepi_seged/pages/contacts/presentation/views/contact_edit_view.dart';
|
||||
import 'package:terepi_seged/pages/map_survey/presentations/views/measured_points_table_dialog.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/tracking/presentation/controllers/tracking_controller.dart';
|
||||
import 'package:terepi_seged/services/app_database.dart';
|
||||
import 'package:terepi_seged/services/contact_service.dart';
|
||||
import 'package:terepi_seged/services/coord_converter_service.dart';
|
||||
import 'package:terepi_seged/services/device_identity_service.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_service.dart';
|
||||
import 'package:terepi_seged/services/ntrip_service.dart';
|
||||
import 'package:terepi_seged/services/permission_service.dart';
|
||||
import 'package:terepi_seged/services/project_service.dart';
|
||||
import 'package:terepi_seged/services/stakeout_service.dart';
|
||||
import 'package:terepi_seged/widgets/map/all_layer_overlay.dart';
|
||||
@@ -237,6 +242,7 @@ class MapSurveyController extends GetxController implements StyleEditable {
|
||||
);
|
||||
|
||||
case MapEditTool.point:
|
||||
case MapEditTool.contact:
|
||||
case MapEditTool.none:
|
||||
return '';
|
||||
}
|
||||
@@ -300,10 +306,14 @@ class MapSurveyController extends GetxController implements StyleEditable {
|
||||
|
||||
gpsHeightController.text = '1.8';
|
||||
|
||||
ever(ProjectService.to.activeProject, (_) => _loadNoteItems());
|
||||
ever(ProjectService.to.activeProject, (_) {
|
||||
_loadNoteItems();
|
||||
_loadContactMarkers();
|
||||
});
|
||||
|
||||
await _loadNoteItems();
|
||||
await _loadMeasurePoints();
|
||||
await _loadContactMarkers();
|
||||
|
||||
_subscribeToTeamPosition();
|
||||
}
|
||||
@@ -1088,6 +1098,8 @@ class MapSurveyController extends GetxController implements StyleEditable {
|
||||
return Icons.polyline_outlined;
|
||||
case MapEditTool.polygon:
|
||||
return Icons.border_outer_outlined;
|
||||
case MapEditTool.contact:
|
||||
return Icons.person_pin_circle_outlined;
|
||||
case MapEditTool.none:
|
||||
return Icons.edit_location_alt_outlined;
|
||||
}
|
||||
@@ -1101,6 +1113,8 @@ class MapSurveyController extends GetxController implements StyleEditable {
|
||||
return 'Vonal rögzítése';
|
||||
case MapEditTool.polygon:
|
||||
return 'Terület rögzítése';
|
||||
case MapEditTool.contact:
|
||||
return 'Kapcsolat hozzáadása';
|
||||
case MapEditTool.none:
|
||||
return '';
|
||||
}
|
||||
@@ -1114,6 +1128,8 @@ class MapSurveyController extends GetxController implements StyleEditable {
|
||||
return 'Hosszan nyomj a térképre a töréspontokhoz';
|
||||
case MapEditTool.polygon:
|
||||
return 'Hosszan nyomj a térképre a sarokpontokhoz.';
|
||||
case MapEditTool.contact:
|
||||
return 'Hosszan nyomj a térképre a kapcsolat helyéhez.';
|
||||
case MapEditTool.none:
|
||||
return '';
|
||||
}
|
||||
@@ -1127,6 +1143,8 @@ class MapSurveyController extends GetxController implements StyleEditable {
|
||||
return editorPointCount >= 2;
|
||||
case MapEditTool.polygon:
|
||||
return editorPointCount >= 3;
|
||||
case MapEditTool.contact:
|
||||
return false;
|
||||
case MapEditTool.none:
|
||||
return false;
|
||||
}
|
||||
@@ -1140,6 +1158,8 @@ class MapSurveyController extends GetxController implements StyleEditable {
|
||||
return 'Kész';
|
||||
case MapEditTool.polygon:
|
||||
return 'Lezárás';
|
||||
case MapEditTool.contact:
|
||||
return 'Kész';
|
||||
case MapEditTool.none:
|
||||
return 'Kész';
|
||||
}
|
||||
@@ -1150,6 +1170,16 @@ class MapSurveyController extends GetxController implements StyleEditable {
|
||||
activeEditLabel.value = '';
|
||||
}
|
||||
|
||||
void startContactTool() {
|
||||
if (!PermissionService.to.canContacts) {
|
||||
Get.snackbar(
|
||||
'Nincs jogosultság', 'Kapcsolatok kezeléséhez jogosultság szükséges.',
|
||||
snackPosition: SnackPosition.BOTTOM);
|
||||
return;
|
||||
}
|
||||
activeEditTool.value = MapEditTool.contact;
|
||||
}
|
||||
|
||||
void startLineTool() {
|
||||
polygonEditorController.clear();
|
||||
polygonEditorController.setMode(PolygonEditorMode.line);
|
||||
@@ -1207,6 +1237,87 @@ class MapSurveyController extends GetxController implements StyleEditable {
|
||||
//draftPoints.clear();
|
||||
}
|
||||
|
||||
// ── Kapcsolatok a térképen ──────────────────────────────────────────
|
||||
|
||||
final contactMarkers = <Marker>[].obs;
|
||||
|
||||
Future<void> saveContactAtPoint(LatLng point) async {
|
||||
activeEditTool.value = MapEditTool.none;
|
||||
if (!PermissionService.to.canContacts) return;
|
||||
|
||||
final projectId = ProjectService.to.activeProject.value?.uuid;
|
||||
if (projectId == null) return;
|
||||
|
||||
final eov = CoordConverterService.to.wgsToEovPoint(
|
||||
point.longitude,
|
||||
point.latitude,
|
||||
);
|
||||
|
||||
final draft = Contact(
|
||||
projectId: projectId,
|
||||
name: '',
|
||||
lat: point.latitude,
|
||||
lon: point.longitude,
|
||||
eovY: eov.x,
|
||||
eovX: eov.y,
|
||||
);
|
||||
|
||||
final result =
|
||||
await Get.to(() => const ContactEditView(), arguments: draft);
|
||||
if (result != null) {
|
||||
await _loadContactMarkers();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadContactMarkers() async {
|
||||
if (!PermissionService.to.canContacts) {
|
||||
contactMarkers.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
final projectId = ProjectService.to.activeProject.value?.uuid;
|
||||
if (projectId == null) {
|
||||
contactMarkers.clear();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final all = await ContactService.to.listMerged(projectId);
|
||||
contactMarkers.value = all
|
||||
.where((c) => c.contact.hasLocation)
|
||||
.map(_markerFromContact)
|
||||
.toList();
|
||||
} catch (_) {
|
||||
// Offline/hiba: a meglévő jelölők maradnak, nem törli ki csendben.
|
||||
}
|
||||
}
|
||||
|
||||
Marker _markerFromContact(ContactWithState c) {
|
||||
return Marker(
|
||||
key: ValueKey('contact_${c.contact.id ?? c.localUuid}'),
|
||||
point: LatLng(c.contact.lat!, c.contact.lon!),
|
||||
width: 34,
|
||||
height: 34,
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
final result =
|
||||
await Get.to(() => const ContactEditView(), arguments: c.contact);
|
||||
if (result != null) await _loadContactMarkers();
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: c.isPending ? Colors.orange : Colors.indigo,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.white, width: 2),
|
||||
boxShadow: const [
|
||||
BoxShadow(color: Colors.black26, blurRadius: 4),
|
||||
],
|
||||
),
|
||||
child: const Icon(Icons.person, color: Colors.white, size: 20),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Marker _markerFromNoteItem(NoteItem item) {
|
||||
return Marker(
|
||||
key: ValueKey('note_point_${item.id}'),
|
||||
@@ -1730,6 +1841,7 @@ class MapSurveyController extends GetxController implements StyleEditable {
|
||||
break;
|
||||
|
||||
case MapEditTool.point:
|
||||
case MapEditTool.contact:
|
||||
case MapEditTool.none:
|
||||
draftLengthMeters.value = 0.0;
|
||||
draftAreaSquareMeters.value = 0.0;
|
||||
|
||||
@@ -64,6 +64,10 @@ class MapSurveyView extends GetView<MapSurveyController> {
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (controller.activeEditTool.value == MapEditTool.contact) {
|
||||
controller.saveContactAtPoint(point);
|
||||
return;
|
||||
}
|
||||
if (controller.activeEditTool.value == MapEditTool.line ||
|
||||
controller.activeEditTool.value == MapEditTool.polygon) {
|
||||
controller.polygonEditorController.addPoint(point);
|
||||
@@ -142,6 +146,13 @@ class MapSurveyView extends GetView<MapSurveyController> {
|
||||
|
||||
return MarkerLayer(markers: [...controller.pointNotes]);
|
||||
}),
|
||||
Obx(() {
|
||||
// Kapcsolatok - terepi bejárás
|
||||
if (controller.mode.value != MapSurveyMode.fieldWalk) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return MarkerLayer(markers: [...controller.contactMarkers]);
|
||||
}),
|
||||
Obx(() {
|
||||
// Vonalak - terepbejárás
|
||||
if (controller.mode.value != MapSurveyMode.fieldWalk) {
|
||||
|
||||
@@ -46,7 +46,7 @@ class AppDatabase {
|
||||
final path = p.join(dbDir.path, 'terepi_seged.db');
|
||||
|
||||
return openDatabase(path,
|
||||
version: 6,
|
||||
version: 7,
|
||||
onConfigure: (db) => db.execute('PRAGMA foreign_keys = ON'),
|
||||
onCreate: _onCreate,
|
||||
onUpgrade: _onUpgrade);
|
||||
@@ -271,7 +271,9 @@ class AppDatabase {
|
||||
|
||||
await _createStakeoutTable(db);
|
||||
await _createContactsOutbox(db);
|
||||
|
||||
await _addAppInstanceIdColumns(db);
|
||||
await _addContactLocationColumns(db);
|
||||
|
||||
await _createVibratorNavTables(db);
|
||||
|
||||
@@ -312,6 +314,7 @@ class AppDatabase {
|
||||
|
||||
await _addAppInstanceIdColumns(db);
|
||||
await _createVibratorNavTables(db);
|
||||
await _addContactLocationColumns(db);
|
||||
}
|
||||
|
||||
Future<void> _migrateToV4(Database db) async {
|
||||
@@ -1327,6 +1330,10 @@ class AppDatabase {
|
||||
phone TEXT NOT NULL DEFAULT '',
|
||||
email TEXT NOT NULL DEFAULT '',
|
||||
note TEXT NOT NULL DEFAULT '',
|
||||
lat REAL,
|
||||
lon REAL,
|
||||
eov_y REAL,
|
||||
eov_x REAL,
|
||||
created_at TEXT NOT NULL
|
||||
)
|
||||
''');
|
||||
@@ -1334,6 +1341,14 @@ class AppDatabase {
|
||||
'ON contacts_outbox(project_id)');
|
||||
}
|
||||
|
||||
/// Idempotens oszlop-pótlás, ha a contacts_outbox már létezik (korábbi
|
||||
/// telepítéseknél) — ugyanaz a minta, mint az app_instance_id-nél.
|
||||
Future<void> _addContactLocationColumns(Database db) async {
|
||||
for (final col in ['lat', 'lon', 'eov_y', 'eov_x']) {
|
||||
await _tryExec(db, 'ALTER TABLE contacts_outbox ADD COLUMN $col REAL');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> insertPendingContact(Map<String, dynamic> row) async {
|
||||
final db = await database;
|
||||
await db.insert('contacts_outbox', row);
|
||||
|
||||
@@ -108,6 +108,10 @@ class ContactService extends GetxService {
|
||||
'phone': c.phone.trim(),
|
||||
'email': c.email.trim(),
|
||||
'note': c.note.trim(),
|
||||
'lat': c.lat,
|
||||
'lon': c.lon,
|
||||
'eov_y': c.eovY,
|
||||
'eov_x': c.eovX,
|
||||
'created_at': DateTime.now().toIso8601String(),
|
||||
});
|
||||
return true; // várólistán
|
||||
@@ -160,6 +164,10 @@ class ContactService extends GetxService {
|
||||
'phone': m['phone'],
|
||||
'email': m['email'],
|
||||
'note': m['note'],
|
||||
'lat': m['lat'],
|
||||
'lon': m['lon'],
|
||||
'eov_y': m['eov_y'],
|
||||
'eov_x': m['eov_x'],
|
||||
}, onConflict: 'client_uuid', ignoreDuplicates: true);
|
||||
|
||||
// Sikeres felküldés → a lokális példány törölhető.
|
||||
|
||||
@@ -32,6 +32,7 @@ class LabelFieldState extends State<LabelField> {
|
||||
MapEditTool.point => 'Pont neve...',
|
||||
MapEditTool.line => 'Vonal neve...',
|
||||
MapEditTool.polygon => 'Terület neve...',
|
||||
MapEditTool.contact => 'Felirat ...',
|
||||
MapEditTool.none => 'Felirat...',
|
||||
};
|
||||
return Column(
|
||||
|
||||
@@ -116,6 +116,8 @@ class LineOrPolygonDrawingContent extends StatelessWidget {
|
||||
return 'min. 3';
|
||||
case MapEditTool.point:
|
||||
return '1 pont';
|
||||
case MapEditTool.contact:
|
||||
return '';
|
||||
case MapEditTool.none:
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.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/services/permission_service.dart';
|
||||
|
||||
import 'map_toolbar_action.dart';
|
||||
import 'map_toolbar_divider.dart';
|
||||
@@ -65,6 +66,13 @@ class MapEditCompactToolbar extends StatelessWidget {
|
||||
selected: activeTool == MapEditTool.polygon,
|
||||
onTap: controller.startPolygonTool,
|
||||
),
|
||||
if (PermissionService.to.canContacts)
|
||||
ToolbarAction(
|
||||
icon: Icons.person_pin_circle_outlined,
|
||||
label: 'Kapcsolat',
|
||||
selected: activeTool == MapEditTool.contact,
|
||||
onTap: controller.startContactTool,
|
||||
),
|
||||
const ToolbarDivider(),
|
||||
ToolbarAction(
|
||||
icon: Icons.list_alt_outlined,
|
||||
@@ -72,12 +80,12 @@ class MapEditCompactToolbar extends StatelessWidget {
|
||||
selected: false,
|
||||
onTap: () {},
|
||||
),
|
||||
ToolbarAction(
|
||||
icon: Icons.layers_outlined,
|
||||
label: 'Rétegek',
|
||||
selected: false,
|
||||
onTap: () {},
|
||||
),
|
||||
// ToolbarAction(
|
||||
// icon: Icons.layers_outlined,
|
||||
// label: 'Rétegek',
|
||||
// selected: false,
|
||||
// onTap: () {},
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user