Bejárás: pont színének megadása. Projekt export geopackage formátumban
This commit is contained in:
parent
729aa9bc7f
commit
65b355edd9
550
lib/core/geopackage_exporter.dart
Normal file
550
lib/core/geopackage_exporter.dart
Normal file
@ -0,0 +1,550 @@
|
|||||||
|
// GeoPackage (.gpkg) export — OGC szabvány, QGIS-kompatibilis
|
||||||
|
//
|
||||||
|
// Tartalom:
|
||||||
|
// - note_items_point : rögzített pontok
|
||||||
|
// - note_items_line : rögzített vonalak
|
||||||
|
// - note_items_polygon : rögzített területek
|
||||||
|
// - measured_points : bemért pontok
|
||||||
|
// - tracks : GPS nyomvonalak
|
||||||
|
//
|
||||||
|
// Kimenet: ZIP archív (.gpkg + médiafájlok)
|
||||||
|
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:archive/archive_io.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
import 'package:sqflite/sqflite.dart';
|
||||||
|
import 'package:terepi_seged/models/measured_point.dart';
|
||||||
|
import 'package:terepi_seged/services/app_database.dart';
|
||||||
|
import 'package:terepi_seged/services/project_service.dart';
|
||||||
|
|
||||||
|
import '../models/note_item.dart';
|
||||||
|
import '../enums/note_type.dart';
|
||||||
|
|
||||||
|
class GeoPackageExporter {
|
||||||
|
// ── Publikus belépési pont ────────────────────────────────────────
|
||||||
|
|
||||||
|
Future<void> exportProject() async {
|
||||||
|
final project = ProjectService.to.activeProject.value;
|
||||||
|
final projectId = ProjectService.to.activeProjectId;
|
||||||
|
final name = project?.name ?? 'projekt';
|
||||||
|
|
||||||
|
final ts = DateFormat('yyyyMMdd_HHmm').format(DateTime.now());
|
||||||
|
final tmpDir = await getTemporaryDirectory();
|
||||||
|
final workDir = Directory(p.join(tmpDir.path, 'gpkg_export_$ts'));
|
||||||
|
await workDir.create(recursive: true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. GeoPackage létrehozása
|
||||||
|
final gpkgPath = p.join(workDir.path, '$name.gpkg');
|
||||||
|
await _buildGpkg(gpkgPath, projectId);
|
||||||
|
|
||||||
|
// 2. Médiafájlok összegyűjtése
|
||||||
|
final mediaDir = Directory(p.join(workDir.path, 'media'));
|
||||||
|
await _collectMedia(projectId, mediaDir);
|
||||||
|
|
||||||
|
// 3. ZIP csomagolás
|
||||||
|
final zipPath = p.join(tmpDir.path, '${name}_$ts.zip');
|
||||||
|
await _packZip(workDir, zipPath);
|
||||||
|
|
||||||
|
// 4. Megosztás
|
||||||
|
await SharePlus.instance.share(ShareParams(
|
||||||
|
files: [XFile(zipPath, mimeType: 'application/zip')],
|
||||||
|
subject: 'GeoPackage export — $name',
|
||||||
|
));
|
||||||
|
} finally {
|
||||||
|
// Ideiglenes munkamappa törlése
|
||||||
|
await workDir.delete(recursive: true).catchError((_) {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── GeoPackage (.gpkg) létrehozása ───────────────────────────────
|
||||||
|
|
||||||
|
Future<void> _buildGpkg(String path, int? projectId) async {
|
||||||
|
final db = await openDatabase(path);
|
||||||
|
try {
|
||||||
|
await _initGpkg(db);
|
||||||
|
await _exportNoteItems(db, projectId);
|
||||||
|
await _exportMeasuredPoints(db, projectId);
|
||||||
|
await _exportTracks(db, projectId);
|
||||||
|
} finally {
|
||||||
|
await db.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── GeoPackage kötelező táblák ────────────────────────────────────
|
||||||
|
|
||||||
|
Future<void> _initGpkg(Database db) async {
|
||||||
|
// GeoPackage magic numbers
|
||||||
|
await db.execute('PRAGMA application_id = 1196444487');
|
||||||
|
await db.execute('PRAGMA user_version = 10200');
|
||||||
|
|
||||||
|
// Spatial reference systems
|
||||||
|
await db.execute('''
|
||||||
|
CREATE TABLE gpkg_spatial_ref_sys (
|
||||||
|
srs_name TEXT NOT NULL,
|
||||||
|
srs_id INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
organization TEXT NOT NULL,
|
||||||
|
organization_coordsys_id INTEGER NOT NULL,
|
||||||
|
definition TEXT NOT NULL,
|
||||||
|
description TEXT
|
||||||
|
)''');
|
||||||
|
|
||||||
|
// WGS84 bejegyzés
|
||||||
|
await db.insert('gpkg_spatial_ref_sys', {
|
||||||
|
'srs_name': 'WGS 84 geographic 2D',
|
||||||
|
'srs_id': 4326,
|
||||||
|
'organization': 'EPSG',
|
||||||
|
'organization_coordsys_id': 4326,
|
||||||
|
'definition': 'GEOGCS["WGS 84",DATUM["WGS_1984",'
|
||||||
|
'SPHEROID["WGS 84",6378137,298.257223563]],'
|
||||||
|
'PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433]]',
|
||||||
|
'description': 'longitude/latitude coordinates in decimal degrees',
|
||||||
|
});
|
||||||
|
// Undefined srs
|
||||||
|
await db.insert('gpkg_spatial_ref_sys', {
|
||||||
|
'srs_name': 'Undefined Cartesian SRS',
|
||||||
|
'srs_id': -1,
|
||||||
|
'organization': 'NONE',
|
||||||
|
'organization_coordsys_id': -1,
|
||||||
|
'definition': 'undefined',
|
||||||
|
});
|
||||||
|
await db.insert('gpkg_spatial_ref_sys', {
|
||||||
|
'srs_name': 'Undefined geographic SRS',
|
||||||
|
'srs_id': 0,
|
||||||
|
'organization': 'NONE',
|
||||||
|
'organization_coordsys_id': 0,
|
||||||
|
'definition': 'undefined',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Contents tábla
|
||||||
|
await db.execute('''
|
||||||
|
CREATE TABLE gpkg_contents (
|
||||||
|
table_name TEXT NOT NULL PRIMARY KEY,
|
||||||
|
data_type TEXT NOT NULL,
|
||||||
|
identifier TEXT,
|
||||||
|
description TEXT DEFAULT '',
|
||||||
|
last_change DATETIME NOT NULL
|
||||||
|
DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', CURRENT_TIMESTAMP)),
|
||||||
|
min_x REAL, min_y REAL, max_x REAL, max_y REAL,
|
||||||
|
srs_id INTEGER,
|
||||||
|
CONSTRAINT fk_gc_r_srs_id
|
||||||
|
FOREIGN KEY (srs_id) REFERENCES gpkg_spatial_ref_sys(srs_id)
|
||||||
|
)''');
|
||||||
|
|
||||||
|
// Geometry columns tábla
|
||||||
|
await db.execute('''
|
||||||
|
CREATE TABLE gpkg_geometry_columns (
|
||||||
|
table_name TEXT NOT NULL,
|
||||||
|
column_name TEXT NOT NULL,
|
||||||
|
geometry_type_name TEXT NOT NULL,
|
||||||
|
srs_id INTEGER NOT NULL,
|
||||||
|
z TINYINT NOT NULL,
|
||||||
|
m TINYINT NOT NULL,
|
||||||
|
CONSTRAINT pk_geom_cols PRIMARY KEY (table_name, column_name),
|
||||||
|
CONSTRAINT fk_gc_tn
|
||||||
|
FOREIGN KEY (table_name) REFERENCES gpkg_contents(table_name),
|
||||||
|
CONSTRAINT fk_gc_srs
|
||||||
|
FOREIGN KEY (srs_id) REFERENCES gpkg_spatial_ref_sys(srs_id)
|
||||||
|
)''');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Feature tábla regisztrálása ───────────────────────────────────
|
||||||
|
|
||||||
|
Future<void> _registerLayer(
|
||||||
|
Database db, {
|
||||||
|
required String tableName,
|
||||||
|
required String geomType, // 'POINT', 'LINESTRING', 'POLYGON'
|
||||||
|
required String identifier,
|
||||||
|
List<double>? extent, // [minX, minY, maxX, maxY]
|
||||||
|
}) async {
|
||||||
|
await db.insert('gpkg_contents', {
|
||||||
|
'table_name': tableName,
|
||||||
|
'data_type': 'features',
|
||||||
|
'identifier': identifier,
|
||||||
|
'description': '',
|
||||||
|
'last_change': DateTime.now().toUtc().toIso8601String(),
|
||||||
|
'min_x': extent?[0],
|
||||||
|
'min_y': extent?[1],
|
||||||
|
'max_x': extent?[2],
|
||||||
|
'max_y': extent?[3],
|
||||||
|
'srs_id': 4326,
|
||||||
|
});
|
||||||
|
await db.insert('gpkg_geometry_columns', {
|
||||||
|
'table_name': tableName,
|
||||||
|
'column_name': 'geom',
|
||||||
|
'geometry_type_name': geomType,
|
||||||
|
'srs_id': 4326,
|
||||||
|
'z': 0,
|
||||||
|
'm': 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── NoteItem exportok ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
Future<void> _exportNoteItems(Database db, int? projectId) async {
|
||||||
|
final items = await AppDatabase.instance.listNoteItems(projectId);
|
||||||
|
final points = items.where((i) => i.type == NoteType.point).toList();
|
||||||
|
final lines = items.where((i) => i.type == NoteType.line).toList();
|
||||||
|
final polygons = items.where((i) => i.type == NoteType.polygon).toList();
|
||||||
|
|
||||||
|
if (points.isNotEmpty) await _exportPoints(db, points);
|
||||||
|
if (lines.isNotEmpty) await _exportLines(db, lines);
|
||||||
|
if (polygons.isNotEmpty) await _exportPolygons(db, polygons);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _exportPoints(Database db, List<NoteItem> items) async {
|
||||||
|
await db.execute('''
|
||||||
|
CREATE TABLE note_items_point (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
geom BLOB NOT NULL,
|
||||||
|
label TEXT,
|
||||||
|
color_hex TEXT,
|
||||||
|
created_at TEXT,
|
||||||
|
photo_count INTEGER DEFAULT 0,
|
||||||
|
audio_count INTEGER DEFAULT 0
|
||||||
|
)''');
|
||||||
|
await _registerLayer(db,
|
||||||
|
tableName: 'note_items_point',
|
||||||
|
geomType: 'POINT',
|
||||||
|
identifier: 'Rögzített pontok',
|
||||||
|
extent: _extent(items));
|
||||||
|
|
||||||
|
for (final item in items) {
|
||||||
|
final pt = item.points.first;
|
||||||
|
final pc = await AppDatabase.instance.countNotePhotos(item.id!);
|
||||||
|
final ac = await AppDatabase.instance.countNoteAudios(item.id!);
|
||||||
|
await db.insert('note_items_point', {
|
||||||
|
'id': item.id,
|
||||||
|
'geom': _gpkgPoint(pt.longitude, pt.latitude),
|
||||||
|
'label': item.label,
|
||||||
|
'color_hex':
|
||||||
|
'#${item.color.value.toRadixString(16).padLeft(8, '0').substring(2)}',
|
||||||
|
'created_at': item.createdAt.toIso8601String(),
|
||||||
|
'photo_count': pc,
|
||||||
|
'audio_count': ac,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _exportLines(Database db, List<NoteItem> items) async {
|
||||||
|
await db.execute('''
|
||||||
|
CREATE TABLE note_items_line (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
geom BLOB NOT NULL,
|
||||||
|
label TEXT,
|
||||||
|
color_hex TEXT,
|
||||||
|
stroke_width REAL,
|
||||||
|
point_count INTEGER,
|
||||||
|
created_at TEXT,
|
||||||
|
photo_count INTEGER DEFAULT 0,
|
||||||
|
audio_count INTEGER DEFAULT 0
|
||||||
|
)''');
|
||||||
|
await _registerLayer(db,
|
||||||
|
tableName: 'note_items_line',
|
||||||
|
geomType: 'LINESTRING',
|
||||||
|
identifier: 'Rögzített vonalak',
|
||||||
|
extent: _extent(items));
|
||||||
|
|
||||||
|
for (final item in items) {
|
||||||
|
final pc = await AppDatabase.instance.countNotePhotos(item.id!);
|
||||||
|
final ac = await AppDatabase.instance.countNoteAudios(item.id!);
|
||||||
|
await db.insert('note_items_line', {
|
||||||
|
'id': item.id,
|
||||||
|
'geom': _gpkgLineString(item.points),
|
||||||
|
'label': item.label,
|
||||||
|
'color_hex':
|
||||||
|
'#${item.color.value.toRadixString(16).padLeft(8, '0').substring(2)}',
|
||||||
|
'stroke_width': item.strokeWidth,
|
||||||
|
'point_count': item.points.length,
|
||||||
|
'created_at': item.createdAt.toIso8601String(),
|
||||||
|
'photo_count': pc,
|
||||||
|
'audio_count': ac,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _exportPolygons(Database db, List<NoteItem> items) async {
|
||||||
|
await db.execute('''
|
||||||
|
CREATE TABLE note_items_polygon (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
geom BLOB NOT NULL,
|
||||||
|
label TEXT,
|
||||||
|
color_hex TEXT,
|
||||||
|
opacity REAL,
|
||||||
|
stroke_width REAL,
|
||||||
|
point_count INTEGER,
|
||||||
|
created_at TEXT,
|
||||||
|
photo_count INTEGER DEFAULT 0,
|
||||||
|
audio_count INTEGER DEFAULT 0
|
||||||
|
)''');
|
||||||
|
await _registerLayer(db,
|
||||||
|
tableName: 'note_items_polygon',
|
||||||
|
geomType: 'POLYGON',
|
||||||
|
identifier: 'Rögzített területek',
|
||||||
|
extent: _extent(items));
|
||||||
|
|
||||||
|
for (final item in items) {
|
||||||
|
final pc = await AppDatabase.instance.countNotePhotos(item.id!);
|
||||||
|
final ac = await AppDatabase.instance.countNoteAudios(item.id!);
|
||||||
|
await db.insert('note_items_polygon', {
|
||||||
|
'id': item.id,
|
||||||
|
'geom': _gpkgPolygon(item.points),
|
||||||
|
'label': item.label,
|
||||||
|
'color_hex':
|
||||||
|
'#${item.color.value.toRadixString(16).padLeft(8, '0').substring(2)}',
|
||||||
|
'opacity': item.opacity,
|
||||||
|
'stroke_width': item.strokeWidth,
|
||||||
|
'point_count': item.points.length,
|
||||||
|
'created_at': item.createdAt.toIso8601String(),
|
||||||
|
'photo_count': pc,
|
||||||
|
'audio_count': ac,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Bemért pontok exportja ────────────────────────────────────────
|
||||||
|
|
||||||
|
Future<void> _exportMeasuredPoints(Database db, int? projectId) async {
|
||||||
|
final points = projectId != null
|
||||||
|
? await AppDatabase.instance.listMeasuredPoints(projectId)
|
||||||
|
: <MeasuredPoint>[];
|
||||||
|
if (points.isEmpty) return;
|
||||||
|
|
||||||
|
await db.execute('''
|
||||||
|
CREATE TABLE measured_points (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
geom BLOB NOT NULL,
|
||||||
|
name TEXT,
|
||||||
|
eov_y REAL,
|
||||||
|
eov_x REAL,
|
||||||
|
eov_z REAL,
|
||||||
|
accuracy REAL,
|
||||||
|
fix_quality INTEGER,
|
||||||
|
timestamp TEXT,
|
||||||
|
note TEXT
|
||||||
|
)''');
|
||||||
|
await _registerLayer(db,
|
||||||
|
tableName: 'measured_points',
|
||||||
|
geomType: 'POINT',
|
||||||
|
identifier: 'Bemért pontok');
|
||||||
|
|
||||||
|
for (final pt in points) {
|
||||||
|
if (pt.latitude == null || pt.longitude == null) continue;
|
||||||
|
await db.insert('measured_points', {
|
||||||
|
'id': pt.id,
|
||||||
|
'geom': _gpkgPoint(pt.longitude!, pt.latitude!),
|
||||||
|
'name': pt.name,
|
||||||
|
'eov_y': pt.eovY,
|
||||||
|
'eov_x': pt.eovX,
|
||||||
|
'eov_z': pt.eovZ,
|
||||||
|
'accuracy': pt.accuracy,
|
||||||
|
'fix_quality': pt.fixQuality,
|
||||||
|
'timestamp': pt.timestamp.toIso8601String(),
|
||||||
|
'note': pt.note,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Track exportja ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
Future<void> _exportTracks(Database db, int? projectId) async {
|
||||||
|
final tracks = await AppDatabase.instance.listTracks();
|
||||||
|
final filtered = projectId != null
|
||||||
|
? tracks.where((t) => t.projectId == projectId).toList()
|
||||||
|
: tracks;
|
||||||
|
if (filtered.isEmpty) return;
|
||||||
|
|
||||||
|
await db.execute('''
|
||||||
|
CREATE TABLE tracks (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
geom BLOB NOT NULL,
|
||||||
|
name TEXT,
|
||||||
|
distance_meters REAL,
|
||||||
|
start_time TEXT,
|
||||||
|
end_time TEXT,
|
||||||
|
point_count INTEGER
|
||||||
|
)''');
|
||||||
|
await _registerLayer(db,
|
||||||
|
tableName: 'tracks',
|
||||||
|
geomType: 'LINESTRING',
|
||||||
|
identifier: 'GPS nyomvonalak');
|
||||||
|
|
||||||
|
for (final track in filtered) {
|
||||||
|
final pts = await AppDatabase.instance.getLatLons(track.id!);
|
||||||
|
if (pts.length < 2) continue;
|
||||||
|
|
||||||
|
// Egyszerű record list → LatLng-szerű párok
|
||||||
|
final coords = pts.map((p) => (lon: p.lon, lat: p.lat)).toList();
|
||||||
|
await db.insert('tracks', {
|
||||||
|
'id': track.id,
|
||||||
|
'geom': _gpkgLineStringFromRecords(coords),
|
||||||
|
'name': track.name,
|
||||||
|
'distance_meters': track.distanceMeters,
|
||||||
|
'start_time': track.startTime.toIso8601String(),
|
||||||
|
'end_time': track.endTime?.toIso8601String(),
|
||||||
|
'point_count': pts.length,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Médiafájlok másolása ──────────────────────────────────────────
|
||||||
|
|
||||||
|
Future<void> _collectMedia(int? projectId, Directory mediaDir) async {
|
||||||
|
if (projectId == null) return;
|
||||||
|
|
||||||
|
final photos = Directory(p.join(mediaDir.path, 'photos'));
|
||||||
|
final audios = Directory(p.join(mediaDir.path, 'audio'));
|
||||||
|
await photos.create(recursive: true);
|
||||||
|
await audios.create(recursive: true);
|
||||||
|
|
||||||
|
final items = await AppDatabase.instance.listNoteItems(projectId);
|
||||||
|
for (final item in items) {
|
||||||
|
// Fotók
|
||||||
|
final photoList = await AppDatabase.instance.listNotePhotos(item.id!);
|
||||||
|
for (final photo in photoList) {
|
||||||
|
final src = File(photo.localPath);
|
||||||
|
if (await src.exists()) {
|
||||||
|
final dst = File(p.join(photos.path, p.basename(photo.localPath)));
|
||||||
|
await src.copy(dst.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Hangok
|
||||||
|
final audioList = await AppDatabase.instance.listNoteAudios(item.id!);
|
||||||
|
for (final audio in audioList) {
|
||||||
|
final src = File(audio.localPath);
|
||||||
|
if (await src.exists()) {
|
||||||
|
final dst = File(p.join(audios.path, p.basename(audio.localPath)));
|
||||||
|
await src.copy(dst.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── ZIP csomagolás ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
Future<void> _packZip(Directory workDir, String zipPath) async {
|
||||||
|
final archive = Archive();
|
||||||
|
|
||||||
|
for (final entity in workDir.listSync(recursive: true)) {
|
||||||
|
if (entity is! File) continue;
|
||||||
|
final bytes = entity.readAsBytesSync();
|
||||||
|
final rel = p.relative(entity.path, from: workDir.path);
|
||||||
|
final archiveName = rel.replaceAll('\\', '/'); // Windows path fix
|
||||||
|
archive.addFile(ArchiveFile(archiveName, bytes.length, bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
final encoded = ZipEncoder().encode(archive);
|
||||||
|
if (encoded == null || encoded.isEmpty) {
|
||||||
|
throw Exception(
|
||||||
|
'ZIP encoding sikertelen — nincsenek exportálható adatok');
|
||||||
|
}
|
||||||
|
await File(zipPath).writeAsBytes(Uint8List.fromList(encoded));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── WKB/GeoPackage bináris kódolás ───────────────────────────────
|
||||||
|
//
|
||||||
|
// GPKG binary header (8 byte):
|
||||||
|
// 0x47 0x50 – 'GP' magic
|
||||||
|
// 0x00 – version 0
|
||||||
|
// 0x01 – flags: little-endian, no envelope
|
||||||
|
// srs_id (int32) – 4326 (WGS84)
|
||||||
|
// + WKB geometry
|
||||||
|
|
||||||
|
Uint8List _gpkgPoint(double lon, double lat) {
|
||||||
|
final b = ByteData(8 + 21); // header + wkb point
|
||||||
|
_writeGpkgHeader(b, 0);
|
||||||
|
b.setUint8(8, 1); // WKB: little endian
|
||||||
|
b.setUint32(9, 1, Endian.little); // type: Point
|
||||||
|
b.setFloat64(13, lon, Endian.little);
|
||||||
|
b.setFloat64(21, lat, Endian.little);
|
||||||
|
return b.buffer.asUint8List();
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint8List _gpkgLineString(List<dynamic> points) {
|
||||||
|
final n = points.length;
|
||||||
|
final b = ByteData(8 + 9 + n * 16);
|
||||||
|
_writeGpkgHeader(b, 0);
|
||||||
|
int o = 8;
|
||||||
|
b.setUint8(o++, 1);
|
||||||
|
b.setUint32(o, 2, Endian.little);
|
||||||
|
o += 4; // type: LineString
|
||||||
|
b.setUint32(o, n, Endian.little);
|
||||||
|
o += 4;
|
||||||
|
for (final pt in points) {
|
||||||
|
b.setFloat64(o, pt.longitude, Endian.little);
|
||||||
|
o += 8;
|
||||||
|
b.setFloat64(o, pt.latitude, Endian.little);
|
||||||
|
o += 8;
|
||||||
|
}
|
||||||
|
return b.buffer.asUint8List();
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint8List _gpkgLineStringFromRecords(
|
||||||
|
List<({double lon, double lat})> coords) {
|
||||||
|
final n = coords.length;
|
||||||
|
final b = ByteData(8 + 9 + n * 16);
|
||||||
|
_writeGpkgHeader(b, 0);
|
||||||
|
int o = 8;
|
||||||
|
b.setUint8(o++, 1);
|
||||||
|
b.setUint32(o, 2, Endian.little);
|
||||||
|
o += 4;
|
||||||
|
b.setUint32(o, n, Endian.little);
|
||||||
|
o += 4;
|
||||||
|
for (final c in coords) {
|
||||||
|
b.setFloat64(o, c.lon, Endian.little);
|
||||||
|
o += 8;
|
||||||
|
b.setFloat64(o, c.lat, Endian.little);
|
||||||
|
o += 8;
|
||||||
|
}
|
||||||
|
return b.buffer.asUint8List();
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint8List _gpkgPolygon(List<dynamic> ring) {
|
||||||
|
final n = ring.length;
|
||||||
|
final b = ByteData(8 + 9 + 4 + n * 16);
|
||||||
|
_writeGpkgHeader(b, 0);
|
||||||
|
int o = 8;
|
||||||
|
b.setUint8(o++, 1);
|
||||||
|
b.setUint32(o, 3, Endian.little);
|
||||||
|
o += 4; // type: Polygon
|
||||||
|
b.setUint32(o, 1, Endian.little);
|
||||||
|
o += 4; // 1 ring
|
||||||
|
b.setUint32(o, n, Endian.little);
|
||||||
|
o += 4;
|
||||||
|
for (final pt in ring) {
|
||||||
|
b.setFloat64(o, pt.longitude, Endian.little);
|
||||||
|
o += 8;
|
||||||
|
b.setFloat64(o, pt.latitude, Endian.little);
|
||||||
|
o += 8;
|
||||||
|
}
|
||||||
|
return b.buffer.asUint8List();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _writeGpkgHeader(ByteData b, int offset) {
|
||||||
|
b.setUint8(offset, 0x47); // 'G'
|
||||||
|
b.setUint8(offset + 1, 0x50); // 'P'
|
||||||
|
b.setUint8(offset + 2, 0x00); // version
|
||||||
|
b.setUint8(offset + 3, 0x01); // flags: little-endian, no envelope
|
||||||
|
b.setInt32(offset + 4, 4326, Endian.little); // WGS84
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Segédek ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Befoglaló téglalap az összes NoteItem pontjaiból
|
||||||
|
List<double>? _extent(List<NoteItem> items) {
|
||||||
|
final pts = items.expand((i) => i.points).toList();
|
||||||
|
if (pts.isEmpty) return null;
|
||||||
|
return [
|
||||||
|
pts.map((p) => p.longitude).reduce((a, b) => a < b ? a : b),
|
||||||
|
pts.map((p) => p.latitude).reduce((a, b) => a < b ? a : b),
|
||||||
|
pts.map((p) => p.longitude).reduce((a, b) => a > b ? a : b),
|
||||||
|
pts.map((p) => p.latitude).reduce((a, b) => a > b ? a : b),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -23,6 +23,7 @@ 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/core/geometry_measure.dart';
|
import 'package:terepi_seged/core/geometry_measure.dart';
|
||||||
import 'package:terepi_seged/core/geometry_measure_formatter.dart';
|
import 'package:terepi_seged/core/geometry_measure_formatter.dart';
|
||||||
|
import 'package:terepi_seged/core/geopackage_exporter.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/enums/note_type.dart';
|
||||||
@ -45,6 +46,7 @@ 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/services/project_service.dart';
|
||||||
import 'package:terepi_seged/widgets/map/imported_layer_overlay.dart';
|
import 'package:terepi_seged/widgets/map/imported_layer_overlay.dart';
|
||||||
|
import 'package:terepi_seged/widgets/map_edit_tools/color_row.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';
|
||||||
import 'package:terepi_seged/widgets/map_edit_tools/note_item_list_sheet.dart';
|
import 'package:terepi_seged/widgets/map_edit_tools/note_item_list_sheet.dart';
|
||||||
import 'package:terepi_seged/widgets/shared_map_widgets.dart';
|
import 'package:terepi_seged/widgets/shared_map_widgets.dart';
|
||||||
@ -142,6 +144,9 @@ class MapSurveyController extends GetxController {
|
|||||||
final pointsToMeasureLabel = <PolyWidget>[].obs;
|
final pointsToMeasureLabel = <PolyWidget>[].obs;
|
||||||
final pointsToMeasureDropDownMenuItem = <DropdownMenuItem<int>>[].obs;
|
final pointsToMeasureDropDownMenuItem = <DropdownMenuItem<int>>[].obs;
|
||||||
|
|
||||||
|
final allNoteItems = <NoteItem>[].obs;
|
||||||
|
final showGeometryLabels = false.obs;
|
||||||
|
|
||||||
// ── Pont adatok ───────────────────────────────────────────────────
|
// ── Pont adatok ───────────────────────────────────────────────────
|
||||||
final RxList<PointToMeasure> pointsToMeasure = <PointToMeasure>[].obs;
|
final RxList<PointToMeasure> pointsToMeasure = <PointToMeasure>[].obs;
|
||||||
final RxList<PointWithDescription> pointWithDescriptionList =
|
final RxList<PointWithDescription> pointWithDescriptionList =
|
||||||
@ -1251,6 +1256,8 @@ class MapSurveyController extends GetxController {
|
|||||||
final projectId = ProjectService.to.activeProject.value?.id;
|
final projectId = ProjectService.to.activeProject.value?.id;
|
||||||
final items = await AppDatabase.instance.listNoteItems(projectId);
|
final items = await AppDatabase.instance.listNoteItems(projectId);
|
||||||
|
|
||||||
|
allNoteItems.assignAll(items);
|
||||||
|
|
||||||
// Listák resetelése
|
// Listák resetelése
|
||||||
pointNotes.clear();
|
pointNotes.clear();
|
||||||
polylineNotes.clear();
|
polylineNotes.clear();
|
||||||
@ -1341,6 +1348,9 @@ class MapSurveyController extends GetxController {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await updateNoteItem(updated);
|
await updateNoteItem(updated);
|
||||||
|
|
||||||
|
allNoteItems.removeWhere((i) => i.id == id);
|
||||||
|
allNoteItems.add(updated);
|
||||||
editingNoteItemId = null;
|
editingNoteItemId = null;
|
||||||
activeEditTool.value = MapEditTool.none;
|
activeEditTool.value = MapEditTool.none;
|
||||||
draftAreaSquareMeters.value = 0.0;
|
draftAreaSquareMeters.value = 0.0;
|
||||||
@ -1413,6 +1423,7 @@ class MapSurveyController extends GetxController {
|
|||||||
).then((saved) {
|
).then((saved) {
|
||||||
if (saved == null) return;
|
if (saved == null) return;
|
||||||
pointNotes.add(_markerFromNoteItem(saved));
|
pointNotes.add(_markerFromNoteItem(saved));
|
||||||
|
allNoteItems.add(saved);
|
||||||
});
|
});
|
||||||
activeEditTool.value = MapEditTool.none;
|
activeEditTool.value = MapEditTool.none;
|
||||||
}
|
}
|
||||||
@ -1510,6 +1521,7 @@ class MapSurveyController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteNoteItem(NoteItem item) async {
|
Future<void> deleteNoteItem(NoteItem item) async {
|
||||||
|
allNoteItems.removeWhere((i) => i.id == item.id);
|
||||||
await AppDatabase.instance.deleteNoteItem(item.id!);
|
await AppDatabase.instance.deleteNoteItem(item.id!);
|
||||||
|
|
||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
@ -1634,6 +1646,7 @@ class MapSurveyController extends GetxController {
|
|||||||
);
|
);
|
||||||
if (saved != null) {
|
if (saved != null) {
|
||||||
polylineNotes.add(saved.toPolyline());
|
polylineNotes.add(saved.toPolyline());
|
||||||
|
allNoteItems.add(saved);
|
||||||
}
|
}
|
||||||
polygonEditorController.clear();
|
polygonEditorController.clear();
|
||||||
activeEditTool.value = MapEditTool.none;
|
activeEditTool.value = MapEditTool.none;
|
||||||
@ -1796,15 +1809,23 @@ class MapSurveyController extends GetxController {
|
|||||||
Get.dialog(
|
Get.dialog(
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
title: const Text('Pont mentése'),
|
title: const Text('Pont mentése'),
|
||||||
content: TextField(
|
content: Column(
|
||||||
controller: labelCtrl,
|
mainAxisSize: MainAxisSize.min,
|
||||||
autofocus: true,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
decoration: const InputDecoration(
|
children: [
|
||||||
hintText: 'Pont neve (opcionális)',
|
ColorRow(ctrl: this, circleSize: 34),
|
||||||
border: OutlineInputBorder(),
|
SizedBox(height: 8),
|
||||||
),
|
TextField(
|
||||||
textCapitalization: TextCapitalization.sentences,
|
controller: labelCtrl,
|
||||||
onSubmitted: (_) => _doSavePoint(point, labelCtrl.text),
|
autofocus: true,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
hintText: 'Pont neve (opcionális)',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
textCapitalization: TextCapitalization.sentences,
|
||||||
|
onSubmitted: (_) => _doSavePoint(point, labelCtrl.text),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
@ -1853,4 +1874,45 @@ class MapSurveyController extends GetxController {
|
|||||||
ignoreSafeArea: false,
|
ignoreSafeArea: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void toggleGeometryLabels() =>
|
||||||
|
showGeometryLabels.value = !showGeometryLabels.value;
|
||||||
|
|
||||||
|
Future<void> exportProject() async {
|
||||||
|
Get.dialog(
|
||||||
|
Center(
|
||||||
|
child: Material(
|
||||||
|
borderRadius: BorderRadius.circular(14),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(28),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(14),
|
||||||
|
),
|
||||||
|
child: const Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
CircularProgressIndicator(),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
'GeoPackage generálása...',
|
||||||
|
style: TextStyle(fontSize: 16),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
barrierDismissible: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await GeoPackageExporter().exportProject();
|
||||||
|
} catch (e) {
|
||||||
|
Get.snackbar('Export hiba', e.toString(),
|
||||||
|
snackPosition: SnackPosition.BOTTOM);
|
||||||
|
} finally {
|
||||||
|
Get.back(); // ← dialóg bezárása, akár sikerült akár nem
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import 'package:terepi_seged/utils/rive_utils.dart';
|
|||||||
import 'package:terepi_seged/widgets/coordinate_panel.dart';
|
import 'package:terepi_seged/widgets/coordinate_panel.dart';
|
||||||
import 'package:terepi_seged/widgets/map/imported_layer_overlay.dart';
|
import 'package:terepi_seged/widgets/map/imported_layer_overlay.dart';
|
||||||
import 'package:terepi_seged/widgets/map/measure_bottom_panel.dart';
|
import 'package:terepi_seged/widgets/map/measure_bottom_panel.dart';
|
||||||
|
import 'package:terepi_seged/widgets/map/note_item_label_layer.dart';
|
||||||
import 'package:terepi_seged/widgets/map_bottom_panel.dart';
|
import 'package:terepi_seged/widgets/map_bottom_panel.dart';
|
||||||
import 'package:terepi_seged/widgets/map_edit_tools/map_edit_drawing_toolbar.dart';
|
import 'package:terepi_seged/widgets/map_edit_tools/map_edit_drawing_toolbar.dart';
|
||||||
import 'package:terepi_seged/widgets/map_edit_tools/map_edit_toolbar.dart';
|
import 'package:terepi_seged/widgets/map_edit_tools/map_edit_toolbar.dart';
|
||||||
@ -171,6 +172,9 @@ class MapSurveyView extends GetView<MapSurveyController> {
|
|||||||
controller: controller.polygonEditorController,
|
controller: controller.polygonEditorController,
|
||||||
throttleDuration: Duration.zero);
|
throttleDuration: Duration.zero);
|
||||||
}),
|
}),
|
||||||
|
NoteItemLabelLayer(
|
||||||
|
controller: controller,
|
||||||
|
),
|
||||||
Obx(() {
|
Obx(() {
|
||||||
final isGpsActive = GnssService.to.activeConnectionType.value !=
|
final isGpsActive = GnssService.to.activeConnectionType.value !=
|
||||||
GnssConnectionType.none;
|
GnssConnectionType.none;
|
||||||
|
|||||||
@ -252,7 +252,9 @@ class _DrawerFooter extends StatelessWidget {
|
|||||||
FutureBuilder<PackageInfo>(
|
FutureBuilder<PackageInfo>(
|
||||||
future: PackageInfo.fromPlatform(),
|
future: PackageInfo.fromPlatform(),
|
||||||
builder: (_, snap) => Text(
|
builder: (_, snap) => Text(
|
||||||
snap.hasData ? 'v${snap.data!.version}' : '',
|
snap.hasData
|
||||||
|
? 'v${snap.data!.version}+${snap.data!.buildNumber}'
|
||||||
|
: '',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
color: Colors.grey.shade500,
|
color: Colors.grey.shade500,
|
||||||
|
|||||||
@ -153,7 +153,7 @@ class ShellMapAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
controller: TrackingController.to,
|
controller: TrackingController.to,
|
||||||
onTap: () => _openTrackingSheet(context),
|
onTap: () => _openTrackingSheet(context),
|
||||||
),
|
),
|
||||||
PopupMenuButton(
|
PopupMenuButton<int>(
|
||||||
tooltip: 'További funkciók',
|
tooltip: 'További funkciók',
|
||||||
icon: const Icon(Icons.more_vert),
|
icon: const Icon(Icons.more_vert),
|
||||||
onSelected: (value) {
|
onSelected: (value) {
|
||||||
@ -163,16 +163,48 @@ class ShellMapAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
if (value == 0) {
|
if (value == 0) {
|
||||||
controller.openNoteItemList();
|
controller.openNoteItemList();
|
||||||
}
|
}
|
||||||
|
if (value == 2) {
|
||||||
|
controller.toggleGeometryLabels();
|
||||||
|
}
|
||||||
|
if (value == 3) {
|
||||||
|
controller.exportProject();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
itemBuilder: (context) => const [
|
itemBuilder: (context) => [
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: 0,
|
value: 0,
|
||||||
child: Text('Feljegyzések'),
|
child: ListTile(
|
||||||
|
leading: Icon(Icons.line_axis),
|
||||||
|
title: Text('Geometriák'),
|
||||||
|
dense: true),
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
value: 2,
|
||||||
|
child: ListTile(
|
||||||
|
leading: Icon(
|
||||||
|
controller.showGeometryLabels.value
|
||||||
|
? Icons.label
|
||||||
|
: Icons.label_off_outlined,
|
||||||
|
color: controller.showGeometryLabels.value
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: null),
|
||||||
|
title: Text(controller.showGeometryLabels.value
|
||||||
|
? "Feliartok elrejtése"
|
||||||
|
: 'Feliratok megjelenítése'),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: 1,
|
value: 1,
|
||||||
child: Text('Rétegek'),
|
child: Text('Rétegek'),
|
||||||
),
|
),
|
||||||
|
PopupMenuDivider(),
|
||||||
|
PopupMenuItem(
|
||||||
|
value: 3,
|
||||||
|
child: ListTile(
|
||||||
|
leading: const Icon(Icons.archive_outlined),
|
||||||
|
title: const Text('Projekt exportálása'),
|
||||||
|
dense: true,
|
||||||
|
))
|
||||||
])
|
])
|
||||||
],
|
],
|
||||||
bottom: PreferredSize(
|
bottom: PreferredSize(
|
||||||
|
|||||||
116
lib/widgets/map/note_item_label_layer.dart
Normal file
116
lib/widgets/map/note_item_label_layer.dart
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
// Szöveges feliratok a rögzített pontokhoz és vonalakhoz.
|
||||||
|
// Poligonoknál a Polygon.label-t a view kezeli (flutter_map beépített).
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:latlong2/latlong.dart';
|
||||||
|
import 'package:terepi_seged/enums/map_survey_mode.dart';
|
||||||
|
|
||||||
|
import '../../enums/note_type.dart';
|
||||||
|
import '../../pages/map_survey/presentations/controllers/map_survey_controller.dart';
|
||||||
|
|
||||||
|
class NoteItemLabelLayer extends StatelessWidget {
|
||||||
|
final MapSurveyController controller;
|
||||||
|
const NoteItemLabelLayer({super.key, required this.controller});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Obx(() {
|
||||||
|
if (!controller.showGeometryLabels.value) return const SizedBox.shrink();
|
||||||
|
if (controller.mode.value != MapSurveyMode.fieldWalk)
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
|
||||||
|
final markers = <Marker>[];
|
||||||
|
|
||||||
|
for (final item in controller.allNoteItems) {
|
||||||
|
if (item.label.isEmpty) continue;
|
||||||
|
|
||||||
|
switch (item.type) {
|
||||||
|
// ── Pont — felirat a marker felett ──────────────────────
|
||||||
|
case NoteType.point:
|
||||||
|
markers.add(Marker(
|
||||||
|
point: item.points.first,
|
||||||
|
width: 110,
|
||||||
|
height: 48, // 22 szöveg + 22 pont marker helye
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
_LabelBubble(label: item.label, color: item.color),
|
||||||
|
//const SizedBox(height: 4), // pont marker fölé kerül
|
||||||
|
],
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
// ── Vonal — felirat a felezőponton ──────────────────────
|
||||||
|
case NoteType.line:
|
||||||
|
if (item.points.isEmpty) continue;
|
||||||
|
final mid = _midpoint(item.points);
|
||||||
|
markers.add(Marker(
|
||||||
|
point: mid,
|
||||||
|
width: 120,
|
||||||
|
height: 24,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
_LabelBubble(label: item.label, color: item.color),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
case NoteType.polygon:
|
||||||
|
break; // poligonnál a Polygon.label kezeli a view-ban
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (markers.isEmpty) return const SizedBox.shrink();
|
||||||
|
return MarkerLayer(markers: markers);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Vonal felezőpontja
|
||||||
|
LatLng _midpoint(List<LatLng> points) {
|
||||||
|
final mid = points[points.length ~/ 2];
|
||||||
|
return mid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Felirat buborék ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
class _LabelBubble extends StatelessWidget {
|
||||||
|
final String label;
|
||||||
|
final Color color;
|
||||||
|
const _LabelBubble({required this.label, required this.color});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white.withOpacity(0.92),
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
border: Border(left: BorderSide(color: color, width: 3)),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.18),
|
||||||
|
blurRadius: 4,
|
||||||
|
offset: const Offset(0, 1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.black87,
|
||||||
|
height: 1.2,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,6 +4,8 @@ import 'package:terepi_seged/pages/map_survey/presentations/controllers/map_surv
|
|||||||
|
|
||||||
class ColorRow extends StatelessWidget {
|
class ColorRow extends StatelessWidget {
|
||||||
final MapSurveyController ctrl;
|
final MapSurveyController ctrl;
|
||||||
|
final double circleSize;
|
||||||
|
|
||||||
static const _palette = [
|
static const _palette = [
|
||||||
Color(0xFF6C63FF),
|
Color(0xFF6C63FF),
|
||||||
Color(0xFFE74C3C),
|
Color(0xFFE74C3C),
|
||||||
@ -13,7 +15,7 @@ class ColorRow extends StatelessWidget {
|
|||||||
Color(0xFF3498DB),
|
Color(0xFF3498DB),
|
||||||
Color(0xFF8BC34A),
|
Color(0xFF8BC34A),
|
||||||
];
|
];
|
||||||
const ColorRow({required this.ctrl});
|
const ColorRow({required this.ctrl, this.circleSize = 40});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -31,8 +33,8 @@ class ColorRow extends StatelessWidget {
|
|||||||
onTap: () => ctrl.activeEditColor.value = color,
|
onTap: () => ctrl.activeEditColor.value = color,
|
||||||
child: AnimatedContainer(
|
child: AnimatedContainer(
|
||||||
duration: const Duration(milliseconds: 150),
|
duration: const Duration(milliseconds: 150),
|
||||||
width: 40,
|
width: circleSize,
|
||||||
height: 40,
|
height: circleSize,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: color,
|
color: color,
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user