184 lines
5.4 KiB
Dart
184 lines
5.4 KiB
Dart
|
|
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!,
|
||
|
|
);
|
||
|
|
}
|