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
252 lines
6.5 KiB
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); |
|
} |