2019-07-23 16:56:51 +03:00
|
|
|
import 'dart:async';
|
|
|
|
import 'dart:convert';
|
|
|
|
|
|
|
|
import 'package:flutter/widgets.dart';
|
2019-09-15 02:33:07 +03:00
|
|
|
import 'package:info_tren/hidden_webview.dart';
|
|
|
|
import 'package:info_tren/utils/webview_invoke.dart';
|
2019-07-23 16:56:51 +03:00
|
|
|
import 'package:json_annotation/json_annotation.dart';
|
2019-09-15 02:33:07 +03:00
|
|
|
import 'package:webview_flutter/webview_flutter.dart';
|
2019-07-23 16:56:51 +03:00
|
|
|
|
|
|
|
part 'train_data.g.dart';
|
|
|
|
|
2019-09-15 02:33:07 +03:00
|
|
|
enum TrainLookupResult {
|
|
|
|
FOUND,
|
|
|
|
NOT_FOUND,
|
|
|
|
OTHER
|
|
|
|
}
|
|
|
|
|
|
|
|
class OnDemandInvalidatedException implements Exception {
|
|
|
|
final String propertyName;
|
|
|
|
final OnDemand onDemandClass;
|
|
|
|
|
|
|
|
OnDemandInvalidatedException({this.propertyName, this.onDemandClass});
|
2019-07-23 16:56:51 +03:00
|
|
|
|
|
|
|
@override
|
2019-09-15 02:33:07 +03:00
|
|
|
String toString() {
|
|
|
|
return "OnDemandInvalidatedException: An attempt to get $propertyName from ${onDemandClass.runtimeType} failed because the source was invalidated.";
|
2019-07-23 16:56:51 +03:00
|
|
|
}
|
2019-09-15 02:33:07 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
class OnDemand {
|
|
|
|
bool valid;
|
2019-07-23 16:56:51 +03:00
|
|
|
|
2019-09-15 02:33:07 +03:00
|
|
|
final Function onInvalidation;
|
2019-07-23 16:56:51 +03:00
|
|
|
|
2019-09-15 02:33:07 +03:00
|
|
|
void invalidate() {
|
|
|
|
if (valid) {
|
|
|
|
valid = false;
|
|
|
|
if (onInvalidation != null) onInvalidation();
|
|
|
|
}
|
2019-07-23 16:56:51 +03:00
|
|
|
}
|
2019-09-15 02:33:07 +03:00
|
|
|
|
|
|
|
OnDemand(this.onInvalidation): valid = true;
|
2019-07-23 16:56:51 +03:00
|
|
|
}
|
|
|
|
|
2019-09-15 02:33:07 +03:00
|
|
|
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,
|
2019-07-23 16:56:51 +03:00
|
|
|
);
|
|
|
|
|
2019-09-15 02:33:07 +03:00
|
|
|
return tempRes.split(" ").last;
|
|
|
|
}
|
2019-07-23 16:56:51 +03:00
|
|
|
|
2019-09-15 02:33:07 +03:00
|
|
|
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));
|
2019-07-23 16:56:51 +03:00
|
|
|
}
|
2019-09-15 02:33:07 +03:00
|
|
|
else if (input.startsWith("min", i)) {
|
|
|
|
result += Duration(minutes: int.parse(buffer.toString()));
|
|
|
|
buffer = StringBuffer();
|
|
|
|
i += 2;
|
2019-07-23 16:56:51 +03:00
|
|
|
}
|
2019-09-15 02:33:07 +03:00
|
|
|
else if (input.startsWith("h", i)) {
|
|
|
|
result += Duration(hours: int.parse(buffer.toString()));
|
|
|
|
buffer = StringBuffer();
|
2019-07-23 16:56:51 +03:00
|
|
|
}
|
2019-09-15 02:33:07 +03:00
|
|
|
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;
|
2021-08-22 17:18:55 +03:00
|
|
|
List<OnDemand> issuedOnDemands = [];
|
2019-09-15 02:33:07 +03:00
|
|
|
|
|
|
|
@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;
|
2019-07-23 16:56:51 +03:00
|
|
|
}
|
2019-09-15 02:33:07 +03:00
|
|
|
|
|
|
|
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,
|
2019-07-23 16:56:51 +03:00
|
|
|
);
|
2019-09-15 02:33:07 +03:00
|
|
|
issuedOnDemands.add(ods);
|
|
|
|
yield ods;
|
2019-07-23 16:56:51 +03:00
|
|
|
}
|
|
|
|
}
|
2019-09-15 02:33:07 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
class OnDemandStation extends OnDemand {
|
|
|
|
final WebViewController _controller;
|
|
|
|
final int index;
|
2019-07-23 16:56:51 +03:00
|
|
|
|
2019-09-15 02:33:07 +03:00
|
|
|
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,
|
|
|
|
);
|
2019-07-23 16:56:51 +03:00
|
|
|
}
|
|
|
|
|
2019-09-15 02:33:07 +03:00
|
|
|
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,
|
|
|
|
);
|
2019-07-23 16:56:51 +03:00
|
|
|
}
|
|
|
|
|
2019-09-15 02:33:07 +03:00
|
|
|
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,
|
|
|
|
);
|
|
|
|
}
|
2019-07-23 16:56:51 +03:00
|
|
|
}
|
|
|
|
|
2019-09-15 02:33:07 +03:00
|
|
|
enum RealOrEstimate {
|
|
|
|
real,
|
|
|
|
estimate,
|
|
|
|
UNKNOWN
|
2019-07-23 16:56:51 +03:00
|
|
|
}
|
|
|
|
|
2019-09-15 02:33:07 +03:00
|
|
|
class TrainDataWebViewAdapter extends StatefulWidget {
|
|
|
|
final WidgetBuilder builder;
|
|
|
|
|
|
|
|
TrainDataWebViewAdapter({@required this.builder});
|
|
|
|
|
|
|
|
@override
|
|
|
|
State<StatefulWidget> createState() {
|
|
|
|
return _TrainDataWebViewAdapterState();
|
|
|
|
}
|
2019-07-23 16:56:51 +03:00
|
|
|
|
2019-09-15 02:33:07 +03:00
|
|
|
static _TrainDataWebViewAdapterState of(BuildContext context) =>
|
2021-08-22 17:18:55 +03:00
|
|
|
(context.findAncestorWidgetOfExactType<_TrainDataWebViewAdapterInheritedWidget>())
|
2019-09-15 02:33:07 +03:00
|
|
|
.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;
|
|
|
|
}
|
|
|
|
|
2021-08-22 17:18:55 +03:00
|
|
|
List<OnDemand> currentDatas = [];
|
2019-09-15 02:33:07 +03:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2019-07-23 16:56:51 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
@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) {
|
2019-09-15 02:33:07 +03:00
|
|
|
if (dateAndTime == null || dateAndTime.isEmpty) return null;
|
|
|
|
|
2019-07-23 16:56:51 +03:00
|
|
|
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);
|
2019-09-15 02:33:07 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
2019-07-23 16:56:51 +03:00
|
|
|
}
|