210 lines
6.5 KiB
Dart
210 lines
6.5 KiB
Dart
|
import 'dart:convert';
|
||
|
|
||
|
import 'package:flutter/widgets.dart';
|
||
|
import 'package:info_tren/components/select_train_suggestions/select_train_suggestions_cupertino.dart';
|
||
|
import 'package:info_tren/components/select_train_suggestions/select_train_suggestions_material.dart';
|
||
|
import 'package:info_tren/models/train_operator_lines.dart';
|
||
|
import 'package:info_tren/models/ui_design.dart';
|
||
|
import 'package:info_tren/utils/default_ui_design.dart';
|
||
|
import 'package:tuple/tuple.dart';
|
||
|
|
||
|
class SelectTrainSuggestions extends StatefulWidget {
|
||
|
final UiDesign? uiDesign;
|
||
|
final String userInput;
|
||
|
final void Function(int trainNumber) onTrainSelected;
|
||
|
|
||
|
const SelectTrainSuggestions({ Key? key, required this.uiDesign, required this.userInput, required this.onTrainSelected }) : super(key: key);
|
||
|
|
||
|
@override
|
||
|
SelectTrainSuggestionsState createState() {
|
||
|
final uiDesign = this.uiDesign ?? defaultUiDesign;
|
||
|
switch(uiDesign) {
|
||
|
case UiDesign.MATERIAL:
|
||
|
return SelectTrainSuggestionsStateMaterial();
|
||
|
case UiDesign.CUPERTINO:
|
||
|
return SelectTrainSuggestionsStateCupertino();
|
||
|
default:
|
||
|
throw UnmatchedUiDesignException(uiDesign);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
abstract class SelectTrainSuggestionsState extends State<SelectTrainSuggestions> {
|
||
|
late String userInput;
|
||
|
|
||
|
List<TrainOperatorLines> operators = [];
|
||
|
|
||
|
Future loadOperators(BuildContext context) async {
|
||
|
operators = [];
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@override
|
||
|
void initState() {
|
||
|
super.initState();
|
||
|
userInput = widget.userInput;
|
||
|
|
||
|
loadOperators(context).then((_) {
|
||
|
setState(() {});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
@override
|
||
|
void didChangeDependencies() {
|
||
|
super.didChangeDependencies();
|
||
|
if (userInput != widget.userInput) {
|
||
|
setState(() {
|
||
|
userInput = widget.userInput;
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
String getUseCurrentInputWidgetText(int currentInput) => 'Caută trenul cu numărul $currentInput';
|
||
|
Widget getUseCurrentInputWidget(int currentInput, void Function(int) onTrainSelected);
|
||
|
|
||
|
@override
|
||
|
Widget build(BuildContext context) {
|
||
|
var sliversTuple = operators.map(
|
||
|
(op) => Tuple2(
|
||
|
getFilteredLines(op, userInput),
|
||
|
op.operator,
|
||
|
)
|
||
|
).where((tuple) => tuple.item1.isNotEmpty).toList();
|
||
|
if (userInput.isNotEmpty) sliversTuple.sort((a, b) {
|
||
|
final aTrain = a.item1.first;
|
||
|
final bTrain = b.item1.first;
|
||
|
|
||
|
final inputAsRegExp = RegExp(userInput);
|
||
|
|
||
|
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) => OperatorAutocompleteSliver(
|
||
|
uiDesign: widget.uiDesign,
|
||
|
operatorName: tuple.item2,
|
||
|
trains: tuple.item1,
|
||
|
onTrainSelected: widget.onTrainSelected,
|
||
|
)).toList();
|
||
|
|
||
|
return CustomScrollView(
|
||
|
slivers: <Widget>[
|
||
|
...slivers,
|
||
|
SliverToBoxAdapter(
|
||
|
child: int.tryParse(userInput) != null ? getUseCurrentInputWidget(int.parse(userInput), widget.onTrainSelected) : Container(),
|
||
|
),
|
||
|
SliverToBoxAdapter(
|
||
|
child: Container(
|
||
|
height: MediaQuery.of(context).viewPadding.bottom,
|
||
|
),
|
||
|
),
|
||
|
],
|
||
|
);
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class OperatorAutocompleteSliver extends StatelessWidget {
|
||
|
final UiDesign? uiDesign;
|
||
|
final String operatorName;
|
||
|
final List<TrainOperatorTrainDescription> trains;
|
||
|
final void Function(int) onTrainSelected;
|
||
|
|
||
|
const OperatorAutocompleteSliver({ Key? key, required this.uiDesign, required this.operatorName, required this.trains, required this.onTrainSelected }) : super(key: key);
|
||
|
|
||
|
Widget mapTrainToItem(TrainOperatorTrainDescription train) {
|
||
|
final uiDesign = this.uiDesign ?? defaultUiDesign;
|
||
|
switch (uiDesign) {
|
||
|
case UiDesign.MATERIAL:
|
||
|
return OperatorAutocompleteTileMaterial(
|
||
|
onTrainSelected: onTrainSelected,
|
||
|
operatorName: operatorName,
|
||
|
train: train,
|
||
|
);
|
||
|
case UiDesign.CUPERTINO:
|
||
|
return OperatorAutocompleteTileCupertino(
|
||
|
onTrainSelected: onTrainSelected,
|
||
|
operatorName: operatorName,
|
||
|
train: train,
|
||
|
);
|
||
|
default:
|
||
|
throw UnmatchedUiDesignException(uiDesign);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@override
|
||
|
Widget build(BuildContext context) {
|
||
|
if (trains.isEmpty) {
|
||
|
return SliverToBoxAdapter(child: Container(),);
|
||
|
}
|
||
|
|
||
|
return SliverPrototypeExtentList(
|
||
|
prototypeItem: Column(
|
||
|
children: <Widget>[
|
||
|
mapTrainToItem(TrainOperatorTrainDescription()),
|
||
|
],
|
||
|
),
|
||
|
delegate: SliverChildBuilderDelegate(
|
||
|
(context, index) {
|
||
|
return Column(
|
||
|
children: <Widget>[
|
||
|
mapTrainToItem(trains[index]),
|
||
|
],
|
||
|
);
|
||
|
},
|
||
|
childCount: trains.length,
|
||
|
addSemanticIndexes: true,
|
||
|
),
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
abstract class OperatorAutocompleteTile extends StatelessWidget {
|
||
|
final String operatorName;
|
||
|
final TrainOperatorTrainDescription train;
|
||
|
final void Function(int) onTrainSelected;
|
||
|
|
||
|
const OperatorAutocompleteTile({ Key? key, required this.onTrainSelected, required this.operatorName, required this.train }) : super(key: key);
|
||
|
}
|