Files
oeffimaster/lib/main.dart
T
fbachus aa9258e3af feat(transport_helper): add function to query multiple journeys
could query all, but the last 40 should be fine, no?
could raise to no limit, but should be done as generator for that
2026-07-30 15:11:04 +02:00

550 lines
14 KiB
Dart

import 'package:flutter/material.dart';
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());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Oeffimaster',
theme: ThemeData(
colorScheme: .fromSeed(seedColor: Colors.yellow),
),
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});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
//String API_KEY = ApiHandler.accessID;
String API_KEY = "no telly";
//Future<Map<String, dynamic>> ?id;
List<Station> stationList = [];
String id = '';
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
// TODO :find a better way to show the list
void showResult(Future<List<Station>> futureId) async {
stationList = await futureId;
id = stationList[0].id;
setState(() {});
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
//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(
child: Column(
mainAxisAlignment: .center,
children: [
const Text('You have pushed the button this many times:'),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
Text('API key from ENV is $API_KEY'),
MaterialButton(
onPressed: () {
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'),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
class ChooseStationPage extends StatefulWidget {
const ChooseStationPage({super.key});
@override
State<ChooseStationPage> createState() => _ChooseStationPageState();
}
class _ChooseStationPageState extends State<ChooseStationPage> {
Station? _selectedStation;
List<Station> stationList = [];
void findNearbyStations() async {
stationList = await ApiHandler.nearbyStations(
// TODO: replace with location
"52.55",
"13.55",
maxStations: 20,
);
}
void findStationsByName(
String name, {
int? resultnum,
}) async {
stationList = await ApiHandler.findStations(name);
}
void showResult(Future<List<Station>> futureId) async {
stationList = await futureId;
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<Station> stationList;
final ValueChanged<Station> onTapped;
StationList({
super.key,
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;
final int? journeyId;
const ChooseDeparturePage({
super.key,
this.journeyId,
required this.startStation,
});
@override
State<ChooseDeparturePage> createState() => _ChooseDeparturePageState();
}
class _ChooseDeparturePageState extends State<ChooseDeparturePage> {
List<Departure> departureList = [];
Journey? singleJourney;
void loadDepartures(Station startStation) async {
departureList = await ApiHandler.departures(startStation.id);
setState(() {});
}
void loadJourney(int journeyId) async {
singleJourney = await Journey.dbGet(journeyId);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => JourneyDetailPage(
singleJourney: singleJourney as Journey,
),
),
);
}
@override
initState() {
super.initState();
loadDepartures(widget.startStation);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'RoutePicker for ${widget.startStation.name} // ${widget.journeyId}',
),
),
body: Center(
child: Column(
children: [
Expanded(
child: SizedBox(
height: 400,
child: DepartureList(
departureList: departureList,
onTapped: _handleDepartureTapped,
),
),
),
ElevatedButton(
onPressed: () {
if (widget.journeyId != null) {
loadJourney(widget.journeyId as int);
}
},
child: const Text('Finish Route'),
),
],
),
),
);
}
void _handleDepartureTapped(Departure departure) {
// setState(() {
// _selectedDeparture = departure;
// });
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChooseArrivalPage(
departure: departure,
journeyId: widget.journeyId,
),
),
);
}
}
class DepartureList extends StatelessWidget {
final List<Departure> departureList;
final ValueChanged<Departure> onTapped;
const DepartureList({
super.key,
required this.departureList,
required this.onTapped,
});
@override
Widget build(BuildContext context) {
return ListView(
children: [
for (Departure departure in departureList)
ListTile(
title: Text('${departure.vehicleName}: ${departure.direction}'),
subtitle: Text(departure.departureTime.toString()),
onTap: () => onTapped(departure),
),
],
);
}
}
class ChooseArrivalPage extends StatefulWidget {
final Departure departure;
final int? journeyId;
const ChooseArrivalPage({
super.key,
this.journeyId,
required this.departure,
});
@override
State<ChooseArrivalPage> createState() => _ChooseArrivalPageState();
}
class _ChooseArrivalPageState extends State<ChooseArrivalPage> {
List<Departure> arrivalList = [];
Departure? _selectedDeparture;
void loadArrivals(String ref) async {
arrivalList = await ApiHandler.tripDetail(
ref,
startStationId: widget.departure.stationId,
);
setState(() {});
}
@override
initState() {
super.initState();
loadArrivals(widget.departure.ref);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'Arrivals for ${widget.departure.vehicleName}// ${widget.journeyId}',
),
),
body: Center(
child: Column(
children: [
Expanded(
child: SizedBox(
height: 400,
child: ArrivalList(
departure: widget.departure,
arrivalList: arrivalList,
onTapped: _handleArrivalTapped,
),
),
),
],
),
),
);
}
void _handleArrivalTapped(Departure departure, Departure arrival) async {
// setState(() {
// _selectedDeparture = departure;
// });
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 = 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,
journeyId: journeyId,
),
),
);
}
}
class ArrivalList extends StatelessWidget {
final Departure departure;
final List<Departure> arrivalList;
final Function onTapped;
const ArrivalList({
super.key,
required this.departure,
required this.arrivalList,
required this.onTapped,
});
@override
Widget build(BuildContext context) {
return ListView(
children: [
for (Departure arrival in arrivalList)
ListTile(
title: Text(arrival.stationName),
subtitle: Text(arrival.arrivalTime.toString()),
onTap: () => onTapped(departure, arrival),
),
],
);
}
}
class JourneyDetailPage extends StatefulWidget {
final Journey singleJourney;
const JourneyDetailPage({
super.key,
required this.singleJourney,
});
@override
State<JourneyDetailPage> createState() => _JourneyDetailPageState();
}
class _JourneyDetailPageState extends State<JourneyDetailPage> {
Duration duration = Duration(minutes: 0);
@override
initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Connection Details \n$duration')),
body: Center(
child: SegmentList(journey: widget.singleJourney),
),
);
}
}
class SegmentList extends StatelessWidget {
final Journey journey;
const SegmentList({
super.key,
required this.journey,
});
@override
Widget build(BuildContext context) {
return ListView(
children: [
for (Segment seg in journey.segments)
ListTile(
title: Text(seg.start.direction),
subtitle: Text(seg.start.departureTime.toString()),
),
],
);
}
}
class JourneyOverviewPage extends StatefulWidget {
const JourneyOverviewPage({super.key});
@override
State<JourneyOverviewPage> createState() => _JourneyOverviewPageState();
}
class _JourneyOverviewPageState extends State<JourneyOverviewPage> {
List<Journey> allJourneys = <Journey>[];
@override
initState() async {
super.initState();
// TODO: implement batch query to get a list (all) of journeys
allJourneys = await Journey.dbGetAll();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: JourneyList(journeys: allJourneys),
),
);
}
}
class JourneyList extends StatelessWidget {
final List<Journey> journeys;
const JourneyList({
super.key,
required this.journeys,
});
@override
Widget build(BuildContext context) {
return ListView(
children: [
for (Journey jour in journeys)
ListTile(title: Text('${jour.startStation} to ${jour.endStation}')),
],
);
}
}