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(); } } /// this class covers two database tables: "route" and "routesegments" /// while route will for now only hold an id, routesegments will store the /// m-n/ 1-n relations between routes and segments - the segments contain the most /// relevant info, while routesegments stores which route the segments belong to /// and in which order they make up a route class Route { // TODO: add last_viewed, last_modified and such int? id; List segments = List.empty(); Duration duration = Duration(minutes: 0); Route({ this.id, required this.segments, required this.duration, }); /// toMap for Route but it's just route ids for now Map toMapRoute() { return {"id": id}; } /// toMap for routesegments relational table List> toMapRouteSegments() { //asMap() apparently enumerates List> tmp = List.empty(); for (final (segmentIdx, segment) in segments.indexed) { tmp.add({ "route": id, "segmentid": segment.id, "segmentorder": segmentIdx, }); } return tmp; } // from trip API request // TODO: how to connect this best with the database for read/write? factory Route.fromJson(Map json) { Route newRoute = Route( segments: json["LegList"]["Leg"].map((leg) => Segment.fromJson(leg)), duration: json["LegList"]["Leg"][-1]["Origin"]["time"] - json["LegList"]["Leg"][0]["Origin"]["time"], ); newRoute.dbInsertRoute(); return newRoute; } static void initRouteTable() async { Database database = DbHelper.db; database.execute("""CREATE TABLE IF NOT EXISTS route( id INTEGER PRIMARY KEY AUTOINCREMENT, ); """); } static void initRouteSegmentsTable() async { Database database = DbHelper.db; String segment = Segment.tableName; database.execute("""CREATE TABLE IF NOT EXISTS routesegments( id TEXT PRIMARY KEY AUTOINCREMENT, route INTEGER, segment INTEGER, segmentorder INTEGER, -- TODO: implement sorting FOREIGN KEY(route) REFERENCES route(id), FOREIGN KEY(segment) REFERENCES $segment(id) ); """); } void dbInsertRoute() async { Database database = DbHelper.db; id = await database.insert( "route", toMapRoute(), conflictAlgorithm: ConflictAlgorithm.replace, ); } void dbInsertRouteSegments() async { Database database = DbHelper.db; var routeSegments = toMapRouteSegments(); for (var i = 0; i < routeSegments.length; i++) { database.insert( "routesegments", routeSegments[i], ); } } void dbDelete() async { Database database = DbHelper.db; int result = await database.delete( "route", where: "id = ?", whereArgs: [id], ); assert(result == 0); } void dbDeleteLastRouteSegment() { Database database = DbHelper.db; database.delete( "routesegments", where: "route = ? AND segmentorder = ?", whereArgs: [id, segments.length - 1], // TODO: test the index ); } void dbDeleteTrailingRouteSegment(int position) { Database database = DbHelper.db; if (position <= segments.length) { database.delete( "routesegments", where: "route = ? AND segmentorder >= ?", whereArgs: [id, position], ); } } // TODO: delete leading segments and decrement following segments } class Segment { static const String tableName = "segment"; int? id; Station startPoint; Station endPoint; DateTime startTime; DateTime endTime; Vehicle vehicle; Segment({ this.id, // might throw errors - needs testing, I guess required this.startPoint, required this.endPoint, required this.startTime, required this.endTime, required this.vehicle, }); Map toMap() { return { "startPoint": startPoint.id, "endpoint": endPoint.id, "starttime": startTime, "endtime": endTime, "vehicle": vehicle.id, }; } 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.dbInsert(); return tmp; } static void initSegmentTable() async { Database database = DbHelper.db; String station = Station.tableName; String vehicle = Vehicle.tableName; database.execute(""" CREATE TABLE IF NOT EXISTS $tableName( 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) ); """); } void dbInsert() async { Database database = DbHelper.db; id = await database.insert(tableName, toMap()); } void dbDelete() { Database database = DbHelper.db; database.delete( tableName, where: "rowid = ?", whereArgs: [id], ); } } class Station { static const String tableName = "station"; String id; String name; List transportLines; // could be normalized away Station({ required this.id, required this.name, required this.transportLines, }); Map toMap() { return {"id": id, "name": name}; } List> toMapLines() { return transportLines .map( (line) => {"stationid": id, "lineid": line}, ) .toList(); } 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( stationid TEXT PRIMARY KEY, name TEXT ); """); } static void initStationLinesTable() async { Database database = DbHelper.db; database.execute("""CREATE TABLE IF NOT EXISTS stationlines( id INTEGER PRIMARY KEY AUTOINCREMENT, stationid TEXT, lineid TEXT, FOREIGN KEY (stationid) REFERENCES station(stationid), FOREIGN KEY (lineid) REFERENCES line(lineid) ); """); } void dbInsertStation() async { Database database = DbHelper.db; database.insert(tableName, toMap()); } void dbInsertStationLines() async { Database database = DbHelper.db; var stationLines = toMapLines(); for (var i = 0; i < stationLines.length; i++) { database.insert("stationlines", stationLines[i]); } } void dbDelete() async { Database database = DbHelper.db; database.delete(tableName, where: "stationid = ?", whereArgs: [id]); } /// remove all Lines connected to a Station - should be used together with some kind of reparsing. void dbDeleteStationLines() async { Database database = DbHelper.db; database.delete("stationlines", where: "stationid = ?", whereArgs: [id]); } } class Vehicle { static const String tableName = "vehicle"; String id; Line line; Vehicle({ required this.id, required this.line, }); Map toMap() { return { "id": id, "line": line.lineId, }; } // 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; String line = Line.tableName; database.execute("""CREATE TABLE IF NOT EXISTS $tableName( id TEXT PRIMARY KEY, line TEXT, FOREIGN KEY(line) REFERENCES $line(lineid) ); """); } void dbInsert() async { Database database = DbHelper.db; database.insert(tableName, toMap()); } void dbDelete() async { Database database = DbHelper.db; database.delete(tableName, where: "id = ?", whereArgs: [id]); } } class Line { static const String tableName = "line"; String lineId; Station? destination; Station? direction; VehicleType mode; String name; Color? fgColor; Color? bgColor; Line({ required this.lineId, required this.name, required this.mode, this.fgColor, this.bgColor, }); Map toMap() { return { "lineid": lineId, "destination": destination, "direction": direction, "mode": mode, "name": name, "fgcolor": fgColor, "bgcolor": bgColor, }; } // TODO: EXPLAIN!!!!!!!!!!!! static const _vehicleTypeAsCatCode = [ VehicleType.metro, VehicleType.subway, VehicleType.PLACEHOLDER, VehicleType.bus, ]; factory Line.fromJson(Map json) => Line( lineId: 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 $tableName( lineid TEXT PRIMARY KEY, destination TEXT, direction TEXT, mode TEXT, name TEXT, fgcolor TEXT, bgcolor TEXT ); """); } void dbInsert() async { Database database = DbHelper.db; database.insert(tableName, toMap()); } void dbDelete() async { Database database = DbHelper.db; database.delete(tableName, where: "lineid = ?", whereArgs: [lineId]); } } // TODO: add fromJson class Departure { static const String tableName = "departure"; int? id; Vehicle vehicle; Station station; DateTime departureTime; Departure({ this.id, required this.vehicle, required this.station, required this.departureTime, }); Map toMap() { return { "id": id, "vehicle": vehicle, "station": station, "departuretime": departureTime, }; } static void initDepartureTable() async { Database database = DbHelper.db; String vehicle = Vehicle.tableName; String station = Station.tableName; database.execute("""CREATE TABLE IF NOT EXISTS $tableName( id INTEGER, vehicle TEXT, station TEXT, departuretime TEXT, FOREIGN KEY(vehicle) REFERENCES $vehicle(id), FOREIGN KEY(station) REFERENCES $station(id) ); """); } void dbInsert() async { Database database = DbHelper.db; id = await database.insert(tableName, toMap()); } void dbDelete() async { Database database = DbHelper.db; database.delete(tableName, where: "id = ?", whereArgs: [id]); } } class Color { int r; int g; 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, }