You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

382 lines
11 KiB

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