| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605 | // ignore_for_file: use_build_context_synchronouslyimport '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<ConsumerStatefulWidget> createState() => _CrewlistPageState();}class _CrewlistPageState extends ConsumerState<CrewlistPage> {  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<CrewlistLeg> 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<Map?> _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<Map?> _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;}
 |