Initial arrivals/departures support
This commit is contained in:
parent
39a9bf3321
commit
beb8bfb0f4
19 changed files with 741 additions and 9 deletions
|
@ -1,3 +1,6 @@
|
||||||
|
v2.5.0
|
||||||
|
Initial arrivals/departures support
|
||||||
|
|
||||||
v2.4.1
|
v2.4.1
|
||||||
Fixed DateTime (UTC -> local)
|
Fixed DateTime (UTC -> local)
|
||||||
|
|
||||||
|
|
1
lib/api/common.dart
Normal file
1
lib/api/common.dart
Normal file
|
@ -0,0 +1 @@
|
||||||
|
const authority = 'scraper.infotren.dcdevelop.xyz';
|
10
lib/api/station_data.dart
Normal file
10
lib/api/station_data.dart
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:info_tren/api/common.dart';
|
||||||
|
import 'package:info_tren/models/station_data.dart';
|
||||||
|
|
||||||
|
Future<StationData> getStationData(String stationName) async {
|
||||||
|
final response = await http.get(Uri.https(authority, 'v2/station/$stationName'));
|
||||||
|
return StationData.fromJson(jsonDecode(response.body));
|
||||||
|
}
|
11
lib/api/stations.dart
Normal file
11
lib/api/stations.dart
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:info_tren/api/common.dart';
|
||||||
|
import 'package:info_tren/models/stations_result.dart';
|
||||||
|
|
||||||
|
Future<List<StationsResult>> get stations async {
|
||||||
|
final result = await http.get(Uri.https(authority, 'v2/stations'));
|
||||||
|
final data = jsonDecode(result.body) as List<dynamic>;
|
||||||
|
return data.map((e) => StationsResult.fromJson(e)).toList(growable: false,);
|
||||||
|
}
|
|
@ -1,9 +1,8 @@
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:info_tren/api/common.dart';
|
||||||
import 'package:info_tren/models/train_data.dart';
|
import 'package:info_tren/models/train_data.dart';
|
||||||
|
|
||||||
const AUTHORITY = 'scraper.infotren.dcdevelop.xyz';
|
|
||||||
|
|
||||||
Future<TrainData> getTrain(String trainNumber) async {
|
Future<TrainData> getTrain(String trainNumber) async {
|
||||||
final response = await http.get(Uri.https(AUTHORITY, 'v2/train/$trainNumber'));
|
final response = await http.get(Uri.https(authority, 'v2/train/$trainNumber'));
|
||||||
return trainDataFromJson(response.body);
|
return trainDataFromJson(response.body);
|
||||||
}
|
}
|
43
lib/components/cupertino_listtile.dart
Normal file
43
lib/components/cupertino_listtile.dart
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
|
||||||
|
class CupertinoListTile extends StatelessWidget {
|
||||||
|
final Widget? leading;
|
||||||
|
final Widget? title;
|
||||||
|
final Widget? subtitle;
|
||||||
|
final Widget? trailing;
|
||||||
|
|
||||||
|
const CupertinoListTile({ Key? key, this.leading, this.title, this.subtitle, this.trailing, }) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
if (leading != null)
|
||||||
|
leading!,
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
if (title != null)
|
||||||
|
title!,
|
||||||
|
if (subtitle != null)
|
||||||
|
CupertinoTheme(
|
||||||
|
child: subtitle!,
|
||||||
|
data: CupertinoTheme.of(context).copyWith(
|
||||||
|
textTheme: CupertinoTextThemeData(
|
||||||
|
textStyle: TextStyle(
|
||||||
|
fontSize: CupertinoTheme.of(context).textTheme.textStyle.fontSize! - 2,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (trailing != null)
|
||||||
|
trailing!,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,8 @@ import 'package:flutter/material.dart';
|
||||||
// import 'package:flutter_redux/flutter_redux.dart';
|
// import 'package:flutter_redux/flutter_redux.dart';
|
||||||
import 'package:info_tren/models/ui_design.dart';
|
import 'package:info_tren/models/ui_design.dart';
|
||||||
import 'package:info_tren/pages/main/main_page.dart';
|
import 'package:info_tren/pages/main/main_page.dart';
|
||||||
|
import 'package:info_tren/pages/station_arrdep_page/select_station/select_station.dart';
|
||||||
|
import 'package:info_tren/pages/station_arrdep_page/view_station/view_station.dart';
|
||||||
import 'package:info_tren/pages/train_info_page/view_train/train_info.dart';
|
import 'package:info_tren/pages/train_info_page/view_train/train_info.dart';
|
||||||
import 'package:info_tren/pages/train_info_page/select_train/select_train.dart';
|
import 'package:info_tren/pages/train_info_page/select_train/select_train.dart';
|
||||||
|
|
||||||
|
@ -36,6 +38,15 @@ Map<String, WidgetBuilder> routesByUiDesign(UiDesign uiDesign) => {
|
||||||
uiDesign: uiDesign,
|
uiDesign: uiDesign,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
SelectStationPage.routeName: (context) {
|
||||||
|
return SelectStationPage(uiDesign: uiDesign,);
|
||||||
|
},
|
||||||
|
ViewStationPage.routeName: (context) {
|
||||||
|
return ViewStationPage(
|
||||||
|
stationName: ModalRoute.of(context)!.settings.arguments as String,
|
||||||
|
uiDesign: uiDesign,
|
||||||
|
);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
class StartPoint extends StatelessWidget {
|
class StartPoint extends StatelessWidget {
|
||||||
|
|
68
lib/models/station_data.dart
Normal file
68
lib/models/station_data.dart
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
|
part 'station_data.g.dart';
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class StationData {
|
||||||
|
final String date;
|
||||||
|
final String stationName;
|
||||||
|
final List<StationArrival>? arrivals;
|
||||||
|
final List<StationDeparture>? departures;
|
||||||
|
|
||||||
|
const StationData({required this.date, required this.stationName, required this.arrivals, required this.departures});
|
||||||
|
|
||||||
|
factory StationData.fromJson(Map<String, dynamic> json) => _$StationDataFromJson(json);
|
||||||
|
Map<String, dynamic> toJson() => _$StationDataToJson(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class StationArrival {
|
||||||
|
final int? stoppingTime;
|
||||||
|
final DateTime time;
|
||||||
|
final StationTrainArr train;
|
||||||
|
|
||||||
|
const StationArrival({required this.stoppingTime, required this.time, required this.train,});
|
||||||
|
|
||||||
|
factory StationArrival.fromJson(Map<String, dynamic> json) => _$StationArrivalFromJson(json);
|
||||||
|
Map<String, dynamic> toJson() => _$StationArrivalToJson(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class StationDeparture {
|
||||||
|
final int? stoppingTime;
|
||||||
|
final DateTime time;
|
||||||
|
final StationTrainDep train;
|
||||||
|
|
||||||
|
const StationDeparture({required this.stoppingTime, required this.time, required this.train,});
|
||||||
|
|
||||||
|
factory StationDeparture.fromJson(Map<String, dynamic> json) => _$StationDepartureFromJson(json);
|
||||||
|
Map<String, dynamic> toJson() => _$StationDepartureToJson(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class StationTrainArr {
|
||||||
|
final String rank;
|
||||||
|
final String number;
|
||||||
|
final String operator;
|
||||||
|
final String origin;
|
||||||
|
final List<String>? route;
|
||||||
|
|
||||||
|
StationTrainArr({required this.rank, required this.number, required this.operator, required this.origin, this.route,});
|
||||||
|
|
||||||
|
factory StationTrainArr.fromJson(Map<String, dynamic> json) => _$StationTrainArrFromJson(json);
|
||||||
|
Map<String, dynamic> toJson() => _$StationTrainArrToJson(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class StationTrainDep {
|
||||||
|
final String rank;
|
||||||
|
final String number;
|
||||||
|
final String operator;
|
||||||
|
final String destination;
|
||||||
|
final List<String>? route;
|
||||||
|
|
||||||
|
StationTrainDep({required this.rank, required this.number, required this.operator, required this.destination, this.route,});
|
||||||
|
|
||||||
|
factory StationTrainDep.fromJson(Map<String, dynamic> json) => _$StationTrainDepFromJson(json);
|
||||||
|
Map<String, dynamic> toJson() => _$StationTrainDepToJson(this);
|
||||||
|
}
|
92
lib/models/station_data.g.dart
Normal file
92
lib/models/station_data.g.dart
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'station_data.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
StationData _$StationDataFromJson(Map<String, dynamic> json) => StationData(
|
||||||
|
date: json['date'] as String,
|
||||||
|
stationName: json['stationName'] as String,
|
||||||
|
arrivals: (json['arrivals'] as List<dynamic>?)
|
||||||
|
?.map((e) => StationArrival.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
departures: (json['departures'] as List<dynamic>?)
|
||||||
|
?.map((e) => StationDeparture.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$StationDataToJson(StationData instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'date': instance.date,
|
||||||
|
'stationName': instance.stationName,
|
||||||
|
'arrivals': instance.arrivals,
|
||||||
|
'departures': instance.departures,
|
||||||
|
};
|
||||||
|
|
||||||
|
StationArrival _$StationArrivalFromJson(Map<String, dynamic> json) =>
|
||||||
|
StationArrival(
|
||||||
|
stoppingTime: json['stoppingTime'] as int?,
|
||||||
|
time: DateTime.parse(json['time'] as String),
|
||||||
|
train: StationTrainArr.fromJson(json['train'] as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$StationArrivalToJson(StationArrival instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'stoppingTime': instance.stoppingTime,
|
||||||
|
'time': instance.time.toIso8601String(),
|
||||||
|
'train': instance.train,
|
||||||
|
};
|
||||||
|
|
||||||
|
StationDeparture _$StationDepartureFromJson(Map<String, dynamic> json) =>
|
||||||
|
StationDeparture(
|
||||||
|
stoppingTime: json['stoppingTime'] as int?,
|
||||||
|
time: DateTime.parse(json['time'] as String),
|
||||||
|
train: StationTrainDep.fromJson(json['train'] as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$StationDepartureToJson(StationDeparture instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'stoppingTime': instance.stoppingTime,
|
||||||
|
'time': instance.time.toIso8601String(),
|
||||||
|
'train': instance.train,
|
||||||
|
};
|
||||||
|
|
||||||
|
StationTrainArr _$StationTrainArrFromJson(Map<String, dynamic> json) =>
|
||||||
|
StationTrainArr(
|
||||||
|
rank: json['rank'] as String,
|
||||||
|
number: json['number'] as String,
|
||||||
|
operator: json['operator'] as String,
|
||||||
|
origin: json['origin'] as String,
|
||||||
|
route:
|
||||||
|
(json['route'] as List<dynamic>?)?.map((e) => e as String).toList(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$StationTrainArrToJson(StationTrainArr instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'rank': instance.rank,
|
||||||
|
'number': instance.number,
|
||||||
|
'operator': instance.operator,
|
||||||
|
'origin': instance.origin,
|
||||||
|
'route': instance.route,
|
||||||
|
};
|
||||||
|
|
||||||
|
StationTrainDep _$StationTrainDepFromJson(Map<String, dynamic> json) =>
|
||||||
|
StationTrainDep(
|
||||||
|
rank: json['rank'] as String,
|
||||||
|
number: json['number'] as String,
|
||||||
|
operator: json['operator'] as String,
|
||||||
|
destination: json['destination'] as String,
|
||||||
|
route:
|
||||||
|
(json['route'] as List<dynamic>?)?.map((e) => e as String).toList(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$StationTrainDepToJson(StationTrainDep instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'rank': instance.rank,
|
||||||
|
'number': instance.number,
|
||||||
|
'operator': instance.operator,
|
||||||
|
'destination': instance.destination,
|
||||||
|
'route': instance.route,
|
||||||
|
};
|
14
lib/models/stations_result.dart
Normal file
14
lib/models/stations_result.dart
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
|
part 'stations_result.g.dart';
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class StationsResult {
|
||||||
|
final String name;
|
||||||
|
final List<String>? stoppedAtBy;
|
||||||
|
|
||||||
|
const StationsResult({required this.name, this.stoppedAtBy});
|
||||||
|
|
||||||
|
factory StationsResult.fromJson(Map<String, dynamic> json) => _$StationsResultFromJson(json);
|
||||||
|
Map<String, dynamic> toJson() => _$StationsResultToJson(this);
|
||||||
|
}
|
21
lib/models/stations_result.g.dart
Normal file
21
lib/models/stations_result.g.dart
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'stations_result.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
StationsResult _$StationsResultFromJson(Map<String, dynamic> json) =>
|
||||||
|
StationsResult(
|
||||||
|
name: json['name'] as String,
|
||||||
|
stoppedAtBy: (json['stoppedAtBy'] as List<dynamic>?)
|
||||||
|
?.map((e) => e as String)
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$StationsResultToJson(StationsResult instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'name': instance.name,
|
||||||
|
'stoppedAtBy': instance.stoppedAtBy,
|
||||||
|
};
|
|
@ -2,6 +2,7 @@ import 'package:flutter/widgets.dart';
|
||||||
import 'package:info_tren/models/ui_design.dart';
|
import 'package:info_tren/models/ui_design.dart';
|
||||||
import 'package:info_tren/pages/main/main_page_cupertino.dart';
|
import 'package:info_tren/pages/main/main_page_cupertino.dart';
|
||||||
import 'package:info_tren/pages/main/main_page_material.dart';
|
import 'package:info_tren/pages/main/main_page_material.dart';
|
||||||
|
import 'package:info_tren/pages/station_arrdep_page/select_station/select_station.dart';
|
||||||
import 'package:info_tren/pages/train_info_page/select_train/select_train.dart';
|
import 'package:info_tren/pages/train_info_page/select_train/select_train.dart';
|
||||||
import 'package:info_tren/utils/default_ui_design.dart';
|
import 'package:info_tren/utils/default_ui_design.dart';
|
||||||
|
|
||||||
|
@ -37,8 +38,9 @@ abstract class MainPageShared extends StatelessWidget {
|
||||||
),
|
),
|
||||||
MainPageOption(
|
MainPageOption(
|
||||||
name: 'Tabelă plecari/sosiri',
|
name: 'Tabelă plecari/sosiri',
|
||||||
// TODO: Implement departure/arrival
|
action: (context) {
|
||||||
action: null,
|
onStationBoardPageInvoke(context);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
MainPageOption(
|
MainPageOption(
|
||||||
name: 'Planificare rută',
|
name: 'Planificare rută',
|
||||||
|
@ -52,7 +54,7 @@ abstract class MainPageShared extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
onStationBoardPageInvoke(BuildContext context) {
|
onStationBoardPageInvoke(BuildContext context) {
|
||||||
|
Navigator.of(context).pushNamed(SelectStationPage.routeName);
|
||||||
}
|
}
|
||||||
|
|
||||||
onRoutePlanPageInvoke(BuildContext context) {
|
onRoutePlanPageInvoke(BuildContext context) {
|
||||||
|
|
|
@ -2,7 +2,9 @@ import 'package:flutter/widgets.dart';
|
||||||
import 'package:info_tren/models/ui_design.dart';
|
import 'package:info_tren/models/ui_design.dart';
|
||||||
import 'package:info_tren/pages/station_arrdep_page/select_station/select_station_cupertino.dart';
|
import 'package:info_tren/pages/station_arrdep_page/select_station/select_station_cupertino.dart';
|
||||||
import 'package:info_tren/pages/station_arrdep_page/select_station/select_station_material.dart';
|
import 'package:info_tren/pages/station_arrdep_page/select_station/select_station_material.dart';
|
||||||
|
import 'package:info_tren/pages/station_arrdep_page/view_station/view_station.dart';
|
||||||
import 'package:info_tren/utils/default_ui_design.dart';
|
import 'package:info_tren/utils/default_ui_design.dart';
|
||||||
|
import 'package:info_tren/api/stations.dart' as apiStations;
|
||||||
|
|
||||||
class SelectStationPage extends StatefulWidget {
|
class SelectStationPage extends StatefulWidget {
|
||||||
final UiDesign? uiDesign;
|
final UiDesign? uiDesign;
|
||||||
|
@ -24,6 +26,55 @@ class SelectStationPage extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class SelectStationPageState extends State<SelectStationPage> {
|
abstract class SelectStationPageState extends State<SelectStationPage> {
|
||||||
|
static const pageTitle = 'Plecări/sosiri stație';
|
||||||
|
static const textFieldLabel = 'Numele stației';
|
||||||
|
static const roToEn = {
|
||||||
|
'ă': 'a',
|
||||||
|
'â': 'a',
|
||||||
|
'î': 'i',
|
||||||
|
'ș': 's',
|
||||||
|
'ț': 't',
|
||||||
|
};
|
||||||
|
List<String> stations = [];
|
||||||
|
|
||||||
|
List<String> get filteredStations {
|
||||||
|
final filter = textEditingController.text
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
.replaceAllMapped(RegExp(r'[ăâîșț]'), (match) => roToEn[match[0]!]!);
|
||||||
|
if (filter.isEmpty) {
|
||||||
|
return stations;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stations.where(
|
||||||
|
(e) => e
|
||||||
|
.toLowerCase()
|
||||||
|
.replaceAllMapped(RegExp(r'[ăâîșț]'), (match) {
|
||||||
|
return roToEn[match[0]!]!;
|
||||||
|
})
|
||||||
|
.contains(filter)
|
||||||
|
).toList(growable: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
apiStations.stations.then((value) {
|
||||||
|
setState(() {
|
||||||
|
stations = value.map((e) => e.name).toList(growable: false,);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void onTextChanged(String newText) {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void onSuggestionSelected(String suggestion) {
|
||||||
|
Navigator.of(context).pushNamed(ViewStationPage.routeName, arguments: suggestion);
|
||||||
|
}
|
||||||
|
|
||||||
final TextEditingController textEditingController = TextEditingController();
|
final TextEditingController textEditingController = TextEditingController();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,53 @@
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:info_tren/components/cupertino_divider.dart';
|
||||||
import 'package:info_tren/pages/station_arrdep_page/select_station/select_station.dart';
|
import 'package:info_tren/pages/station_arrdep_page/select_station/select_station.dart';
|
||||||
|
|
||||||
class SelectStationPageStateCupertino extends SelectStationPageState {
|
class SelectStationPageStateCupertino extends SelectStationPageState {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container();
|
return CupertinoPageScaffold(
|
||||||
|
navigationBar: CupertinoNavigationBar(
|
||||||
|
middle: Text(SelectStationPageState.pageTitle),
|
||||||
|
),
|
||||||
|
child: SafeArea(
|
||||||
|
bottom: false,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
child: CupertinoTextField(
|
||||||
|
controller: textEditingController,
|
||||||
|
autofocus: true,
|
||||||
|
placeholder: SelectStationPageState.textFieldLabel,
|
||||||
|
textInputAction: TextInputAction.search,
|
||||||
|
onChanged: onTextChanged,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Text(filteredStations[index]),
|
||||||
|
),
|
||||||
|
onTap: () => onSuggestionSelected(filteredStations[index]),
|
||||||
|
),
|
||||||
|
CupertinoDivider(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
itemCount: filteredStations.length,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,52 @@ import 'package:info_tren/pages/station_arrdep_page/select_station/select_statio
|
||||||
class SelectStationPageStateMaterial extends SelectStationPageState {
|
class SelectStationPageStateMaterial extends SelectStationPageState {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container();
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(SelectStationPageState.pageTitle),
|
||||||
|
centerTitle: true,
|
||||||
|
),
|
||||||
|
body: SafeArea(
|
||||||
|
bottom: false,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
child: TextField(
|
||||||
|
controller: textEditingController,
|
||||||
|
autofocus: true,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
labelText: SelectStationPageState.textFieldLabel,
|
||||||
|
),
|
||||||
|
textInputAction: TextInputAction.search,
|
||||||
|
onChanged: onTextChanged,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
dense: true,
|
||||||
|
title: Text(filteredStations[index]),
|
||||||
|
onTap: () => onSuggestionSelected(filteredStations[index]),
|
||||||
|
),
|
||||||
|
Divider(
|
||||||
|
height: 1,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
itemCount: filteredStations.length,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
89
lib/pages/station_arrdep_page/view_station/view_station.dart
Normal file
89
lib/pages/station_arrdep_page/view_station/view_station.dart
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:info_tren/api/station_data.dart';
|
||||||
|
import 'package:info_tren/components/refresh_future_builder.dart';
|
||||||
|
import 'package:info_tren/models/station_data.dart';
|
||||||
|
import 'package:info_tren/models/ui_design.dart';
|
||||||
|
import 'package:info_tren/pages/station_arrdep_page/view_station/view_station_cupertino.dart';
|
||||||
|
import 'package:info_tren/pages/station_arrdep_page/view_station/view_station_material.dart';
|
||||||
|
import 'package:info_tren/pages/train_info_page/view_train/train_info.dart';
|
||||||
|
import 'package:info_tren/utils/default_ui_design.dart';
|
||||||
|
|
||||||
|
class ViewStationPage extends StatefulWidget {
|
||||||
|
final UiDesign? uiDesign;
|
||||||
|
final String stationName;
|
||||||
|
|
||||||
|
const ViewStationPage({ Key? key, required this.stationName, this.uiDesign }) : super(key: key);
|
||||||
|
|
||||||
|
static String routeName = '/stationArrDep/viewStation';
|
||||||
|
|
||||||
|
@override
|
||||||
|
ViewStationPageState createState() {
|
||||||
|
final uiDesign = this.uiDesign ?? defaultUiDesign;
|
||||||
|
switch (uiDesign) {
|
||||||
|
case UiDesign.MATERIAL:
|
||||||
|
return ViewStationPageStateMaterial();
|
||||||
|
case UiDesign.CUPERTINO:
|
||||||
|
return ViewStationPageStateCupertino();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class ViewStationPageState extends State<ViewStationPage> {
|
||||||
|
static const arrivals = 'Sosiri';
|
||||||
|
static const departures = 'Pleacări';
|
||||||
|
static const loadingText = 'Se încarcă...';
|
||||||
|
static const arrivesFrom = 'Sosește de la';
|
||||||
|
static const departsTo = 'Pleacă către';
|
||||||
|
|
||||||
|
ViewStationPageTab tab = ViewStationPageTab.departures;
|
||||||
|
late String stationName;
|
||||||
|
late Future<StationData> Function() futureCreator;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
initData();
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
if (stationName != widget.stationName) {
|
||||||
|
setState(() {
|
||||||
|
initData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
super.didChangeDependencies();
|
||||||
|
}
|
||||||
|
|
||||||
|
void initData() {
|
||||||
|
stationName = widget.stationName;
|
||||||
|
futureCreator = () => getStationData(stationName);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildContent(BuildContext context, Future Function() refresh, RefreshFutureBuilderSnapshot<StationData> snapshot);
|
||||||
|
Widget buildStationArrivalItem(BuildContext context, StationArrival item);
|
||||||
|
Widget buildStationDepartureItem(BuildContext context, StationDeparture item);
|
||||||
|
|
||||||
|
void onTabChange(int index) {
|
||||||
|
setState(() {
|
||||||
|
tab = ViewStationPageTab.values[index];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void onTrainTapped(String trainNumber) {
|
||||||
|
Navigator.of(context).pushNamed(TrainInfo.routeName, arguments: trainNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return RefreshFutureBuilder(
|
||||||
|
futureCreator: futureCreator,
|
||||||
|
builder: buildContent,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ViewStationPageTab {
|
||||||
|
arrivals,
|
||||||
|
departures,
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:info_tren/components/loading/loading.dart';
|
||||||
|
import 'package:info_tren/components/refresh_future_builder.dart';
|
||||||
|
import 'package:flutter/src/widgets/framework.dart';
|
||||||
|
import 'package:info_tren/components/sliver_persistent_header_padding.dart';
|
||||||
|
import 'package:info_tren/models/station_data.dart';
|
||||||
|
import 'package:info_tren/pages/station_arrdep_page/view_station/view_station.dart';
|
||||||
|
|
||||||
|
class ViewStationPageStateCupertino extends ViewStationPageState {
|
||||||
|
@override
|
||||||
|
Widget buildContent(BuildContext context, Future Function() refresh, RefreshFutureBuilderSnapshot<StationData> snapshot) {
|
||||||
|
return CupertinoPageScaffold(
|
||||||
|
navigationBar: CupertinoNavigationBar(
|
||||||
|
middle: Text(snapshot.hasData ? snapshot.data!.stationName : stationName),
|
||||||
|
),
|
||||||
|
child: snapshot.hasData ? CupertinoTabScaffold(
|
||||||
|
tabBar: CupertinoTabBar(
|
||||||
|
items: [
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: Icon(CupertinoIcons.arrow_down),
|
||||||
|
label: ViewStationPageState.arrivals,
|
||||||
|
),
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: Icon(CupertinoIcons.arrow_up),
|
||||||
|
label: ViewStationPageState.departures,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onTap: onTabChange,
|
||||||
|
currentIndex: tab.index,
|
||||||
|
),
|
||||||
|
tabBuilder: (context, index) {
|
||||||
|
final topPadding = MediaQuery.of(context).padding.top;
|
||||||
|
return NestedScrollView(
|
||||||
|
headerSliverBuilder: (context, _) {
|
||||||
|
return [SliverPersistentHeaderPadding(maxHeight: topPadding)];
|
||||||
|
},
|
||||||
|
body: CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
SliverList(
|
||||||
|
delegate: SliverChildBuilderDelegate(
|
||||||
|
(context, index) {
|
||||||
|
return tab == ViewStationPageTab.arrivals ? buildStationArrivalItem(context, snapshot.data!.arrivals![index]) : buildStationDepartureItem(context, snapshot.data!.departures![index]);
|
||||||
|
},
|
||||||
|
childCount: tab == ViewStationPageTab.arrivals ? snapshot.data!.arrivals?.length ?? 0 : snapshot.data!.departures?.length ?? 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
) : snapshot.state == RefreshFutureBuilderState.waiting ? Loading(text: ViewStationPageState.loadingText, uiDesign: widget.uiDesign,) : Container(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildStationArrivalItem(BuildContext context, StationArrival item) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () => onTrainTapped(item.train.number),
|
||||||
|
child: CupertinoFormRow(
|
||||||
|
child: Text('${item.time.toLocal().hour.toString().padLeft(2, '0')}:${item.time.toLocal().minute.toString().padLeft(2, '0')}'),
|
||||||
|
prefix: Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: item.train.rank,
|
||||||
|
style: TextStyle(
|
||||||
|
color: item.train.rank.startsWith('IR') ? Color.fromARGB(255, 255, 0, 0) : null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(text: ' '),
|
||||||
|
TextSpan(text: item.train.number,),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
helper: Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(text: ViewStationPageState.arrivesFrom),
|
||||||
|
TextSpan(text: ' '),
|
||||||
|
TextSpan(text: item.train.origin),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildStationDepartureItem(BuildContext context, StationDeparture item) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () => onTrainTapped(item.train.number),
|
||||||
|
child: CupertinoFormRow(
|
||||||
|
child: Text('${item.time.toLocal().hour.toString().padLeft(2, '0')}:${item.time.toLocal().minute.toString().padLeft(2, '0')}'),
|
||||||
|
prefix: Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: item.train.rank,
|
||||||
|
style: TextStyle(
|
||||||
|
color: item.train.rank.startsWith('IR') ? Color.fromARGB(255, 255, 0, 0) : null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(text: ' '),
|
||||||
|
TextSpan(text: item.train.number,),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
helper: Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(text: ViewStationPageState.departsTo),
|
||||||
|
TextSpan(text: ' '),
|
||||||
|
TextSpan(text: item.train.destination),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:info_tren/components/loading/loading.dart';
|
||||||
|
import 'package:info_tren/components/refresh_future_builder.dart';
|
||||||
|
import 'package:flutter/src/widgets/framework.dart';
|
||||||
|
import 'package:info_tren/models/station_data.dart';
|
||||||
|
import 'package:info_tren/pages/station_arrdep_page/view_station/view_station.dart';
|
||||||
|
|
||||||
|
class ViewStationPageStateMaterial extends ViewStationPageState {
|
||||||
|
@override
|
||||||
|
Widget buildContent(BuildContext context, Future Function() refresh, RefreshFutureBuilderSnapshot<StationData> snapshot) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(snapshot.hasData ? snapshot.data!.stationName : stationName),
|
||||||
|
centerTitle: true,
|
||||||
|
),
|
||||||
|
body: snapshot.state == RefreshFutureBuilderState.waiting ? Loading(text: ViewStationPageState.loadingText, uiDesign: widget.uiDesign,) : CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
SliverToBoxAdapter(child: SafeArea(child: Container(), left: false, bottom: false, right: false,),),
|
||||||
|
SliverList(
|
||||||
|
delegate: SliverChildBuilderDelegate(
|
||||||
|
(context, index) {
|
||||||
|
return tab == ViewStationPageTab.arrivals ? buildStationArrivalItem(context, snapshot.data!.arrivals![index]) : buildStationDepartureItem(context, snapshot.data!.departures![index]);
|
||||||
|
},
|
||||||
|
childCount: tab == ViewStationPageTab.arrivals ? snapshot.data!.arrivals?.length ?? 0 : snapshot.data!.departures?.length ?? 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
bottomNavigationBar: snapshot.hasData ? BottomNavigationBar(
|
||||||
|
items: [
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: Icon(Icons.arrow_downward),
|
||||||
|
label: ViewStationPageState.arrivals,
|
||||||
|
),
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: Icon(Icons.arrow_upward),
|
||||||
|
label: ViewStationPageState.departures,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
currentIndex: tab.index,
|
||||||
|
onTap: onTabChange,
|
||||||
|
) : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildStationArrivalItem(BuildContext context, StationArrival item) {
|
||||||
|
return ListTile(
|
||||||
|
leading: Text('${item.time.toLocal().hour.toString().padLeft(2, '0')}:${item.time.toLocal().minute.toString().padLeft(2, '0')}'),
|
||||||
|
title: Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: item.train.rank,
|
||||||
|
style: TextStyle(
|
||||||
|
color: item.train.rank.startsWith('IR') ? Color.fromARGB(255, 255, 0, 0) : null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(text: ' '),
|
||||||
|
TextSpan(text: item.train.number,),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
subtitle: Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(text: ViewStationPageState.arrivesFrom),
|
||||||
|
TextSpan(text: ' '),
|
||||||
|
TextSpan(text: item.train.origin),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () => onTrainTapped(item.train.number),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildStationDepartureItem(BuildContext context, StationDeparture item) {
|
||||||
|
return ListTile(
|
||||||
|
leading: Text('${item.time.toLocal().hour.toString().padLeft(2, '0')}:${item.time.toLocal().minute.toString().padLeft(2, '0')}'),
|
||||||
|
title: Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: item.train.rank,
|
||||||
|
style: TextStyle(
|
||||||
|
color: item.train.rank.startsWith('IR') ? Color.fromARGB(255, 255, 0, 0) : null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(text: ' '),
|
||||||
|
TextSpan(text: item.train.number,),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
subtitle: Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(text: ViewStationPageState.departsTo),
|
||||||
|
TextSpan(text: ' '),
|
||||||
|
TextSpan(text: item.train.destination),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () => onTrainTapped(item.train.number),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,7 +11,7 @@ description: O aplicație de vizualizare a datelor puse la dispoziție de Inform
|
||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
version: 2.4.1
|
version: 2.5.0
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.12.0 <3.0.0"
|
sdk: ">=2.12.0 <3.0.0"
|
||||||
|
|
Loading…
Add table
Reference in a new issue