10 Commits
6 changed files with 135 additions and 39 deletions
+37 -10
View File
@@ -1,17 +1,44 @@
# oeffimaster
A new Flutter project.
Aggregating information to create your own routes in public transit effortlessly.
## Getting Started
This project is a starting point for a Flutter application.
## Goal of this project
Do you ever get a route from google maps and see a 13 minute switch time, for a train that comes every ten minutes? Do you then prompt another app with better results. What if you'd normally have to wait 9 minutes for your train, but the previous train is late, so in reality you can switch with only a minutes worth of waiting? Now you can get an earlier train for the next connection, and suddenly the 3rd-fastest connection in google's algorithm becomes the fastest, and you saved 10 minutes - or could have, if you knew.
My app will not solve this issue.
As someone who is familiar with good chunks of the cities public transport network, I often enough *think* I can get faster to my target than the routing served by commonplace apps, but I have a hard time of knowing that for sure. Other apps allow some degree of comparing routes to one another, but it's not a priority, and editing any part of it is not supported at all.
Oeffimaster aims to make that type of information easily accessible and aggregate it in the most helpful way, allowing comparison and editing of routes.
A few resources to get you started if this is your first Flutter project:
## Usage
I am currently providing artifacts for linux and android. To make your own builds, you will need an api key for your network.
- [Learn Flutter](https://docs.flutter.dev/get-started/learn-flutter)
- [Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Flutter learning resources](https://docs.flutter.dev/reference/learning-resources)
## Build the project
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
### Get the API key
For the VBB network in Berlin/Brandenburg
1. Installing flutter: https://docs.flutter.dev/install/quick
2. Get an API key for VBB: https://unternehmen.vbb.de/digitale-services/api/
It will take some time, but they will send you an API key and the url to access it.
this should work for other public transit networks using the HAFAS api, but you will have to test this yourself
### The build part
Create a `.env` in the project root and add the following to it
```env
VBB_API_KEY={YOUR_API_KEY}
VBB_API_URL={YOUR_API_URL}
```
For local builds and connected devices simply run `flutter run` in your terminal.
To build for linux:
```bash
flutter build linux -DVBB_API_KEY={API_KEY} -DVBB_API_URL={API_URL}
```
To build for android:
```
flutter build apk -DVBB_API_KEY={API_KEY} -DVBB_API_URL={API_URL}
```
+4 -10
View File
@@ -5,14 +5,7 @@ import 'dart:core';
import 'dart:convert';
import 'transport_helper.dart';
String timeOf(DateTime fullDT) {
// doing it this way so we always get two digits on the hour
String full = fullDT.toString();
List<String> splits = full.split(" ")[1].split(":");
String time = '${splits[0]}:${splits[1]}';
return time;
}
import 'utilities.dart';
class ApiHandler {
// TODO: Split next line for better Error Handling
@@ -79,7 +72,7 @@ class ApiHandler {
static Future<List<Departure>> departures(
String stationId, {
int? maxDepartures = -1,
DateTime? time,
DateTime? datetime,
int? duration = 0,
}) async {
String urlPath = '/api/fahrinfo/latest/departureBoard';
@@ -88,8 +81,9 @@ class ApiHandler {
Uri url = Uri.https(baseUrl, urlPath, {
'accessId': accessID,
'id': stationString,
'date': dateOf(datetime ?? DateTime.now()),
'time': timeOf(
time ?? DateTime.now(),
datetime ?? DateTime.now(),
), //time from which departures will be shown
'duration': duration.toString(), //interval time in minutes
'maxJourneys': maxDepartures.toString(),
+73 -15
View File
@@ -3,13 +3,17 @@ import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'api_handler.dart';
import 'transport_helper.dart';
import 'dart:async';
// import 'package:sqflite_common_ffi/sqflite_ffi.dart'; //for testing on desktop
import 'dart:io';
import 'package:sqflite_common_ffi/sqflite_ffi.dart'; //for testing on desktop
import 'utilities.dart';
void main() async {
await dotenv.load();
const String dbName = "oeffimasterTransportDb";
// databaseFactory = databaseFactoryFfi; // for debugging and desktop test
if (Platform.isLinux) {
databaseFactory = databaseFactoryFfi; // for debugging and desktop test
}
DbHelper.initDb(dbName);
runApp(const MyApp());
}
@@ -177,7 +181,7 @@ class _ChooseStationPageState extends State<ChooseStationPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('RoutePicker')),
appBar: AppBar(title: const Text('Start a Journey')),
body: Center(
child: Column(
children: [
@@ -191,7 +195,7 @@ class _ChooseStationPageState extends State<ChooseStationPage> {
builder: (BuildContext context, SearchController controller) {
return SearchBar(
controller: controller,
hintText: 'Search for a Station',
hintText: 'Search your start Station',
padding: const WidgetStatePropertyAll<EdgeInsets>(
EdgeInsets.symmetric(horizontal: 16.0),
),
@@ -292,7 +296,7 @@ class _ChooseDeparturePageState extends State<ChooseDeparturePage> {
void loadDepartures(Station startStation) async {
departureList = await ApiHandler.departures(
startStation.id,
time: widget.arrival?.arrivalTime ?? DateTime.now(),
datetime: widget.arrival?.arrivalTime ?? DateTime.now(),
);
setState(() {});
}
@@ -320,12 +324,27 @@ class _ChooseDeparturePageState extends State<ChooseDeparturePage> {
return Scaffold(
appBar: AppBar(
title: Text(
'RoutePicker for ${widget.startStation.name} // ${widget.journeyId}',
'Departures for ${widget.startStation.name}',
),
),
body: Center(
child: Column(
children: [
Builder(
builder: (context) {
if (widget.arrival != null) {
return ListTile(
title: Text(
'arrive at ${widget.arrival?.arrivalTime}',
),
);
} else {
return ListTile(
title: Text(''),
);
}
},
),
Expanded(
child: SizedBox(
height: 400,
@@ -436,7 +455,7 @@ class _ChooseArrivalPageState extends State<ChooseArrivalPage> {
return Scaffold(
appBar: AppBar(
title: Text(
'Arrivals for ${widget.departure.vehicleName}// ${widget.journeyId}',
'Arrivals for ${widget.departure.vehicleName}',
),
),
body: Center(
@@ -541,7 +560,16 @@ class _JourneyDetailPageState extends State<JourneyDetailPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Connection Details \n$duration')),
appBar: AppBar(
title: ListTile(
title: Text(
'${widget.singleJourney.startStation}${widget.singleJourney.endStation}',
),
subtitle: Text(
'${widget.singleJourney.duration.toString().split(".")[0]}h',
),
),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
@@ -553,10 +581,10 @@ class _JourneyDetailPageState extends State<JourneyDetailPage> {
),
),
Container(
margin: const EdgeInsets.all(20.0),
margin: const EdgeInsets.only(bottom: 60.0),
child: ElevatedButton(
onPressed: () {
Navigator.pop(
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => JourneyOverviewPage(),
@@ -586,9 +614,39 @@ class SegmentList extends StatelessWidget {
return ListView(
children: [
for (Segment seg in journey.segments)
ListTile(
title: Text(seg.start.direction),
subtitle: Text(seg.start.departureTime.toString()),
Card(
child: Column(
mainAxisSize: .min,
children: [
Container(
padding: EdgeInsets.only(left: 10, top: 10),
child: Row(
children: [
Text(
seg.vehicle.name,
textScaler: TextScaler.linear(1.1),
),
Icon(Icons.arrow_right_alt),
Text('${seg.start.direction}'),
],
),
),
ListTile(
leading: Text(
timeOf(seg.start.departureTime),
textScaler: TextScaler.linear(1.4),
),
title: Text(seg.start.stationName),
),
ListTile(
leading: Text(
timeOf(seg.end.arrivalTime),
textScaler: TextScaler.linear(1.4),
),
title: Text(seg.end.stationName),
),
],
),
),
],
);
@@ -641,7 +699,7 @@ class _JourneyOverviewPageState extends State<JourneyOverviewPage> {
height: 100.0,
child: FittedBox(
fit: BoxFit.contain,
alignment: Alignment.centerLeft,
alignment: Alignment.center,
child: Text("Your Journeys"),
),
),
+6 -4
View File
@@ -1,4 +1,5 @@
import 'package:sqflite/sqflite.dart';
import 'utilities.dart';
// import './state_helper.dart';
// import './db_helper.dart';
@@ -94,13 +95,15 @@ class Journey {
Map<String, Object?> journeyMaps,
List<Segment> segmentMaps,
) {
return Journey(
Journey journey = Journey(
id: journeyMaps["id"] as int,
startStation: journeyMaps["startstation"] as String?,
endStation: journeyMaps["endstation"] as String?,
segments: segmentMaps,
duration: Duration(minutes: 0),
);
journey.sync();
return journey;
}
// from trip API request
@@ -134,8 +137,8 @@ class Journey {
if (segments.isNotEmpty) {
startStation = segments.first.start.stationName;
endStation = segments.last.end.stationName;
duration = segments.first.start.departureTime.difference(
segments.last.end.arrivalTime,
duration = segments.last.end.arrivalTime.difference(
segments.first.start.departureTime,
);
} else {
// if the journey has no segments yet, at least the startStation should remain,
@@ -223,7 +226,6 @@ class Journey {
static Future<Journey> fromMapWrapper(Map<String, Object?> journeyMap) async {
Future<List<Segment>> segs = _dbGetJourneySegments(journeyMap["id"] as int);
Journey res = Journey.fromMap(journeyMap, await segs);
res.sync();
return res;
}
+13
View File
@@ -0,0 +1,13 @@
String timeOf(DateTime fullDT) {
// doing it this way so we always get two digits on the hour
String full = fullDT.toString();
List<String> splits = full.split(" ")[1].split(":");
String time = '${splits[0]}:${splits[1]}';
return time;
}
String dateOf(DateTime fullDT) {
String full = fullDT.toString();
String date = full.split(" ")[0];
return date;
}
+2
View File
@@ -37,6 +37,8 @@ dependencies:
flutter_dotenv: ^6.0.1
http: ^1.6.0
sqflite: ^2.4.2+1
sqlite3: ^3.3.0
sqflite_common_ffi: ^2.4.0+3
dev_dependencies:
flutter_test:
sdk: flutter