Browse Source

v2.5.0

Initial arrivals/departures support
tmp v2.5.0
Dan Cojocaru 3 years ago
parent
commit
b676fc95c1
  1. 3
      CHANGELOG.TXT
  2. 1
      lib/api/common.dart
  3. 10
      lib/api/station_data.dart
  4. 11
      lib/api/stations.dart
  5. 5
      lib/api/train_data.dart
  6. 43
      lib/components/cupertino_listtile.dart
  7. 11
      lib/main.dart
  8. 68
      lib/models/station_data.dart
  9. 92
      lib/models/station_data.g.dart
  10. 14
      lib/models/stations_result.dart
  11. 21
      lib/models/stations_result.g.dart
  12. 8
      lib/pages/main/main_page.dart
  13. 51
      lib/pages/station_arrdep_page/select_station/select_station.dart
  14. 46
      lib/pages/station_arrdep_page/select_station/select_station_cupertino.dart
  15. 48
      lib/pages/station_arrdep_page/select_station/select_station_material.dart
  16. 89
      lib/pages/station_arrdep_page/view_station/view_station.dart
  17. 120
      lib/pages/station_arrdep_page/view_station/view_station_cupertino.dart
  18. 107
      lib/pages/station_arrdep_page/view_station/view_station_material.dart
  19. 2
      pubspec.yaml

3
CHANGELOG.TXT

@ -1,3 +1,6 @@
v2.5.0
Initial arrivals/departures support
v2.4.1
Fixed DateTime (UTC -> local)

1
lib/api/common.dart

@ -0,0 +1 @@
const authority = 'scraper.infotren.dcdevelop.xyz';

10
lib/api/station_data.dart

@ -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

@ -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,);
}

5
lib/api/train_data.dart

@ -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

@ -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!,
],
);
}
}

11
lib/main.dart

@ -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

@ -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

@ -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

@ -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

@ -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,
};

8
lib/pages/main/main_page.dart

@ -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) {

51
lib/pages/station_arrdep_page/select_station/select_station.dart

@ -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();
}

46
lib/pages/station_arrdep_page/select_station/select_station_cupertino.dart

@ -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,
),
),
],
),
),
);
}
}

48
lib/pages/station_arrdep_page/select_station/select_station_material.dart

@ -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

@ -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,
}

120
lib/pages/station_arrdep_page/view_station/view_station_cupertino.dart

@ -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),
],
),
),
),
);
}
}

107
lib/pages/station_arrdep_page/view_station/view_station_material.dart

@ -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),
);
}
}

2
pubspec.yaml

@ -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…
Cancel
Save