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(String 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 { late String userInput; List 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(String currentInput) => 'Caută trenul cu numărul $currentInput'; Widget getUseCurrentInputWidget(String currentInput, void Function(String) 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: [ ...slivers, SliverToBoxAdapter( child: int.tryParse(userInput) != null ? getUseCurrentInputWidget(userInput, widget.onTrainSelected) : Container(), ), SliverToBoxAdapter( child: Container( height: MediaQuery.of(context).viewPadding.bottom, ), ), ], ); } List 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 trains; final void Function(String) 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: [ mapTrainToItem(TrainOperatorTrainDescription()), ], ), delegate: SliverChildBuilderDelegate( (context, index) { return Column( children: [ mapTrainToItem(trains[index]), ], ); }, childCount: trains.length, addSemanticIndexes: true, ), ); } } abstract class OperatorAutocompleteTile extends StatelessWidget { final String operatorName; final TrainOperatorTrainDescription train; final void Function(String) onTrainSelected; const OperatorAutocompleteTile({ Key? key, required this.onTrainSelected, required this.operatorName, required this.train }) : super(key: key); }