ftl_page copy.dart.bak 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787
  1. // ignore_for_file: use_build_context_synchronously
  2. import 'package:collection/collection.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:flutter_riverpod/flutter_riverpod.dart';
  5. import 'package:gap/gap.dart';
  6. import 'package:go_router/go_router.dart';
  7. import 'package:hive_flutter/hive_flutter.dart';
  8. import 'package:jiffy/jiffy.dart';
  9. import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
  10. import 'package:tp5/core/basic_page.dart';
  11. import 'package:tp5/core/utils.dart';
  12. import 'package:tp5/ftl/provider/ftl.dart';
  13. import 'package:tp5/ftl/widget/w_shadowbox.dart';
  14. import 'package:tp5/roster/api/crewlink_api.dart';
  15. import 'package:tp5/roster/models/duty.dart';
  16. import 'package:scroll_to_index/scroll_to_index.dart';
  17. import 'package:tp5/roster/widgets/w_airport.dart';
  18. import 'package:tp5/roster/widgets/w_hour.dart';
  19. class FtlPage extends ConsumerStatefulWidget {
  20. const FtlPage({super.key, required this.params});
  21. final FtlPageParams params;
  22. @override
  23. ConsumerState<ConsumerStatefulWidget> createState() => _FtlPageState();
  24. }
  25. class _FtlPageState extends ConsumerState<FtlPage> {
  26. late String crewlinkUser;
  27. late String crewlinkPass;
  28. late String startdate;
  29. late String enddate;
  30. Map? roster;
  31. List<Duty> duties = [];
  32. String _rosterKey({String? clUser, String? start, String? end}) =>
  33. "roster_${clUser ?? crewlinkUser}_${start ?? startdate}_${end ?? enddate}";
  34. String fileidroster({String? clUser, String? start, String? end}) =>
  35. PathTo().crewlinkFile(
  36. "${_rosterKey(clUser: clUser, start: start, end: end)}.pdf");
  37. // String get _rosterKey =>
  38. // "roster_${crewlinkUser}_${widget.params.datestart}_${widget.params.dateend}";
  39. // String get fileidroster => PathTo().crewlinkFile("$_rosterKey.pdf");
  40. final AutoScrollController _scrollCtrl = AutoScrollController();
  41. late Jiffy now;
  42. @override
  43. void initState() {
  44. crewlinkUser = widget.params.crewlinkuser ??
  45. Hive.box("profile").get("crewlink_user") ??
  46. "";
  47. crewlinkPass = widget.params.crewlinkpass ??
  48. Hive.box("profile").get("crewlink_pass") ??
  49. "";
  50. startdate = widget.params.datestart ??
  51. Jiffy.now().toUtc().startOf(Unit.month).format(pattern: "ddMMMyy");
  52. enddate = widget.params.dateend ??
  53. Jiffy.now()
  54. .toUtc()
  55. //.add(months: 1)
  56. .endOf(Unit.month)
  57. .format(pattern: "ddMMMyy");
  58. Future.delayed(Duration.zero, () async {
  59. await _loadMinMax();
  60. //await _loadRoster();
  61. await _loadOldRoster();
  62. await _ftlCalc(base: 'DJE');
  63. }).then(
  64. (value) => Future.delayed(const Duration(milliseconds: 200), () async {
  65. _scrollToDate();
  66. }));
  67. super.initState();
  68. }
  69. bool _isLoading = false;
  70. Future<Map?> _loadRosterOnline(
  71. {String? clUser, String? start, String? end}) async {
  72. //print("FtlPage: Requesting ONline roster $start -> $end");
  73. if (!ref.read(crewlinkapiProvider).logged) {
  74. final login = await ref
  75. .read(crewlinkapiProvider)
  76. .login(username: crewlinkUser, password: crewlinkPass);
  77. if (login["data"]?["logged"] != true) {
  78. context.showError(login["error"] ?? "Unknown error");
  79. return null;
  80. }
  81. }
  82. final roster = await ref.read(crewlinkapiProvider).roster(
  83. start: start ?? startdate
  84. //??Jiffy.now().toUtc().startOf(Unit.month).format(pattern: "ddMMMyy")
  85. ,
  86. end: end ?? enddate
  87. //??Jiffy.now().toUtc().endOf(Unit.month).format(pattern: "ddMMMyy")
  88. ,
  89. fileid: fileidroster(clUser: clUser, start: start, end: end));
  90. if (roster?["data"]?["msg"] != null) {
  91. context.showError(roster?["data"]?["msg"] ?? "Unknown error");
  92. }
  93. if (roster["error"] == null && roster["data"] != null) {
  94. if (roster["msg"] != null) context.showAlert(roster["msg"]);
  95. Hive.box("crewlink")
  96. .put(_rosterKey(clUser: clUser, start: start, end: end), roster);
  97. return roster;
  98. }
  99. return null;
  100. }
  101. Future<Map?> _loadRosterOffline(
  102. {String? clUser, String? start, String? end}) async {
  103. //print("FtlPage: Requesting OFFline roster $start -> $end");
  104. return Hive.box("crewlink")
  105. .get(_rosterKey(clUser: clUser, start: start, end: end));
  106. }
  107. _loadRoster(
  108. {String? clUser,
  109. String? start,
  110. String? end,
  111. bool cachefirst = false}) async {
  112. try {
  113. ref.read(isLoadingProvider.notifier).state = true;
  114. final rosterOffline =
  115. await _loadRosterOffline(clUser: clUser, start: start, end: end);
  116. if (rosterOffline?["data"]?["decoded"] is Map) {
  117. print(
  118. "FTL Page: found offline: $start ${rosterOffline?["data"]?["decoded"] is Map}");
  119. _convertRoster(rosterOffline);
  120. setState(() {});
  121. }
  122. if (rosterOffline?["data"]?["decoded"] is! Map || !cachefirst) {
  123. final rosterOnline =
  124. await _loadRosterOnline(clUser: clUser, start: start, end: end);
  125. if (rosterOnline?["data"]?["decoded"] is Map) {
  126. print(
  127. "FTL Page: found online: $start ${rosterOnline?["data"]?["decoded"] is Map}");
  128. _convertRoster(rosterOnline);
  129. setState(() {});
  130. } else if (rosterOffline?["data"]?["decoded"] is Map) {
  131. print(
  132. "FTL Page: found offline: $start ${rosterOffline?["data"]?["decoded"] is Map}");
  133. _convertRoster(rosterOnline);
  134. setState(() {});
  135. }
  136. }
  137. } finally {
  138. ref.read(isLoadingProvider.notifier).state = false;
  139. }
  140. }
  141. _loadOldRoster() async {
  142. bool exitnow = false;
  143. // _rosterMax = Jiffy.now().toUtc().endOf(Unit.month).subtract(hours: 10);
  144. Jiffy date1 = Jiffy.parse(startdate, pattern: "ddMMMyy")
  145. .subtract(months: 11)
  146. .startOf(Unit.day)
  147. .startOf(Unit.month)
  148. .max(_rosterMin ?? Jiffy.now().toUtc().startOf(Unit.month));
  149. while (!exitnow) {
  150. Jiffy date2 = date1
  151. .endOf(Unit.month)
  152. .min(_rosterMax?.endOf(Unit.day) ?? Jiffy.now().toUtc().add(days: 3));
  153. if (date2.isAfter(date1) &&
  154. date2.endOf(Unit.month) == date1.endOf(Unit.month)) {
  155. //print("ftlpage: loadoldroster: loading ${DTInterval(date1, date2)}");
  156. await _loadRoster(
  157. start: date1.format(pattern: "ddMMMyy"),
  158. end: date2.format(pattern: "ddMMMyy"),
  159. cachefirst: date1
  160. .endOf(Unit.month)
  161. .isBefore(Jiffy.now().endOf(Unit.month)));
  162. } else {
  163. exitnow = true;
  164. }
  165. date1 = date1.add(months: 1).startOf(Unit.month);
  166. }
  167. }
  168. String get _rosterMinMaxKey => "minmax_$crewlinkUser";
  169. _loadMinMax() async {
  170. final resoff = Hive.box("crewlink").get(_rosterMinMaxKey);
  171. //print("ftlpage: resoff: $resoff");
  172. final reson = await ref.read(crewlinkapiProvider).rosterMinMax();
  173. //print("ftlpage: reson: $reson");
  174. ref.read(isLoadingProvider.notifier).state = false;
  175. dynamic res;
  176. if (reson != null && reson["error"] == null && reson?["data"] != null) {
  177. Hive.box("crewlink").put(_rosterMinMaxKey, reson["data"]);
  178. res = reson["data"];
  179. } else if (resoff == null) {
  180. //print(reson["error"] ?? "Unknown error");
  181. }
  182. res = res ?? resoff;
  183. _rosterMin =
  184. Jiffy.parse(res["mindate"], pattern: "yyyy-MM-dd", isUtc: true);
  185. _rosterMax =
  186. Jiffy.parse(res["maxdate"], pattern: "yyyy-MM-dd", isUtc: true);
  187. }
  188. Jiffy? _rosterMin;
  189. Jiffy? _rosterMax;
  190. _scrollToDate({Jiffy? date}) async {
  191. final jdate = date ?? Jiffy.now().toUtc();
  192. bool found = false;
  193. int id = 0;
  194. for (Duty duty in duties) {
  195. // if (duty.jdate.yMd == jdate.yMd) {
  196. if (duty.jdate.isSameOrAfter(jdate)) {
  197. found = true;
  198. break;
  199. }
  200. id++;
  201. }
  202. if (found && mounted && _scrollCtrl.hasClients) {
  203. // await _scrollCtrl.scrollToIndex(duties.length - 30,
  204. // duration: const Duration(milliseconds: 500),
  205. // preferPosition: AutoScrollPosition.end);
  206. await _scrollCtrl.scrollToIndex(0,
  207. duration: const Duration(milliseconds: 1000),
  208. preferPosition: AutoScrollPosition.end);
  209. await _scrollCtrl.scrollToIndex(id,
  210. duration: const Duration(milliseconds: 1000),
  211. preferPosition: AutoScrollPosition.end);
  212. }
  213. }
  214. int i = 0;
  215. _convertRoster(Map? input) {
  216. roster = input;
  217. final decoded = (input?["data"]?["decoded"]?["roster"] ?? {});
  218. for (var date in decoded.keys) {
  219. duties = duties.where((e) => e.date != date).toList();
  220. var dutylist = (decoded[date] as List);
  221. for (var duty in dutylist) {
  222. i++;
  223. duties.add(
  224. Duty(date: date, type: duty["type"], data: duty["data"], order: i));
  225. //print("${(duties.last).jdate.yMd} ${(duties.last).type} ${(duties.last).start?.Hm} ${(duties.last).end?.Hm}");
  226. }
  227. }
  228. }
  229. Ftl ftldata = Ftl.fromCrewlink(clDuties: [], base: 'DJE');
  230. Jiffy ftlcalcdate = Jiffy.now().subtract(days: 30).startOf(Unit.month);
  231. _ftlCalc({required String base}) async {
  232. ref.read(isLoadingProvider.notifier).state = true;
  233. ftldata = Ftl.fromCrewlink(clDuties: duties, base: base);
  234. //await compute(ftlCalc, {"clDuties": duties, "base": base});
  235. // await ftldata.calcduties(date: null);
  236. await ftldata.calcduties(date: ftlcalcdate);
  237. ref.read(isLoadingProvider.notifier).state = false;
  238. }
  239. @override
  240. Widget build(BuildContext context) {
  241. return BasicPage(
  242. actions: [
  243. ElevatedButton(
  244. onPressed: _isLoading
  245. ? null
  246. : () async {
  247. setState(() {
  248. _isLoading = true;
  249. });
  250. await _ftlCalc(base: "DJE");
  251. setState(() {
  252. _isLoading = false;
  253. });
  254. },
  255. child: const Text("calc"))
  256. ],
  257. title: "FTL",
  258. body: ftldata.duties.isEmpty
  259. ? const Text("Please wait, calculations in progress...")
  260. : ListView.builder(
  261. itemCount: ftldata.duties.length,
  262. itemBuilder: (contexte, index) => AutoScrollTag(
  263. key: ValueKey(index),
  264. controller: _scrollCtrl,
  265. index: index,
  266. child: (ftldata.duties
  267. .elementAt(index)
  268. .start!
  269. .isSameOrAfter(ftlcalcdate))
  270. ? _getItem(contexte, index)
  271. : Container(),
  272. ),
  273. shrinkWrap: false,
  274. controller: _scrollCtrl,
  275. ),
  276. );
  277. }
  278. bool _is36h(DTInterval? e, String station) {
  279. if (e == null) return false;
  280. final firstnight = DTInterval.fromHm(
  281. apartir: Ftl.changeTz(e.start, station).toUtc(),
  282. h: 23,
  283. m: 0,
  284. duration: const Duration(hours: 7, minutes: 59),
  285. ap: station);
  286. final secondnight =
  287. DTInterval(firstnight.start.add(days: 1), firstnight.end.add(days: 1));
  288. if (e.duration.inHours >= 36 &&
  289. e.contains(firstnight) &&
  290. e.contains(secondnight)) {
  291. return true;
  292. } else {
  293. return false;
  294. }
  295. }
  296. bool _is48h(DTInterval? e, String station) {
  297. if (e == null) return false;
  298. final localdays = DTInterval.fromHm(
  299. apartir: Ftl.changeTz(e.start, station).toUtc(),
  300. h: 0,
  301. m: 0,
  302. duration: const Duration(hours: 47, minutes: 59),
  303. ap: station);
  304. if (e.contains(localdays)) {
  305. return true;
  306. } else {
  307. return false;
  308. }
  309. }
  310. _showDutyInfo(int i) {
  311. final duty = ftldata.duties[i];
  312. //print("ftlpage: ${duty.interval} ${duty.type}");
  313. final margin =
  314. ((duty.fdpExt ? duty.fdpExtMax : duty.fdpMax) ?? Duration.zero)
  315. .subtract(duty.fdpLength);
  316. showMaterialModalBottomSheet(
  317. bounce: true,
  318. context: context,
  319. builder: (context) => Container(
  320. padding: const EdgeInsets.all(10),
  321. decoration: const BoxDecoration(
  322. border: Border(top: BorderSide(width: 3, color: Colors.white70))),
  323. //height: 250,
  324. child: SingleChildScrollView(
  325. child: Column(
  326. children: [
  327. const Gap(5),
  328. ElevatedButton(
  329. onPressed: () {
  330. context.pop();
  331. },
  332. child: const Text(
  333. "Close",
  334. style: TextStyle(
  335. color: Colors.red, fontWeight: FontWeight.w700),
  336. )),
  337. const Gap(5),
  338. const Text(
  339. "Duty FTL Details",
  340. style: TextStyle(fontSize: 22),
  341. ),
  342. const Gap(10),
  343. Row(
  344. children: [
  345. const Text("Duty:",
  346. style: TextStyle(fontWeight: FontWeight.w800)),
  347. const Gap(20),
  348. _titleInfo(duty.start!.yMMMEd, duty.start!.Hm),
  349. const Gap(20),
  350. _titleInfo(duty.start!.yMMMEd, duty.end!.Hm)
  351. ],
  352. ),
  353. const Gap(20),
  354. if (duty.fdpStart != null)
  355. Row(
  356. children: [
  357. const Text("FDP:",
  358. style: TextStyle(fontWeight: FontWeight.w800)),
  359. const Gap(20),
  360. _titleInfo("FDP Length", duty.fdpLength.tohhmm),
  361. const Gap(20),
  362. _titleInfo("Fdp Max", duty.fdpMax!.tohhmm),
  363. const Gap(20),
  364. _titleInfo("Fdp Ext Max", duty.fdpExtMax!.tohhmm),
  365. const Gap(20),
  366. _titleInfo(margin.isNegative ? "Exceeded" : "Margin",
  367. margin.abs().tohhmm,
  368. color: margin.isNegative ? Colors.red : Colors.green,
  369. sizeinfo: 12),
  370. ],
  371. ),
  372. const Gap(20),
  373. if (duty.fdpStart != null)
  374. Row(
  375. children: [
  376. const Text("Acclim:",
  377. style: TextStyle(fontWeight: FontWeight.w800)),
  378. const Gap(20),
  379. _titleInfo("Acclim type", duty.acclim),
  380. const Gap(20),
  381. _titleInfo("Ref time", duty.reftime),
  382. ],
  383. ),
  384. const Gap(20),
  385. if (duty.fdpStart != null)
  386. Row(
  387. children: [
  388. const Text("Report:",
  389. style: TextStyle(fontWeight: FontWeight.w800)),
  390. const Gap(10),
  391. _titleInfo("Report time", duty.start?.Hm ?? "----"),
  392. const Gap(10),
  393. _titleInfo(
  394. "Delayed Report1", duty.reportdelay1?.Hm ?? "----"),
  395. const Gap(10),
  396. _titleInfo(
  397. "Delayed Report1", duty.reportdelay2?.Hm ?? "----"),
  398. ],
  399. ),
  400. const Gap(20),
  401. if (duty.fdpStart != null &&
  402. (duty.lateFinish || duty.earlyStart || duty.nightDuty))
  403. Row(
  404. children: [
  405. const Text("Disruptive:",
  406. style: TextStyle(fontWeight: FontWeight.w800)),
  407. const Gap(10),
  408. if (duty.earlyStart)
  409. const Text("early start duty",
  410. style: TextStyle(
  411. fontWeight: FontWeight.w800,
  412. fontSize: 12,
  413. color: Colors.cyan)),
  414. const Gap(10),
  415. if (duty.lateFinish)
  416. const Text("late finish duty",
  417. style: TextStyle(
  418. fontWeight: FontWeight.w800,
  419. fontSize: 12,
  420. color: Colors.cyan)),
  421. const Gap(10),
  422. if (duty.nightDuty)
  423. const Text("night duty",
  424. style: TextStyle(
  425. fontWeight: FontWeight.w800,
  426. fontSize: 12,
  427. color: Colors.cyan)),
  428. const Gap(10),
  429. ],
  430. ),
  431. const Gap(20),
  432. Row(
  433. children: [
  434. SingleChildScrollView(
  435. scrollDirection: Axis.horizontal,
  436. child: Row(
  437. mainAxisSize: MainAxisSize.max,
  438. mainAxisAlignment: MainAxisAlignment.start,
  439. children: [
  440. const Text("Detail:",
  441. style: TextStyle(fontWeight: FontWeight.w800)),
  442. ...(duty.dutiesDetailsIndex.map((e) {
  443. final detail = ftldata.dutiesDetails[e];
  444. final label = (detail.placeEnd != null)
  445. ? "${detail.placeStart}-${detail.placeEnd}"
  446. : "${detail.label}";
  447. return [
  448. const Gap(20),
  449. _titleInfo(label,
  450. "${detail.start?.Hm}-${detail.end?.Hm}",
  451. sizeinfo: 10)
  452. ];
  453. }).flattened),
  454. ]),
  455. ),
  456. ],
  457. ),
  458. const Gap(20),
  459. Row(
  460. children: [
  461. const Text("Tot duties hrs:",
  462. style: TextStyle(fontWeight: FontWeight.w800)),
  463. const Gap(10),
  464. _titleInfo("7 days",
  465. "${duty.dutyTotal?.dutylast7?.tohhmm}|${Ftl.maxdutylast7.tohhmm}",
  466. sizeinfo: 10),
  467. const Gap(5),
  468. _titleInfo("14 days",
  469. "${duty.dutyTotal?.dutylast14?.tohhmm}|${Ftl.maxdutylast14.tohhmm}",
  470. sizeinfo: 10),
  471. const Gap(5),
  472. _titleInfo("28 days",
  473. "${duty.dutyTotal?.dutylast28?.tohhmm}|${Ftl.maxdutylast28.tohhmm}",
  474. sizeinfo: 10),
  475. ],
  476. ),
  477. const Gap(20),
  478. Row(
  479. children: [
  480. const Text("Tot Flt hrs:",
  481. style: TextStyle(fontWeight: FontWeight.w800)),
  482. const Gap(10),
  483. _titleInfo("28 days",
  484. "${duty.dutyTotal?.fltlast28?.tohhmm}|${Ftl.maxfltlast28.tohhmm}",
  485. sizeinfo: 10),
  486. const Gap(5),
  487. _titleInfo("12 months",
  488. "${duty.dutyTotal?.fltlast12?.tohhmm}|${Ftl.maxfltlast12.tohhmm}",
  489. sizeinfo: 10),
  490. const Gap(5),
  491. _titleInfo("year",
  492. "${duty.dutyTotal?.fltyear?.tohhmm}|${Ftl.maxfltlastyear.tohhmm}",
  493. sizeinfo: 10),
  494. ],
  495. ),
  496. ],
  497. ),
  498. ),
  499. ),
  500. );
  501. }
  502. _titleInfo(String? title, String? info,
  503. {double sizetitle = 10,
  504. double sizeinfo = 16,
  505. Color color = Colors.blueGrey}) =>
  506. Column(
  507. children: [
  508. Text(
  509. title ?? "---",
  510. style: TextStyle(color: Colors.grey, fontSize: sizetitle),
  511. ),
  512. Text(info ?? "---",
  513. style: TextStyle(color: color, fontSize: sizeinfo)),
  514. ],
  515. );
  516. Widget _getItem(BuildContext _, int i) {
  517. final duty = ftldata.duties.elementAt(i);
  518. final lastduty = i == 0 ? null : ftldata.duties.elementAt(i - 1);
  519. final rest =
  520. lastduty == null ? null : DTInterval(lastduty.end!, duty.start!);
  521. Color legal;
  522. if (duty.legals.isEmpty) {
  523. legal = Colors.green[900]!;
  524. } else if (duty.legals.every((e) => (e.condition ?? "") != "")) {
  525. legal = Colors.amber;
  526. } else {
  527. legal = Colors.red;
  528. }
  529. return Column(
  530. children: [
  531. if (lastduty != null)
  532. WShadowbox(
  533. child: Column(children: [
  534. Container(
  535. decoration: const BoxDecoration(
  536. color: Color.fromARGB(255, 8, 9, 9),
  537. border: Border(top: BorderSide(width: 5)),
  538. borderRadius: BorderRadius.all(Radius.circular(10.0)),
  539. ),
  540. padding: const EdgeInsets.all(5),
  541. // width: double.infinity,
  542. child: Row(
  543. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  544. children: [
  545. Column(
  546. children: [
  547. _titleInfo("Rest",
  548. "${(rest?.duration.inDays ?? 0)} days, ${(rest?.duration.inHours ?? 0) % 24} hours, ${(rest?.duration.inMinutes ?? 0) % 60} minutes",
  549. sizeinfo: 10),
  550. ],
  551. ),
  552. Column(children: [
  553. if (_is48h(rest, lastduty.placeEnd ?? "UTC"))
  554. Container(
  555. color: Colors.teal[900],
  556. padding: const EdgeInsets.all(5),
  557. child: Text(
  558. "48h with 2 local days",
  559. style: TextStyle(
  560. color: Colors.yellow[600]!,
  561. fontSize: 11,
  562. fontWeight: FontWeight.w600),
  563. )),
  564. if (_is36h(rest, lastduty.placeEnd ?? "UTC"))
  565. Container(
  566. color: Colors.teal[800],
  567. padding: const EdgeInsets.all(5),
  568. child: Text(
  569. "36h with 2 local nights",
  570. style: TextStyle(
  571. color: Colors.yellow[500]!,
  572. fontSize: 11,
  573. fontWeight: FontWeight.w600),
  574. ))
  575. ]),
  576. ],
  577. ),
  578. )
  579. ])),
  580. InkWell(
  581. onTap: () => _showDutyInfo(i),
  582. child: WShadowbox(
  583. child: Column(
  584. children: [
  585. Container(
  586. decoration: BoxDecoration(
  587. color: const Color.fromARGB(255, 1, 45, 47),
  588. border: Border(top: BorderSide(width: 5, color: legal)),
  589. borderRadius:
  590. const BorderRadius.vertical(top: Radius.circular(10.0)),
  591. ),
  592. padding: const EdgeInsets.symmetric(horizontal: 20),
  593. // width: double.infinity,
  594. child: Row(
  595. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  596. children: [
  597. Text(
  598. "${duty.start?.format(pattern: "dd MMM'yy")}",
  599. style: const TextStyle(color: Colors.white54),
  600. ),
  601. if (duty.start != null) WHour(jiffy: duty.start!),
  602. ],
  603. ),
  604. ),
  605. Container(
  606. width: double.infinity,
  607. padding: const EdgeInsets.only(left: 20, right: 10),
  608. decoration: const BoxDecoration(
  609. color: Colors.black,
  610. ),
  611. child: Column(
  612. crossAxisAlignment: CrossAxisAlignment.start,
  613. children: duty.legals
  614. .map((e) => Text(
  615. "+ ${e.condition ?? ""} ${e.legalCauseMsg}",
  616. style: const TextStyle(fontSize: 10),
  617. ))
  618. .toList()),
  619. ),
  620. //details
  621. ...duty.dutiesDetailsIndex.map((index) {
  622. final FtlDutyDetails dutydetail =
  623. ftldata.dutiesDetails[index];
  624. return Row(
  625. children: [
  626. const Gap(40),
  627. SizedBox(
  628. width: 50,
  629. child: Row(children: [
  630. Text(
  631. dutydetail.typeString,
  632. style: TextStyle(
  633. backgroundColor: Colors.blueGrey[900],
  634. color: Colors.yellow,
  635. fontSize: 10,
  636. fontWeight: FontWeight.w600,
  637. ),
  638. )
  639. ]),
  640. ),
  641. SizedBox(
  642. width: 60,
  643. child: Row(
  644. children: [
  645. WHour(
  646. jiffy: dutydetail.start!,
  647. size: 12,
  648. color: Colors.white38,
  649. ),
  650. const Gap(5),
  651. WHour(
  652. jiffy: dutydetail.end!,
  653. size: 12,
  654. color: Colors.white38,
  655. ),
  656. ],
  657. ),
  658. ),
  659. SizedBox(
  660. // width: 80,
  661. child: Row(children: [
  662. const Gap(10),
  663. if (dutydetail.placeStart != null)
  664. WAirport(iata: dutydetail.placeStart!, size: 12),
  665. if (dutydetail.placeEnd != null) ...[
  666. const Icon(Icons.arrow_right_alt, size: 9),
  667. WAirport(iata: dutydetail.placeEnd!, size: 12)
  668. ] else if ((dutydetail.label ?? "") != "")
  669. Text(" (${dutydetail.label})",
  670. style: const TextStyle(
  671. color: Colors.white54,
  672. fontSize: 10,
  673. letterSpacing: 2),
  674. overflow: TextOverflow.ellipsis),
  675. ]),
  676. ),
  677. ],
  678. );
  679. }),
  680. Container(
  681. decoration: const BoxDecoration(
  682. color: Color.fromARGB(255, 1, 45, 47),
  683. borderRadius:
  684. BorderRadius.vertical(bottom: Radius.circular(10.0)),
  685. ),
  686. padding: const EdgeInsets.symmetric(horizontal: 20),
  687. // width: double.infinity,
  688. child: Row(
  689. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  690. children: [
  691. if (duty.end != null && duty.start != null)
  692. _titleInfo(
  693. "Duty length",
  694. duty.end!.dateTime
  695. .difference(duty.start!.dateTime)
  696. .tohhmm),
  697. const Row(children: []),
  698. Column(
  699. mainAxisSize: MainAxisSize.min,
  700. children: [
  701. Text(
  702. (duty.end?.yMMMd != duty.start?.yMMMd)
  703. ? "${duty.end?.format(pattern: "dd MMM")}"
  704. : "",
  705. style: const TextStyle(
  706. color: Colors.white54, fontSize: 12),
  707. ),
  708. if (duty.end != null) WHour(jiffy: duty.end!),
  709. ],
  710. ),
  711. ],
  712. ),
  713. ),
  714. ],
  715. ),
  716. ),
  717. ),
  718. ],
  719. );
  720. }
  721. }
  722. class FtlPageParams {
  723. const FtlPageParams({
  724. this.dateend,
  725. this.datestart,
  726. this.crewlinkuser,
  727. this.crewlinkpass,
  728. });
  729. final String? dateend;
  730. final String? datestart;
  731. final String? crewlinkuser;
  732. final String? crewlinkpass;
  733. }