382 lines
No EOL
11 KiB
Dart
382 lines
No EOL
11 KiB
Dart
import 'dart:convert';
|
|
import 'dart:io' show Platform;
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:info_tren/train_info_page/train_info.dart';
|
|
import 'package:json_annotation/json_annotation.dart';
|
|
import 'package:tuple/tuple.dart';
|
|
|
|
part 'train_info_prompt.g.dart';
|
|
|
|
typedef TrainSelectedCallback(int trainNumber);
|
|
|
|
mixin TrainInfoPromptCommon {
|
|
static String routeName = "/trainInfo/chooseTrain";
|
|
|
|
onTrainSelected(BuildContext context, int selection) {
|
|
Navigator.of(context).pushNamed(TrainInfo.routeName, arguments: selection);
|
|
}
|
|
}
|
|
|
|
mixin TrainInfoPromptListHandling {
|
|
List<TrainOperatorLines> operators = List();
|
|
|
|
Future loadOperators(BuildContext context) async {
|
|
operators = List();
|
|
|
|
final operatorsString = await DefaultAssetBundle.of(context).loadString("assets/lines/files.txt");
|
|
final operatorsFilesList = operatorsString.split("\n");
|
|
|
|
final decoder = JsonDecoder();
|
|
|
|
for (final operatorFile in operatorsFilesList) {
|
|
final operatorString = await DefaultAssetBundle.of(context).loadString("assets/lines/$operatorFile");
|
|
final operatorData = decoder.convert(operatorString);
|
|
final _operator = TrainOperatorLines.fromJson(operatorData);
|
|
operators.add(_operator);
|
|
}
|
|
}
|
|
|
|
Widget getOperatorsListView(BuildContext context, {String currentInput = "", @required TrainSelectedCallback onTrainSelected}) {
|
|
var sliversTuple = operators.map(
|
|
(op) => Tuple2(
|
|
getFilteredLines(op, currentInput),
|
|
getOperatorSliver(context, op, currentInput, onTrainSelected)
|
|
)
|
|
)
|
|
.where((tuple) => tuple.item1.isNotEmpty).toList();
|
|
if (currentInput.isNotEmpty) sliversTuple.sort((a, b) {
|
|
final aTrain = a.item1.first;
|
|
final bTrain = b.item1.first;
|
|
|
|
final inputAsRegExp = RegExp(currentInput);
|
|
|
|
final matchOnA = inputAsRegExp.firstMatch(aTrain.number);
|
|
final matchOnB = inputAsRegExp.firstMatch(bTrain.number);
|
|
|
|
if (matchOnA.start != matchOnB.start) return matchOnA.start - matchOnB.start;
|
|
|
|
if (aTrain.number.length != bTrain.number.length) return aTrain.number.length - bTrain.number.length;
|
|
|
|
return aTrain.number.compareTo(bTrain.number);
|
|
});
|
|
var slivers = sliversTuple.map((tuple) => tuple.item2).toList();
|
|
|
|
return CustomScrollView(
|
|
slivers: <Widget>[
|
|
...slivers,
|
|
SliverToBoxAdapter(
|
|
child: getUseCurrentInputWidget(currentInput, onTrainSelected),
|
|
),
|
|
SliverToBoxAdapter(
|
|
child: Container(
|
|
height: MediaQuery.of(context).viewPadding.bottom,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget getUseCurrentInputWidget(String currentInput, TrainSelectedCallback onTrainSelected) {
|
|
if (currentInput.isEmpty) {
|
|
return Container();
|
|
}
|
|
|
|
if (int.tryParse(currentInput) == null) {
|
|
return Container();
|
|
}
|
|
|
|
return Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: <Widget>[
|
|
if (Platform.isAndroid)
|
|
ListTile(
|
|
title: Text("Caută trenul cu numărul $currentInput"),
|
|
onTap: () {
|
|
onTrainSelected(int.parse(currentInput));
|
|
},
|
|
)
|
|
else if (Platform.isIOS)
|
|
GestureDetector(
|
|
onTap: () {
|
|
onTrainSelected(int.parse(currentInput));
|
|
},
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(8),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: <Widget>[
|
|
Text("Caută trenul cu numărul $currentInput")
|
|
],
|
|
)
|
|
),
|
|
),
|
|
Divider(),
|
|
],
|
|
);
|
|
}
|
|
|
|
List<_TrainOperatorTrainDescription> getFilteredLines(TrainOperatorLines _operator, String currentInput) {
|
|
if (currentInput.isNotEmpty) {
|
|
final filteredLines = _operator.trains
|
|
.where((elem) => elem.number.contains(currentInput))
|
|
.toList();
|
|
|
|
filteredLines.sort((a, b) {
|
|
final inputAsRegExp = RegExp(currentInput);
|
|
|
|
final matchOnA = inputAsRegExp.firstMatch(a.number);
|
|
final matchOnB = inputAsRegExp.firstMatch(b.number);
|
|
|
|
if (matchOnA.start != matchOnB.start) return matchOnA.start - matchOnB.start;
|
|
|
|
if (a.number.length != b.number.length) return a.number.length - b.number.length;
|
|
|
|
return a.number.compareTo(b.number);
|
|
});
|
|
|
|
return filteredLines;
|
|
}
|
|
else {
|
|
return _operator.trains;
|
|
}
|
|
}
|
|
|
|
Widget getOperatorSliver(BuildContext context, TrainOperatorLines _operator, String currentInput, TrainSelectedCallback onTrainSelected) {
|
|
final filteredLines = getFilteredLines(_operator, currentInput);
|
|
|
|
if (filteredLines.isEmpty) {
|
|
return SliverToBoxAdapter(child: Container(),);
|
|
}
|
|
|
|
return SliverPrototypeExtentList(
|
|
prototypeItem: Column(
|
|
children: <Widget>[
|
|
getLineListItem(
|
|
context,
|
|
op: TrainOperatorLines(),
|
|
line: _TrainOperatorTrainDescription()
|
|
),
|
|
Divider(),
|
|
],
|
|
),
|
|
delegate: SliverChildBuilderDelegate(
|
|
(context, index) {
|
|
return Column(
|
|
children: <Widget>[
|
|
getLineListItem(
|
|
context,
|
|
op: _operator,
|
|
line: filteredLines[index],
|
|
onTrainSelected: onTrainSelected
|
|
),
|
|
Divider(),
|
|
],
|
|
);
|
|
},
|
|
childCount: filteredLines.length,
|
|
addSemanticIndexes: true,
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget getLineListItem(BuildContext context, {TrainOperatorLines op, _TrainOperatorTrainDescription line, TrainSelectedCallback onTrainSelected}) {
|
|
if (Platform.isAndroid) {
|
|
return ListTile(
|
|
dense: true,
|
|
title: Text("${line.rang ?? ""} ${line.number ?? ""}"),
|
|
subtitle: Text(op.operator ?? ""),
|
|
onTap: () {
|
|
onTrainSelected(line.internalNumber);
|
|
},
|
|
);
|
|
}
|
|
else if (Platform.isIOS) {
|
|
return GestureDetector(
|
|
onTap: () {
|
|
onTrainSelected(line.internalNumber);
|
|
},
|
|
child: Padding(
|
|
padding: const EdgeInsets.fromLTRB(16, 2, 16, 2),
|
|
child: SizedBox(
|
|
width: double.infinity,
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: <Widget>[
|
|
Text(
|
|
op.operator ?? "",
|
|
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(fontSize: 10, fontWeight: FontWeight.w200),
|
|
textAlign: TextAlign.left,
|
|
),
|
|
Text(
|
|
"${line.rang ?? ""} ${line.number ?? ""}",
|
|
textAlign: TextAlign.left,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
class TrainInfoPromptMaterial extends StatefulWidget {
|
|
@override
|
|
_TrainInfoPromptMaterialState createState() => _TrainInfoPromptMaterialState();
|
|
}
|
|
|
|
class _TrainInfoPromptMaterialState extends State<TrainInfoPromptMaterial> with TrainInfoPromptCommon, TrainInfoPromptListHandling {
|
|
TextEditingController trainNoController = TextEditingController();
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
loadOperators(context).then((_) {
|
|
setState(() {});
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: Text("Informații despre tren"),
|
|
centerTitle: true,
|
|
),
|
|
body: SafeArea(
|
|
bottom: false,
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.max,
|
|
children: <Widget>[
|
|
Padding(
|
|
padding: const EdgeInsets.all(4),
|
|
child: TextField(
|
|
controller: trainNoController,
|
|
autofocus: true,
|
|
decoration: InputDecoration(
|
|
border: OutlineInputBorder(),
|
|
labelText: "Numărul trenului",
|
|
),
|
|
inputFormatters: [
|
|
WhitelistingTextInputFormatter.digitsOnly,
|
|
],
|
|
textInputAction: TextInputAction.search,
|
|
keyboardType: TextInputType.number,
|
|
onChanged: (_) {
|
|
setState(() {});
|
|
},
|
|
),
|
|
),
|
|
Expanded(
|
|
child: getOperatorsListView(context, currentInput: trainNoController.text, onTrainSelected: (number) {
|
|
onTrainSelected(context, number);
|
|
})
|
|
)
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class TrainInfoPromptCupertino extends StatefulWidget {
|
|
@override
|
|
_TrainInfoPromptCupertinoState createState() => _TrainInfoPromptCupertinoState();
|
|
}
|
|
|
|
class _TrainInfoPromptCupertinoState extends State<TrainInfoPromptCupertino> with TrainInfoPromptCommon, TrainInfoPromptListHandling {
|
|
TextEditingController trainNoController = TextEditingController();
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
loadOperators(context).then((_) {
|
|
setState(() {});
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return CupertinoPageScaffold(
|
|
navigationBar: CupertinoNavigationBar(
|
|
middle: Text("Informații despre tren"),
|
|
),
|
|
child: SafeArea(
|
|
bottom: false,
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.max,
|
|
children: <Widget>[
|
|
Padding(
|
|
padding: const EdgeInsets.all(4),
|
|
child: CupertinoTextField(
|
|
controller: trainNoController,
|
|
autofocus: true,
|
|
placeholder: "Numărul trenului",
|
|
textInputAction: TextInputAction.search,
|
|
keyboardType: TextInputType.number,
|
|
onChanged: (_) {
|
|
setState(() {});
|
|
},
|
|
inputFormatters: [
|
|
WhitelistingTextInputFormatter.digitsOnly,
|
|
],
|
|
),
|
|
),
|
|
Expanded(
|
|
child: getOperatorsListView(context, currentInput: trainNoController.text, onTrainSelected: (number) {
|
|
onTrainSelected(context, number);
|
|
})
|
|
)
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
@JsonSerializable()
|
|
class TrainOperatorLines {
|
|
@JsonKey(name: "short_name")
|
|
final String shortName;
|
|
final String operator;
|
|
@JsonKey(name: "versiune")
|
|
final String version;
|
|
@JsonKey(name: "trenuri")
|
|
final List<_TrainOperatorTrainDescription> trains;
|
|
|
|
TrainOperatorLines({
|
|
this.operator,
|
|
this.shortName = "",
|
|
this.version,
|
|
this.trains,
|
|
});
|
|
|
|
factory TrainOperatorLines.fromJson(Map<String, dynamic> json) => _$TrainOperatorLinesFromJson(json);
|
|
Map<String, dynamic> toJson() => _$TrainOperatorLinesToJson(this);
|
|
}
|
|
|
|
@JsonSerializable()
|
|
class _TrainOperatorTrainDescription {
|
|
final String rang;
|
|
@JsonKey(name: "numar")
|
|
final String number;
|
|
@JsonKey(name: "numar_intern")
|
|
final int internalNumber;
|
|
|
|
_TrainOperatorTrainDescription({
|
|
this.number,
|
|
this.rang,
|
|
this.internalNumber
|
|
});
|
|
|
|
factory _TrainOperatorTrainDescription.fromJson(Map<String, dynamic> json) => _$_TrainOperatorTrainDescriptionFromJson(json);
|
|
Map<String, dynamic> toJson() => _$_TrainOperatorTrainDescriptionToJson(this);
|
|
} |