import 'package:sqflite/sqflite.dart'; // import './state_helper.dart'; // import './db_helper.dart'; // TODO: get access to DB class DbHelper { static late Database db; static String dbName = ""; // DbHelper({ // required this.db, // required this.dbName, // }); // remnant from when DB was not static // factory DbHelper._openDb(Database database, String dbName) => DbHelper( // db: database, // dbName: dbName, // ); static void initDb(String dbName) async { // var db = await openDatabase('/var/db/oeffimaster/transport_db.db'); var databasesPath = await getDatabasesPath(); var path = databasesPath + dbName; // var newDb = await openDatabase(path); // var dbHelper = DbHelper._openDb(newDb, dbName); db = await openDatabase(path); dbName = dbName; // TODO: Make sure the directory exists // TODO: future optimisation: batch transactions Route.initRouteTable(); Segment.initSegmentTable(); Station.initStationTable(); Vehicle.initVehicleTable(); Departure.initDepartureTable(); Route.updateHighestId(); } } class Route { static int highestId = 0; static const String tableName = "route"; int id; List segments = List.empty(); Duration duration = Duration(minutes: 0); Route({ required this.id, required this.segments, required this.duration, }); // from trip API request // TODO: how to connect this best with the database for read/write? factory Route.fromJson(Map json) { Route newRoute = Route( id: highestId, segments: json["LegList"]["Leg"].map((leg) => Segment.fromJson(leg)), duration: json["LegList"]["Leg"][-1]["Origin"]["time"] - json["LegList"]["Leg"][0]["Origin"]["time"], ); highestId += 1; return newRoute; } // rowids will give us new ids for every route-segment connection, but routeid // is needed as well, so how do we create these - query highest existing routeid? I think so // TODO: get highest routeid in route table (fast) static void initRouteTable() async { Database database = DbHelper.db; database.execute("""CREATE TABLE IF NOT EXISTS routesegments( id TEXT, segmentid INTEGER, segmentorder INTEGER, -- TODO: implement sorting FOREIGN KEY(segmentid) REFERENCES segment(id) ); """); } static void updateHighestId() async { Database database = DbHelper.db; int newHigh = await getHighestId(); highestId = newHigh + 1; } /* TODO: better separation between production db and test db * they share the highestId field regardless, which wont do * so is an attribut for database needed? */ /// parse the highest id from the database to continue with ongoing ids but /// create the db entry after the class, for now // -- this might be making it more complicated than it needs to be static Future getHighestId() async { Database database = DbHelper.db; try { var tmp = await database.query( "routesegments", columns: ["id"], distinct: true, orderBy: "id", whereArgs: ["id"], limit: 1, ); if (tmp.isEmpty) { return 0; } if (tmp[0].isEmpty) { return 0; } // entry "routeid" should definitely exist assert(tmp[0]["id"] != null); // Source - https://stackoverflow.com/a/52632172 // Posted by Günter Zöchbauer, modified by community. See post 'Timeline' for change history // Retrieved 2026-06-08, License - CC BY-SA 4.0 T? cast(x) => x is T ? x : null; // -- //doing this in two steps go deal with nullability String highestId = cast(tmp[0]["routeid"]) ?? "0"; return int.parse(highestId); } catch (e) { print('Error: Could not get id: $e'); // TODO: use logging } return 0; } void writeNewToTable(Route route) async { Database database = DbHelper.db; for (var i = 0; i < route.segments.length; i++) { database.insert("routesegments", { "id": route.id, "segmentid": route.segments[i].id, "segmentorder": i, }); } } } class Segment { final int id = 0; final Station startPoint; final Station endPoint; final DateTime startTime; final DateTime endTime; final Vehicle vehicle; Segment({ required this.startPoint, required this.endPoint, required this.startTime, required this.endTime, required this.vehicle, }); factory Segment.fromJson(Map json) { Database database = DbHelper.db; Segment tmp = Segment( startPoint: Station.fromJson(json["Origin"]), endPoint: Station.fromJson(json["Destination"]), startTime: json["Origin"]["rtTime"], endTime: json["Destination"]["rtTime"], vehicle: Vehicle.fromJson(json["Product"]), ); tmp.writeNewToTable(); // TODO: we need to write to db and get the ID!!!!!!! return tmp; } static void initSegmentTable() async { Database database = DbHelper.db; database.execute(""" CREATE TABLE IF NOT EXISTS segment( startpoint TEXT, endpoint TEXT, starttime TEXT, endtime TEXT, vehicle INTEGER, FOREIGN KEY(startpoint) REFERENCES station(stationid), FOREIGN KEY(endpoint) REFERENCES station(stationid), FOREIGN KEY(vehicle) REFERENCES vehicle(vehicleid) ); """); } Future writeNewToTable() async { Database database = DbHelper.db; int id = await database.insert("segment", { "startpoint": startPoint.id, "endpoint": startPoint.id, "startTime": startTime, "endTime": endTime, "vehicle": vehicle.id, }); return id; } } class Station { final String id; final String name; List? transportLines; // could be normalized away Station({ required this.id, required this.name, required this.transportLines, //TODO: make extra db table }); factory Station.fromJson(Map json) => Station( id: json["id"] as String? ?? json["extId"] as String, name: json["name"], transportLines: json.containsKey("productAtStop") ? json["productAtStop"].map( (jsonLine) => Line.fromJson(jsonLine as Map), ) : const [], ); static void initStationTable() async { Database database = DbHelper.db; database.execute("""CREATE TABLE IF NOT EXISTS station( id TEXT PRIMARY KEY, name TEXT ); """); } } class Vehicle { final String id; final Line line; Vehicle({ required this.id, required this.line, }); // we can use the "product" class from within any JSON response // (within hafas API, on the appropriate level) factory Vehicle.fromJson(Map json) => Vehicle( id: json["matchId"], line: Line.fromJson(json), ); static void initVehicleTable() async { Database database = DbHelper.db; database.execute("""CREATE TABLE IF NOT EXISTS vehicle( id TEXT PRIMARY KEY, line TEXT, FOREIGN KEY(line) REFERENCES line(lineid) ); """); } } class Line { final String id; Station? destination; Station? direction; final VehicleType mode; final String name; Color? fgColor; Color? bgColor; Line({ required this.id, required this.name, required this.mode, this.fgColor, this.bgColor, }); // TODO: EXPLAIN!!!!!!!!!!!! static const _vehicleTypeAsCatCode = [ VehicleType.metro, VehicleType.subway, VehicleType.PLACEHOLDER, VehicleType.bus, ]; factory Line.fromJson(Map json) => Line( id: json["lineId"], name: json["name"], mode: _vehicleTypeAsCatCode[int.parse(json["catCode"])], fgColor: Color.fromJson(json["foregroundColor"]) as Color? ?? Color(r: 255, g: 255, b: 255), // fallback bgColor: Color.fromJson(json["backgroundColor"]) as Color? ?? Color(r: 50, g: 50, b: 50), // fallback ); static void initLineTable() async { Database database = DbHelper.db; database.execute("""CREATE TABLE IF NOT EXISTS line( lineid TEXT PRIMARY KEY, destination TEXT, direction TEXT, mode TEXT, name TEXT, fgcolor TEXT, bgcolor TEXT ); """); } } class Departure { String? id; final Vehicle vehicle; final Station station; final DateTime departureTime; Departure({ required this.vehicle, required this.station, required this.departureTime, }); static void initDepartureTable() async { Database database = DbHelper.db; database.execute("""CREATE TABLE IF NOT EXISTS departure( id INTEGER, vehicle TEXT, station TEXT, departuretime TEXT, FOREIGN KEY(vehicle) REFERENCES vehicle(id), FOREIGN KEY(station) REFERENCES station(id) ); """); } } class Color { final int r; final int g; final int b; Color({ required this.r, required this.g, required this.b, }); factory Color.fromJson(Map json) => Color( r: json["r"], g: json["g"], b: json["b"], ); } enum VehicleType { bus, tram, metro, subway, regionalTrain, PLACEHOLDER, }