From a2cefb161b305b31336cf967e5e7dcc1909d0d11 Mon Sep 17 00:00:00 2001 From: Doug Borg Date: Wed, 18 Feb 2026 09:24:53 -0700 Subject: [PATCH] Use generated Dart client for deflock-router API Replace manual JSON construction and parsing in routing_service.dart with generated types (Coordinate, DirectionsRequest, RouteGeometry) from the OpenAPI spec. The deflock_router_client package lives in the FlockHopper repo alongside the canonical spec and TS client. Co-Authored-By: Claude Opus 4.6 --- lib/services/routing_service.dart | 89 ++++++++++++++----------------- pubspec.lock | 7 +++ pubspec.yaml | 2 + 3 files changed, 48 insertions(+), 50 deletions(-) diff --git a/lib/services/routing_service.dart b/lib/services/routing_service.dart index 3163f496..4f3ddb38 100644 --- a/lib/services/routing_service.dart +++ b/lib/services/routing_service.dart @@ -3,6 +3,7 @@ import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'package:latlong2/latlong.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:deflock_router_client/api.dart' as router; import '../app_state.dart'; import '../dev_config.dart'; @@ -12,13 +13,13 @@ class RouteResult { final List waypoints; final double distanceMeters; final double durationSeconds; - + const RouteResult({ required this.waypoints, required this.distanceMeters, required this.durationSeconds, }); - + @override String toString() { return 'RouteResult(waypoints: ${waypoints.length}, distance: ${(distanceMeters/1000).toStringAsFixed(1)}km, duration: ${(durationSeconds/60).toStringAsFixed(1)}min)'; @@ -44,40 +45,29 @@ class RoutingService { final avoidanceDistance = prefs.getInt('navigation_avoidance_distance') ?? 250; final enabledProfiles = AppState.instance.enabledProfiles.map((p) { - final full = p.toJson(); - final tags = Map.from(full['tags'] as Map); + final tags = Map.from(p.tags); tags.removeWhere((key, value) => value.isEmpty); - return { - 'id': full['id'], - 'name': full['name'], - 'tags': tags, - }; + return router.NodeProfile(id: p.id, name: p.name, tags: tags); }).toList(); - + + final request = router.DirectionsRequest( + start: router.Coordinate(latitude: start.latitude, longitude: start.longitude), + end: router.Coordinate(latitude: end.latitude, longitude: end.longitude), + avoidanceDistance: avoidanceDistance, + enabledProfiles: enabledProfiles, + showExclusionZone: false, + ); + final uri = Uri.parse(_baseUrl); - final params = { - 'start': { - 'longitude': start.longitude, - 'latitude': start.latitude - }, - 'end': { - 'longitude': end.longitude, - 'latitude': end.latitude - }, - 'avoidance_distance': avoidanceDistance, - 'enabled_profiles': enabledProfiles, - 'show_exclusion_zone': false, // for debugging: if true, returns a GeoJSON Feature MultiPolygon showing what areas are avoided in calculating the route - }; - - debugPrint('[RoutingService] alprwatch request: $uri $params'); - + debugPrint('[RoutingService] alprwatch request: $uri ${request.toJson()}'); + try { final response = await _client.post( uri, headers: { 'Content-Type': 'application/json' }, - body: json.encode(params) + body: json.encode(request.toJson()) ).timeout(kNavigationRoutingTimeout); if (response.statusCode != 200) { @@ -93,42 +83,41 @@ class RoutingService { } throw RoutingException('HTTP ${response.statusCode}: ${response.reasonPhrase}'); } - + final data = json.decode(response.body) as Map; debugPrint('[RoutingService] alprwatch response data: $data'); - + // Check alprwatch response status final ok = data['ok'] as bool? ?? false; if ( ! ok ) { final message = data['error'] as String? ?? 'Unknown routing error'; throw RoutingException('alprwatch error: $message'); } - - final route = data['result']['route'] as Map?; - if (route == null) { + + final resultData = data['result'] as Map?; + if (resultData == null) { + throw RoutingException('No result in response'); + } + + final directionsResult = router.DirectionsResult.fromJson(resultData); + if (directionsResult == null) { throw RoutingException('No route found between these points'); } - - final waypoints = (route['coordinates'] as List?) - ?.map((inner) { - final pair = inner as List; - if (pair.length != 2) return null; - final lng = (pair[0] as num).toDouble(); - final lat = (pair[1] as num).toDouble(); - return LatLng(lat, lng); - }).whereType().toList() ?? []; - final distance = (route['distance'] as num?)?.toDouble() ?? 0.0; - final duration = (route['duration'] as num?)?.toDouble() ?? 0.0; - + + final routeGeometry = directionsResult.route; + final waypoints = routeGeometry.coordinates.map((pair) { + return LatLng(pair[1], pair[0]); // [lon, lat] -> LatLng(lat, lon) + }).toList(); + final result = RouteResult( waypoints: waypoints, - distanceMeters: distance, - durationSeconds: duration, + distanceMeters: routeGeometry.distance, + durationSeconds: routeGeometry.duration, ); - + debugPrint('[RoutingService] Route calculated: $result'); return result; - + } catch (e) { debugPrint('[RoutingService] Route calculation failed: $e'); if (e is RoutingException) { @@ -142,9 +131,9 @@ class RoutingService { class RoutingException implements Exception { final String message; - + const RoutingException(this.message); - + @override String toString() => 'RoutingException: $message'; } diff --git a/pubspec.lock b/pubspec.lock index 27e167b0..0094f8aa 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -161,6 +161,13 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.11" + deflock_router_client: + dependency: "direct main" + description: + path: "../FlockHopper/packages/deflock_router_client" + relative: true + source: path + version: "1.0.0" desktop_webview_window: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 6523ba46..b4bd95b7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -38,6 +38,8 @@ dependencies: package_info_plus: ^8.0.0 csv: ^6.0.0 collection: ^1.18.0 + deflock_router_client: + path: ../FlockHopper/packages/deflock_router_client dev_dependencies: flutter_test: