diff --git a/lib/main.dart b/lib/main.dart index 1488a68..1bdd6c2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,7 +6,7 @@ import 'transport_helper.dart'; void main() async { await dotenv.load(); const String dbName = "oeffimasterTransportDb"; - initDB(dbName); + DbHelper.initDb(dbName); runApp(const MyApp()); } @@ -145,4 +145,4 @@ class _MyHomePageState extends State { ), ); } -} \ No newline at end of file +} diff --git a/lib/transport_helper.dart b/lib/transport_helper.dart index 522c0b0..431d3a0 100644 --- a/lib/transport_helper.dart +++ b/lib/transport_helper.dart @@ -1,48 +1,151 @@ import 'package:sqflite/sqflite.dart'; +// import './state_helper.dart'; +// import './db_helper.dart'; -void initDB(dbName) async { - var databasesPath = await getDatabasesPath(); - var path = databasesPath + dbName; - var db = await openDatabase(path); - // var db = await openDatabase('/var/db/oeffimaster/transport_db.db'); +// TODO: get access to DB - // TODO: Make sure the directory exists +class DbHelper { + static late Database db; + static String dbName = ""; - // TODO: future optimisation: batch transactions + // DbHelper({ + // required this.db, + // required this.dbName, + // }); - // TODO: create tables for all classes - // - [] route - // - [] segment - // - [] station - // - [] vehicle - // - [] line - // - [] departure + // 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 - factory Route.fromJson(Map json) => Route( - segments: json["LegList"]["Leg"].map((leg) => Segment.fromJson(leg)), - duration: - json["LegList"]["Leg"][-1]["Origin"]["time"] - - json["LegList"]["Leg"][0]["Origin"]["time"], - ); + // 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; + } - // TODO: do we need route in the database???? // what beyond and ID is needed? - void initRouteTable(Database database) async { - database.execute("CREATE TABLE IF NOT EXISTS route();"); + // 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; @@ -57,15 +160,21 @@ class Segment { required this.vehicle, }); - factory Segment.fromJson(Map json) => Segment( - startPoint: Station.fromJson(json["Origin"]), - endPoint: Station.fromJson(json["Destination"]), - startTime: json["Origin"]["rtTime"], - endTime: json["Destination"]["rtTime"], - vehicle: Vehicle.fromJson(json["Product"]), - ); + 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; + } - void initSegmentTable(Database database) async { + static void initSegmentTable() async { + Database database = DbHelper.db; database.execute(""" CREATE TABLE IF NOT EXISTS segment( startpoint TEXT, @@ -74,26 +183,38 @@ class Segment { endtime TEXT, vehicle INTEGER, FOREIGN KEY(startpoint) REFERENCES station(stationid), - FOREIGN KEY(endpoint) 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 stationId; + final String id; final String name; List? transportLines; // could be normalized away Station({ - required this.stationId, + required this.id, required this.name, required this.transportLines, //TODO: make extra db table }); factory Station.fromJson(Map json) => Station( - stationId: json["id"] as String? ?? json["extId"] as String, + id: json["id"] as String? ?? json["extId"] as String, name: json["name"], transportLines: json.containsKey("productAtStop") ? json["productAtStop"].map( @@ -102,9 +223,10 @@ class Station { : const [], ); - void initStationTable(Database database) async { + static void initStationTable() async { + Database database = DbHelper.db; database.execute("""CREATE TABLE IF NOT EXISTS station( - stationid TEXT PRIMARY KEY, + id TEXT PRIMARY KEY, name TEXT ); """); @@ -112,24 +234,25 @@ class Station { } class Vehicle { - final String vehicleId; + final String id; final Line line; Vehicle({ - required this.vehicleId, + 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( - vehicleId: json["matchId"], + id: json["matchId"], line: Line.fromJson(json), ); - void initVehicleTable(Database database) async { + static void initVehicleTable() async { + Database database = DbHelper.db; database.execute("""CREATE TABLE IF NOT EXISTS vehicle( - vehicleid TEXT PRIMARY KEY, + id TEXT PRIMARY KEY, line TEXT, FOREIGN KEY(line) REFERENCES line(lineid) ); @@ -138,7 +261,7 @@ class Vehicle { } class Line { - final String lineId; + final String id; Station? destination; Station? direction; final VehicleType mode; @@ -147,7 +270,7 @@ class Line { Color? bgColor; Line({ - required this.lineId, + required this.id, required this.name, required this.mode, this.fgColor, @@ -163,7 +286,7 @@ class Line { ]; factory Line.fromJson(Map json) => Line( - lineId: json["lineId"], + id: json["lineId"], name: json["name"], mode: _vehicleTypeAsCatCode[int.parse(json["catCode"])], fgColor: @@ -173,7 +296,8 @@ class Line { Color.fromJson(json["backgroundColor"]) as Color? ?? Color(r: 50, g: 50, b: 50), // fallback ); - void initLineTable(Database database) async { + static void initLineTable() async { + Database database = DbHelper.db; database.execute("""CREATE TABLE IF NOT EXISTS line( lineid TEXT PRIMARY KEY, destination TEXT, @@ -199,14 +323,15 @@ class Departure { required this.departureTime, }); - void initDepartureTable(Database database) async { + static void initDepartureTable() async { + Database database = DbHelper.db; database.execute("""CREATE TABLE IF NOT EXISTS departure( - departureid INTEGER, + id INTEGER, vehicle TEXT, station TEXT, - departuretime TEXT - FOREIGN KEY(vehicle) REFERENCES vehicle(vehicleid), - FOREIGN KEY(station) REFERENCES station(stationid) + departuretime TEXT, + FOREIGN KEY(vehicle) REFERENCES vehicle(id), + FOREIGN KEY(station) REFERENCES station(id) ); """); }