You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1077 lines
30 KiB

5 years ago
import 'dart:async';
import 'dart:convert';
import 'package:flutter/widgets.dart';
import 'package:info_tren/hidden_webview.dart';
import 'package:info_tren/utils/webview_invoke.dart';
5 years ago
import 'package:json_annotation/json_annotation.dart';
import 'package:webview_flutter/webview_flutter.dart';
5 years ago
part 'train_data.g.dart';
enum TrainLookupResult {
FOUND,
NOT_FOUND,
OTHER
}
class OnDemandInvalidatedException implements Exception {
final String propertyName;
final OnDemand onDemandClass;
OnDemandInvalidatedException({this.propertyName, this.onDemandClass});
5 years ago
@override
String toString() {
return "OnDemandInvalidatedException: An attempt to get $propertyName from ${onDemandClass.runtimeType} failed because the source was invalidated.";
5 years ago
}
}
class OnDemand {
bool valid;
5 years ago
final Function onInvalidation;
5 years ago
void invalidate() {
if (valid) {
valid = false;
if (onInvalidation != null) onInvalidation();
}
5 years ago
}
OnDemand(this.onInvalidation): valid = true;
5 years ago
}
class OnDemandTrainData extends OnDemand {
final WebViewController _controller;
OnDemandTrainData({
WebViewController controller,
Function onInvalidation
})
: _controller = controller,
_route = OnDemandTrainRoute(controller: controller),
_lastInfo = OnDemandLastInfo(controller: controller),
_destination = OnDemandDestination(controller: controller),
_nextStop = OnDemandNextStop(controller: controller),
_stations = OnDemandStations(controller: controller),
super(onInvalidation);
@override
invalidate() {
super.invalidate();
route.invalidate();
lastInfo.invalidate();
destination.invalidate();
nextStop.invalidate();
stations.invalidate();
}
Future<String> get _originalDepartureDate async {
if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "_originalDepartureDate");
final tempRes = await wInvoke(
webViewController: _controller,
jsFunctionContent: """
(() => {
let table = document.querySelector("#DetailsView1");
let field = table.querySelector("caption");
return field.textContent.trim();
})()
""",
isFunctionAlready: true,
5 years ago
);
return tempRes.split(" ").last;
}
5 years ago
Future<DateTime> get departureDate async {
final str = await _originalDepartureDate;
final parts = str.split(".").map((str) => int.parse(str)).toList();
return DateTime(parts[2], parts[1], parts[0]);
}
Future<String> get rang async {
if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "rang");
return await wInvoke(
webViewController: _controller,
jsFunctionContent: """
(() => {
let table = document.querySelector("#DetailsView1");
let rows = table.querySelectorAll("tr");
let currentRow = rows[0];
let currentDataCell = currentRow.querySelectorAll("td")[1];
return currentDataCell.textContent;
})()
""",
isFunctionAlready: true,
);
}
Future<String> get trainNumber async {
if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "trainNumber");
return await wInvoke(
webViewController: _controller,
jsFunctionContent: """
(() => {
let table = document.querySelector("#DetailsView1");
let rows = table.querySelectorAll("tr");
let currentRow = rows[1];
let currentDataCell = currentRow.querySelectorAll("td")[1];
return currentDataCell.textContent;
})()
""",
isFunctionAlready: true,
);
}
Future<String> get operator async {
if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "operator");
return await wInvoke(
webViewController: _controller,
jsFunctionContent: """
(() => {
let table = document.querySelector("#DetailsView1");
let rows = table.querySelectorAll("tr");
let currentRow = rows[2];
let currentDataCell = currentRow.querySelectorAll("td")[1];
return currentDataCell.textContent;
})()
""",
isFunctionAlready: true,
);
}
final OnDemandTrainRoute _route;
OnDemandTrainRoute get route => _route;
Future<String> get state async {
if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "state");
return await wInvoke(
webViewController: _controller,
jsFunctionContent: """
(() => {
let table = document.querySelector("#DetailsView1");
let rows = table.querySelectorAll("tr");
let currentRow = rows[4];
let currentDataCell = currentRow.querySelectorAll("td")[1];
return currentDataCell.textContent;
})()
""",
isFunctionAlready: true,
);
}
final OnDemandLastInfo _lastInfo;
OnDemandLastInfo get lastInfo => _lastInfo;
final OnDemandDestination _destination;
OnDemandDestination get destination => _destination;
final OnDemandNextStop _nextStop;
OnDemandNextStop get nextStop => _nextStop;
Future<String> get routeDistance async {
if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "routeDistance");
final result = (await wInvoke(
webViewController: _controller,
jsFunctionContent: """
(() => {
let table = document.querySelector("#DetailsView1");
let rows = table.querySelectorAll("tr");
let currentRow = rows[12];
let currentDataCell = currentRow.querySelectorAll("td")[1];
return currentDataCell.textContent;
})()
""",
isFunctionAlready: true,
)).trim();
return takeWhile(result, (char) => char != ' '.codeUnitAt(0));
}
Future<String> get _routeDuration async {
if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "_routeDuration");
var result = (await wInvoke(
webViewController: _controller,
jsFunctionContent: """
(() => {
let table = document.querySelector("#DetailsView1");
let rows = table.querySelectorAll("tr");
let currentRow = rows[13];
let currentDataCell = currentRow.querySelectorAll("td")[1];
return currentDataCell.textContent;
})()
""",
isFunctionAlready: true,
)).trim();
if (result[result.length - 1] == '.') result = result.substring(0, result.length - 1);
return result;
}
Future<Duration> get routeDuration async {
final input = await _routeDuration;
var result = Duration();
StringBuffer buffer = StringBuffer();
for (var i = 0; i < input.length; i++) {
if ('0'.codeUnitAt(0) <= input.codeUnitAt(i) && input.codeUnitAt(i) <= '9'.codeUnitAt(0)) {
buffer.writeCharCode(input.codeUnitAt(i));
5 years ago
}
else if (input.startsWith("min", i)) {
result += Duration(minutes: int.parse(buffer.toString()));
buffer = StringBuffer();
i += 2;
5 years ago
}
else if (input.startsWith("h", i)) {
result += Duration(hours: int.parse(buffer.toString()));
buffer = StringBuffer();
5 years ago
}
else throw FormatException("Unrecognised!");
}
return result;
}
final OnDemandStations _stations;
OnDemandStations get stations => _stations;
}
class OnDemandTrainRoute extends OnDemand {
final WebViewController _controller;
OnDemandTrainRoute({
WebViewController controller,
Function onInvalidation
}) : _controller = controller, super(onInvalidation);
Future<String> get original async {
if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "original");
return await wInvoke(
webViewController: _controller,
jsFunctionContent: """
(() => {
let table = document.querySelector("#DetailsView1");
let rows = table.querySelectorAll("tr");
let currentRow = rows[3];
let currentDataCell = currentRow.querySelectorAll("td")[1];
return currentDataCell.textContent;
})()
""",
isFunctionAlready: true,
);
}
Future<String> get from async {
final original = await this.original;
return original.split("-")[0];
}
Future<String> get to async {
final original = await this.original;
return original.split("-")[1];
}
}
class OnDemandLastInfo extends OnDemand {
final WebViewController _controller;
OnDemandLastInfo({
WebViewController controller,
Function onInvalidation
}) : _controller = controller, super(onInvalidation);
Future<String> get _lastInfoOriginal async {
if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "_lastInfoOriginal");
return await wInvoke(
webViewController: _controller,
jsFunctionContent: """
(() => {
let table = document.querySelector("#DetailsView1");
let rows = table.querySelectorAll("tr");
let currentRow = rows[5];
let currentDataCell = currentRow.querySelectorAll("td")[1];
return currentDataCell.textContent;
})()
""",
isFunctionAlready: true,
);
}
Future<String> get station async {
final original = await _lastInfoOriginal;
return original
.split("[")[0]
.trim();
}
Future<String> get event async {
final original = await _lastInfoOriginal;
return original
.split("[")[1]
.split("]")[0]
.trim();
}
Future<String> get originalDateAndTime async {
if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "originalDateAndTime");
return await wInvoke(
webViewController: _controller,
jsFunctionContent: """
(() => {
let table = document.querySelector("#DetailsView1");
let rows = table.querySelectorAll("tr");
let currentRow = rows[6];
let currentDataCell = currentRow.querySelectorAll("td")[1];
return currentDataCell.textContent;
})()
""",
isFunctionAlready: true,
);
}
Future<DateTime> get dateAndTime async => parseCFRDateTime(await originalDateAndTime);
Future<int> get delay async {
if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "delay");
return int.parse(
await wInvoke(
webViewController: _controller,
jsFunctionContent: """
(() => {
let table = document.querySelector("#DetailsView1");
let rows = table.querySelectorAll("tr");
let currentRow = rows[7];
let currentDataCell = currentRow.querySelectorAll("td")[1];
return currentDataCell.textContent;
})()
""",
isFunctionAlready: true,
)
);
}
}
class OnDemandDestination extends OnDemand {
final WebViewController _controller;
OnDemandDestination({
WebViewController controller,
Function onInvalidation
}) : _controller = controller, super(onInvalidation);
Future<String> get stationName async {
if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "destinationStation");
final result = (await wInvoke(
webViewController: _controller,
jsFunctionContent: """
(() => {
let table = document.querySelector("#DetailsView1");
let rows = table.querySelectorAll("tr");
let currentRow = rows[8];
let currentDataCell = currentRow.querySelectorAll("td")[1];
return currentDataCell.textContent;
})()
""",
isFunctionAlready: true,
)).trim();
if (result.isEmpty) return null;
else return result;
}
Future<String> get _originalDestinationArrival async {
if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "_originalDestinationArrival");
final result = (await wInvoke(
webViewController: _controller,
jsFunctionContent: """
(() => {
let table = document.querySelector("#DetailsView1");
let rows = table.querySelectorAll("tr");
let currentRow = rows[9];
let currentDataCell = currentRow.querySelectorAll("td")[1];
return currentDataCell.textContent;
})()
""",
isFunctionAlready: true,
)).trim();
if (result.isEmpty) return null;
else return result;
}
Future<DateTime> get arrival => _originalDestinationArrival.then((value) => parseCFRDateTime(value));
}
class OnDemandNextStop extends OnDemand {
final WebViewController _controller;
OnDemandNextStop({
WebViewController controller,
Function onInvalidation
}) : _controller = controller, super(onInvalidation);
Future<String> get stationName async {
if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "destinationStation");
final result = (await wInvoke(
webViewController: _controller,
jsFunctionContent: """
(() => {
let table = document.querySelector("#DetailsView1");
let rows = table.querySelectorAll("tr");
let currentRow = rows[10];
let currentDataCell = currentRow.querySelectorAll("td")[1];
return currentDataCell.textContent;
})()
""",
isFunctionAlready: true,
)).trim();
if (result.isEmpty) return null;
else return result;
}
Future<String> get _originalNextStopArrival async {
if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "_originalDestinationArrival");
final result = (await wInvoke(
webViewController: _controller,
jsFunctionContent: """
(() => {
let table = document.querySelector("#DetailsView1");
let rows = table.querySelectorAll("tr");
let currentRow = rows[11];
let currentDataCell = currentRow.querySelectorAll("td")[1];
return currentDataCell.textContent;
})()
""",
isFunctionAlready: true,
)).trim();
if (result.isEmpty) return null;
else return result;
}
Future<DateTime> get arrival => _originalNextStopArrival.then((value) => parseCFRDateTime(value));
}
class OnDemandStations extends OnDemand {
final WebViewController _controller;
List<OnDemand> issuedOnDemands = [];
@override
void invalidate() {
issuedOnDemands.map((od) => od.invalidate());
super.invalidate();
}
OnDemandStations({
@required WebViewController controller,
Function onInvalidation
}) : _controller = controller, super(onInvalidation);
Future<bool> get _stationsLoaded async {
if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "_stationsLoaded");
final result = await wInvoke(
webViewController: _controller,
jsFunctionContent: """
(() => JSON.stringify(document.querySelector("#GridView1") == null))()
""",
isFunctionAlready: true,
);
final decoder = JsonDecoder();
return !(decoder.convert(result) as bool);
}
Stream<OnDemandStation> call({@required Future pageLoadFuture}) async* {
if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "call");
if (!await _stationsLoaded) {
await wInvoke(
webViewController: _controller,
jsFunctionContent: """
(() => {
const button = document.querySelector("#Button2");
button.click();
})()
""",
isFunctionAlready: true,
);
await pageLoadFuture;
5 years ago
}
final count = int.parse(await wInvoke(
webViewController: _controller,
jsFunctionContent: """
(() => {
const table = document.querySelector("#GridView1");
const rows = table.querySelectorAll("tr");
const rowsArray = Array.from(rows);
const count = rowsArray.length - 1;
return String(count);
})()
""",
isFunctionAlready: true,
));
for (int i = 1; i <= count; i++) {
final ods = OnDemandStation(
controller: _controller,
index: i,
5 years ago
);
issuedOnDemands.add(ods);
yield ods;
5 years ago
}
}
}
class OnDemandStation extends OnDemand {
final WebViewController _controller;
final int index;
5 years ago
OnDemandStation({
@required WebViewController controller,
@required this.index,
Function onInvalidation
}) : _controller = controller, super(onInvalidation);
Future<int> get km async {
if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "km");
return int.parse(await wInvoke(
webViewController: _controller,
jsFunctionContent: """
(() => {
const table = document.querySelector("#GridView1");
const rows = table.querySelectorAll("tr");
const rowsArray = Array.from(rows);
const row = rowsArray[$index];
const columns = row.querySelectorAll("td");
const kmCell = columns[0];
return kmCell.textContent;
})()
""",
isFunctionAlready: true,
));
}
Future<String> get stationName async {
if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "stationName");
return await wInvoke(
webViewController: _controller,
jsFunctionContent: """
(() => {
const table = document.querySelector("#GridView1");
const rows = table.querySelectorAll("tr");
const rowsArray = Array.from(rows);
const row = rowsArray[$index];
const columns = row.querySelectorAll("td");
const kmCell = columns[1];
return kmCell.textContent;
})()
""",
isFunctionAlready: true,
);
5 years ago
}
Future<String> get arrivalTime async {
if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "arrivalTime");
return await wInvoke(
webViewController: _controller,
jsFunctionContent: """
(() => {
const table = document.querySelector("#GridView1");
const rows = table.querySelectorAll("tr");
const rowsArray = Array.from(rows);
const row = rowsArray[$index];
const columns = row.querySelectorAll("td");
const kmCell = columns[2];
return kmCell.textContent.trim();
})()
""",
isFunctionAlready: true,
);
5 years ago
}
Future<String> get stopsFor async {
if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "stopsFor");
return await wInvoke(
webViewController: _controller,
jsFunctionContent: """
(() => {
const table = document.querySelector("#GridView1");
const rows = table.querySelectorAll("tr");
const rowsArray = Array.from(rows);
const row = rowsArray[$index];
const columns = row.querySelectorAll("td");
const kmCell = columns[3];
return kmCell.textContent.trim();
})()
""",
isFunctionAlready: true,
);
}
Future<String> get departureTime async {
if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "departureTime");
return await wInvoke(
webViewController: _controller,
jsFunctionContent: """
(() => {
const table = document.querySelector("#GridView1");
const rows = table.querySelectorAll("tr");
const rowsArray = Array.from(rows);
const row = rowsArray[$index];
const columns = row.querySelectorAll("td");
const kmCell = columns[4];
return kmCell.textContent.trim();
})()
""",
isFunctionAlready: true,
);
}
Future<RealOrEstimate> get realOrEstimate async {
if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "realOrEstimate");
final value = await wInvoke(
webViewController: _controller,
jsFunctionContent: """
(() => {
const table = document.querySelector("#GridView1");
const rows = table.querySelectorAll("tr");
const rowsArray = Array.from(rows);
const row = rowsArray[$index];
const columns = row.querySelectorAll("td");
const kmCell = columns[5];
return kmCell.textContent.trim();
})()
""",
isFunctionAlready: true,
);
if (value == "Real") return RealOrEstimate.real;
else if (value == "Estimat") return RealOrEstimate.estimate;
else return RealOrEstimate.UNKNOWN;
}
Future<int> get delay async {
if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "delay");
final value = await wInvoke(
webViewController: _controller,
jsFunctionContent: """
(() => {
const table = document.querySelector("#GridView1");
const rows = table.querySelectorAll("tr");
const rowsArray = Array.from(rows);
const row = rowsArray[$index];
const columns = row.querySelectorAll("td");
const kmCell = columns[6];
return kmCell.textContent.trim();
})()
""",
isFunctionAlready: true,
);
if (value.isEmpty) return 0;
else return int.parse(value);
}
Future<String> get observations async {
if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "observations");
return await wInvoke(
webViewController: _controller,
jsFunctionContent: """
(() => {
const table = document.querySelector("#GridView1");
const rows = table.querySelectorAll("tr");
const rowsArray = Array.from(rows);
const row = rowsArray[$index];
const columns = row.querySelectorAll("td");
const kmCell = columns[7];
return kmCell.textContent.trim();
})()
""",
isFunctionAlready: true,
);
}
5 years ago
}
enum RealOrEstimate {
real,
estimate,
UNKNOWN
5 years ago
}
class TrainDataWebViewAdapter extends StatefulWidget {
final WidgetBuilder builder;
TrainDataWebViewAdapter({@required this.builder});
@override
State<StatefulWidget> createState() {
return _TrainDataWebViewAdapterState();
}
5 years ago
static _TrainDataWebViewAdapterState of(BuildContext context) =>
(context.findAncestorWidgetOfExactType<_TrainDataWebViewAdapterInheritedWidget>())
.state;
}
class ProgressReport {
final int current;
final int total;
final String description;
ProgressReport({@required this.current, @required this.total, this.description});
@override
String toString() {
return description == null ? "ProgressReport($current/$total)" : "ProgressReport($current/$total: $description)";
}
}
class _TrainDataWebViewAdapterState extends State<TrainDataWebViewAdapter> {
Completer<WebViewController> _webViewControllerCompleter = Completer();
Future<WebViewController> get webViewController => _webViewControllerCompleter.future;
StreamController<String> _pageLoadController;
Stream<String> pageLoadStream;
Future<String> get nextLoadFuture => pageLoadStream.take(1).first;
StreamController<ProgressReport> _progressController;
Stream<ProgressReport> progressStream;
Future<TrainLookupResult> loadTrain(int trainNo) async {
currentDatas.removeWhere((ondemand) {
ondemand.invalidate();
return true;
});
final controller = await webViewController;
var nlf;
nlf = nextLoadFuture;
await controller.loadUrl("https://appiris.infofer.ro/MytrainRO.aspx");
await nlf;
_reportStatus(
current: 2,
description: "Loaded Informatica Feroviară webpage"
);
nlf = nextLoadFuture;
await controller.evaluateJavascript("""
( () => {
let inputField = document.querySelector("#TextTrnNo");
inputField.value = $trainNo;
let submitButton = document.querySelector("#Button1");
submitButton.click();
} ) ()
""");
await nlf;
_reportStatus(
current: 3,
description: "Loaded train information"
);
var result = await wInvoke(
webViewController: controller,
jsFunctionContent: """
(() => {
let errorMessage = document.querySelector("#Lblx");
return errorMessage.textContent;
})()
""",
isFunctionAlready: true,
);
if (result.isNotEmpty) {
return TrainLookupResult.NOT_FOUND;
}
final jsonDecoder = JsonDecoder();
final foundTable = jsonDecoder.convert(await wInvoke(
webViewController: controller,
jsFunctionContent: """
(() => {
let table = document.querySelector("#DetailsView1");
return JSON.stringify(table !== null);
})()
""",
isFunctionAlready: true,
)) as bool;
if (foundTable) {
return TrainLookupResult.FOUND;
}
/// Should not happen, report error in this case
return TrainLookupResult.OTHER;
}
List<OnDemand> currentDatas = [];
Future<OnDemandTrainData> trainData({Function onInvalidation}) async {
final controller = await webViewController;
final result = OnDemandTrainData(
controller: controller,
onInvalidation: onInvalidation
);
currentDatas.add(result);
return result;
}
int lastStatusReported;
_reportStatus({@required int current, String description}) {
lastStatusReported = current;
_progressController.add(ProgressReport(
current: current,
total: TOTAL_PROGRESS,
description: description
));
}
recallLastReport() {
_progressController.add(ProgressReport(current: lastStatusReported, total: TOTAL_PROGRESS));
}
restartProgressReport() {
lastStatusReported = 0;
webViewController.then((_) {
_reportStatus(current: 1, description: "WebView created");
});
}
@override
void initState() {
_pageLoadController = StreamController();
pageLoadStream = _pageLoadController.stream.asBroadcastStream();
_progressController = StreamController();
progressStream = _progressController.stream.asBroadcastStream();
lastStatusReported = 0;
super.initState();
}
@override
void dispose() {
_pageLoadController.close();
_progressController.close();
super.dispose();
}
static const int TOTAL_PROGRESS = 3;
@override
Widget build(BuildContext context) {
return HiddenWebView(
webView: WebView(
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (controller) {
_webViewControllerCompleter.complete(controller);
_reportStatus(
current: 1,
description: "WebView created"
);
},
onPageFinished: (url) {
_pageLoadController.add(url);
},
),
child: _TrainDataWebViewAdapterInheritedWidget(
child: Builder(
builder: widget.builder,
),
state: this,
),
);
}
}
class _TrainDataWebViewAdapterInheritedWidget extends InheritedWidget {
final _TrainDataWebViewAdapterState state;
_TrainDataWebViewAdapterInheritedWidget({this.state, Widget child, Key key})
:super(key: key, child: child);
@override
bool updateShouldNotify(InheritedWidget oldWidget) {
return true;
}
5 years ago
}
@JsonSerializable()
class TrainData {
final String rang;
@JsonKey(name: "tren")
final String trainNumber;
final String operator;
@JsonKey(name: "relatia")
final String route;
@JsonKey(name: "stare")
final String state;
@JsonKey(name: "ultima_informatie")
final LastInfo lastInfo;
@JsonKey(name: "destinatie")
final StopInfo destination;
@JsonKey(name: "urmatoarea_oprire")
final StopInfo nextStop;
@JsonKey(name: "durata_calatoriei")
final String tripLength;
@JsonKey(name: "distanta")
final String distance;
@JsonKey(name: "stations")
List<StationEntry> stations;
TrainData({this.rang, this.trainNumber, this.operator, this.lastInfo,
this.state, this.route, this.tripLength, this.stations, this.nextStop,
this.distance, this.destination});
factory TrainData.fromJson(Map<String, dynamic> json) {
var result = _$TrainDataFromJson(json);
var foundEstimat = false;
result.stations = result.stations.map((station) {
if (station.realOrEstimate == "Estimat") {
foundEstimat = true;
}
station.realOrEstimate = foundEstimat ? "Estimat" : "Real";
return station;
}).toList();
return result;
}
Map<String, dynamic> toJson() => _$TrainDataToJson(this);
}
@JsonSerializable()
class LastInfo {
@JsonKey(name: "statia")
final String station;
@JsonKey(name: "eveniment")
final String event;
@JsonKey(name: "data_si_ora")
final String dateAndTime;
DateTime get formattedDateAndTime {
return parseCFRDateTime(dateAndTime);
}
@JsonKey(name: "intarziere")
final int delay;
LastInfo({this.dateAndTime, this.delay, this.event, this.station});
factory LastInfo.fromJson(Map<String, dynamic> json) => _$LastInfoFromJson(json);
Map<String, dynamic> toJson() => _$LastInfoToJson(this);
}
@JsonSerializable()
class StopInfo {
@JsonKey(name: "statia")
final String station;
@JsonKey(name: "data_si_ora")
final String dateAndTime;
DateTime get formattedDateAndTime {
return parseCFRDateTime(dateAndTime);
}
StopInfo({this.station, this.dateAndTime});
factory StopInfo.fromJson(Map<String, dynamic> json) => _$StopInfoFromJson(json);
Map<String, dynamic> toJson() => _$StopInfoToJson(this);
}
@JsonSerializable()
class StationEntry {
final String km;
@JsonKey(name: "statia")
final String name;
@JsonKey(name: "sosire")
final String arrivalTime;
@JsonKey(name: "stationeaza_pentru")
final String waitTime;
@JsonKey(name: "plecare")
final String departureTime;
@JsonKey(name: "real/estimat")
String realOrEstimate;
bool get real {
return realOrEstimate == "Real";
}
@JsonKey(name: "intarziere")
final int delay;
@JsonKey(name: "observatii")
final String observations;
StationEntry({this.name, this.delay, this.realOrEstimate,
this.arrivalTime, this.departureTime, this.km, this.observations,
this.waitTime});
factory StationEntry.fromJson(Map<String, dynamic> json) => _$StationEntryFromJson(json);
Map<String, dynamic> toJson() => _$StationEntryToJson(this);
}
DateTime parseCFRDateTime(String dateAndTime) {
if (dateAndTime == null || dateAndTime.isEmpty) return null;
5 years ago
final parts = dateAndTime.split(" ");
final dateParts = parts[0].split(".");
final day = int.parse(dateParts[0]);
final month = int.parse(dateParts[1]);
final year = int.parse(dateParts[2]);
final timeParts = parts[1].split(":");
final hour = int.parse(timeParts[0]);
final minute = int.parse(timeParts[1]);
return DateTime(year, month, day, hour, minute);
}
String takeWhile(String input, Function charValidator) {
StringBuffer output = StringBuffer();
for (final char in input.codeUnits) {
if (charValidator(char)) output.writeCharCode(char);
else break;
}
return output.toString();
5 years ago
}