diff --git a/lib/main.dart b/lib/main.dart index 1bdd6c2..39a1e07 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,9 +3,12 @@ import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'api_handler.dart'; import 'transport_helper.dart'; +import 'package:sqflite_common_ffi/sqflite_ffi.dart'; //for testing on desktop + void main() async { await dotenv.load(); const String dbName = "oeffimasterTransportDb"; + databaseFactory = databaseFactoryFfi; // for debugging and desktop test DbHelper.initDb(dbName); runApp(const MyApp()); } @@ -17,42 +20,21 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - title: 'Flutter Demo', + title: 'Oeffimaster', theme: ThemeData( - // This is the theme of your application. - // - // TRY THIS: Try running your application with "flutter run". You'll see - // the application has a purple toolbar. Then, without quitting the app, - // try changing the seedColor in the colorScheme below to Colors.green - // and then invoke "hot reload" (save your changes or press the "hot - // reload" button in a Flutter-supported IDE, or press "r" if you used - // the command line to start the app). - // - // Notice that the counter didn't reset back to zero; the application - // state is not lost during the reload. To reset the state, use hot - // restart instead. - // - // This works for code too, not just values: Most code changes can be - // tested with just a hot reload. - colorScheme: .fromSeed(seedColor: Colors.deepPurple), + colorScheme: .fromSeed(seedColor: Colors.yellow), ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), + home: const MyHomePage(title: 'Oeffimaster'), //this is what matters + // routes: { + // //'/': (context) => const MyHomePage(title: 'Oeffimaster'), + // '/stations': (context) => const ChooseStationPage(), + // }, ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - final String title; @override @@ -64,6 +46,7 @@ class _MyHomePageState extends State { //String API_KEY = ApiHandler.accessID; String API_KEY = "no telly"; //Future> ?id; + List stationList = []; String id = ''; void _incrementCounter() { @@ -78,9 +61,9 @@ class _MyHomePageState extends State { } // TODO :find a better way to show the list - void show_result(Future> future_id) async { - var tmp = await future_id; - id = tmp[0]; + void showResult(Future> future_id) async { + stationList = await future_id; + id = stationList[0].id; setState(() {}); } @@ -94,31 +77,14 @@ class _MyHomePageState extends State { // than having to individually change instances of widgets. return Scaffold( appBar: AppBar( - // TRY THIS: Try changing the color here to a specific color (to - // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar - // change color while the other colors stay the same. - backgroundColor: Theme.of(context).colorScheme.inversePrimary, + //backgroundColor: Theme.of(context).colorScheme.inversePrimary, + //backgroundColor: Colors.amber, // Here we take the value from the MyHomePage object that was created by // the App.build method, and use it to set our appbar title. title: Text(widget.title), ), body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - // - // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" - // action in the IDE, or press "p" in the console), to see the - // wireframe for each widget. mainAxisAlignment: .center, children: [ const Text('You have pushed the button this many times:'), @@ -129,12 +95,20 @@ class _MyHomePageState extends State { Text('API key from ENV is $API_KEY'), MaterialButton( onPressed: () { - show_result(ApiHandler.findStations('S Biesdorf')); - setState(() {}); + showResult(ApiHandler.findStations('S Biesdorf')); }, child: Text('Search for \'S Biesdorf\''), ), Text(id), + ElevatedButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => ChooseStationPage()), + ); + }, + child: const Text('Start a route'), + ), ], ), ), @@ -146,3 +120,191 @@ class _MyHomePageState extends State { ); } } + +class ChooseStationPage extends StatefulWidget { + const ChooseStationPage({super.key}); + + @override + State createState() => _ChooseStationPageState(); +} + +class _ChooseStationPageState extends State { + Station? _selectedStation; + List stationList = []; + + void findNearbyStations() async { + stationList = await ApiHandler.nearbyStations( + // TODO: replace with location + "52.55", + "13.55", + maxStations: 20, + ); + } + /* TODO: function to get stations along a vehicles route past current station, + * and run the correct function on switching to the screen + */ + + void findStationsByName( + String name, { + int? resultnum, + }) async { + stationList = await ApiHandler.findStations(name); + } + + void showResult(Future> future_id) async { + stationList = await future_id; + setState(() {}); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('RoutePicker')), + body: Center( + child: Column( + children: [ + ElevatedButton( + onPressed: () { + showResult(ApiHandler.findStations('S Biesdorf')); + }, + child: Text('Search for \'S Biesdorf\''), + ), + Expanded( + child: SizedBox( + height: 200, + child: StationList( + stationList: stationList, + onTapped: _handleStationTapped, + ), + ), + ), + ], + ), + ), + ); + } + + void _handleStationTapped(Station station) { + // setState(() { + // _selectedStation = station; + // }); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ChooseDeparturePage(startStation: station), + ), + ); + } +} + +class StationList extends StatelessWidget { + final List stationList; + final ValueChanged onTapped; + + StationList({ + required this.stationList, + required this.onTapped, + }); + + @override + Widget build(BuildContext context) { + return ListView( + children: [ + for (Station station in stationList) + ListTile( + title: Text(station.name), + onTap: () => onTapped(station), + ), + ], + ); + } +} + +class ChooseDeparturePage extends StatefulWidget { + final Station startStation; + const ChooseDeparturePage({super.key, required this.startStation}); + + @override + State createState() => _ChooseDeparturePageState(); +} + +class _ChooseDeparturePageState extends State { + List departureList = []; + Departure? _selectedDeparture; + void loadDepartures(Station startStation) async { + departureList = await ApiHandler.departures(startStation.id); + setState(() {}); + } + + @override + initState() { + loadDepartures(widget.startStation); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('RoutePicker for ${widget.startStation.name}'), + ), + body: Center( + child: Column( + children: [ + Expanded( + child: SizedBox( + height: 400, + child: DepartureList( + departureList: departureList, + onTapped: _handleDepartureTapped, + ), + ), + ), + ElevatedButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => ChooseStationPage()), + ); + }, + child: const Text('Vehicle picked!'), + ), + ], + ), + ), + ); + } + + void _handleDepartureTapped(Departure departure) { + // setState(() { + // _selectedDeparture = departure; + // }); + Navigator.push( + context, + MaterialPageRoute(builder: (context) => ChooseStationPage()), + ); + } +} + +class DepartureList extends StatelessWidget { + final List departureList; + final ValueChanged onTapped; + + DepartureList({ + required this.departureList, + required this.onTapped, + }); + + @override + Widget build(BuildContext context) { + return ListView( + children: [ + for (Departure departure in departureList) + ListTile( + title: Text(departure.vehicleName), + subtitle: Text(departure.departureTime.toString()), + onTap: () => onTapped(departure), + ), + ], + ); + } +} diff --git a/lib/transport_helper.dart b/lib/transport_helper.dart index 3a0fa4a..23cbd6a 100644 --- a/lib/transport_helper.dart +++ b/lib/transport_helper.dart @@ -438,7 +438,7 @@ class Station { }); Map _toMap() { - return {"id": id, "name": name}; + return {"stationid": id, "name": name}; } List> _toMapLines() { @@ -455,20 +455,26 @@ class Station { List? stationLines, }) { return Station( - id: map["id"] as String, + id: map["stationid"] as String, name: map["name"] as String, transportLines: stationLines ?? List.empty(), ); } factory Station.fromJson(Map json) => Station( - id: json["id"] ?? json["extId"] as String, - name: json["name"], - transportLines: json.containsKey("productAtStop") - ? json["productAtStop"].map( - (jsonLine) => Line.fromJson(jsonLine as Map), - ) - : const [], + id: json["id"] as String, + name: json["name"] as String, + transportLines: + json.containsKey("productAtStop") && json["productAtStop"] != Null + ? json["productAtStop"] + .map( + (jsonLine) => //jsonLine != Null + /*?*/ Line.fromJson(jsonLine! as Map), + //: Null, + ) + .toList() + .cast() + : [], ); static void initStationTable() async { @@ -563,16 +569,19 @@ class Vehicle { static const String tableName = "vehicle"; String id; + String name; String vehicleLineId; Vehicle({ required this.id, + required this.name, required this.vehicleLineId, }); Map _toMap() { return { "id": id, + "name": name, "line": vehicleLineId, }; } @@ -582,6 +591,7 @@ class Vehicle { ) { return Vehicle( id: map["id"] as String, + name: map["name"] as String, vehicleLineId: map["line"] as String, ); } @@ -589,7 +599,8 @@ class Vehicle { // 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"], + id: json["num"], + name: json["name"], vehicleLineId: Line.fromJson(json).lineId, ); @@ -597,7 +608,8 @@ class Vehicle { Database database = DbHelper.db; String line = Line.tableName; database.execute("""CREATE TABLE IF NOT EXISTS $tableName( - id TEXT PRIMARY KEY, + id TEXT PRIMARY KEY, + name TEXT, line TEXT, FOREIGN KEY(line) REFERENCES $line(lineid) ); @@ -684,14 +696,14 @@ class Line { ]; factory Line.fromJson(Map json) => Line( - lineId: json["lineId"], - name: json["name"], - mode: _vehicleTypeAsCatCode[int.parse(json["catCode"])], + lineId: json["lineId"] as String, + name: json["name"] as String, + mode: _vehicleTypeAsCatCode[int.parse(json["catCode"] as String)], fgColor: - Color.fromJson(json["foregroundColor"]) as Color? ?? + Color.fromJson(json["icon"]["foregroundColor"]) as Color? ?? Color(r: 255, g: 255, b: 255), // fallback bgColor: - Color.fromJson(json["backgroundColor"]) as Color? ?? + Color.fromJson(json["icon"]["backgroundColor"]) as Color? ?? Color(r: 50, g: 50, b: 50), // fallback ); static void initLineTable() async { @@ -744,12 +756,14 @@ class Departure { int? id; String vehicleId; + String vehicleName; String stationId; DateTime departureTime; Departure({ this.id, required this.vehicleId, + required this.vehicleName, required this.stationId, required this.departureTime, }); @@ -758,6 +772,7 @@ class Departure { return { "id": id, "vehicle": vehicleId, + "vehiclename": vehicleName, "station": stationId, "departuretime": departureTime, }; @@ -767,19 +782,33 @@ class Departure { return Departure( id: map["id"] as int, vehicleId: map["vehicle"] as String, + vehicleName: map["vehiclename"] as String, stationId: map["station"] as String, departureTime: map["departuretime"] as DateTime, ); } factory Departure.fromJson(Map json) { - Vehicle vehicle = Vehicle.fromJson(json["Product"]); - vehicle.dbInsert(); - Station station = Station.fromJson(json["stop"]); - station.dbInsert(); + Vehicle vehicle = Vehicle.fromJson(json["Product"][0]); + // try { + // vehicle.dbInsert(); + // } catch (e) { + // vehicle.dbUpdate(); + // } + Station station = Station( + id: json["stopid"], + name: json["stop"], + transportLines: [], + ); + // try { + // station.dbInsert(); + // } catch (e) { + // station.dbUpdate(); + // } return Departure( vehicleId: vehicle.id, + vehicleName: vehicle.name, stationId: station.id, - departureTime: json["time"], + departureTime: DateTime.parse('${json["date"]}T${json["time"]}'), ); }