diff --git a/lib/main.dart b/lib/main.dart index 709247d..102bd2c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -222,7 +222,12 @@ class StationList extends StatelessWidget { class ChooseDeparturePage extends StatefulWidget { final Station startStation; - const ChooseDeparturePage({super.key, required this.startStation}); + final int? journeyId; + const ChooseDeparturePage({ + super.key, + this.journeyId, + required this.startStation, + }); @override State createState() => _ChooseDeparturePageState(); @@ -231,6 +236,7 @@ class ChooseDeparturePage extends StatefulWidget { class _ChooseDeparturePageState extends State { List departureList = []; Departure? _selectedDeparture; + void loadDepartures(Station startStation) async { departureList = await ApiHandler.departures(startStation.id); setState(() {}); @@ -281,7 +287,10 @@ class _ChooseDeparturePageState extends State { Navigator.push( context, MaterialPageRoute( - builder: (context) => ChooseArrivalPage(departure: departure), + builder: (context) => ChooseArrivalPage( + departure: departure, + journeyId: widget.journeyId, + ), ), ); } @@ -313,8 +322,11 @@ class DepartureList extends StatelessWidget { class ChooseArrivalPage extends StatefulWidget { final Departure departure; + final int? journeyId; + const ChooseArrivalPage({ super.key, + this.journeyId, required this.departure, }); @@ -323,11 +335,11 @@ class ChooseArrivalPage extends StatefulWidget { } class _ChooseArrivalPageState extends State { - List departureList = []; + List arrivalList = []; Departure? _selectedDeparture; void loadArrivals(String ref) async { - departureList = await ApiHandler.tripDetail( + arrivalList = await ApiHandler.tripDetail( ref, startStationId: widget.departure.stationId, ); @@ -352,7 +364,8 @@ class _ChooseArrivalPageState extends State { child: SizedBox( height: 400, child: ArrivalList( - departureList: departureList, + departure: widget.departure, + arrivalList: arrivalList, onTapped: _handleArrivalTapped, ), ), @@ -372,26 +385,46 @@ class _ChooseArrivalPageState extends State { ); } - void _handleArrivalTapped(Departure departure) async { + void _handleArrivalTapped(Departure departure, Departure arrival) async { // setState(() { // _selectedDeparture = departure; // }); - Station startStation = await Station.dbGet(departure.stationId); + departure.dbInsert; + arrival.dbInsert; + Segment newSegment = await Segment.fromDepartures(departure, arrival); + Station startStation = await Station.dbGet(arrival.stationId); + int journeyId; + if (widget.journeyId == null) { + Journey journey = await Journey.fromSegment(newSegment); + journeyId = journey.id as int; + } else { + Segment segment = await newSegment; + Journey.dbAppendJourneySegment( + widget.journeyId as int, + segment.id as int, + ); + journeyId = widget.journeyId as int; + } Navigator.push( context, MaterialPageRoute( - builder: (context) => ChooseDeparturePage(startStation: startStation), + builder: (context) => ChooseDeparturePage( + startStation: startStation, + journeyId: journeyId, + ), ), ); } } class ArrivalList extends StatelessWidget { - final List departureList; - final ValueChanged onTapped; + final Departure departure; + final List arrivalList; + final Function onTapped; const ArrivalList({ - required this.departureList, + required this.departure, + required this.arrivalList, required this.onTapped, }); @@ -399,11 +432,11 @@ class ArrivalList extends StatelessWidget { Widget build(BuildContext context) { return ListView( children: [ - for (Departure departure in departureList) + for (Departure arrival in arrivalList) ListTile( - title: Text(departure.stationName), - subtitle: Text(departure.arrivalTime.toString()), - onTap: () => onTapped(departure), + title: Text(arrival.stationName), + subtitle: Text(arrival.arrivalTime.toString()), + onTap: () => onTapped(departure, arrival), ), ], ); diff --git a/lib/transport_helper.dart b/lib/transport_helper.dart index acda9b7..0fe9d58 100644 --- a/lib/transport_helper.dart +++ b/lib/transport_helper.dart @@ -32,7 +32,8 @@ class DbHelper { // TODO: future optimisation: batch transactions - Route.initRouteTable(); + Journey.initJourneyTable(); + Journey.initJourneySegmentsTable(); Segment.initSegmentTable(); Station.initStationTable(); Station.initStationLinesTable(); @@ -47,15 +48,16 @@ class DbHelper { /// 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 { +class Journey { + static const String tableName = "journey"; // TODO: add last_viewed, last_modified and such int? id; String? startStation; String? endStation; - List segments = List.empty(); + List segments = []; Duration duration = Duration(minutes: 0); - Route({ + Journey({ this.id, this.startStation, this.endStation, @@ -63,38 +65,38 @@ class Route { required this.duration, }); - /// toMap for Route but it's just route ids for now - Map _toMapRoute() { + /// toMap for Journey but it's just route ids for now + Map _toMapJourney() { return {"id": id, "startstation": startStation, "endstation": endStation}; } /// toMap for routesegments relational table - List> _toMapRouteSegments() { + List> _toMapJourneySegments() { //asMap() apparently enumerates - List> tmp = List.empty(); + List> tmp = []; for (final (segmentIdx, segment) in segments.indexed) { tmp.add({ "route": id, - "segmentid": segment.id, + "segment": segment.id, "segmentorder": segmentIdx, }); } return tmp; } - factory Route.empty(Station start) { - return Route( + factory Journey.empty(Station start) { + return Journey( startStation: start.name, segments: List.empty(), duration: Duration(minutes: 0), ); } - factory Route.fromMap( + factory Journey.fromMap( Map routeMaps, List segmentMaps, ) { - return Route( + return Journey( id: routeMaps["id"] as int, startStation: routeMaps["startstation"] as String?, endStation: routeMaps["endstation"] as String?, @@ -105,15 +107,29 @@ class Route { // from trip API request // TODO: how to connect this best with the database for read/write? - factory Route.fromJson(Map json) { - Route newRoute = Route( + factory Journey.fromJson(Map json) { + Journey newJourney = Journey( segments: json["LegList"]["Leg"].map((leg) => Segment.fromJson(leg)), duration: json["LegList"]["Leg"][-1]["Origin"]["time"] - json["LegList"]["Leg"][0]["Origin"]["time"], ); - newRoute._dbInsert(); - return newRoute; + newJourney._dbInsert(); + newJourney.sync(); + return newJourney; + } + + static Future fromSegment(Segment startSeg) async { + Journey newJourney = Journey( + segments: [startSeg], + duration: startSeg.end.arrivalTime.difference( + startSeg.start.departureTime, + ), + ); + newJourney.id = await newJourney._dbInsert(); + newJourney._dbInsertLastJourneySegment(); + newJourney.sync(); + return newJourney; } /// recalculate some technically redundant data for fast and easy access @@ -136,17 +152,17 @@ class Route { void addSegment(Segment newSeg) { segments.add(newSeg); - _dbInsertLastRouteSegment(); + _dbInsertLastJourneySegment(); } void removeLastSegment() { - _dbDeleteLastRouteSegment(); + _dbDeleteLastJourneySegment(); segments.removeLast(); } void removeSegmentsTillEnd(int idx) { segments.removeRange(idx, segments.length - 1); - _dbDeleteTrailingRouteSegments(idx); + _dbDeleteTrailingJourneySegments(idx); } void _removeSegment(int idx) { @@ -160,7 +176,7 @@ class Route { segments[idx - 1].end.arrivalTime, )) { segments[idx] = replacement; - _dbUpdateRouteSegments(); + _dbUpdateJourneySegments(); } else if (idx >= segments.length) { addSegment(replacement); } else { @@ -168,9 +184,9 @@ class Route { } } - static void initRouteTable() async { + static void initJourneyTable() async { Database database = DbHelper.db; - database.execute("""CREATE TABLE IF NOT EXISTS route( + database.execute("""CREATE TABLE IF NOT EXISTS '$tableName'( id INTEGER PRIMARY KEY AUTOINCREMENT, startstation TEXT, endstation TEXT, @@ -180,7 +196,7 @@ class Route { """); } - static void initRouteSegmentsTable() async { + static void initJourneySegmentsTable() async { Database database = DbHelper.db; String segment = Segment.tableName; database.execute("""CREATE TABLE IF NOT EXISTS routesegments( @@ -188,26 +204,26 @@ class Route { route INTEGER, segment INTEGER, segmentorder INTEGER, -- TODO: implement sorting - FOREIGN KEY(route) REFERENCES route(id), + FOREIGN KEY('$tableName') REFERENCES '$tableName'(id), FOREIGN KEY(segment) REFERENCES '$segment'(id) ); """); } - static Future dbGet(int id) async { + static Future dbGet(int id) async { Database database = DbHelper.db; - Future> routeSegments = _dbGetRouteSegments(id); + Future> routeSegments = _dbGetJourneySegments(id); var result = await database.query( - "route", + tableName, where: "id = ?", whereArgs: [id], ); assert(result.length < 2, "found more than 1 route for id $id"); - Route route = Route.fromMap(result.first, await routeSegments); + Journey route = Journey.fromMap(result.first, await routeSegments); return route; } - static Future> _dbGetRouteSegments( + static Future> _dbGetJourneySegments( int routeId, ) async { Database database = DbHelper.db; @@ -220,24 +236,40 @@ class Route { return tmp.map((x) => Segment.fromMap(x)).toList(); } - void _dbInsert() async { + Future _dbInsert() async { Database database = DbHelper.db; - id = await database.insert( - "route", - _toMapRoute(), + Future id_tmp = database.insert( + tableName, + _toMapJourney(), conflictAlgorithm: ConflictAlgorithm.replace, ); + id = await id_tmp; + return id_tmp; + } + + static void dbAppendJourneySegment(int routeId, int segmentid) { + Database database = DbHelper.db; + // generate segmentorder to be 0 if no segment exists for route, otherwise + // take highest segmentorder+1 for given route + database.rawInsert( + """INSERT INTO routesegments(route, segment, segmentorder) VALUES + ($routeId, $segmentid, CASE WHEN EXISTS ( + SELECT segmentorder FROM routesegments r WHERE r.route=$routeId) THEN ( + SELECT segmentorder AS so FROM routesegments r WHERE r.route=$routeId + ORDER BY so DESC LIMIT 1) +1 ELSE 0 END); + """, + ); } - void _dbInsertLastRouteSegment() async { + void _dbInsertLastJourneySegment() async { Database database = DbHelper.db; - var lastRouteSegment = _toMapRouteSegments().last; - database.insert("routesegments", lastRouteSegment); + var lastJourneySegment = _toMapJourneySegments().last; + database.insert("routesegments", lastJourneySegment); } - void _dbInsertRouteSegments() async { + void _dbInsertJourneySegments() async { Database database = DbHelper.db; - var routeSegments = _toMapRouteSegments(); + var routeSegments = _toMapJourneySegments(); for (var i = 0; i < routeSegments.length; i++) { database.insert( "routesegments", @@ -249,17 +281,17 @@ class Route { void _dbUpdate() async { Database database = DbHelper.db; id = await database.update( - "route", - _toMapRoute(), + tableName, + _toMapJourney(), where: "id = ?", whereArgs: [id], conflictAlgorithm: ConflictAlgorithm.replace, ); } - void _dbUpdateRouteSegments() async { + void _dbUpdateJourneySegments() async { Database database = DbHelper.db; - var routeSegments = _toMapRouteSegments(); + var routeSegments = _toMapJourneySegments(); for (var i = 0; i < routeSegments.length; i++) { database.update( "routesegments", @@ -273,14 +305,14 @@ class Route { void dbDelete() async { Database database = DbHelper.db; int result = await database.delete( - "route", + tableName, where: "id = ?", whereArgs: [id], ); assert(result == 0); } - void _dbDeleteRouteSegment(int idx) { + void _dbDeleteJourneySegment(int idx) { Database database = DbHelper.db; database.delete( "routesegments", @@ -289,7 +321,7 @@ class Route { ); } - void _dbDeleteLastRouteSegment() { + void _dbDeleteLastJourneySegment() { Database database = DbHelper.db; database.delete( "routesegments", @@ -298,7 +330,7 @@ class Route { ); } - void _dbDeleteTrailingRouteSegments(int position) { + void _dbDeleteTrailingJourneySegments(int position) { Database database = DbHelper.db; if (position <= segments.length) { database.delete( @@ -368,7 +400,7 @@ class Segment { return tmp; } - Future fromDepartures(Departure start, Departure end) async { + static Future fromDepartures(Departure start, Departure end) async { Segment tmp = Segment( ref: start.ref, start: start, @@ -1074,4 +1106,4 @@ enum VehicleType { subway, regionalTrain, PLACEHOLDER, -} \ No newline at end of file +}