// ignore_for_file: use_build_context_synchronously import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:gap/gap.dart'; import 'package:go_router/go_router.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:jiffy/jiffy.dart'; import 'package:tp5/core/core.dart'; import 'package:tp5/roster/api/crewlink_api.dart'; import 'package:tp5/roster/models/crewlist_leg.dart'; import 'package:tp5/roster/widgets/w_crewlist.dart'; import 'package:tp5/roster/widgets/w_day.dart'; import 'package:tp5/core/basic_page.dart'; import 'package:scroll_to_index/scroll_to_index.dart'; class CrewlistPage extends ConsumerStatefulWidget { const CrewlistPage({required this.params, super.key}); final CrewlistPageParams params; @override ConsumerState createState() => _CrewlistPageState(); } class _CrewlistPageState extends ConsumerState { late String crewlinkUser; late String crewlinkPass; TextEditingController ctrldep = TextEditingController(); TextEditingController ctrldes = TextEditingController(); TextEditingController ctrlairline = TextEditingController(); TextEditingController ctrlfnum = TextEditingController(); TextEditingController ctrlstartdate = TextEditingController(); TextEditingController ctrlenddate = TextEditingController(); Map? apires; List legs = []; String get _crewlistKey => "crewlist_${widget.params.datestart}_${widget.params.dateend}_${widget.params.al}_${widget.params.fnum}_${widget.params.dep}_${widget.params.des}"; String get _crewlistMinMaxKey => "minmax_crewlist"; final AutoScrollController _scrollCtrl = AutoScrollController(); late Jiffy now; @override void initState() { crewlinkUser = widget.params.crewlinkuser!; crewlinkPass = widget.params.crewlinkpass!; Future.delayed(Duration.zero, () => _loadCrewlist()); super.initState(); } Future _loadCrewlistOnline() async { if (!ref.read(crewlinkapiProvider).logged) { final login = await ref .read(crewlinkapiProvider) .login(username: crewlinkUser, password: crewlinkPass); if (login["data"]?["logged"] != true) { context.showError(login["error"] ?? "Unknown error"); return null; } } final res = await ref.read(crewlinkapiProvider).showCrew( crewlist: false, start: widget.params.datestart!, end: widget.params.dateend!, al: widget.params.al!, fnum: widget.params.fnum!, dep: widget.params.dep!, des: widget.params.des!); if (res?["data"]?["msg"] != null) { context.showError(res?["data"]?["msg"] ?? "Unknown error"); } if (res["error"] == null && res["data"] != null) { if (res["msg"] != null) context.showAlert(res["msg"]); Hive.box("crewlink").put(_crewlistKey, res); return res; } return null; } Future _loadCrewlistOffline() async { return Hive.box("crewlink").get(_crewlistKey); } _loadCrewlist() async { try { ref.read(isLoadingProvider.notifier).state = true; final crewlistOffline = await _loadCrewlistOffline(); if (crewlistOffline != null && mounted) { _convertCrewlist(crewlistOffline); setState(() {}); _scrollToDate(); } final crewlistOnline = await _loadCrewlistOnline(); if (crewlistOnline != null && mounted) { _convertCrewlist(crewlistOnline); setState(() {}); _scrollToDate(); } } finally { if (mounted) { ref.read(isLoadingProvider.notifier).state = false; } } } _scrollToDate({Jiffy? date}) { final jdate = date ?? Jiffy.now().toUtc(); print("scroll request to ${jdate.Hm}"); bool found = false; int id = 0; if (legs.isNotEmpty && jdate.isSameOrAfter(legs.map((e) => e.jdep).first) && jdate.isSameOrBefore(legs.map((e) => e.jdep).last)) { for (CrewlistLeg leg in legs) { if (leg.jdep.isSameOrAfter(jdate)) { found = true; break; } id++; } } Future.delayed(const Duration(milliseconds: 100)).then((value) { if ((found || jdate.yMd == Jiffy.now().toUtc().yMd) && mounted && _scrollCtrl.hasClients) { print("scrolling to id:$id ${jdate.Hm}"); _scrollCtrl.scrollToIndex(id, duration: const Duration(milliseconds: 1300), preferPosition: AutoScrollPosition.begin); } }); } void _convertCrewlist(Map? input) { // print("${input?["data"]["leglist"].first[7].runtimeType}"); apires = input; legs = []; for (var leg in input?["data"]?["leglist"] ?? []) { final CrewlistLeg newleg = CrewlistLeg(leg); if ((newleg.jdep.isSameOrAfter(Jiffy.parse(widget.params.datestart ?? "", pattern: "ddMMMyy", isUtc: true) .startOf(Unit.day))) && (newleg.jdep.isSameOrBefore(Jiffy.parse(widget.params.dateend ?? "", pattern: "ddMMMyy", isUtc: true) .endOf(Unit.day))) && (legs.firstWhereOrNull((e) => "${e.al}${e.fnum}${e.dep}${e.des}${e.jdep.millisecondsSinceEpoch}" == "${newleg.al}${newleg.fnum}${newleg.dep}${newleg.des}${newleg.jdep.millisecondsSinceEpoch}") == null)) legs.add(newleg); } legs.sort((a, b) { if (a.jdep.isBefore(b.jdep)) { return -1; } else if (a.jdep.isAfter(b.jdep)) { return 1; } else { return 0; } }); } final bottomnavstyle = ElevatedButton.styleFrom( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(5.0), ), backgroundColor: const Color.fromARGB(255, 0, 36, 53) //elevated btton background color ); @override Widget build(BuildContext context) { now = ref.watch(clockProvider); return BasicPage( actions: [ IconButton( onPressed: () => context.push("/crewlink/settings"), icon: const Icon(Icons.settings)), const Gap(10) ], bottomNavigationBar: Container( padding: const EdgeInsets.all(8), // color: Colors.black, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.grey[700]!, Colors.black, ], )), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ ElevatedButton.icon( onPressed: () { _changeDay(context); }, icon: const Icon( Icons.calendar_month), //icon data for elevated button label: const Text("Change\n Day"), //label text style: bottomnavstyle, ), const Gap(10), ElevatedButton.icon( onPressed: () { _searchFlights(context); }, icon: const Icon(Icons.manage_search), label: const Text("Search\nFlights"), style: bottomnavstyle, ), const Gap(10), //icon data for elevated button ], ), ), title: "CrewLink / Crewlist", body: ListView.builder( itemCount: legs.length, itemBuilder: (_, index) => AutoScrollTag( key: ValueKey(index), controller: _scrollCtrl, index: index, child: _getItem(_, index)), shrinkWrap: true, controller: _scrollCtrl, ), ); } Widget _getItem(BuildContext ctx, int i) { final leg = legs.elementAt(i); CrewlistLeg? firstitem = legs.firstWhereOrNull((CrewlistLeg el) => el.date == leg.date); bool isfirstitem = false; if (firstitem != null && firstitem.data == leg.data) isfirstitem = true; return Column( children: [ if (i == 0) ...[ const Gap(20), InkWell( onTap: () => context.push("/crewlink/crewlist", extra: CrewlistPageParams( al: widget.params.al, fnum: widget.params.fnum, dep: widget.params.dep, des: widget.params.des, datestart: Jiffy.parse(widget.params.datestart ?? leg.date, pattern: "ddMMMyy", isUtc: true) .subtract(days: 1) .format(pattern: "ddMMMyy"), dateend: Jiffy.parse(widget.params.dateend ?? leg.date, pattern: "ddMMMyy", isUtc: true) .subtract(days: 1) .format(pattern: "ddMMMyy"), )), child: Container( margin: const EdgeInsets.symmetric(horizontal: 15), width: double.infinity, padding: const EdgeInsets.all(5), decoration: BoxDecoration(border: Border.all(color: Colors.grey)), child: const Text( "Load Previous Day", textAlign: TextAlign.center, style: TextStyle(fontSize: 12), ), )), const Gap(20) ], if (isfirstitem) WDay( date: leg.jdate, highlight: now.yMd == leg.jdate.yMd, onTap: () => context.push("/crewlink/crewlist", extra: CrewlistPageParams( datestart: leg.jdate.format(pattern: "ddMMMyy"))), ), SingleChildScrollView( scrollDirection: Axis.horizontal, child: WCrewlist( leg: leg, ), ), Divider( height: 1, thickness: 1, color: Colors.grey[700], ), const Gap(5), if (i == legs.length - 1) ...[ const Gap(20), InkWell( onTap: () => context.push("/crewlink/crewlist", extra: CrewlistPageParams( al: widget.params.al, fnum: widget.params.fnum, dep: widget.params.dep, des: widget.params.des, datestart: Jiffy.parse(widget.params.datestart ?? leg.date, pattern: "ddMMMyy", isUtc: true) .add(days: 1) .format(pattern: "ddMMMyy"), dateend: Jiffy.parse(widget.params.dateend ?? leg.date, pattern: "ddMMMyy", isUtc: true) .add(days: 1) .format(pattern: "ddMMMyy"), )), child: Container( margin: const EdgeInsets.symmetric(horizontal: 15), width: double.infinity, padding: const EdgeInsets.all(5), decoration: BoxDecoration(border: Border.all(color: Colors.grey)), child: const Text( "Load Next Day", textAlign: TextAlign.center, style: TextStyle(fontSize: 12), ), )), const Gap(20) ], ], ); } void _changeDay(xcontext) async { ref.read(isLoadingProvider.notifier).state = true; final resoff = Hive.box("crewlink").get(_crewlistMinMaxKey); final reson = await ref.read(crewlinkapiProvider).showCrewMinMax(); if (mounted) { ref.read(isLoadingProvider.notifier).state = false; } dynamic res; if (reson["error"] == null && reson?["data"] != null) { Hive.box("crewlink").put(_crewlistMinMaxKey, reson["data"]); res = reson["data"]; } else if (resoff == null) { context.showError(reson["error"] ?? "Unknown error"); return; } res = res ?? resoff; if (res != null) { final firstDate = Jiffy.parse(res["mindate"], pattern: "yyyy-MM-dd", isUtc: true); final lastDate = Jiffy.parse(res["maxdate"], pattern: "yyyy-MM-dd", isUtc: true); final initialDate = Jiffy.parse(widget.params.datestart ?? "", pattern: "ddMMMyy", isUtc: true) .max(firstDate) .min(lastDate); final DateTime? pickedDate = await showDatePicker( context: xcontext, confirmText: null, initialDate: initialDate.dateTime, firstDate: firstDate.dateTime, lastDate: lastDate.dateTime, currentDate: Jiffy.now().toUtc().dateTime, selectableDayPredicate: (day) => true, initialDatePickerMode: DatePickerMode.day, ); if (pickedDate != null) { context.push("/crewlink/crewlist", extra: CrewlistPageParams( datestart: Jiffy.parseFromDateTime(pickedDate) .format(pattern: "ddMMMyy"))); } } } Widget _searchFilter(BuildContext xcontext, DateTime min, DateTime max) => Container( padding: const EdgeInsets.all(20), child: Column( mainAxisSize: MainAxisSize.min, children: [ Row( children: [ Expanded( child: TextField( decoration: const InputDecoration(labelText: "Departure Airport"), maxLength: 3, // Set maximum length controller: ctrldep, ), ), const SizedBox(width: 10), Expanded( child: TextField( decoration: const InputDecoration(labelText: "Arrival Airport"), maxLength: 3, // Set maximum length controller: ctrldes, ), ), ], ), const SizedBox(height: 15), Row( children: [ Expanded( child: TextField( controller: ctrlairline, decoration: const InputDecoration(labelText: "Airline IATA code"), maxLength: 2, // Set maximum length ), ), const SizedBox(width: 10), Expanded( child: TextField( controller: ctrlfnum, decoration: const InputDecoration(labelText: "Flight number"), maxLength: 6, // Set maximum length ), ), ], ), const SizedBox(height: 15), Row( children: [ Expanded( child: TextField( decoration: const InputDecoration(labelText: "Starting date"), controller: ctrlstartdate, readOnly: true, //set it true, so that user will not able to edit text onTap: () async { DateTime? pickedDate = await showDatePicker( context: context, initialDate: DateTime.now().toUtc(), firstDate: min, //DateTime.now () - not to allow to choose before today. lastDate: max); if (pickedDate != null) { String formattedDate = Jiffy.parseFromDateTime(pickedDate).format( pattern: "ddMMMyy"); //formatted date output using intl package => 2021-03-16 setState(() { ctrlstartdate.text = formattedDate; if (Jiffy.parseFromDateTime(pickedDate).isAfter( Jiffy.parse(ctrlenddate.text, pattern: "ddMMMyy", isUtc: true))) { ctrlenddate.text = formattedDate; } }); } else { print("Date is not selected"); } }), ), const SizedBox(width: 10), Expanded( child: TextField( decoration: const InputDecoration(labelText: "Ending date"), controller: ctrlenddate, readOnly: true, //set it true, so that user will not able to edit text onTap: () async { DateTime? pickedDate = await showDatePicker( context: context, initialDate: DateTime.now().toUtc(), firstDate: min, lastDate: max); if (pickedDate != null) { String formattedDate = Jiffy.parseFromDateTime(pickedDate) .format(pattern: "ddMMMyy"); setState(() { ctrlenddate.text = formattedDate; if (Jiffy.parseFromDateTime(pickedDate).isBefore( Jiffy.parse(ctrlstartdate.text, pattern: "ddMMMyy", isUtc: true))) { ctrlstartdate.text = formattedDate; } }); } else { print("Date is not selected"); } }), ), ], ), const SizedBox(height: 35), SizedBox( width: 360, child: ElevatedButton( onPressed: () => Navigator.pop(context, true), // Close the bottom sheet style: ElevatedButton.styleFrom( backgroundColor: Colors.green, // Set button color to green shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15), // Add rounded corners ), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15), ), child: const Text( 'Search Flights', style: TextStyle(fontSize: 18, color: Colors.white), ), ), ), ], ), ); void _searchFlights(xcontext) async { ref.read(isLoadingProvider.notifier).state = true; final resoff = Hive.box("crewlink").get(_crewlistMinMaxKey); final reson = await ref.read(crewlinkapiProvider).showCrewMinMax(); if (mounted) { ref.read(isLoadingProvider.notifier).state = false; } dynamic res; if (reson["error"] == null && reson?["data"] != null) { Hive.box("crewlink").put(_crewlistMinMaxKey, reson["data"]); res = reson["data"]; } else if (resoff == null) { context.showError(reson["error"] ?? "Unknown error"); return; } res = res ?? resoff; if (res != null) { ctrldep.text = widget.params.dep!; ctrldes.text = widget.params.des!; ctrlairline.text = widget.params.al!; ctrlfnum.text = widget.params.fnum!; ctrlstartdate.text = widget.params.datestart!; ctrlenddate.text = widget.params.dateend!; final out = await showModalBottomSheet( context: context, builder: (context) => _searchFilter( xcontext, Jiffy.parse(res["mindate"], isUtc: true, pattern: "yyyy-MM-dd") .dateTime, Jiffy.parse(res["maxdate"], isUtc: true, pattern: "yyyy-MM-dd") .dateTime)); if (out == true) { context.push("/crewlink/crewlist", extra: CrewlistPageParams( dep: ctrldep.text.toUpperCase(), des: ctrldes.text.toUpperCase(), al: ctrlairline.text.toUpperCase(), fnum: ctrlfnum.text, datestart: ctrlstartdate.text, dateend: ctrlenddate.text, )); } } } } class CrewlistPageParams { const CrewlistPageParams({ this.dateend, this.datestart, this.al, this.fnum, this.dep, this.des, this.crewlinkuser, this.crewlinkpass, }); final String? dateend; final String? datestart; final String? al; final String? fnum; final String? dep; final String? des; final String? crewlinkuser; final String? crewlinkpass; }