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
|
||||
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:info_tren/api/common.dart';
|
||||
import 'package:info_tren/models/train_data.dart';
|
||||
|
||||
const AUTHORITY = 'scraper.infotren.dcdevelop.xyz';
|
||||
|
||||
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);
|
||||
}
|
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:info_tren/models/ui_design.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/select_train/select_train.dart';
|
||||
|
||||
|
@ -36,6 +38,15 @@ Map<String, WidgetBuilder> routesByUiDesign(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 {
|
||||
|
|
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/pages/main/main_page_cupertino.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/utils/default_ui_design.dart';
|
||||
|
||||
|
@ -37,8 +38,9 @@ abstract class MainPageShared extends StatelessWidget {
|
|||
),
|
||||
MainPageOption(
|
||||
name: 'Tabelă plecari/sosiri',
|
||||
// TODO: Implement departure/arrival
|
||||
action: null,
|
||||
action: (context) {
|
||||
onStationBoardPageInvoke(context);
|
||||
},
|
||||
),
|
||||
MainPageOption(
|
||||
name: 'Planificare rută',
|
||||
|
@ -52,7 +54,7 @@ abstract class MainPageShared extends StatelessWidget {
|
|||
}
|
||||
|
||||
onStationBoardPageInvoke(BuildContext context) {
|
||||
|
||||
Navigator.of(context).pushNamed(SelectStationPage.routeName);
|
||||
}
|
||||
|
||||
onRoutePlanPageInvoke(BuildContext context) {
|
||||
|
|
|
@ -2,7 +2,9 @@ import 'package:flutter/widgets.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_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/api/stations.dart' as apiStations;
|
||||
|
||||
class SelectStationPage extends StatefulWidget {
|
||||
final UiDesign? uiDesign;
|
||||
|
@ -24,6 +26,55 @@ class SelectStationPage extends StatefulWidget {
|
|||
}
|
||||
|
||||
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();
|
||||
|
||||
}
|
||||
|
|
|
@ -1,9 +1,53 @@
|
|||
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';
|
||||
|
||||
class SelectStationPageStateCupertino extends SelectStationPageState {
|
||||
@override
|
||||
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 {
|
||||
@override
|
||||
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.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 2.4.1
|
||||
version: 2.5.0
|
||||
|
||||
environment:
|
||||
sdk: ">=2.12.0 <3.0.0"
|
||||
|
|
Loading…
Add table
Reference in a new issue