import 'package:http/http.dart' as http; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'dart:core'; import 'dart:convert'; import 'transport_helper.dart'; String timeOf(DateTime fullDT) { // doing it this way so we always get two digits on the hour String full = fullDT.toString(); List splits = full.split(" ")[1].split(":"); String time = '${splits[0]}:${splits[1]}'; return time; } class ApiHandler { // TODO: Split next line for better Error Handling static final String baseUrl = dotenv.get("VBB_API_URL"); static final String accessID = dotenv.get("VBB_API_KEY"); //static Future> findStations(String locationName) async { static Future> findStations( String locationName, { int? maxStations = 5, }) async { String urlPath = '/api/fahrinfo/latest/location.name'; Uri url = Uri.https(baseUrl, urlPath, { 'accessId': accessID, 'input': locationName, 'maxNo': maxStations.toString(), 'type': 'S', // Station/Stops only }); //print(url); var jsonResponse = await http.get( url, headers: {'Accept': 'application/json'}, ); final httpPackageJson = json.decode(jsonResponse.body) as Map; //print(httpPackageJson["stopLocationOrCoordLocation"]); List stationList = httpPackageJson["stopLocationOrCoordLocation"] .map( (x) => Station.fromJson(x["StopLocation"]), ) .toList() .cast(); return stationList; } static Future> nearbyStations( String latitude, String longitude, { int? maxStations = 5, }) async { String urlPath = '/api/fahrinfo/latest/location.nearbystops'; Uri url = Uri.https(baseUrl, urlPath, { 'accessId': accessID, 'originCoordLat': latitude, 'originCoordLong': longitude, 'maxNo': maxStations.toString(), 'type': 'S', // Station/Stops only }); var jsonResponse = await http.get( url, headers: {'Accept': 'application/json'}, ); final httpPackageJson = json.decode(jsonResponse.body) as Map; final stationList = httpPackageJson["stopLocationOrCoordLocation"] .map( (x) => Station.fromJson(x["StopLocation"]), ) .toList() .cast(); return stationList; } static Future> departures( String stationId, { int? maxDepartures = -1, DateTime? time, int? duration = 0, }) async { String urlPath = '/api/fahrinfo/latest/departureBoard'; String stationString = stationId.replaceAll(' ', '%20'); // reform for URL Uri url = Uri.https(baseUrl, urlPath, { 'accessId': accessID, 'id': stationString, 'time': timeOf( time ?? DateTime.now(), ), //time from which departures will be shown 'duration': duration.toString(), //interval time in minutes 'maxJourneys': maxDepartures.toString(), //'type': 'S', // Station/Stops only }); var jsonResponse = await http.get( url, headers: {'Accept': 'application/json'}, ); final httpPackageJson = json.decode(jsonResponse.body) as Map; // try { List departureList = httpPackageJson["Departure"] .map( (x) => Departure.fromJson(x), ) .toList() .cast(); return departureList; // } catch (e) { // // TODO: logging // // empty response // print('Error: $e'); // // print( // // 'Response for Station-id "$stationString" contains no field "Departure": $httpPackageJson', // // ); // return []; // } } static Future> departuresAggregate( List stationList, { int? maxDepartures = -1, int? duration = -1, }) async { String urlPath = '/api/fahrinfo/latest/multiDepartureBoard'; String stationListString = stationList .reduce((value, element) => '$value|$element') .replaceAll(' ', '%20'); // reform for URL Uri url = Uri.https(baseUrl, urlPath, { 'accessId': accessID, 'id': stationListString, ////'time': /current time //time from which departures will be shown 'duration': duration.toString(), //interval time in minutes 'maxJourneys': maxDepartures.toString(), 'type': 'S', // Station/Stops only }); var jsonResponse = await http.get( url, headers: {'Accept': 'application/json'}, ); final httpPackageJson = json.decode(jsonResponse.body) as Map; List departureList = httpPackageJson["Departure"] .map( (x) => Departure.fromJson(x), ) .toList() .cast(); return departureList; } static Future> trip( String originId, String destinationId, { String? via, String? viaID, String? date, String? time, int? maxChange, int? minChangeTime, int? maxChangeTime, bool? arrivalTime, // int? products, // which transport modi to use - ignore for now // String rtMode, // wherether to use real time data // String trainFilter, }) async { String urlPath = '/api/fahrinfo/latest/trip'; String startId = originId.replaceAll(' ', '%20'); // reform for URL String endId = destinationId.replaceAll(' ', '%20'); // reform for URL Uri url = Uri.https(baseUrl, urlPath, { 'accessId': accessID, 'originId': startId, 'destId': endId, ////'time': /current time //time from which departures will be shown 'time': time.toString(), //interval time in minutes 'maxChange': maxChange.toString(), 'type': 'S', // Station/Stops only }); var jsonResponse = await http.get( url, headers: {'Accept': 'application/json'}, ); final httpPackageJson = json.decode(jsonResponse.body) as Map; // TODO: here lurk problems - do we get a list of list of segments? can't cast it– // what do we want from this? List SegmentList = httpPackageJson["Trip"] .map( (x) => x["LegList"]["Leg"].map((y) => Segment.fromJson(y)), ) .toList(); return SegmentList; } static Future> tripDetail( String id, { String? date, String? fromId, int? fromIdx, String? toId, int? toIdx, String? startStationId, }) async { String urlPath = '/api/fahrinfo/latest/journeyDetail'; Uri url = Uri.https(baseUrl, urlPath, { 'accessId': accessID, 'id': id, 'fromId': fromId, 'toId': toId, }); var jsonResponse = await http.get( url, headers: {'Accept': 'application/json'}, ); final httpPackageJson = json.decode(jsonResponse.body) as Map; Iterable DepartureList = httpPackageJson["Stops"]["Stop"] .map( (x) => Departure.fromJson( // needed because I'm too lazy to write a second mostly identical // constructor and a wrapper _expandDeparturesJson(httpPackageJson, x, [ "ref", "Product", "Directions", ]), ), ) .cast(); // preferably this happens always? if (startStationId != null) { // show Stations after the one we are at return DepartureList.skipWhile( (x) => x.stationId != startStationId, ).skip(1).toList(); } return DepartureList.toList().cast(); } // workaround for the fact that json from tripDetail and DepartureBoard are // quite different from each other in their structure... but I don't wish to // build a second constructor >:( // so this function duplicates topLevel Maps into a lower level to be used in // mapping functions and such static Map _expandDeparturesJson( Map topLevelJson, Map subLevelJson, List topLevelKeys, ) { for (final String entry in topLevelKeys) { subLevelJson[entry] = topLevelJson[entry]; } return subLevelJson; } }