mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-06-14 22:55:12 +10:00
Add GPX export functionality and related UI components
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:gpx/gpx.dart';
|
||||
import 'package:meshcore_open/connector/meshcore_connector.dart';
|
||||
import 'package:meshcore_open/connector/meshcore_protocol.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
class ContactExport {
|
||||
final String name;
|
||||
final double lat;
|
||||
final double lon;
|
||||
final String desc;
|
||||
final double? ele;
|
||||
|
||||
ContactExport({
|
||||
required this.name,
|
||||
required this.lat,
|
||||
required this.lon,
|
||||
required this.desc,
|
||||
this.ele,
|
||||
});
|
||||
}
|
||||
|
||||
const int GpxExportFailed = -1;
|
||||
const int GpxExportSuccess = 1;
|
||||
const int GpxExportNoContacts = 2;
|
||||
const int GpxExportCancelled = 3;
|
||||
const int GpxExportNotAvailable = 4;
|
||||
|
||||
class GpxExport {
|
||||
MeshCoreConnector _connector;
|
||||
List<ContactExport> _contacts = [];
|
||||
|
||||
GpxExport(this._connector);
|
||||
|
||||
void _addContact(String name, double lat, double lon, String desc, [double? ele]) {
|
||||
_contacts.add(ContactExport(
|
||||
name: name.trim(),
|
||||
lat: lat,
|
||||
lon: lon,
|
||||
desc: desc.trim(),
|
||||
ele: ele,
|
||||
));
|
||||
}
|
||||
|
||||
void addRepeaters() {
|
||||
final contacts = _connector.contacts;
|
||||
final repeaters = contacts.where((c) => c.type == advTypeRepeater || c.type == advTypeRoom).toList();
|
||||
for (var repeater in repeaters) {
|
||||
if (repeater.latitude == null || repeater.longitude == null) {
|
||||
continue;
|
||||
}
|
||||
_addContact(
|
||||
repeater.name,
|
||||
repeater.latitude ?? 0.0,
|
||||
repeater.longitude ?? 0.0,
|
||||
"Type: ${repeater.typeLabel}\nPublic Key: ${repeater.publicKeyHex}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void addContacts() {
|
||||
final contacts = _connector.contacts;
|
||||
final repeaters = contacts.where((c) => c.type == advTypeChat).toList();
|
||||
for (var repeater in repeaters) {
|
||||
if (repeater.latitude == null || repeater.longitude == null) {
|
||||
continue;
|
||||
}
|
||||
_addContact(
|
||||
repeater.name,
|
||||
repeater.latitude ?? 0.0,
|
||||
repeater.longitude ?? 0.0,
|
||||
"Type: ${repeater.typeLabel}\nPublic Key: ${repeater.publicKeyHex}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void addAll() {
|
||||
final contacts = _connector.contacts;
|
||||
for (var repeater in contacts.toList()) {
|
||||
if (repeater.latitude == null || repeater.longitude == null) {
|
||||
continue;
|
||||
}
|
||||
_addContact(
|
||||
repeater.name,
|
||||
repeater.latitude ?? 0.0,
|
||||
repeater.longitude ?? 0.0,
|
||||
"Type: ${repeater.typeLabel}\nPublic Key: ${repeater.publicKeyHex}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> exportGPX() async {
|
||||
if (_contacts.isEmpty) {
|
||||
debugPrint("No repeaters to export – nothing to share.");
|
||||
return GpxExportNoContacts;
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. Build GPX content (your existing logic – unchanged here)
|
||||
final gpx = Gpx()
|
||||
..version = '1.1'
|
||||
..creator = 'meshcore-open Repeater Exporter'
|
||||
..metadata = Metadata(
|
||||
name: 'Meshcore Repeaters',
|
||||
desc: 'Repeater & room locations exported from meshcore-open',
|
||||
time: DateTime.now().toUtc(),
|
||||
);
|
||||
|
||||
gpx.wpts = _contacts.map((c) => Wpt(
|
||||
lat: c.lat,
|
||||
lon: c.lon,
|
||||
ele: c.ele,
|
||||
name: c.name,
|
||||
desc: c.desc,
|
||||
)).toList();
|
||||
|
||||
final xml = GpxWriter().asString(gpx, pretty: true);
|
||||
|
||||
// 2. Save to file
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
final timestamp = DateTime.now().toUtc().toIso8601String()
|
||||
.replaceAll(':', '-')
|
||||
.replaceAll('.', '-')
|
||||
.split('T')
|
||||
.join('_');
|
||||
final path = '${dir.path}/meshcore_repeaters_$timestamp.gpx';
|
||||
|
||||
final file = File(path);
|
||||
await file.writeAsString(xml);
|
||||
|
||||
// 3. Modern share call (2025+ style)
|
||||
final result = await SharePlus.instance.share(
|
||||
ShareParams(
|
||||
text: 'Repeater locations exported from meshcore-open app as GPX file.',
|
||||
subject: 'Meshcore Repeaters GPX Export',
|
||||
files: [XFile(path)],
|
||||
// Optional: sharePositionOrigin: ... (if you want iPad popover positioning)
|
||||
),
|
||||
);
|
||||
|
||||
// 4. Handle result
|
||||
switch (result.status) {
|
||||
case ShareResultStatus.success:
|
||||
debugPrint('Share successful – user completed the action.');
|
||||
return GpxExportSuccess;
|
||||
case ShareResultStatus.dismissed:
|
||||
debugPrint('Share sheet was dismissed / cancelled by user.');
|
||||
return GpxExportCancelled;
|
||||
case ShareResultStatus.unavailable:
|
||||
debugPrint('Sharing is not available on this platform / context.');
|
||||
return GpxExportNotAvailable;
|
||||
}
|
||||
|
||||
// Optional cleanup (uncomment if you don't want to keep the file)
|
||||
// await file.delete();
|
||||
} catch (e, stack) {
|
||||
debugPrint('Export or share failed: $e\n$stack');
|
||||
// → here you could show a SnackBar / AlertDialog in real UI code
|
||||
}
|
||||
return GpxExportFailed;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user