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.

252 lines
6.5 KiB

5 years ago
import 'dart:async';
import 'dart:convert';
import 'package:flutter/widgets.dart';
import 'package:http/http.dart' as http;
import 'package:json_annotation/json_annotation.dart';
import 'package:rxdart/subjects.dart';
part 'train_data.g.dart';
class TrainDataSourceWidget extends InheritedWidget {
final TrainDataSource dataSource;
@override
bool updateShouldNotify(InheritedWidget oldWidget) {
return true;
}
TrainDataSourceWidget({Widget child, Key key})
: dataSource = TrainDataSource(),
super(key: key, child: child);
static TrainDataSource of(BuildContext context) {
return (context.inheritFromWidgetOfExactType(TrainDataSourceWidget) as TrainDataSourceWidget).dataSource;
}
}
class TrainDataSource {
Future lookup(String trainNumber, [bool alternate = false]) async {
_controller.add(
TrainDataEvent(
trainNumber: trainNumber,
kind: TrainDataEventKind.LOADING
)
);
try {
final response = await http.get(
"http://cfr-scrapper.herokuapp.com/train/$trainNumber${alternate ? "?alt" : ""}", headers: {
"Authorization": "Flutter - Info Tren",
"X-Issued-By": "Flutter App"
}
);
if (200 <= response.statusCode && response.statusCode <= 299) {
// OK
final text = response.body;
final ser = json.decode(text
);
final data = TrainData.fromJson(ser
);
_controller.add(
TrainDataEvent(
kind: TrainDataEventKind.GOT_DATA,
trainNumber: trainNumber,
trainData: data
)
);
}
else if (response.statusCode == 404) {
_controller.addError(
TrainDataEvent(
kind: TrainDataEventKind.NOT_FOUND,
trainNumber: trainNumber,
errorStatusCode: 404
)
);
}
else if (response.statusCode == 503) {
_controller.addError(
TrainDataEvent(
kind: TrainDataEventKind.TIMEOUT,
trainNumber: trainNumber,
errorStatusCode: 503
)
);
}
else {
// Throw error
_controller.addError(
TrainDataEvent(
kind: TrainDataEventKind.UNKNOWN_ERROR,
trainNumber: trainNumber,
errorStatusCode: response.statusCode
)
);
}
}
catch (e) {
_controller.addError(
TrainDataEvent(
kind: TrainDataEventKind.UNKNOWN_ERROR,
trainNumber: trainNumber,
errorStatusCode: -1
)
);
}
}
TrainDataSource() {
_controller = BehaviorSubject<TrainDataEvent>();
}
dispose() {
_controller.close();
}
BehaviorSubject<TrainDataEvent> _controller;
Stream<TrainDataEvent> get dataStream => _controller.stream;
}
enum TrainDataEventKind {
LOADING,
TIMEOUT,
NOT_FOUND,
UNKNOWN_ERROR,
GOT_DATA
}
class TrainDataEvent {
final TrainData trainData;
final TrainDataEventKind kind;
final String trainNumber;
final int errorStatusCode;
TrainDataEvent({this.kind, this.trainData, this.trainNumber, this.errorStatusCode});
}
@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) {
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);
}