// Bemért pontok listája: // - Pont neve + EOV Y/X // - Törlés swipe-pal // - CSV export (share_plus) // - SQLite alapú (nem memória) import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:path_provider/path_provider.dart'; import 'package:share_plus/share_plus.dart'; import '../controllers/map_survey_controller.dart'; import '../../../../../../models/measured_point.dart'; import '../../../../../../services/app_database.dart'; import '../../../../../../services/project_service.dart'; class MeasuredPointsSheet extends StatefulWidget { const MeasuredPointsSheet({super.key}); @override State createState() => _MeasuredPointsSheetState(); } class _MeasuredPointsSheetState extends State { List _points = []; bool _loading = true; @override void initState() { super.initState(); _load(); } Future _load() async { final projectId = ProjectService.to.activeProjectId; if (projectId == null) { setState(() => _loading = false); return; } final pts = await AppDatabase.instance.listMeasuredPoints(projectId); if (mounted) setState(() { _points = pts; _loading = false; }); } @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; return Container( decoration: BoxDecoration( color: cs.surface, borderRadius: const BorderRadius.vertical(top: Radius.circular(16)), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.15), blurRadius: 16, offset: const Offset(0, -4)), ], ), child: Column( children: [ // ── Handle ─────────────────────────────────────────────── Center( child: Container( margin: const EdgeInsets.symmetric(vertical: 10), width: 40, height: 4, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(2)), ), ), // ── Fejléc ─────────────────────────────────────────────── Padding( padding: const EdgeInsets.fromLTRB(20, 0, 12, 8), child: Row(children: [ const Icon(Icons.location_on, size: 20), const SizedBox(width: 8), Text('Bemért pontok', style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w600)), const SizedBox(width: 6), if (!_loading) Container( padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2), decoration: BoxDecoration( color: cs.primaryContainer, borderRadius: BorderRadius.circular(10), ), child: Text('${_points.length}', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: cs.onPrimaryContainer)), ), const Spacer(), // CSV export if (_points.isNotEmpty) IconButton( icon: const Icon(Icons.download_outlined), tooltip: 'CSV export', onPressed: _exportCsv, ), // Összes törlése if (_points.isNotEmpty) IconButton( icon: Icon(Icons.delete_sweep_outlined, color: Colors.red.shade300), tooltip: 'Összes törlése', onPressed: _deleteAll, ), ]), ), const Divider(height: 1), // ── Lista ───────────────────────────────────────────────── Expanded( child: _loading ? const Center(child: CircularProgressIndicator()) : _points.isEmpty ? _emptyState() : Column(children: [ // Fejléc sor _TableHeader(), const Divider(height: 1), Expanded( child: ListView.separated( itemCount: _points.length, separatorBuilder: (_, __) => const Divider(height: 1), itemBuilder: (_, i) => _PointTile( point: _points[i], onDelete: () => _deleteOne(_points[i]), ), ), ), ]), ), // Alsó padding SizedBox(height: MediaQuery.of(context).padding.bottom + 8), ], ), ); } // ── Törlés ──────────────────────────────────────────────────────── Future _deleteOne(MeasuredPoint pt) async { final ok = await Get.dialog(AlertDialog( title: const Text('Pont törlése'), content: Text('"${pt.name}" törlése visszavonohatalan.'), actions: [ TextButton(onPressed: Get.back, child: const Text('Mégse')), FilledButton( style: FilledButton.styleFrom(backgroundColor: Colors.red), onPressed: () => Get.back(result: true), child: const Text('Törlés'), ), ], )); if (ok != true) return; await AppDatabase.instance.deleteMeasuredPoint(pt.id!); setState(() => _points.remove(pt)); // Controller marker listából is eltávolítjuk final ctrl = Get.find(); if (pt.latitude != null && pt.longitude != null) { ctrl.pointNotesMarker.removeWhere((m) => (m.point.latitude - pt.latitude!).abs() < 1e-9 && (m.point.longitude - pt.longitude!).abs() < 1e-9); } ctrl.pointWithDescriptionList .removeWhere((p) => p.pointId.toString() == pt.name); } Future _deleteAll() async { final ok = await Get.dialog(AlertDialog( title: const Text('Összes pont törlése'), content: Text('${_points.length} pont törlése visszavonohatalan.'), actions: [ TextButton(onPressed: Get.back, child: const Text('Mégse')), FilledButton( style: FilledButton.styleFrom(backgroundColor: Colors.red), onPressed: () => Get.back(result: true), child: const Text('Összes törlése'), ), ], )); if (ok != true) return; final projectId = ProjectService.to.activeProjectId; if (projectId != null) { await AppDatabase.instance.deleteAllMeasuredPoints(projectId); } final ctrl = Get.find(); ctrl.pointWithDescriptionList.clear(); ctrl.pointNotesMarker.clear(); setState(() => _points.clear()); } // ── CSV export ──────────────────────────────────────────────────── Future _exportCsv() async { final buf = StringBuffer(); buf.writeln(MeasuredPoint.csvHeader()); for (final pt in _points) { buf.writeln(pt.toCsvRow()); } final projectName = ProjectService.to.activeProject.value?.name ?? 'projekt'; final ts = DateTime.now().toIso8601String().replaceAll(':', '-').substring(0, 16); final name = '${projectName}_bemert_pontok_$ts.csv'; final dir = await getTemporaryDirectory(); final file = File('${dir.path}/$name'); await file.writeAsString(buf.toString(), encoding: utf8); await Share.shareXFiles( [XFile(file.path, mimeType: 'text/csv; charset=utf-8')], subject: 'Bemért pontok — $projectName', ); } Widget _emptyState() => Center( child: Column(mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.location_off_outlined, size: 56, color: Colors.grey.shade300), const SizedBox(height: 12), Text('Még nincs bemért pont', style: TextStyle(color: Colors.grey.shade500)), ]), ); } // ─── Fejléc sor ────────────────────────────────────────────────────────────── class _TableHeader extends StatelessWidget { const _TableHeader(); @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; final style = TextStyle( fontSize: 11, fontWeight: FontWeight.w600, color: cs.onSurfaceVariant); return Container( color: cs.surfaceContainerHighest, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), child: Row(children: [ SizedBox(width: 52, child: Text('Pont', style: style)), Expanded(child: Text('EOV Y', style: style)), Expanded(child: Text('EOV X', style: style)), SizedBox( width: 72, child: Text('Leírás', style: style, textAlign: TextAlign.center)), const SizedBox(width: 40), // törlés gomb helye ]), ); } } // ─── Egy pont sor ──────────────────────────────────────────────────────────── class _PointTile extends StatelessWidget { final MeasuredPoint point; final VoidCallback onDelete; const _PointTile({required this.point, required this.onDelete}); @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; final hasEov = point.eovY != null && point.eovX != null; final accColor = _accColor(point.accuracy); return Dismissible( key: ValueKey(point.id), direction: DismissDirection.endToStart, confirmDismiss: (_) async { onDelete(); return false; // mi kezeljük a törlést }, background: Container( color: Colors.red, alignment: Alignment.centerRight, padding: const EdgeInsets.only(right: 20), child: const Icon(Icons.delete, color: Colors.white), ), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), child: Row(children: [ // Pont neve SizedBox( width: 52, child: Text(point.name, style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 14), overflow: TextOverflow.ellipsis), ), // EOV Y Expanded( child: Text( hasEov ? point.eovY!.toStringAsFixed(1) : '–', style: const TextStyle( fontSize: 13, fontFeatures: [FontFeature.tabularFigures()]), ), ), // EOV X Expanded( child: Text( hasEov ? point.eovX!.toStringAsFixed(1) : '–', style: const TextStyle( fontSize: 13, fontFeatures: [FontFeature.tabularFigures()]), ), ), // Pontosság SizedBox( width: 72, child: Text( point.note.isEmpty ? '---' : point.note, style: TextStyle( fontSize: 12, color: point.note.isEmpty ? Colors.grey.shade400 : null, ), overflow: TextOverflow.ellipsis, ), ), // Törlés gomb SizedBox( width: 40, child: IconButton( icon: Icon(Icons.delete_outline, size: 18, color: Colors.grey.shade400), onPressed: onDelete, padding: EdgeInsets.zero, constraints: const BoxConstraints(minWidth: 36, minHeight: 36), ), ), ]), ), ); } Color _accColor(double? acc) { if (acc == null) return Colors.grey; if (acc <= 0.05) return Colors.green; if (acc <= 0.20) return Colors.lightGreen.shade700; if (acc <= 1.00) return Colors.orange; return Colors.red; } }