diff --git a/.gitignore b/.gitignore index 1336b68..1a9b0d4 100644 --- a/.gitignore +++ b/.gitignore @@ -44,4 +44,8 @@ app.*.map.json /android/app/release key.properties -/key \ No newline at end of file +/key + +later.* + +to_remember.txt \ No newline at end of file diff --git a/assets/compass.svg b/assets/compass.svg new file mode 100644 index 0000000..918efdd --- /dev/null +++ b/assets/compass.svg @@ -0,0 +1,962 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + 64 + 4 + 8 + 12 + 16 + 60 + 56 + 52 + 48 + 32 + 28 + 24 + 20 + 36 + 40 + 44 + + + + + + + + + + + + + + + + + + + + + 360 + 10 + 20 + 30 + 40 + 50 + 60 + 70 + 80 + 90 + 350 + 340 + 330 + 320 + 310 + 300 + 290 + 280 + 270 + 180 + 170 + 160 + 150 + 140 + 130 + 120 + 110 + 100 + 190 + 200 + 210 + 220 + 230 + 240 + 250 + 260 + + + + + + + + + + + + + N + W + O + S + WNW + NNW + SSW + WSW + NW + SW + NNO + NO + ONO + SSO + SO + OSO + + diff --git a/assets/needle.svg b/assets/needle.svg new file mode 100644 index 0000000..254f57a --- /dev/null +++ b/assets/needle.svg @@ -0,0 +1,71 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/boxes/compass.dart b/lib/boxes/compass.dart new file mode 100644 index 0000000..2189363 --- /dev/null +++ b/lib/boxes/compass.dart @@ -0,0 +1,133 @@ +import 'dart:async'; +import 'dart:ffi'; +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:ju_rc_app/lib/boxes.dart'; +import 'package:ju_rc_app/lib/sensors.dart'; +import 'package:ju_rc_app/lib/serial.dart'; + +// c-structs +base class GnssLocData extends Struct { + @Uint8() + external int type; + @Uint8() + external int command; + + @Int32() + external int X; + @Int32() + external int Y; + @Int32() + external int Z; + @Int32() + external int angle; +} +// end c-structs + +class Compass extends JuBox { + const Compass({super.key}); + + @override + State createState() => _CompassState(); +} + +class _CompassState extends State { + USerial serial = getSerial(); + late Timer timer; + + int _magX = 0, _magY = 0, _magZ = 0, _magAngle = 0; + + @override + void initState() { + super.initState(); + SensorReader.listen(serialListen); + timer = Timer.periodic(const Duration(milliseconds: 500), (_) async { + serial.sprintln("2200", system: true); + }); + } + + @override + void dispose() { + SensorReader.removeListen(serialListen); + timer.cancel(); + super.dispose(); + } + + void serialListen(int type, int command, Pointer ptr) { + if (type != 0x22 || command != 0x00) return; + var loc = ptr as Pointer; + setState(() { + _magX = loc.ref.X; + _magY = loc.ref.Y; + _magZ = loc.ref.Z; + int newAngle = loc.ref.angle; + if (newAngle - _magAngle > 200) newAngle -= 360; + if (newAngle - _magAngle < -200) newAngle += 360; + _magAngle = newAngle; + }); + } + + @override + Widget build(BuildContext context) => Stack( + children: [ + SvgPicture.asset('assets/compass.svg'), + AnimatedRotation( + turns: _magAngle / 360.0, + duration: const Duration(milliseconds: 500), + alignment: Alignment.center, // Rotate around the center + child: SvgPicture.asset('assets/needle.svg')), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "x: ${(_magX * 8.0 / 32768.0 * 100.0).toStringAsFixed(2)}µT"), + Text( + "y: ${(_magY * 8.0 / 32768.0 * 100.0).toStringAsFixed(2)}µT"), + Text( + "z: ${(_magZ * 8.0 / 32768.0 * 100.0).toStringAsFixed(2)}µT"), + Expanded( + child: Align( + alignment: Alignment.bottomLeft, + child: FilledButton( + child: const Icon(Icons.calculate), + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) => AlertDialog( + title: const Text('Callibrate Compass'), + content: const Text( + 'When clicking okay the compass will be calibrated, \ncompletely blocking all radio communication for 10 sec. \nWhile calibrating rotate the sensor in ever direction.'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, 'Cancel'), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + serial.sprintln("2201", + system: true); + Navigator.pop(context, 'OK'); + }, + child: const Text('OK'), + ), + ], + ), + ); + // + }, + ), + ), + ), + ], + ), + Expanded( + child: Align( + alignment: Alignment.topRight, + child: Text( + "t: ${(sqrt(_magX * _magX + _magY * _magY + _magZ * _magZ) * 8.0 / 32768.0 * 100.0).toStringAsFixed(2)}µT"), + )) + ], + ); +} diff --git a/lib/boxes/maps.dart b/lib/boxes/maps.dart new file mode 100644 index 0000000..217e323 --- /dev/null +++ b/lib/boxes/maps.dart @@ -0,0 +1,475 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:flutter/material.dart'; + +import 'dart:ffi'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:latlong2/latlong.dart'; + +import 'package:ju_rc_app/lib/boxes.dart'; +import 'package:ju_rc_app/lib/sensors.dart'; +import 'package:ju_rc_app/lib/serial.dart'; + +// c-structs +base class GnssLocData extends Struct { + @Uint8() + external int type; + @Uint8() + external int command; + + @Uint8() + external int isValid; + + @Double() + external double lat; + @Double() + external double lng; +} + +base class SpeedLocData extends Struct { + @Uint8() + external int type; + @Uint8() + external int command; + + @Uint8() + external int isValid; + + @Double() + external double speed; +} + +base class CourseLocData extends Struct { + @Uint8() + external int type; + @Uint8() + external int command; + + @Uint8() + external int isValid; + + @Double() + external double course; +} + +base class AltLocData extends Struct { + @Uint8() + external int type; + @Uint8() + external int command; + + @Uint8() + external int isValid; + + @Double() + external double alt; +} + +base class SatLocData extends Struct { + @Uint8() + external int type; + @Uint8() + external int command; + + @Uint8() + external int isValid; + + @Uint32() + external int n; +} + +base class HdopLocData extends Struct { + @Uint8() + external int type; + @Uint8() + external int command; + + @Uint8() + external int isValid; + + @Double() + external double hdop; +} + +// c-structs end + +class Maps extends JuBox { + const Maps({super.key}); + + @override + State createState() => _MapsState(); +} + +class _MapsState extends State with TickerProviderStateMixin { + USerial serial = getSerial(); + late Timer timer; + + double _lat = 51.1667, _long = 10.4500; + int _state = 0; + bool _wasVaild = false; + final _mapController = MapController(); + + @override + void initState() { + super.initState(); + SensorReader.listen(serialListen); + timer = Timer.periodic(const Duration(seconds: 1), (_) async { + serial.sprintln("2300", system: true); + }); + } + + @override + void dispose() { + SensorReader.removeListen(serialListen); + timer.cancel(); + super.dispose(); + } + + void serialListen(int type, int command, Pointer ptr) { + if (type != 0x23 || command != 0x00) return; + var loc = ptr as Pointer; + setState(() { + _lat = loc.ref.lat; + _long = loc.ref.lng; + _state = loc.ref.isValid; + //_mapController.move(LatLng(_lat, _long), _wasVaild ? 17 : 6); + _animatedMapMove( + _mapController, this, LatLng(_lat, _long), _wasVaild ? 17 : 6); + if (_state & 1 > 0) _wasVaild = true; + }); + } + + @override + Widget build(BuildContext context) => FlutterMap( + options: MapOptions( + center: LatLng(_lat, _long), + zoom: _wasVaild ? 17 : 6, + maxZoom: _wasVaild ? 17 : 6, + minZoom: _wasVaild ? 17 : 6, + interactiveFlags: InteractiveFlag.none, + onTap: (TapPosition pos, LatLng ll) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const SerialDetailPage()), + ); + }), + mapController: _mapController, + children: [ + TileLayer( + urlTemplate: 'https://tile.openstreetmap.de/{z}/{x}/{y}.png', + userAgentPackageName: 'de.jusax.ju_rc_app', + ), + MarkerLayer( + markers: [ + Marker( + point: LatLng(_lat, _long), + width: 100, + height: 100, + builder: (context) => Icon( + switch (_state) { + 3 => Icons.location_on, + 2 => Icons.location_off, + 1 => Icons.location_on_outlined, + int() => Icons.location_off_outlined, + }, + color: Theme.of(context).colorScheme.primary, + ), + ), + ], + ), + ], + ); +} + +class SerialDetailPage extends StatefulWidget { + const SerialDetailPage({super.key}); + + @override + State createState() => _SerialDetailPageState(); +} + +class _SerialDetailPageState extends State + with TickerProviderStateMixin { + USerial serial = getSerial(); + late Timer timer; + final _mapController = MapController(); + + double _lat = 51.1667, _long = 10.4500; + int _state = 0; + + double _mps = -1, _course = -1, _alt = -1, _hdop = -1; + bool _mpsState = false, + _courseState = false, + _altState = false, + _satNState = false, + _hdopState = false; + int _satN = -1; + bool _follow = true; + + @override + void initState() { + super.initState(); + timer = Timer.periodic(const Duration(seconds: 2), (_) async { + serial.sprintln("2303", system: true); + sleep(const Duration(milliseconds: 200)); + serial.sprintln("2304", system: true); + sleep(const Duration(milliseconds: 200)); + serial.sprintln("2305", system: true); + sleep(const Duration(milliseconds: 200)); + serial.sprintln("2306", system: true); + sleep(const Duration(milliseconds: 200)); + serial.sprintln("2307", system: true); + }); + SensorReader.listen(serialListen); + } + + @override + void dispose() { + SensorReader.removeListen(serialListen); + timer.cancel(); + super.dispose(); + } + + void serialListen(int type, int command, Pointer ptr) { + if (type != 0x23) return; + switch (command) { + case 0: + { + var loc = ptr as Pointer; + setState(() { + _lat = loc.ref.lat; + _long = loc.ref.lng; + _state = loc.ref.isValid; + if (_follow) { + _animatedMapMove(_mapController, this, LatLng(_lat, _long), + _mapController.zoom, + currentZoom: true); + } + }); + } + break; + case 3: + { + var loc = ptr as Pointer; + setState(() { + _mps = loc.ref.speed; + _mpsState = loc.ref.isValid == 3; + }); + } + break; + case 4: + { + var loc = ptr as Pointer; + setState(() { + _course = loc.ref.course; + _courseState = loc.ref.isValid == 3; + }); + } + break; + case 5: + { + var loc = ptr as Pointer; + setState(() { + _alt = loc.ref.alt; + _altState = loc.ref.isValid == 3; + }); + } + break; + case 6: + { + var loc = ptr as Pointer; + setState(() { + _satN = loc.ref.n; + _satNState = loc.ref.isValid == 3; + }); + } + break; + case 7: + { + var loc = ptr as Pointer; + setState(() { + _hdop = loc.ref.hdop; + _hdopState = loc.ref.isValid == 3; + }); + } + break; + case 0xff: + {} + break; + } + } + + int shade() { + return MediaQuery.of(context).platformBrightness == Brightness.light + ? 100 + : 800; + } + + @override + Widget build(BuildContext context) => Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: const Text( + "Maps", + style: TextStyle( + fontSize: 16, + ), + ), + toolbarHeight: 40, + ), + floatingActionButton: FloatingActionButton( + onPressed: () { + setState(() { + if (!_follow) _mapController.move(LatLng(_lat, _long), 17); + _follow = !_follow; + }); + }, + child: Icon(_follow ? Icons.near_me : Icons.near_me_outlined), + ), + body: Column(children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Speed: ${_mps.toStringAsPrecision(3)}m/s", + style: TextStyle( + backgroundColor: + _mpsState ? Colors.green[shade()] : Colors.red[shade()]), + ), + const VerticalDivider(width: 5), + Text("Course: ${_course.toStringAsFixed(0)}°", + style: TextStyle( + backgroundColor: _courseState + ? Colors.green[shade()] + : Colors.red[shade()])), + const VerticalDivider(width: 5), + Text("Alt: ${_alt.toStringAsPrecision(4)}m", + style: TextStyle( + backgroundColor: _altState + ? Colors.green[shade()] + : Colors.red[shade()])), + const VerticalDivider(width: 5), + Text("Sats: $_satN", + style: TextStyle( + backgroundColor: _satNState + ? Colors.green[shade()] + : Colors.red[shade()])), + const VerticalDivider(width: 5), + Text("Hdop: ${_hdop.toStringAsPrecision(2)}", + style: TextStyle( + backgroundColor: _hdopState + ? Colors.green[shade()] + : Colors.red[shade()])), + ], + ), + Expanded( + child: FlutterMap( + mapController: _mapController, + options: MapOptions( + center: const LatLng(51.1667, 10.4500), + zoom: 6, + maxZoom: 18, + interactiveFlags: InteractiveFlag.drag | + InteractiveFlag.flingAnimation | + InteractiveFlag.pinchMove | + InteractiveFlag.pinchZoom | + InteractiveFlag.doubleTapZoom, + onPositionChanged: (MapPosition pos, bool user) { + if (user) { + setState(() { + _follow = false; + }); + } + }), + children: [ + TileLayer( + urlTemplate: 'https://tile.openstreetmap.de/{z}/{x}/{y}.png', + //urlTemplate: 'https://sgx.geodatenzentrum.de/wmts_basemapde/tile/1.0.0/de_basemapde_web_raster_farbe/default/GLOBAL_WEBMERCATOR/{z}/{y}/{x}.png', + userAgentPackageName: 'de.jusax.ju_rc_app', + ), + MarkerLayer( + markers: [ + Marker( + point: LatLng(_lat, _long), + width: 100, + height: 100, + builder: (context) => Transform.rotate( + angle: _course * + (pi / 180), // Convert degrees to radians + child: Icon( + switch (_state) { + 3 => Icons.navigation, + 2 => Icons.location_off, + 1 => Icons.navigation_outlined, + int() => Icons.location_off_outlined, + }, + color: Theme.of(context).colorScheme.primary, + ), + )), + ], + ), + ], + )) + ])); +} + +// animated Map from Map lib provider + +void _animatedMapMove(MapController mapController, TickerProvider ticker, + LatLng destLocation, double destZoom, + {bool currentZoom = false}) { + const startedId = 'AnimatedMapController#MoveStarted'; + const inProgressId = 'AnimatedMapController#MoveInProgress'; + const finishedId = 'AnimatedMapController#MoveFinished'; + // Create some tweens. These serve to split up the transition from one location to another. + // In our case, we want to split the transition be our current map center and the destination. + final camera = mapController; + final latTween = + Tween(begin: camera.center.latitude, end: destLocation.latitude); + final lngTween = Tween( + begin: camera.center.longitude, end: destLocation.longitude); + final zoomTween = Tween(begin: camera.zoom, end: destZoom); + + // Create a animation controller that has a duration and a TickerProvider. + final controller = AnimationController( + duration: const Duration(milliseconds: 500), vsync: ticker); + // The animation determines what path the animation will take. You can try different Curves values, although I found + // fastOutSlowIn to be my favorite. + final Animation animation = + CurvedAnimation(parent: controller, curve: Curves.fastOutSlowIn); + + // Note this method of encoding the target destination is a workaround. + // When proper animated movement is supported (see #1263) we should be able + // to detect an appropriate animated movement event which contains the + // target zoom/center. + final startIdWithTarget = + '$startedId#${destLocation.latitude},${destLocation.longitude},$destZoom'; + bool hasTriggeredMove = false; + + controller.addListener(() { + final String id; + if (animation.value == 1.0) { + id = finishedId; + } else if (!hasTriggeredMove) { + id = startIdWithTarget; + } else { + id = inProgressId; + } + hasTriggeredMove |= mapController.move( + LatLng(latTween.evaluate(animation), lngTween.evaluate(animation)), + currentZoom ? mapController.zoom : zoomTween.evaluate(animation), + id: id, + ); + }); + + animation.addStatusListener((status) { + if (status == AnimationStatus.completed) { + controller.dispose(); + } else if (status == AnimationStatus.dismissed) { + controller.dispose(); + } + }); + + controller.forward(); +} diff --git a/lib/lib/sensors.dart b/lib/lib/sensors.dart new file mode 100644 index 0000000..fe3ea6a --- /dev/null +++ b/lib/lib/sensors.dart @@ -0,0 +1,51 @@ +import 'package:ju_rc_app/lib/boxes.dart'; +import 'package:ju_rc_app/lib/serial.dart'; +import 'package:convert/convert.dart'; +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; + +base class ArrayCStruct extends Struct { + @Array(32) + external Array buffer; +} + +class SensorReader { + static USerial serial = getSerial(); + + static final List)> _callback = []; + + static void _serialListen(String line, SerialCommand? cmdo) { + if (cmdo == null) return; + var cmd = cmdo; + if (cmd.command != "rfSystemSensor") return; + if (cmd.arg == "lost") print("lost"); + if (["noconn", "lost", "send"].contains(cmd.arg)) return; + final bytes = hex.decode(cmd.arg); + final Pointer buffer = calloc(); + for (int i = 0; i < bytes.length && i < 32; i++) { + buffer.ref.buffer[i] = bytes[i]; + } + for (var fnk in _callback) { + fnk(buffer.ref.buffer[0], buffer.ref.buffer[1], buffer); + } + calloc.free(buffer); + } + + static bool _active = false; + + static void listen(Function(int, int, Pointer) callback) { + _callback.add(callback); + if (!_active) { + _active = true; + serial.listen(_serialListen); + } + } + + static void removeListen(Function(int, int, Pointer) callback) { + _callback.remove(callback); + if (_callback.isEmpty) { + _active = false; + serial.removeListen(_serialListen); + } + } +} diff --git a/lib/lib/serial.dart b/lib/lib/serial.dart index 63a1570..461cc44 100644 --- a/lib/lib/serial.dart +++ b/lib/lib/serial.dart @@ -50,7 +50,7 @@ abstract class USerial { } LimitedList<(SerialMessageType, DateTime, String)> lines = - LimitedList(524288); + LimitedList(1024); final List _callback = []; void _receive(String line) { lines.add((SerialMessageType.received, DateTime.now(), line)); diff --git a/lib/main.dart b/lib/main.dart index 5964297..270dcde 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,9 @@ import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:ju_rc_app/boxes/compass.dart'; import 'package:ju_rc_app/boxes/console.dart'; +import 'package:ju_rc_app/boxes/maps.dart'; import 'package:ju_rc_app/boxes/state.dart'; import 'package:ju_rc_app/connector.dart'; import 'package:ju_rc_app/lib/boxes.dart'; @@ -45,7 +47,12 @@ class MyHomePage extends StatefulWidget { class _MyHomePageState extends State { ConnectionBar conBar = ConnectionBar(); - static List boxes = [const SerialBox(), const ControllerState()]; + static List boxes = [ + const ControllerState(), + const Maps(), + const Compass(), + const SerialBox(), + ]; @override void initState() { diff --git a/pubspec.lock b/pubspec.lock index 58eff46..d77a540 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -74,7 +74,7 @@ packages: source: hosted version: "1.18.0" convert: - dependency: transitive + dependency: "direct main" description: name: convert sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" @@ -114,7 +114,7 @@ packages: source: hosted version: "1.3.1" ffi: - dependency: transitive + dependency: "direct main" description: name: ffi sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" @@ -150,6 +150,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + flutter_map: + dependency: "direct main" + description: + name: flutter_map + sha256: "5286f72f87deb132daa1489442d6cc46e986fc105cb727d9ae1b602b35b1d1f3" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: "8c5d68a82add3ca76d792f058b186a0599414f279f00ece4830b9b231b570338" + url: "https://pub.dev" + source: hosted + version: "2.0.7" flutter_test: dependency: "direct dev" description: flutter @@ -216,6 +232,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.8.1" + latlong2: + dependency: "direct main" + description: + name: latlong2 + sha256: "18712164760cee655bc790122b0fd8f3d5b3c36da2cb7bf94b68a197fbb0811b" + url: "https://pub.dev" + source: hosted + version: "0.9.0" libserialport: dependency: transitive description: @@ -232,6 +256,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + lists: + dependency: transitive + description: + name: lists + sha256: "4ca5c19ae4350de036a7e996cdd1ee39c93ac0a2b840f4915459b7d0a7d4ab27" + url: "https://pub.dev" + source: hosted + version: "1.0.1" matcher: dependency: transitive description: @@ -256,6 +288,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + mgrs_dart: + dependency: transitive + description: + name: mgrs_dart + sha256: fb89ae62f05fa0bb90f70c31fc870bcbcfd516c843fb554452ab3396f78586f7 + url: "https://pub.dev" + source: hosted + version: "2.0.0" path: dependency: transitive description: @@ -264,6 +304,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.3" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf + url: "https://pub.dev" + source: hosted + version: "1.0.1" petitparser: dependency: transitive description: @@ -296,14 +344,30 @@ packages: url: "https://pub.dev" source: hosted version: "3.7.3" + polylabel: + dependency: transitive + description: + name: polylabel + sha256: "41b9099afb2aa6c1730bdd8a0fab1400d287694ec7615dd8516935fa3144214b" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + proj4dart: + dependency: transitive + description: + name: proj4dart + sha256: c8a659ac9b6864aa47c171e78d41bbe6f5e1d7bd790a5814249e6b68bc44324e + url: "https://pub.dev" + source: hosted + version: "2.1.0" rive: dependency: "direct main" description: name: rive - sha256: "5fbb92f9f880cddbb9181342dc24099eee323ca43339e2c8e1ae6fad85df915e" + sha256: f2117a96a189758bc79bf7933865625c7a44a420ae537d2a8f6c492900136a71 url: "https://pub.dev" source: hosted - version: "0.11.16" + version: "0.11.17" rive_common: dependency: transitive description: @@ -373,6 +437,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + unicode: + dependency: transitive + description: + name: unicode + sha256: "0f69e46593d65245774d4f17125c6084d2c20b4e473a983f6e21b7d7762218f1" + url: "https://pub.dev" + source: hosted + version: "0.3.1" usb_serial: dependency: "direct main" description: @@ -381,6 +453,30 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.1" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: "670f6e07aca990b4a2bcdc08a784193c4ccdd1932620244c3a86bb72a0eac67f" + url: "https://pub.dev" + source: hosted + version: "1.1.7" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: "7451721781d967db9933b63f5733b1c4533022c0ba373a01bdd79d1a5457f69f" + url: "https://pub.dev" + source: hosted + version: "1.1.7" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: "80a13c613c8bde758b1464a1755a7b3a8f2b6cec61fbf0f5a53c94c30f03ba2e" + url: "https://pub.dev" + source: hosted + version: "1.1.7" vector_math: dependency: transitive description: @@ -397,6 +493,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.4-beta" + wkt_parser: + dependency: transitive + description: + name: wkt_parser + sha256: "8a555fc60de3116c00aad67891bcab20f81a958e4219cc106e3c037aa3937f13" + url: "https://pub.dev" + source: hosted + version: "2.0.0" xml: dependency: transitive description: @@ -415,4 +519,4 @@ packages: version: "3.1.2" sdks: dart: ">=3.1.0-185.0.dev <4.0.0" - flutter: ">=3.0.0" + flutter: ">=3.10.0" diff --git a/pubspec.yaml b/pubspec.yaml index 7a3ccb6..b2dafdb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,6 +39,11 @@ dependencies: usb_serial: ^0.5.1 rive: ^0.11.16 intl: ^0.18.1 + ffi: ^2.1.0 + convert: ^3.1.1 + flutter_map: ^5.0.0 + latlong2: ^0.9.0 + flutter_svg: ^2.0.7 dev_dependencies: flutter_test: @@ -74,6 +79,9 @@ flutter_launcher_icons: # The following section is specific to Flutter packages. flutter: + assets: + - assets/compass.svg + - assets/needle.svg # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in