feat: implement route creating from segment and in UI

This commit is contained in:
fbachus
2026-07-29 13:50:00 +02:00
parent 6dc3e9569a
commit 3cad483518
2 changed files with 129 additions and 64 deletions
+48 -15
View File
@@ -222,7 +222,12 @@ class StationList extends StatelessWidget {
class ChooseDeparturePage extends StatefulWidget { class ChooseDeparturePage extends StatefulWidget {
final Station startStation; final Station startStation;
const ChooseDeparturePage({super.key, required this.startStation}); final int? journeyId;
const ChooseDeparturePage({
super.key,
this.journeyId,
required this.startStation,
});
@override @override
State<ChooseDeparturePage> createState() => _ChooseDeparturePageState(); State<ChooseDeparturePage> createState() => _ChooseDeparturePageState();
@@ -231,6 +236,7 @@ class ChooseDeparturePage extends StatefulWidget {
class _ChooseDeparturePageState extends State<ChooseDeparturePage> { class _ChooseDeparturePageState extends State<ChooseDeparturePage> {
List<Departure> departureList = []; List<Departure> departureList = [];
Departure? _selectedDeparture; Departure? _selectedDeparture;
void loadDepartures(Station startStation) async { void loadDepartures(Station startStation) async {
departureList = await ApiHandler.departures(startStation.id); departureList = await ApiHandler.departures(startStation.id);
setState(() {}); setState(() {});
@@ -281,7 +287,10 @@ class _ChooseDeparturePageState extends State<ChooseDeparturePage> {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( 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 { class ChooseArrivalPage extends StatefulWidget {
final Departure departure; final Departure departure;
final int? journeyId;
const ChooseArrivalPage({ const ChooseArrivalPage({
super.key, super.key,
this.journeyId,
required this.departure, required this.departure,
}); });
@@ -323,11 +335,11 @@ class ChooseArrivalPage extends StatefulWidget {
} }
class _ChooseArrivalPageState extends State<ChooseArrivalPage> { class _ChooseArrivalPageState extends State<ChooseArrivalPage> {
List<Departure> departureList = []; List<Departure> arrivalList = [];
Departure? _selectedDeparture; Departure? _selectedDeparture;
void loadArrivals(String ref) async { void loadArrivals(String ref) async {
departureList = await ApiHandler.tripDetail( arrivalList = await ApiHandler.tripDetail(
ref, ref,
startStationId: widget.departure.stationId, startStationId: widget.departure.stationId,
); );
@@ -352,7 +364,8 @@ class _ChooseArrivalPageState extends State<ChooseArrivalPage> {
child: SizedBox( child: SizedBox(
height: 400, height: 400,
child: ArrivalList( child: ArrivalList(
departureList: departureList, departure: widget.departure,
arrivalList: arrivalList,
onTapped: _handleArrivalTapped, onTapped: _handleArrivalTapped,
), ),
), ),
@@ -372,26 +385,46 @@ class _ChooseArrivalPageState extends State<ChooseArrivalPage> {
); );
} }
void _handleArrivalTapped(Departure departure) async { void _handleArrivalTapped(Departure departure, Departure arrival) async {
// setState(() { // setState(() {
// _selectedDeparture = departure; // _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( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => ChooseDeparturePage(startStation: startStation), builder: (context) => ChooseDeparturePage(
startStation: startStation,
journeyId: journeyId,
),
), ),
); );
} }
} }
class ArrivalList extends StatelessWidget { class ArrivalList extends StatelessWidget {
final List<Departure> departureList; final Departure departure;
final ValueChanged<Departure> onTapped; final List<Departure> arrivalList;
final Function onTapped;
const ArrivalList({ const ArrivalList({
required this.departureList, required this.departure,
required this.arrivalList,
required this.onTapped, required this.onTapped,
}); });
@@ -399,11 +432,11 @@ class ArrivalList extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListView( return ListView(
children: [ children: [
for (Departure departure in departureList) for (Departure arrival in arrivalList)
ListTile( ListTile(
title: Text(departure.stationName), title: Text(arrival.stationName),
subtitle: Text(departure.arrivalTime.toString()), subtitle: Text(arrival.arrivalTime.toString()),
onTap: () => onTapped(departure), onTap: () => onTapped(departure, arrival),
), ),
], ],
); );
+80 -48
View File
@@ -32,7 +32,8 @@ class DbHelper {
// TODO: future optimisation: batch transactions // TODO: future optimisation: batch transactions
Route.initRouteTable(); Journey.initJourneyTable();
Journey.initJourneySegmentsTable();
Segment.initSegmentTable(); Segment.initSegmentTable();
Station.initStationTable(); Station.initStationTable();
Station.initStationLinesTable(); Station.initStationLinesTable();
@@ -47,15 +48,16 @@ class DbHelper {
/// m-n/ 1-n relations between routes and segments - the segments contain the most /// 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 /// relevant info, while routesegments stores which route the segments belong to
/// and in which order they make up a route /// 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 // TODO: add last_viewed, last_modified and such
int? id; int? id;
String? startStation; String? startStation;
String? endStation; String? endStation;
List<Segment> segments = List.empty(); List<Segment> segments = <Segment>[];
Duration duration = Duration(minutes: 0); Duration duration = Duration(minutes: 0);
Route({ Journey({
this.id, this.id,
this.startStation, this.startStation,
this.endStation, this.endStation,
@@ -63,38 +65,38 @@ class Route {
required this.duration, required this.duration,
}); });
/// toMap for Route but it's just route ids for now /// toMap for Journey but it's just route ids for now
Map<String, Object?> _toMapRoute() { Map<String, Object?> _toMapJourney() {
return {"id": id, "startstation": startStation, "endstation": endStation}; return {"id": id, "startstation": startStation, "endstation": endStation};
} }
/// toMap for routesegments relational table /// toMap for routesegments relational table
List<Map<String, Object?>> _toMapRouteSegments() { List<Map<String, Object?>> _toMapJourneySegments() {
//asMap() apparently enumerates //asMap() apparently enumerates
List<Map<String, Object?>> tmp = List.empty(); List<Map<String, Object?>> tmp = [];
for (final (segmentIdx, segment) in segments.indexed) { for (final (segmentIdx, segment) in segments.indexed) {
tmp.add({ tmp.add({
"route": id, "route": id,
"segmentid": segment.id, "segment": segment.id,
"segmentorder": segmentIdx, "segmentorder": segmentIdx,
}); });
} }
return tmp; return tmp;
} }
factory Route.empty(Station start) { factory Journey.empty(Station start) {
return Route( return Journey(
startStation: start.name, startStation: start.name,
segments: List.empty(), segments: List.empty(),
duration: Duration(minutes: 0), duration: Duration(minutes: 0),
); );
} }
factory Route.fromMap( factory Journey.fromMap(
Map<String, Object?> routeMaps, Map<String, Object?> routeMaps,
List<Segment> segmentMaps, List<Segment> segmentMaps,
) { ) {
return Route( return Journey(
id: routeMaps["id"] as int, id: routeMaps["id"] as int,
startStation: routeMaps["startstation"] as String?, startStation: routeMaps["startstation"] as String?,
endStation: routeMaps["endstation"] as String?, endStation: routeMaps["endstation"] as String?,
@@ -105,15 +107,29 @@ class Route {
// from trip API request // from trip API request
// TODO: how to connect this best with the database for read/write? // TODO: how to connect this best with the database for read/write?
factory Route.fromJson(Map<String, dynamic> json) { factory Journey.fromJson(Map<String, dynamic> json) {
Route newRoute = Route( Journey newJourney = Journey(
segments: json["LegList"]["Leg"].map((leg) => Segment.fromJson(leg)), segments: json["LegList"]["Leg"].map((leg) => Segment.fromJson(leg)),
duration: duration:
json["LegList"]["Leg"][-1]["Origin"]["time"] - json["LegList"]["Leg"][-1]["Origin"]["time"] -
json["LegList"]["Leg"][0]["Origin"]["time"], json["LegList"]["Leg"][0]["Origin"]["time"],
); );
newRoute._dbInsert(); newJourney._dbInsert();
return newRoute; newJourney.sync();
return newJourney;
}
static Future<Journey> 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 /// recalculate some technically redundant data for fast and easy access
@@ -136,17 +152,17 @@ class Route {
void addSegment(Segment newSeg) { void addSegment(Segment newSeg) {
segments.add(newSeg); segments.add(newSeg);
_dbInsertLastRouteSegment(); _dbInsertLastJourneySegment();
} }
void removeLastSegment() { void removeLastSegment() {
_dbDeleteLastRouteSegment(); _dbDeleteLastJourneySegment();
segments.removeLast(); segments.removeLast();
} }
void removeSegmentsTillEnd(int idx) { void removeSegmentsTillEnd(int idx) {
segments.removeRange(idx, segments.length - 1); segments.removeRange(idx, segments.length - 1);
_dbDeleteTrailingRouteSegments(idx); _dbDeleteTrailingJourneySegments(idx);
} }
void _removeSegment(int idx) { void _removeSegment(int idx) {
@@ -160,7 +176,7 @@ class Route {
segments[idx - 1].end.arrivalTime, segments[idx - 1].end.arrivalTime,
)) { )) {
segments[idx] = replacement; segments[idx] = replacement;
_dbUpdateRouteSegments(); _dbUpdateJourneySegments();
} else if (idx >= segments.length) { } else if (idx >= segments.length) {
addSegment(replacement); addSegment(replacement);
} else { } else {
@@ -168,9 +184,9 @@ class Route {
} }
} }
static void initRouteTable() async { static void initJourneyTable() async {
Database database = DbHelper.db; 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, id INTEGER PRIMARY KEY AUTOINCREMENT,
startstation TEXT, startstation TEXT,
endstation TEXT, endstation TEXT,
@@ -180,7 +196,7 @@ class Route {
"""); """);
} }
static void initRouteSegmentsTable() async { static void initJourneySegmentsTable() async {
Database database = DbHelper.db; Database database = DbHelper.db;
String segment = Segment.tableName; String segment = Segment.tableName;
database.execute("""CREATE TABLE IF NOT EXISTS routesegments( database.execute("""CREATE TABLE IF NOT EXISTS routesegments(
@@ -188,26 +204,26 @@ class Route {
route INTEGER, route INTEGER,
segment INTEGER, segment INTEGER,
segmentorder INTEGER, -- TODO: implement sorting segmentorder INTEGER, -- TODO: implement sorting
FOREIGN KEY(route) REFERENCES route(id), FOREIGN KEY('$tableName') REFERENCES '$tableName'(id),
FOREIGN KEY(segment) REFERENCES '$segment'(id) FOREIGN KEY(segment) REFERENCES '$segment'(id)
); );
"""); """);
} }
static Future<Route> dbGet(int id) async { static Future<Journey> dbGet(int id) async {
Database database = DbHelper.db; Database database = DbHelper.db;
Future<List<Segment>> routeSegments = _dbGetRouteSegments(id); Future<List<Segment>> routeSegments = _dbGetJourneySegments(id);
var result = await database.query( var result = await database.query(
"route", tableName,
where: "id = ?", where: "id = ?",
whereArgs: [id], whereArgs: [id],
); );
assert(result.length < 2, "found more than 1 route for id $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; return route;
} }
static Future<List<Segment>> _dbGetRouteSegments( static Future<List<Segment>> _dbGetJourneySegments(
int routeId, int routeId,
) async { ) async {
Database database = DbHelper.db; Database database = DbHelper.db;
@@ -220,24 +236,40 @@ class Route {
return tmp.map((x) => Segment.fromMap(x)).toList(); return tmp.map((x) => Segment.fromMap(x)).toList();
} }
void _dbInsert() async { Future<int> _dbInsert() async {
Database database = DbHelper.db; Database database = DbHelper.db;
id = await database.insert( Future<int> id_tmp = database.insert(
"route", tableName,
_toMapRoute(), _toMapJourney(),
conflictAlgorithm: ConflictAlgorithm.replace, 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; Database database = DbHelper.db;
var lastRouteSegment = _toMapRouteSegments().last; var lastJourneySegment = _toMapJourneySegments().last;
database.insert("routesegments", lastRouteSegment); database.insert("routesegments", lastJourneySegment);
} }
void _dbInsertRouteSegments() async { void _dbInsertJourneySegments() async {
Database database = DbHelper.db; Database database = DbHelper.db;
var routeSegments = _toMapRouteSegments(); var routeSegments = _toMapJourneySegments();
for (var i = 0; i < routeSegments.length; i++) { for (var i = 0; i < routeSegments.length; i++) {
database.insert( database.insert(
"routesegments", "routesegments",
@@ -249,17 +281,17 @@ class Route {
void _dbUpdate() async { void _dbUpdate() async {
Database database = DbHelper.db; Database database = DbHelper.db;
id = await database.update( id = await database.update(
"route", tableName,
_toMapRoute(), _toMapJourney(),
where: "id = ?", where: "id = ?",
whereArgs: [id], whereArgs: [id],
conflictAlgorithm: ConflictAlgorithm.replace, conflictAlgorithm: ConflictAlgorithm.replace,
); );
} }
void _dbUpdateRouteSegments() async { void _dbUpdateJourneySegments() async {
Database database = DbHelper.db; Database database = DbHelper.db;
var routeSegments = _toMapRouteSegments(); var routeSegments = _toMapJourneySegments();
for (var i = 0; i < routeSegments.length; i++) { for (var i = 0; i < routeSegments.length; i++) {
database.update( database.update(
"routesegments", "routesegments",
@@ -273,14 +305,14 @@ class Route {
void dbDelete() async { void dbDelete() async {
Database database = DbHelper.db; Database database = DbHelper.db;
int result = await database.delete( int result = await database.delete(
"route", tableName,
where: "id = ?", where: "id = ?",
whereArgs: [id], whereArgs: [id],
); );
assert(result == 0); assert(result == 0);
} }
void _dbDeleteRouteSegment(int idx) { void _dbDeleteJourneySegment(int idx) {
Database database = DbHelper.db; Database database = DbHelper.db;
database.delete( database.delete(
"routesegments", "routesegments",
@@ -289,7 +321,7 @@ class Route {
); );
} }
void _dbDeleteLastRouteSegment() { void _dbDeleteLastJourneySegment() {
Database database = DbHelper.db; Database database = DbHelper.db;
database.delete( database.delete(
"routesegments", "routesegments",
@@ -298,7 +330,7 @@ class Route {
); );
} }
void _dbDeleteTrailingRouteSegments(int position) { void _dbDeleteTrailingJourneySegments(int position) {
Database database = DbHelper.db; Database database = DbHelper.db;
if (position <= segments.length) { if (position <= segments.length) {
database.delete( database.delete(
@@ -368,7 +400,7 @@ class Segment {
return tmp; return tmp;
} }
Future<Segment> fromDepartures(Departure start, Departure end) async { static Future<Segment> fromDepartures(Departure start, Departure end) async {
Segment tmp = Segment( Segment tmp = Segment(
ref: start.ref, ref: start.ref,
start: start, start: start,