| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454 |
- // ignore_for_file: use_build_context_synchronously
- import 'dart:developer';
- import 'dart:io';
- import 'package:awesome_dialog/awesome_dialog.dart';
- 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:scroll_to_index/scroll_to_index.dart';
- import 'package:tp5/core/basic_page.dart';
- import 'package:tp5/core/core.dart';
- import 'package:tp5/pdf/pdf_page.dart';
- import 'package:tp5/roster/api/crewlink_api.dart';
- import 'package:tp5/roster/models/duty.dart';
- import 'package:tp5/roster/view/crewlist_page.dart';
- import 'package:tp5/roster/widgets/w_day.dart';
- import 'package:tp5/roster/widgets/w_duty.dart';
- import 'package:tp5/roster/widgets/w_horizontal_month.dart';
- class RosterPage extends ConsumerStatefulWidget {
- const RosterPage({required this.params, super.key});
- final RosterPageParams params;
- @override
- ConsumerState<ConsumerStatefulWidget> createState() => _RosterPageState();
- }
- class _RosterPageState extends ConsumerState<RosterPage> {
- late String crewlinkUser;
- late String crewlinkPass;
- Map? roster;
- List<Duty> duties = [];
- List<Jiffy> get rostermonths => duties
- .fold(
- <Jiffy>[],
- (List<Jiffy> p, Duty e) => [
- ...p,
- if (!p
- .map((Jiffy j) => j.format(pattern: "MMMyy"))
- .contains(e.jdate.format(pattern: "MMMyy")))
- e.jdate
- ])
- .where((Jiffy e) => (duties
- .map((Duty d) => d.jdate.yMd)
- .contains(e.startOf(Unit.month).yMd) &&
- duties
- .map((Duty d) => d.jdate.yMd)
- .contains(e.endOf(Unit.month).yMd)))
- .toList();
- String get _rosterKey =>
- "roster_${crewlinkUser}_${widget.params.datestart}_${widget.params.dateend}";
- String get _rosterMinMaxKey => "minmax_$crewlinkUser";
- String get fileidroster => PathTo().crewlinkFile("$_rosterKey.pdf");
- String get fileidnotif => PathTo().crewlinkFile("notif_$crewlinkUser.pdf");
- final AutoScrollController _scrollCtrl = AutoScrollController();
- late Jiffy now;
- @override
- void initState() {
- // getusername & pass
- // crewlinkPass = Hive.box("profile").get("crewlink_pass");
- crewlinkUser = widget.params.crewlinkuser ??
- Hive.box("profile").get("crewlink_user") ??
- "";
- crewlinkPass = widget.params.crewlinkpass ??
- Hive.box("profile").get("crewlink_pass") ??
- "";
- Future.delayed(Duration.zero, () => _loadRoster());
- super.initState();
- }
- Future<Map?> _loadRosterOnline() 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 roster = await ref.read(crewlinkapiProvider).roster(
- start: widget.params.datestart ??
- Jiffy.now().toUtc().startOf(Unit.month).format(pattern: "ddMMMyy"),
- end: widget.params.dateend ??
- Jiffy.now().toUtc().endOf(Unit.month).format(pattern: "ddMMMyy"),
- fileid: fileidroster);
- if (roster?["data"]?["msg"] != null) {
- context.showError(roster?["data"]?["msg"] ?? "Unknown error");
- }
- if (roster["error"] == null && roster["data"] != null) {
- if (roster["msg"] != null) context.showAlert(roster["msg"]);
- Hive.box("crewlink").put(_rosterKey, roster);
- return roster;
- }
- return null;
- }
- Future<Map?> _loadRosterOffline() async {
- return Hive.box("crewlink").get(_rosterKey);
- }
- _loadRoster() async {
- try {
- ref.read(isLoadingProvider.notifier).state = true;
- final rosterOffline = await _loadRosterOffline();
- if (rosterOffline != null && mounted) {
- _convertRoster(rosterOffline);
- setState(() {});
- _scrollToDate();
- }
- final rosterOnline = await _loadRosterOnline();
- if (rosterOnline != null && mounted) {
- _convertRoster(rosterOnline);
- setState(() {});
- _scrollToDate();
- }
- } finally {
- ref.read(isLoadingProvider.notifier).state = false;
- }
- }
- _scrollToDate({Jiffy? date}) {
- final jdate = date ?? Jiffy.now().toUtc();
- bool found = false;
- int id = 0;
- for (Duty duty in duties) {
- if (duty.jdate.yMd == jdate.yMd) {
- found = true;
- break;
- }
- id++;
- }
- Future.delayed(const Duration(milliseconds: 100)).then((value) {
- if (found && mounted && _scrollCtrl.hasClients) {
- _scrollCtrl.scrollToIndex(id,
- duration: const Duration(milliseconds: 1300),
- preferPosition: AutoScrollPosition.begin);
- }
- });
- }
- _convertRoster(Map? input) {
- roster = input;
- int i = 0;
- duties.clear();
- final decoded = (input?["data"]?["decoded"]?["roster"] ?? {});
- for (var date in decoded.keys) {
- var dutylist = (decoded[date] as List);
- for (var duty in dutylist) {
- i++;
- duties.add(
- Duty(date: date, type: duty["type"], data: duty["data"], order: i));
- // print(
- // "${(duties.last).jdate.yMd} ${(duties.last).type} ${(duties.last).start?.Hm} ${(duties.last).end?.Hm}");
- }
- }
- }
- 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: () {
- _showMonths(context);
- },
- icon: const Icon(
- Icons.calendar_month), //icon data for elevated button
- label: const Text("Change\nMonth"), //label text
- style: bottomnavstyle,
- ),
- const Gap(10),
- ElevatedButton.icon(
- onPressed: () {
- _showPdf(context);
- },
- icon: Image.asset(
- 'assets/pdficon.png',
- width: 28,
- ), //icon data for elevated button
- label: const Text("Roster\n PDF"), //label text
- style: bottomnavstyle,
- ),
- ],
- ),
- ),
- title: "CrewLink / Roster",
- body: ListView.builder(
- itemCount: duties.length,
- itemBuilder: (_, index) => AutoScrollTag(
- key: ValueKey(index),
- controller: _scrollCtrl,
- index: index,
- child: _getItem(_, index)),
- shrinkWrap: false,
- controller: _scrollCtrl,
- ),
- );
- }
- Widget _getItem(BuildContext ctx, int i) {
- final duty = duties.elementAt(i);
- Duty? firstitem =
- duties.firstWhereOrNull((Duty el) => el.date == duty.date);
- bool isfirstitem = false;
- if (firstitem != null &&
- firstitem.type == duty.type &&
- firstitem.data == duty.data) isfirstitem = true;
- final String lastday =
- duty.jdate.subtract(days: 1).format(pattern: "yyyy-MM-dd");
- final Duty? dutyendingtoday = (!isfirstitem)
- ? null
- : duties.firstWhereOrNull((Duty el) =>
- (el.date == lastday) &&
- (el.end != null) &&
- (el.end!.format(pattern: "yyyy-MM-dd") == duty.date));
- return Column(children: [
- if (isfirstitem) ...[
- const Gap(10),
- const Divider(),
- WDay(
- date: duty.jdate,
- highlight: now.yMd == duty.jdate.yMd,
- onTap: () => context.push("/crewlink/crewlist",
- extra: CrewlistPageParams(
- datestart: duty.jdate.format(pattern: "ddMMMyy"))),
- ),
- if (dutyendingtoday != null)
- WDuty(duty: dutyendingtoday, date: duty.date),
- ],
- InkWell(
- highlightColor: Colors.yellow[900],
- onTap: () {
- log("Tap: ${duty.type} ${duty.data}", name: "RosterPage");
- switch (duty.type) {
- case "changed":
- AwesomeDialog(
- context: context,
- dialogType: DialogType.question,
- animType: AnimType.topSlide,
- title: 'Notification',
- desc: 'Load and show pending notification?',
- btnOkOnPress: () async {
- ref.read(isLoadingProvider.notifier).state = true;
- final res = await ref
- .read(crewlinkapiProvider)
- .notif(download: true, fileid: fileidnotif);
- ref.read(isLoadingProvider.notifier).state = false;
- if (res is Map && res["data"]?["id"] != null) {
- _showNotif();
- } else {
- AwesomeDialog(
- context: context,
- dialogType: DialogType.warning,
- animType: AnimType.topSlide,
- title: 'Notification',
- desc: res?["error"] ??
- res?["data"]?["msg"] ??
- 'No pending notification found !!!',
- btnOkOnPress: () async {})
- .show();
- }
- },
- btnCancelOnPress: () {},
- btnOkText: "Load notification",
- showCloseIcon: true)
- .show();
- break;
- default:
- }
- },
- child: WDuty(duty: duty),
- )
- ]);
- }
- void _showNotif() {
- {
- context
- .push("/pdf",
- extra: PdfPageParams(
- file: fileidnotif,
- title: "Roster change",
- bottom: ElevatedButton(
- style: ElevatedButton.styleFrom(
- backgroundColor: Colors.red[900],
- ),
- onPressed: () async {
- ref.read(isLoadingProvider.notifier).state = true;
- final ack =
- await ref.read(crewlinkapiProvider).confirmNotif();
- // final ack = {
- // "error": null,
- // "data": {
- // "msg": null,
- // "error": null,
- // "notif": true
- // }
- // };
- ref.read(isLoadingProvider.notifier).state = false;
- final ackok = (ack is Map &&
- ack["error"] != null &&
- ack["data"]?["error"] != null);
- AwesomeDialog(
- context: context,
- dialogType:
- ackok ? DialogType.success : DialogType.warning,
- animType: AnimType.topSlide,
- title: 'Notification',
- desc:
- // ack["data"]?["msg"] ??
- 'Notification for change was acknowledged.',
- btnOkOnPress: () {
- context.pop({"notif": true});
- }).show();
- },
- child: const Padding(
- padding: EdgeInsets.all(8.0),
- child: Text(
- 'Acknowledge & Confirm changes',
- style: TextStyle(fontSize: 20, color: Colors.yellow),
- ),
- ),
- )))
- .then((res) {
- if (res is Map && res["notif"]) _loadRoster();
- });
- }
- }
- void _showPdf(xcontext) {
- if (File(fileidroster).existsSync()) {
- context.push("/pdf",
- extra: PdfPageParams(
- file: fileidroster,
- title:
- "Roster: ${Jiffy.parse(widget.params.datestart ?? "", pattern: "ddMMMyy", isUtc: true).format(pattern: "MMMM yyyy")}"));
- } else {
- context.showError(
- "Can't find the roster for this month.\n Try to connect to internet and retrieve it from CrewLink?");
- }
- }
- void _showMonths(xcontext) async {
- ref.read(isLoadingProvider.notifier).state = true;
- final resoff = Hive.box("crewlink").get(_rosterMinMaxKey);
- final reson = await ref.read(crewlinkapiProvider).rosterMinMax();
- ref.read(isLoadingProvider.notifier).state = false;
- dynamic res;
- if (reson["error"] == null && reson?["data"] != null) {
- Hive.box("crewlink").put(_rosterMinMaxKey, reson["data"]);
- res = reson["data"];
- } else if (resoff == null) {
- context.showError(reson["error"] ?? "Unknown error");
- return;
- }
- res = res ?? resoff;
- showModalBottomSheet(
- useSafeArea: true,
- backgroundColor: Colors.blueGrey[900],
- context: context,
- builder: (BuildContext bc) {
- return HorizontalMonth(
- start: Jiffy.parse(res["mindate"],
- pattern: "yyyy-MM-dd", isUtc: true)
- // .add(days: 1),
- .add(months: 1)
- .startOf(Unit.month),
- end: Jiffy.parse(res["maxdate"],
- pattern: "yyyy-MM-dd", isUtc: true)
- .subtract(days: 1),
- selectedmonth: rostermonths,
- onmonthclick: (date) {
- bc.push("/crewlink/roster",
- extra: RosterPageParams(
- dateend:
- date.endOf(Unit.month).format(pattern: "ddMMMyy"),
- datestart:
- date.startOf(Unit.month).format(pattern: "ddMMMyy"),
- crewlinkuser: crewlinkUser,
- crewlinkpass: crewlinkPass));
- });
- });
- }
- }
- class RosterPageParams {
- const RosterPageParams({
- this.dateend,
- this.datestart,
- this.crewlinkuser,
- this.crewlinkpass,
- });
- final String? dateend;
- final String? datestart;
- final String? crewlinkuser;
- final String? crewlinkpass;
- }
|