Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: CI
on:
push:
branches-ignore:
- "tools/*"
pull_request:
jobs:
Compiles:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v6
- name: Flutter
uses: subosito/flutter-action@v2
with:
# latest stable version
channel: stable
- name: Write dummy firebase_options.dart for CI build
run: |
cat > lib/firebase_options.dart << 'EOF'
import 'package:firebase_core/firebase_core.dart';
class DefaultFirebaseOptions {
static FirebaseOptions get currentPlatform => const FirebaseOptions(
apiKey: 'fake-key-for-ci',
appId: 'fake-app-id',
messagingSenderId: 'fake-sender-id',
projectId: 'fake-project-id',
);
}
EOF
- name: Generate Client
run: |
rm -rf lib/backend
dart run build_runner clean
dart run swagger_parser
dart run build_runner build
- name: Analyze
run: |
flutter analyze --no-fatal-infos --no-fatal-warnings

2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ google-services*.json
GoogleService-Info.plist
# auto generated
lib/firebase_options.dart
lib/backend
spec.json

# Flutter repo-specific
/bin/cache/
Expand Down
146 changes: 22 additions & 124 deletions lib/bluebus_api.dart
Original file line number Diff line number Diff line change
@@ -1,143 +1,41 @@
import 'dart:convert';
import 'dart:math' as Math;
import 'package:flutter/cupertino.dart';
import 'package:dio/dio.dart';
import 'package:http/http.dart' as http;
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'constants.dart';
import 'models/bus_stop.dart';
import 'models/bus.dart';
import 'models/bus_route_line.dart';
import 'services/route_color_service.dart';
import 'utils/geometry.dart';

class BlueBusApi {
static const String baseUrl = BACKEND_URL;

// Fetch all routes and their polylines/stops
static Future<List<BusRouteLine>> fetchRoutes(Function(String route, String error) onError) async {
final response = await http.get(Uri.parse('$baseUrl/getAllRoutes'));
if (response.statusCode != 200) throw Exception('Failed to load routes');
final data = jsonDecode(response.body);
final routes = <BusRouteLine>[];
final routeJson = data['routes'] as Map<String, dynamic>;

await RouteColorService.initialize();

routeJson.forEach((routeId, subroutes) {
for (final subroute in subroutes) {
try {
final points = <LatLng>[];
final stops = <(int, BusStop)>[];

// Cast to list to be able to be able to get different elements
final pointList = subroute['pt'] as List;

for (int i = 0; i < pointList.length; i++) {
final point = pointList[i];
final isLast = i == pointList.length - 1; // bool to check if last
points.add(
LatLng(
point['lat']?.toDouble() ?? 0,
point['lon']?.toDouble() ?? 0,
),
);
if (point['typ'] == 'S') {
// get rotation of stop
double stopRotation;
if (isLast) {
// use the previous 2 points to calculate rotation
stopRotation = pointRotation(
pointList[i - 2]['lat']?.toDouble() ?? 0,
pointList[i - 2]['lon']?.toDouble() ?? 0,
pointList[i - 1]['lat']?.toDouble() ?? 0,
pointList[i - 1]['lon']?.toDouble() ?? 0,
);
} else {
// use the next 2 points to calculate rotation
stopRotation = pointRotation(
pointList[i + 1]['lat']?.toDouble() ?? 0,
pointList[i + 1]['lon']?.toDouble() ?? 0,
pointList[i + 2]['lat']?.toDouble() ?? 0,
pointList[i + 2]['lon']?.toDouble() ?? 0,
);
}
stops.add((i, BusStop.fromJson(point, routeId, stopRotation, false)));
}
}

// Get route color and image
final routeColor = RouteColorService.getRouteColor(routeId);
final routeImageUrl = RouteColorService.getRouteImageUrl(routeId);

routes.add(
BusRouteLine(
routeId: routeId,
points: points,
stops: stops,
color: routeColor,
imageUrl: routeImageUrl,
),
);

// Handle detour points if present
if (subroute.containsKey('dtrpt')) {
final detourPoints = <LatLng>[];
final detourStops = <(int, BusStop)>[];

// Cast to list to be able to be able to get different elements
final detourPointList = subroute['dtrpt'] as List;
try {
final routes = await backendClient.getApiV4AllMbusRoutes();
await RouteColorService.initialize();

for (int i = 0; i < detourPointList.length; i++) {
final point = detourPointList[i];
final isLast = i == detourPointList.length - 1; // bool to check if last
return routes
.map((r) {
final routeColor = RouteColorService.getRouteColor(r.routeId);
final routeImageUrl = RouteColorService.getRouteImageUrl(r.routeId);

detourPoints.add(
LatLng(
point['lat']?.toDouble() ?? 0,
point['lon']?.toDouble() ?? 0,
),
);
if (point['typ'] == 'S') {
// get rotation of stop
double stopRotation;
if (isLast) {
// use the previous 2 points to calculate rotation
stopRotation = pointRotation(
detourPointList[i - 2]['lat']?.toDouble() ?? 0,
detourPointList[i - 2]['lon']?.toDouble() ?? 0,
detourPointList[i - 1]['lat']?.toDouble() ?? 0,
detourPointList[i - 1]['lon']?.toDouble() ?? 0,
);

} else {
// use the next 2 points to calculate rotation
stopRotation = pointRotation(
detourPointList[i + 1]['lat']?.toDouble() ?? 0,
detourPointList[i + 1]['lon']?.toDouble() ?? 0,
detourPointList[i + 2]['lat']?.toDouble() ?? 0,
detourPointList[i + 2]['lon']?.toDouble() ?? 0,
);
}
detourStops.add((i, BusStop.fromJson(point, routeId, stopRotation, false)));
}
}
return BusRouteLine.fromBackend(r, routeColor, routeImageUrl);
})
.toList();
} on DioException catch (e) {
final res = e.response;

routes.add(
BusRouteLine(
routeId: routeId,
points: detourPoints,
stops: detourStops,
color: routeColor,
imageUrl: routeImageUrl,
),
);
}
} catch (e) {
onError(routeId, e.toString());
}
if (res != null && res.statusCode != 200) {
onError("", "Failed to load routes");
throw Exception('Failed to load routes');
}
});
return routes;
onError("", e.message ?? e.toString());
rethrow;
} catch (e) {
onError("", e.toString());
rethrow;
}
}

// Fetch all buses and their positions
Expand Down
10 changes: 10 additions & 0 deletions lib/constants.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'package:bluebus/backend/backend/backend_client.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

Expand Down Expand Up @@ -38,6 +40,13 @@ const String BACKEND_URL = String.fromEnvironment(
//const String BACKEND_URL = String.fromEnvironment('BACKEND_URL', defaultValue: 'http://35.3.198.105:3000/mbus/api/v3');
//const String BACKEND_URL = String.fromEnvironment('BACKEND_URL', defaultValue: 'http://35.2.102.249:3000/mbus/api/v3/');

// base url to use when for backend requests via openapi generated clients
const String backendOpenApiUrl = String.fromEnvironment(
'BACKEND_OPENAPI_URL'
);

final BackendClient backendClient = BackendClient(Dio(BaseOptions(baseUrl: backendOpenApiUrl)));

List<Map<String, String>> globalAvailableRoutes = [];

// Mapping from route code to full name
Expand All @@ -54,6 +63,7 @@ const Map<String, String> fallback_code_to_name = {

final _whitespacePattern = RegExp(r'\s+');

// KEEP THIS IN SYNC WITH THE CORRESPONDING FUNCTION IN THE BACKEND
String normalizeStopName(String rawStopName) {
// Remove random characters (add them to list if needed), collapse whitespace to a single space, and trim edges.
return rawStopName
Expand Down
12 changes: 12 additions & 0 deletions lib/models/bus_route_line.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'package:bluebus/models/lat_lng.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:flutter/material.dart';
import 'bus_stop.dart';
import 'package:bluebus/backend/export.dart' as backend;

class BusRouteLine {
final String routeId;
Expand All @@ -19,4 +21,14 @@ class BusRouteLine {
this.color,
this.imageUrl,
});

static BusRouteLine fromBackend(backend.BusRouteLine line, Color? color, String? imageUrl) {
return BusRouteLine(
routeId: line.routeId,
points: line.points.map(latLngFromBackend).toList(),
stops: line.stops.map((x) => (x.index, BusStop.fromBackend(x.stop))).toList(),
color: color,
imageUrl: imageUrl
);
}
}
16 changes: 9 additions & 7 deletions lib/models/bus_stop.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:google_maps_flutter/google_maps_flutter.dart';
import '../constants.dart';
import 'package:bluebus/backend/export.dart' as backend;

class BusStop {
final String id;
Expand All @@ -11,14 +12,15 @@ class BusStop {

BusStop({required this.id, required this.name, required this.location, required this.routeId, required this.rotation, required this.isRide});

factory BusStop.fromJson(Map<String, dynamic> json, String routeId, double rotation, bool isRide) {
factory BusStop.fromBackend(backend.BusStop x) {
return BusStop(
id: json['stpid'] ?? '',
name: normalizeStopName(json['stpnm'] ?? ''),
location: LatLng(json['lat']?.toDouble() ?? 0, json['lon']?.toDouble() ?? 0),
routeId: routeId,
rotation: rotation,
isRide: isRide
id: x.id,
name: x.name,
location: LatLng(x.location.lat.toDouble(),
x.location.lon.toDouble()),
routeId: x.routeId,
rotation: x.rotation.toDouble(),
isRide: x.isRide
);
}
}
Expand Down
Loading
Loading