diff --git a/assets/images/drawer/car_parking_icon.svg b/assets/images/drawer/car_parking_icon.svg new file mode 100644 index 0000000..f77afa0 --- /dev/null +++ b/assets/images/drawer/car_parking_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/drawer/courses.svg b/assets/images/drawer/courses.svg new file mode 100644 index 0000000..d15e742 --- /dev/null +++ b/assets/images/drawer/courses.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/images/eid_banner.svg b/assets/images/eid_banner.svg new file mode 100644 index 0000000..f8e4adb --- /dev/null +++ b/assets/images/eid_banner.svg @@ -0,0 +1,606 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/greeting.svg b/assets/images/greeting.svg new file mode 100644 index 0000000..0682c60 --- /dev/null +++ b/assets/images/greeting.svg @@ -0,0 +1,605 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/ticket_bal.svg b/assets/images/ticket_bal.svg new file mode 100644 index 0000000..8fadde8 --- /dev/null +++ b/assets/images/ticket_bal.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/vacation_rule.svg b/assets/images/vacation_rule.svg new file mode 100644 index 0000000..0a536f7 --- /dev/null +++ b/assets/images/vacation_rule.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/assets/images/worklist/approve.svg b/assets/images/worklist/approve.svg new file mode 100644 index 0000000..8d62ae9 --- /dev/null +++ b/assets/images/worklist/approve.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/worklist/reject.svg b/assets/images/worklist/reject.svg new file mode 100644 index 0000000..cb5da34 --- /dev/null +++ b/assets/images/worklist/reject.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 27a5434..9558c2f 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -164,6 +164,7 @@ "quotationAnalysis": "تحليل الاقتباس", "subject": "موضوعات", "description": "وصف", + "pleaseEnterDescription": "الرجاء إدخال الوصف", "supplier": "المورد", "site": "موقع", "buyer": "مشتر", @@ -258,6 +259,7 @@ "fullName": "الأسم الكامل", "remove": "حذف", "submit": "ارسال", + "submitAsAnonymous": "إرسال كمجهول", "areYouSureYouWantToSubmit": "هل أنت متأكد أنك تريد أن تقدم؟", "comments": "تعليقات", "writeComment": "أكتب تعليقا", @@ -531,7 +533,7 @@ "noWinner": "حزين! لم يفز أحد اليوم.", "myTeam": "فريقي", "youCanPlayDemo": "لكن يمكنك لعب العرض", - "group" : "مجموعة", + "group": "مجموعة", "searchGroup": "مجموعة البحث", "connectHmgWifi": "قم بتوصيل HMG WIFI", "connectedHmgWifi": "اتصال HMG WIFI", @@ -572,20 +574,20 @@ "versionStatus": "حالة الإصدار", "supplierNo": "رقم المورد", "general": "عام", - "requesterOperatingUnit":"وحدة تشغيل مقدم الطلب", - "prepareEmpNum":"إعداد رقم الموظف", - "supplierInfo" : "معلومات المورد", + "requesterOperatingUnit": "وحدة تشغيل مقدم الطلب", + "prepareEmpNum": "إعداد رقم الموظف", + "supplierInfo": "معلومات المورد", "supplierAcNo": "رقم حساب المورد", "supplierAcName": "اسم حساب المورد", - "supplierIBAN" : "رقم IBAN للمورد", - "supplierCRNo" :"رقم السجل التجاري", - "suppliedAcNo" : "رقم الحساب المقدم", - "patientRefundInvoice" : "فاتورة استرداد الأموال للمريض", - "patientNumber" : "رقم المريض", - "patientName" : "اسم المريض", - "invoiceDate" : "تاريخ الفاتورة", - "refundInvoice" :"فاتورة الاسترجاع", - "hospitalClinic" : "عيادة المستشفى", + "supplierIBAN": "رقم IBAN للمورد", + "supplierCRNo": "رقم السجل التجاري", + "suppliedAcNo": "رقم الحساب المقدم", + "patientRefundInvoice": "فاتورة استرداد الأموال للمريض", + "patientNumber": "رقم المريض", + "patientName": "اسم المريض", + "invoiceDate": "تاريخ الفاتورة", + "refundInvoice": "فاتورة الاسترجاع", + "hospitalClinic": "عيادة المستشفى", "graphicalAnalysis": "التحليل الرسومي", "itemHistoryAnalysis": "تحليل تاريخ العنصر", "pOno": "امر شراء #", @@ -611,23 +613,23 @@ "members": "الأعضاء", "searchByUserName": "البحث بواسطة اسم المستخدم", "shareScreen": "مشاركة الشاشة", - "start":"يبدأ", - "about":"عن", + "start": "يبدأ", + "about": "عن", "explore": "يستكشف", "mazaya": "مازيا", "benefits": "فوائد", "mazayaDesc": "اكتشف الخصومات والعروض الخاصة المتاحة للموظفين", - "viewallofferMazaya" : "أعرض كل المزايا", + "viewallofferMazaya": "أعرض كل المزايا", "buyerName": "اسم المشتري", "buyerNumber": "رقم المشتري", "highestBidder": "أعلى مزايد", "remarks": "ملاحظات", "faHeader": "تفاصيل رأس الصفحة FA", - "bookTypeCode": "رمز نوع الكتاب", + "bookTypeCode": "رمز نوع الكتاب", "categoryCode": "رمز الفئة", "categoryGroup": "مجموعة الفئات", - "faLINES": "تفاصيل الأصول", - "assetNumber": "رقم الأصول", + "faLINES": "تفاصيل الأصول", + "assetNumber": "رقم الأصول", "assetDescription": "وصف الأصول", "barCodeNumber": "رقم الباركود", "datePlaceInService": "تاريخ وضعه في الخدمة", @@ -635,9 +637,36 @@ "disposedDate": "تاريخ التخلص", "netBookValue": "القيمة الدفترية الصافية", "purchasedPrice": "سعر الشراء", - "usefulLife": "العمر الإنتاجي", + "usefulLife": "العمر الإنتاجي", "yearsUsed": "سنوات الاستخدام", "faRequest": "طلب التخلص من FA", "showMore": "عرض المزيد", - "buyerDetails": "تفاصيل المشتري" -} \ No newline at end of file + "ticketDetails": "تفاصيل التذكرة", + "currentTicketBalance": "رصيد التذكرة الحالي", + "ticketHistory": "تاريخ التذكرة", + "proceedBooking": "المتابعة للحجز", + "courses": "الدورات التدريبية", + "course": "دورة", + "status": "الحالة", + "deadline": "الموعد النهائي", + "overdue": "متأخر", + "noCoursesAvailable": "لا توجد دورات متاحة", + "pullToRefresh": "اسحب للتحديث", + "courseUrlNotAvailable": "رابط الدورة غير متوفر", + "buyerDetails": "تفاصيل المشتري", + "etqanOVR": "الإبلاغ عن حادثة", + "etqanRequest": "طلبات الحوادث", + "selectProject": "حدد المشروع", + "project": "مشروع", + "keen": "حريص", + "reportIncident": "الإبلاغ عن الحادث", + "myIncidents": "حوادثي", + "incidentDetails": "تفاصيل الحادث", + "ticketNumber": "رقم التذكرة", + "ticketId": "معرف التذكرة", + "pleaseSelectProject": "الرجاء اختيار مشروع", + "viewAttachment" : "عرض المرفق", + "parkingQr": "وقوف السيارات QR", + "startNow": "ابدأ الآن" +} + diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index ce0fcac..ba8bd20 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -158,6 +158,7 @@ "subject": "Subject", "quotationAnalysis": "Quotation Analysis", "description": "Description", + "pleaseEnterDescription": "Please enter description", "supplier": "Supplier", "site": "Site", "buyer": "Buyer", @@ -256,6 +257,7 @@ "remove": "Remove", "Attendance": "Attendance", "submit": "Submit", + "submitAsAnonymous": "Submit as Anonymous", "areYouSureYouWantToSubmit": "Are you sure you want to submit?", "comments": "Comments", "writeComment": "Write a comment", @@ -565,7 +567,7 @@ "addAtLeastOneAttachment": "Please add at least one attachment.", "pleaseClickButtonToJoinMarathon": "Press the button below to join the Marathon.", "youCannotJoinTheMarathon": "You cannot join the Marathon because you have exceeded the time limit.", - "requesterOperatingUnit":"Requester Operating Unit", + "requesterOperatingUnit": "Requester Operating Unit", "paymentRequest": "Pay Request", "paymentDetails": "Payment Details", "requestNo": "Request No", @@ -590,19 +592,19 @@ "versionStatus": "Version Status", "supplierNo": "Supplier No", "general": "General", - "prepareEmpNum":"Prepare Employee Num", - "supplierInfo" : "Supplier Information", + "prepareEmpNum": "Prepare Employee Num", + "supplierInfo": "Supplier Information", "supplierAcNo": "Supplier Account No", - "supplierAcName":"Supplier Account Name", - "supplierIBAN" : "Supplier IBAN", - "supplierCRNo" : "CR Number", - "suppliedAcNo" : "Supplied Account No.", - "patientRefundInvoice" : "Patient Refund Invoice", - "patientNumber" : "Patient Number", - "patientName" : "Patient Name", - "invoiceDate" : "Invoice Date", - "refundInvoice" :"Refund Invoice", - "hospitalClinic" : "Hospital Clinic", + "supplierAcName": "Supplier Account Name", + "supplierIBAN": "Supplier IBAN", + "supplierCRNo": "CR Number", + "suppliedAcNo": "Supplied Account No.", + "patientRefundInvoice": "Patient Refund Invoice", + "patientNumber": "Patient Number", + "patientName": "Patient Name", + "invoiceDate": "Invoice Date", + "refundInvoice": "Refund Invoice", + "hospitalClinic": "Hospital Clinic", "graphicalAnalysis": "Graphical Analysis", "itemHistoryAnalysis": "Item History Analysis", "pOno": "P.O #", @@ -611,23 +613,23 @@ "qtyReceived": "Qty. Received", "bonusQty": "Bonus Qty.", "balQty": "Bal. Qty.", - "start":"Start", - "about":"About", + "start": "Start", + "about": "About", "explore": "Explore", "mazaya": "MAZAYA", "benefits": "Benefits", "mazayaDesc": "Discover special Discounts and offers available to Employees", - "viewallofferMazaya" : "View All Offers", + "viewallofferMazaya": "View All Offers", "buyerName": "Buyer Name", "buyerNumber": "Buyer Number", "highestBidder": "Highest Bidder", "remarks": "Remarks", "faHeader": "FA Header Details", - "bookTypeCode": "Book Type Code", + "bookTypeCode": "Book Type Code", "categoryCode": "Category Code", "categoryGroup": "Category Group", - "faLINES": "Asset Details", - "assetNumber": "Asset Number", + "faLINES": "Asset Details", + "assetNumber": "Asset Number", "assetDescription": "Asset Description", "barCodeNumber": "BarCode Number", "datePlaceInService": "Date place In Service", @@ -635,9 +637,35 @@ "disposedDate": "Disposed Date", "netBookValue": "Net Book Value", "purchasedPrice": "Purchased Price", - "usefulLife": "Useful Life", + "usefulLife": "Useful Life", "yearsUsed": "Years Used", - "faRequest":"FA Disposal Request", + "faRequest": "FA Disposal Request", "showMore": "Show More", - "buyerDetails": "Buyer Details" + "ticketDetails": "Ticket Details", + "currentTicketBalance": "Current Ticket Balance", + "ticketHistory": "Ticket History", + "proceedBooking": "Proceed For Booking", + "courses": "Courses", + "course": "Course", + "status": "Status", + "deadline": "Deadline", + "overdue": "Overdue", + "noCoursesAvailable": "No Courses Available", + "pullToRefresh": "Pull to refresh", + "courseUrlNotAvailable": "Course URL not available", + "buyerDetails": "Buyer Details", + "etqanOVR": "Report An Incident", + "etqanRequest": "Incident Requests", + "selectProject": "Select Project", + "project": "Project", + "keen": "Keen", + "reportIncident": "Report Incident", + "myIncidents": "My Incidents", + "incidentDetails": "Incident Details", + "ticketNumber": "Ticket Number", + "ticketId": "Ticket ID", + "pleaseSelectProject": "Please select a project", + "viewAttachment" : "View Attachment", + "parkingQr": "Parking QR", + "startNow": "Start Now" } \ No newline at end of file diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 99cda04..bedf267 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -55,6 +55,8 @@ This app requires photo library access to select image as document & upload it. NSMicrophoneUsageDescription This app requires microphone access to for call. + NSSpeechRecognitionUsageDescription + This app requires speech recognition access to access voice command features. UIBackgroundModes fetch diff --git a/lib/api/dashboard_api_client.dart b/lib/api/dashboard_api_client.dart index 2bddb42..87025f2 100644 --- a/lib/api/dashboard_api_client.dart +++ b/lib/api/dashboard_api_client.dart @@ -15,7 +15,10 @@ import 'package:mohem_flutter_app/models/dashboard/get_accural_ticket_balance_mo import 'package:mohem_flutter_app/models/dashboard/get_attendance_tracking_list_model.dart'; import 'package:mohem_flutter_app/models/dashboard/itg_forms_model.dart'; import 'package:mohem_flutter_app/models/dashboard/list_menu.dart'; +import 'package:mohem_flutter_app/models/dashboard/courses_response_model.dart'; import 'package:mohem_flutter_app/models/generic_response_model.dart'; +import 'package:mohem_flutter_app/models/get_employee_parking_details_model.dart'; +import 'package:mohem_flutter_app/models/greetings/greeting_card_model.dart'; import 'package:mohem_flutter_app/models/itg/itg_main_response.dart'; import 'package:mohem_flutter_app/models/itg/itg_response_model.dart'; import 'package:mohem_flutter_app/models/sso_auth_model.dart'; @@ -226,10 +229,11 @@ class DashboardApiClient { } Future getBookingSSOFinalRedirection({required String token}) async { - token = - "eyJhbGciOiJSUzI1NiIsImtpZCI6IjhjZTE2OWM0YjIwYjQ2ZWM5YTQyOTU3Y2ZhODUzNzQ1IiwidHlwIjoiSldUIn0.eyJ0ZW5hbnRfaWQiOiJhOWY0ZDFhMDU5NmQ0YWVhOGY4MzA5OTJlYzRiZGFjMSIsImVpZCI6IjExNzkzMCIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL3NpZCI6Ijk2MDI0OGM1NzA3YzQ3MmFhYTEzM2I1N2ZhODE1ZmVhIiwibGFuZ3VhZ2UiOiJVUyIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL2VtYWlsYWRkcmVzcyI6IjExNzkzMEBobWcuY29tIiwiZXhwIjoxNzgyNDc1NzY5LCJpc3MiOiJodHRwczovL3Nzby11YXQuaG1nLmNvbSIsImF1ZCI6ImE5ZjRkMWEwNTk2ZDRhZWE4ZjgzMDk5MmVjNGJkYWMxIn0.rJcLVsG8D0XECyLERCTD2uqGeWyvp-OBVGE9uL2qKrX4etFUHgdFt_5kYF6edFTtGy-0PIZadHDmv7e-IOhVWHm5HVMClaukiXoRXR8cDN8XA1wfme3Kd-U5PXN-IRh49AyRTzLO0rYNPvH81ScosWGlsFSkOvA-0hJNa2adHdtvgNvB8wJshSU5p7sAmF8mjdDY6aInG19etu2iEuUDwHHA4ZY_ts4hboHo8fE392hFaYGonExoD7bpW5RMx5xKWeRCmWpG_PK8Aw_z1jGzdB9PANus4pteRGuln1J-kmo2lQC9pVrSyZATAKp1HfgfyZ_vUhaHEfM69cMWaCslJQ"; - // var request = http.MultipartRequest('POST', Uri.parse('https://ek.techmaster.in/SSO/HMG')); - var request = http.MultipartRequest('POST', Uri.parse('https://Paxes-release.techmaster.in/SSO/HMG')); + // token = + // "eyJhbGciOiJSUzI1NiIsImtpZCI6IjhjZTE2OWM0YjIwYjQ2ZWM5YTQyOTU3Y2ZhODUzNzQ1IiwidHlwIjoiSldUIn0.eyJ0ZW5hbnRfaWQiOiJhOWY0ZDFhMDU5NmQ0YWVhOGY4MzA5OTJlYzRiZGFjMSIsImVpZCI6IjExNzkzMCIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL3NpZCI6Ijk2MDI0OGM1NzA3YzQ3MmFhYTEzM2I1N2ZhODE1ZmVhIiwibGFuZ3VhZ2UiOiJVUyIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL2VtYWlsYWRkcmVzcyI6IjExNzkzMEBobWcuY29tIiwiZXhwIjoxNzgyNDc1NzY5LCJpc3MiOiJodHRwczovL3Nzby11YXQuaG1nLmNvbSIsImF1ZCI6ImE5ZjRkMWEwNTk2ZDRhZWE4ZjgzMDk5MmVjNGJkYWMxIn0.rJcLVsG8D0XECyLERCTD2uqGeWyvp-OBVGE9uL2qKrX4etFUHgdFt_5kYF6edFTtGy-0PIZadHDmv7e-IOhVWHm5HVMClaukiXoRXR8cDN8XA1wfme3Kd-U5PXN-IRh49AyRTzLO0rYNPvH81ScosWGlsFSkOvA-0hJNa2adHdtvgNvB8wJshSU5p7sAmF8mjdDY6aInG19etu2iEuUDwHHA4ZY_ts4hboHo8fE392hFaYGonExoD7bpW5RMx5xKWeRCmWpG_PK8Aw_z1jGzdB9PANus4pteRGuln1J-kmo2lQC9pVrSyZATAKp1HfgfyZ_vUhaHEfM69cMWaCslJQ"; + // // var request = http.MultipartRequest('POST', Uri.parse('https://ek.techmaster.in/SSO/HMG')); + var request = http.MultipartRequest('POST', Uri.parse('https://Paxes-release.techmaster.in/SSO/HMG')); // UAT + // var request = http.MultipartRequest('POST', Uri.parse('https://paxes.com/sso/hmg')); // Prod request.fields.addAll({'JWTToken': token}); // request.headers.addAll(headers); @@ -414,4 +418,57 @@ class DashboardApiClient { // postParams, // ); // } + + Future getEmployeeParkingQrCode() async { + String url = "${ApiConsts.erpRest}GET_Employee_Parking"; + Map postParams = {}; + postParams.addAll(AppState().postParamsJson); + return await ApiClient().postJsonForObject( + (json) { + GetEmployeeParking responseData = GetEmployeeParking.fromJson(json); + return responseData; + }, + url, + postParams, + ); + } + + //Fetch All Courses Status + Future fetchAllCoursesStatus() async { + String url = "https://elearning.hmg.com/moodle_dev/api/Fetch_All_Courses_Status.php"; + + Map headers = { + 'x-api-key': 'bLi@mbXJeXTHd/)h&LFh%25+%25(Nbnaq6hBg%7d%5dyQbthY%7bv6ew6-5UT\$NASwucn%7d,_PSJpuwNCCen2%7djj%7b00HR2T-%5b,k7W%7d-0yepK?%258', + 'Content-Type': 'application/x-www-form-urlencoded', + }; + + Map postParams = { + "username": AppState().memberInformationList?.eMPLOYEENUMBER ?? "", + }; + + return await ApiClient().postJsonForObject( + (json) { + CoursesResponseModel responseData = CoursesResponseModel.fromJson(json); + return responseData; + }, + url, + postParams, + headers: headers, + isFormData: false, + ); + } + + Future getGreetingCards() async { + String url = "${ApiConsts.erpRest}Get_GreetingCards"; + Map postParams = {}; + postParams.addAll(AppState().postParamsJson); + return await ApiClient().postJsonForObject( + (json) { + GetGreetingCardsModelResponse responseData = GetGreetingCardsModelResponse.fromJson(json); + return responseData; + }, + url, + postParams, + ); + } } diff --git a/lib/api/etqan_ovr_api_client.dart b/lib/api/etqan_ovr_api_client.dart new file mode 100644 index 0000000..f7512cc --- /dev/null +++ b/lib/api/etqan_ovr_api_client.dart @@ -0,0 +1,140 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:intl/intl.dart'; +import 'package:mohem_flutter_app/api/api_client.dart'; +import 'package:mohem_flutter_app/app_state/app_state.dart'; +import 'package:mohem_flutter_app/classes/consts.dart'; +import 'package:mohem_flutter_app/models/etqan_ovr/etqan_get_employee_incident_report.dart'; +import 'package:mohem_flutter_app/models/etqan_ovr/etqan_get_requests_model.dart'; +import 'package:mohem_flutter_app/models/etqan_ovr/etqan_getprojects_model.dart'; +import 'package:mohem_flutter_app/models/generic_response_model.dart'; +import 'package:mohem_flutter_app/models/mowadhafhi/get_department_sections.dart'; +import 'package:mohem_flutter_app/models/mowadhafhi/get_project_departments.dart'; +import 'package:mohem_flutter_app/models/mowadhafhi/get_projects.dart'; +import 'package:mohem_flutter_app/models/mowadhafhi/get_section_topics.dart'; +import 'package:mohem_flutter_app/models/mowadhafhi/get_ticket_details.dart'; +import 'package:mohem_flutter_app/models/mowadhafhi/get_ticket_transactions.dart'; +import 'package:mohem_flutter_app/models/mowadhafhi/get_ticket_types.dart'; +import 'package:mohem_flutter_app/models/mowadhafhi/get_tickets_list.dart'; +import 'package:mohem_flutter_app/models/mowadhafhi/get_transaction_attachment_model.dart'; + +class EtqanApiClient { + static final EtqanApiClient _instance = EtqanApiClient._internal(); + + EtqanApiClient._internal(); + + factory EtqanApiClient() => _instance; + + Future> getEtqanProjects() async { + String url = "${ApiConsts.cocRest}ETQAN_GetProjects"; + Map postParams = {"P_SELECTED_EMPLOYEE_NUMBER": AppState().memberInformationList?.eMPLOYEENUMBER}; + postParams.addAll(AppState().postParamsJson); + return await ApiClient().postJsonForObject( + (json) { + List? responseData = []; + if (json["ETQAN_GetProjects_Response"] != null && json["ETQAN_GetProjects_Response"].isNotEmpty) { + json["ETQAN_GetProjects_Response"].forEach((element) { + responseData.add(EtqanGetProjectsResponse.fromJson(element)); + }); + } + return responseData; + }, + url, + postParams, + ); + } + + Future> getEmployeeEtqanRequests() async { + String url = "${ApiConsts.cocRest}ETQAN_GetEmployeeOVRRequests"; + Map postParams = { + "ETQAN_EmployeeID": AppState().memberInformationList?.eMPLOYEENUMBER, + "P_SELECTED_EMPLOYEE_NUMBER": AppState().memberInformationList?.eMPLOYEENUMBER, + }; + postParams.addAll(AppState().postParamsJson); + return await ApiClient().postJsonForObject( + (json) { + List responseData = []; + if (json["ETQAN_GetEmployeeOVRRequests_Response"] != null && json["ETQAN_GetEmployeeOVRRequests_Response"].isNotEmpty) { + json["ETQAN_GetEmployeeOVRRequests_Response"].forEach((element) { + responseData.add(EtqanGetEmployeeOvrRequestsResponse.fromJson(element)); + }); + } + + if (responseData.isNotEmpty) { + responseData.sort((EtqanGetEmployeeOvrRequestsResponse a, EtqanGetEmployeeOvrRequestsResponse b) { + if (a.createdDate == null && b.createdDate == null) return 0; + if (a.createdDate == null) return 1; + if (b.createdDate == null) return -1; + + try { + // Parse dates in format "26-Feb-2026 12:54 PM" or "01-Mar-2026 02:45 PM" + DateFormat dateFormat = DateFormat("dd-MMM-yyyy hh:mm a", "en_US"); + DateTime dateA = dateFormat.parse(a.createdDate!); + DateTime dateB = dateFormat.parse(b.createdDate!); + return dateB.compareTo(dateA); // Descending order (newest first) + } catch (e) { + return (b.createdDate ?? '').compareTo(a.createdDate ?? ''); + } + }); + } + return responseData; + }, + url, + postParams, + ); + } + + Future> getEtqanIncidentRequests(String ticketNo) async { + String url = "${ApiConsts.cocRest}ETQAN_GetIncidentRequest"; + Map postParams = {"ETQAN_TicketNumber": ticketNo, "P_SELECTED_EMPLOYEE_NUMBER": AppState().memberInformationList?.eMPLOYEENUMBER}; + postParams.addAll(AppState().postParamsJson); + return await ApiClient().postJsonForObject( + (json) { + List responseData = []; + if (json["ETQAN_GetIncidentRequest_Response"] != null && json["ETQAN_GetIncidentRequest_Response"].isNotEmpty) { + json["ETQAN_GetIncidentRequest_Response"].forEach((element) { + responseData.add(EtqanGetIncidentRequestResponse.fromJson(element)); + }); + } + return responseData; + }, + url, + postParams, + ); + } + + Future?> createRequest(String description, int? projectID, String? attachmentBase64, bool? isAnonymous) async { + String url = "${ApiConsts.cocRest}ETQAN_CreateIncident"; + Map postParams = { + "P_SELECTED_EMPLOYEE_NUMBER": AppState().memberInformationList?.eMPLOYEENUMBER, + "P_USER_NAME": AppState().memberInformationList?.eMPLOYEENUMBER, + "ETQAN_Emp_EmployeeNumber": AppState().memberInformationList?.eMPLOYEENUMBER, + "ETQAN_Description": description, + "ETQAN_ProjectId": projectID, + "ETQAN_FileInfoBase64": attachmentBase64, + "ETQAN_IsAnonymous": isAnonymous ?? false, + }; + + postParams.addAll(AppState().postParamsJson); + return await ApiClient().postJsonForObject( + (json) { + if (json["ETQAN_CreateIncident_Response"] != null) { + var response = json["ETQAN_CreateIncident_Response"]; + if (response["id"] != null && response["ticketNumber"] != null) { + return {"id": response["id"], "ticketNumber": response["ticketNumber"]}; + } + return null; + } + return null; + + // "ETQAN_CreateIncident_Response": { + // "id": "00adabe4-4a32-4b35-1f00-08de4d152e7c", + // "ticketNumber": "OVR/OLY/2026/224" + // }, + }, + url, + postParams, + ); + } +} diff --git a/lib/app_state/app_state.dart b/lib/app_state/app_state.dart index e1a5281..21bf583 100644 --- a/lib/app_state/app_state.dart +++ b/lib/app_state/app_state.dart @@ -90,7 +90,7 @@ class AppState { String get getHuaweiPushToken => _huaweiPushToken; - final PostParamsModel _postParamsInitConfig = PostParamsModel(channel: 33, versionID: 9.5, mobileType: Platform.isAndroid ? "android" : "ios"); + final PostParamsModel _postParamsInitConfig = PostParamsModel(channel: 33, versionID: 9.6, mobileType: Platform.isAndroid ? "android" : "ios"); void setPostParamsInitConfig() { isAuthenticated = false; diff --git a/lib/classes/consts.dart b/lib/classes/consts.dart index dbec87d..d8efe5d 100644 --- a/lib/classes/consts.dart +++ b/lib/classes/consts.dart @@ -4,14 +4,14 @@ class ApiConsts { // static String baseUrl = "http://10.200.204.11"; // Local server // static String baseUrl = "https://erptstapp.srca.org.sa"; // SRCA server - // static String baseUrl = "https://uat.hmgwebservices.com"; // UAT ser343622ver + static String baseUrl = "https://uat.hmgwebservices.com"; // UAT ser343622ver // static String baseUrl = "http://10.201.204.101:2024"; // static String baseUrl = "https://webservices.hmg.com"; // PreProd // static String baseUrl = "https://hmgwebservices.com"; // Live server - static String baseUrl = "https://mohemm.hmg.com"; // New Live server + // static String baseUrl = "https://mohemm.hmg.com"; // New Live server // - // static String baseUrl = "https://uat.hmgwebservices.com"; // UAT ser343622ver + // static String baseUrl = "http://10.20.200.111:1010/"; // static String baseUrl = "https://webservices.hmg.com"; // PreProd @@ -27,7 +27,8 @@ class ApiConsts { static String user = baseUrlServices + "api/User/"; static String cocRest = baseUrlServices + "COCWS.svc/REST/"; - static String ssoAuthRedirection = "https://sso-uat.hmg.com/api/auth/connect"; + static String ssoAuthRedirection = "https://sso-uat.hmg.com/api/auth/connect"; // UAT + // static String ssoAuthRedirection = "https://sso.hmg.com/api/auth/connect"; // Prod //Chat static String chatServerBaseUrl = "https://apiderichat.hmg.com/"; diff --git a/lib/classes/utils.dart b/lib/classes/utils.dart index 6a9b16b..2ae4fe3 100644 --- a/lib/classes/utils.dart +++ b/lib/classes/utils.dart @@ -135,8 +135,8 @@ class Utils { } } - static Future showErrorDialog({required BuildContext context, required VoidCallback onOkTapped, required String message}) async { - return showDialog(context: context, builder: (BuildContext context) => ConfirmDialog(message: message, onTap: onOkTapped)); + static Future showErrorDialog({required BuildContext context, required VoidCallback onOkTapped, required String message, VoidCallback? onCloseTap}) async { + return showDialog(context: context, builder: (BuildContext context) => ConfirmDialog(message: message, onTap: onOkTapped, onCloseTap: onCloseTap)); } static void confirmDialog(cxt, String message, {VoidCallback? onTap}) { diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 4653880..9bae8df 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -10,6 +10,9 @@ import 'package:mohem_flutter_app/ui/chat/favorite_users_screen.dart'; import 'package:mohem_flutter_app/ui/chat/group_chat_detaied_screen.dart'; import 'package:mohem_flutter_app/ui/chat/group_members.dart'; import 'package:mohem_flutter_app/ui/chat/manage_group.dart'; +import 'package:mohem_flutter_app/ui/etqan_ovr/etqan_create_request.dart'; +import 'package:mohem_flutter_app/ui/etqan_ovr/etqan_ovr_home.dart'; +import 'package:mohem_flutter_app/ui/etqan_ovr/etqan_request_detailed.dart'; import 'package:mohem_flutter_app/ui/landing/dashboard_screen.dart'; import 'package:mohem_flutter_app/ui/landing/itg/change_itg_ad_password_screen.dart'; import 'package:mohem_flutter_app/ui/landing/itg/its_add_screen_video_image.dart'; @@ -40,6 +43,7 @@ import 'package:mohem_flutter_app/ui/my_team/profile_details.dart'; import 'package:mohem_flutter_app/ui/my_team/subordinate_leave.dart'; import 'package:mohem_flutter_app/ui/my_team/team_members.dart'; import 'package:mohem_flutter_app/ui/my_team/view_attendance.dart'; +import 'package:mohem_flutter_app/ui/parking_qr/parking_qr.dart'; import 'package:mohem_flutter_app/ui/payslip/monthly_pay_slip_screen.dart'; import 'package:mohem_flutter_app/ui/performance/performance_appraisal_screen.dart'; import 'package:mohem_flutter_app/ui/profile/add_update_family_member.dart'; @@ -54,6 +58,7 @@ import 'package:mohem_flutter_app/ui/profile/profile_screen.dart'; import 'package:mohem_flutter_app/ui/screens/announcements/announcement_details.dart'; import 'package:mohem_flutter_app/ui/screens/announcements/announcements.dart'; import 'package:mohem_flutter_app/ui/screens/child_education/child_education_assistance.dart'; +import 'package:mohem_flutter_app/ui/screens/courses/courses_screen.dart'; // import 'package:mohem_flutter_app/ui/my_attendance/work_from_home_screen.dart'; import 'package:mohem_flutter_app/ui/screens/eit/add_eit.dart'; import 'package:mohem_flutter_app/ui/screens/event_activity/event_activity.dart'; @@ -166,6 +171,9 @@ class AppRoutes { static const String offersAndDiscounts = "/offersAndDiscounts"; static const String offersAndDiscountsDetails = "/offersAndDiscountsDetails"; + // Courses + static const String courses = "/courses"; + //Pay slip static const String monthlyPaySlip = "/monthlyPaySlip"; @@ -211,6 +219,13 @@ class AppRoutes { static const String activityScreen = "/activityScreen"; + static const String etqanOvr = "/etqanOvr"; + static const String etqanCreateRequest = "/etqanCreateRequest"; + static const String etqanGetIncidientRequest = "/etqanGetIncidientRequest"; + + //Parking QR + static const String parkingQr = "/parkingQrScreen"; + static final Map routes = { login: (BuildContext context) => LoginScreen(), verifyLogin: (BuildContext context) => VerifyLoginScreen(), @@ -291,6 +306,9 @@ class AppRoutes { offersAndDiscounts: (BuildContext context) => OffersAndDiscountsHome(), offersAndDiscountsDetails: (BuildContext context) => OffersAndDiscountsDetails(), + // Courses + courses: (BuildContext context) => const CoursesScreen(), + //pay slip monthlyPaySlip: (BuildContext context) => MonthlyPaySlipScreen(), performanceEvaluation: (BuildContext context) => PerformanceAppraisal(), @@ -328,6 +346,16 @@ class AppRoutes { unsafeDeviceScreen: (BuildContext context) => const UnsafeDeviceScreen(), appUpdateScreen: (BuildContext context) => const AppUpdateScreen(), childEducation: (BuildContext context) => ChildEducationAssistance(), - activityScreen:(BuildContext context) => const EventActivityScreen() + activityScreen:(BuildContext context) => const EventActivityScreen(), + + //ETQAN OVR + etqanOvr: (BuildContext context) => EtqanOvrHome(), + etqanCreateRequest: (BuildContext context) => EtqanOvrCreateRequest(), + etqanGetIncidientRequest: (BuildContext context) => EtqanOvrRequestDetailed(), + activityScreen:(BuildContext context) => const EventActivityScreen(), + + //Parking QR + parkingQr: (BuildContext context) => const ParkingQrScreen(), + }; } diff --git a/lib/generated/codegen_loader.g.dart b/lib/generated/codegen_loader.g.dart index 6feb438..f186a22 100644 --- a/lib/generated/codegen_loader.g.dart +++ b/lib/generated/codegen_loader.g.dart @@ -180,6 +180,7 @@ class CodegenLoader extends AssetLoader{ "quotationAnalysis": "تحليل الاقتباس", "subject": "موضوعات", "description": "وصف", + "pleaseEnterDescription": "الرجاء إدخال الوصف", "supplier": "المورد", "site": "موقع", "buyer": "مشتر", @@ -274,6 +275,7 @@ class CodegenLoader extends AssetLoader{ "fullName": "الأسم الكامل", "remove": "حذف", "submit": "ارسال", + "submitAsAnonymous": "إرسال كمجهول", "areYouSureYouWantToSubmit": "هل أنت متأكد أنك تريد أن تقدم؟", "comments": "تعليقات", "writeComment": "أكتب تعليقا", @@ -655,7 +657,33 @@ class CodegenLoader extends AssetLoader{ "yearsUsed": "سنوات الاستخدام", "faRequest": "طلب التخلص من FA", "showMore": "عرض المزيد", - "buyerDetails": "تفاصيل المشتري" + "ticketDetails": "تفاصيل التذكرة", + "currentTicketBalance": "رصيد التذكرة الحالي", + "ticketHistory": "تاريخ التذكرة", + "proceedBooking": "المتابعة للحجز", + "courses": "الدورات التدريبية", + "course": "دورة", + "status": "الحالة", + "deadline": "الموعد النهائي", + "overdue": "متأخر", + "noCoursesAvailable": "لا توجد دورات متاحة", + "pullToRefresh": "اسحب للتحديث", + "courseUrlNotAvailable": "رابط الدورة غير متوفر", + "buyerDetails": "تفاصيل المشتري", + "etqanOVR": "الإبلاغ عن حادثة", + "etqanRequest": "طلبات الحوادث", + "selectProject": "حدد المشروع", + "project": "مشروع", + "keen": "حريص", + "reportIncident": "الإبلاغ عن الحادث", + "myIncidents": "حوادثي", + "incidentDetails": "تفاصيل الحادث", + "ticketNumber": "رقم التذكرة", + "ticketId": "معرف التذكرة", + "pleaseSelectProject": "الرجاء اختيار مشروع", + "viewAttachment": "عرض المرفق", + "parkingQr": "وقوف السيارات QR", + "startNow": "ابدأ الآن" }; static const Map _en_US = { "mohemm": "Mohemm", @@ -817,6 +845,7 @@ static const Map _en_US = { "subject": "Subject", "quotationAnalysis": "Quotation Analysis", "description": "Description", + "pleaseEnterDescription": "Please enter description", "supplier": "Supplier", "site": "Site", "buyer": "Buyer", @@ -915,6 +944,7 @@ static const Map _en_US = { "remove": "Remove", "Attendance": "Attendance", "submit": "Submit", + "submitAsAnonymous": "Submit as Anonymous", "areYouSureYouWantToSubmit": "Are you sure you want to submit?", "comments": "Comments", "writeComment": "Write a comment", @@ -1298,7 +1328,33 @@ static const Map _en_US = { "yearsUsed": "Years Used", "faRequest": "FA Disposal Request", "showMore": "Show More", - "buyerDetails": "Buyer Details" + "ticketDetails": "Ticket Details", + "currentTicketBalance": "Current Ticket Balance", + "ticketHistory": "Ticket History", + "proceedBooking": "Proceed For Booking", + "courses": "Courses", + "course": "Course", + "status": "Status", + "deadline": "Deadline", + "overdue": "Overdue", + "noCoursesAvailable": "No Courses Available", + "pullToRefresh": "Pull to refresh", + "courseUrlNotAvailable": "Course URL not available", + "buyerDetails": "Buyer Details", + "etqanOVR": "Report An Incident", + "etqanRequest": "Incident Requests", + "selectProject": "Select Project", + "project": "Project", + "keen": "Keen", + "reportIncident": "Report Incident", + "myIncidents": "My Incidents", + "incidentDetails": "Incident Details", + "ticketNumber": "Ticket Number", + "ticketId": "Ticket ID", + "pleaseSelectProject": "Please select a project", + "viewAttachment": "View Attachment", + "parkingQr": "Parking QR", + "startNow": "Start Now" }; static const Map> mapLocales = {"ar_SA": _ar_SA, "en_US": _en_US}; } diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 1a1cf95..972583a 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -167,6 +167,7 @@ abstract class LocaleKeys { static const quotationAnalysis = 'quotationAnalysis'; static const subject = 'subject'; static const description = 'description'; + static const pleaseEnterDescription = 'pleaseEnterDescription'; static const supplier = 'supplier'; static const site = 'site'; static const buyer = 'buyer'; @@ -261,6 +262,7 @@ abstract class LocaleKeys { static const fullName = 'fullName'; static const remove = 'remove'; static const submit = 'submit'; + static const submitAsAnonymous = 'submitAsAnonymous'; static const areYouSureYouWantToSubmit = 'areYouSureYouWantToSubmit'; static const comments = 'comments'; static const writeComment = 'writeComment'; @@ -627,6 +629,32 @@ abstract class LocaleKeys { static const yearsUsed = 'yearsUsed'; static const faRequest = 'faRequest'; static const showMore = 'showMore'; + static const ticketDetails = 'ticketDetails'; + static const currentTicketBalance = 'currentTicketBalance'; + static const ticketHistory = 'ticketHistory'; + static const proceedBooking = 'proceedBooking'; + static const courses = 'courses'; + static const course = 'course'; + static const status = 'status'; + static const deadline = 'deadline'; + static const overdue = 'overdue'; + static const noCoursesAvailable = 'noCoursesAvailable'; + static const pullToRefresh = 'pullToRefresh'; + static const courseUrlNotAvailable = 'courseUrlNotAvailable'; static const buyerDetails = 'buyerDetails'; + static const etqanOVR = 'etqanOVR'; + static const etqanRequest = 'etqanRequest'; + static const selectProject = 'selectProject'; + static const project = 'project'; + static const keen = 'keen'; + static const reportIncident = 'reportIncident'; + static const myIncidents = 'myIncidents'; + static const incidentDetails = 'incidentDetails'; + static const ticketNumber = 'ticketNumber'; + static const ticketId = 'ticketId'; + static const pleaseSelectProject = 'pleaseSelectProject'; + static const viewAttachment = 'viewAttachment'; + static const parkingQr = 'parkingQr'; + static const startNow = 'startNow'; } diff --git a/lib/main.dart b/lib/main.dart index a43250e..0105672 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -12,6 +12,7 @@ import 'package:mohem_flutter_app/models/post_params_model.dart'; import 'package:mohem_flutter_app/provider/chat_provider_model.dart'; import 'package:mohem_flutter_app/provider/dashboard_provider_model.dart'; import 'package:mohem_flutter_app/provider/eit_provider_model.dart'; +import 'package:mohem_flutter_app/provider/etqan_ovr_provider.dart'; import 'package:mohem_flutter_app/theme/app_theme.dart'; import 'package:mohem_flutter_app/ui/marathon/marathon_provider.dart'; import 'package:month_year_picker/month_year_picker.dart'; @@ -53,6 +54,7 @@ Future main() async { ChangeNotifierProvider(create: (_) => EITProviderModel()), ChangeNotifierProvider(create: (_) => ChatProviderModel()), ChangeNotifierProvider(create: (_) => MarathonProvider()), + ChangeNotifierProvider(create: (_) => EtqanOvrProviderModel()), // ChangeNotifierProvider( // create: (_) => ChatCallProvider(), // ), diff --git a/lib/models/dashboard/courses_response_model.dart b/lib/models/dashboard/courses_response_model.dart new file mode 100644 index 0000000..730d4a4 --- /dev/null +++ b/lib/models/dashboard/courses_response_model.dart @@ -0,0 +1,94 @@ +class CoursesResponseModel { + bool? success; + int? status; + String? username; + List? coursesStatus; + int? totalCourses; + + CoursesResponseModel({ + this.success, + this.status, + this.username, + this.coursesStatus, + this.totalCourses, + }); + + CoursesResponseModel.fromJson(Map json) { + success = json['success']; + status = json['status']; + username = json['username']; + if (json['courses_status'] != null) { + coursesStatus = []; + json['courses_status'].forEach((v) { + coursesStatus!.add(CourseStatus.fromJson(v)); + }); + } + totalCourses = json['total_courses']; + } + + Map toJson() { + Map data = {}; + data['success'] = success; + data['status'] = status; + data['username'] = username; + if (coursesStatus != null) { + data['courses_status'] = coursesStatus!.map((v) => v.toJson()).toList(); + } + data['total_courses'] = totalCourses; + return data; + } +} + +class CourseStatus { + String? cId; + String? cFullname; + String? cNameAr; + String? cShortname; + String? cStatus; + String? cIsMohemmAffective; + String? cStartdate; + String? cEnddate; + String? cUrl; + String? token; + CourseStatus({ + this.cId, + this.cFullname, + this.cNameAr, + this.cShortname, + this.cStatus, + this.cIsMohemmAffective, + this.cStartdate, + this.cEnddate, + this.cUrl, + this.token + }); + + CourseStatus.fromJson(Map json) { + cId = json['c_id']; + cFullname = json['c_fullname']; + cNameAr = json['c_name_ar']; + cShortname = json['c_shortname']; + cStatus = json['c_status']; + cIsMohemmAffective = json['c_is_mohemm_affective']; + cStartdate = json['c_startdate']; + cEnddate = json['c_enddate']; + cUrl = json['c_url']; + token =json['c_token']; + } + + Map toJson() { + Map data = {}; + data['c_id'] = cId; + data['c_fullname'] = cFullname; + data['c_name_ar'] = cNameAr; + data['c_shortname'] = cShortname; + data['c_status'] = cStatus; + data['c_is_mohemm_affective'] = cIsMohemmAffective; + data['c_startdate'] = cStartdate; + data['c_enddate'] = cEnddate; + data['c_url'] = cUrl; + data['c_token'] = token; + return data; + } +} + diff --git a/lib/models/etqan_ovr/etqan_get_employee_incident_report.dart b/lib/models/etqan_ovr/etqan_get_employee_incident_report.dart new file mode 100644 index 0000000..e4a57fd --- /dev/null +++ b/lib/models/etqan_ovr/etqan_get_employee_incident_report.dart @@ -0,0 +1,57 @@ +import 'dart:convert'; + +class EtqanGetEmployeeIncidentRequestModel { + List? etqanGetIncidentRequestResponse; + + EtqanGetEmployeeIncidentRequestModel({ + this.etqanGetIncidentRequestResponse, + }); + + factory EtqanGetEmployeeIncidentRequestModel.fromRawJson(String str) => EtqanGetEmployeeIncidentRequestModel.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory EtqanGetEmployeeIncidentRequestModel.fromJson(Map json) => EtqanGetEmployeeIncidentRequestModel( + etqanGetIncidentRequestResponse: json["ETQAN_GetIncidentRequest_Response"] == null ? [] : List.from(json["ETQAN_GetIncidentRequest_Response"]!.map((x) => EtqanGetIncidentRequestResponse.fromJson(x))), + ); + + Map toJson() => { + "ETQAN_GetIncidentRequest_Response": etqanGetIncidentRequestResponse == null ? [] : List.from(etqanGetIncidentRequestResponse!.map((x) => x.toJson())), + }; +} + +class EtqanGetIncidentRequestResponse { + String? key; + String? keyAr; + String? value; + String? valueAr; + List? subValues; + + EtqanGetIncidentRequestResponse({ + this.key, + this.keyAr, + this.value, + this.valueAr, + this.subValues, + }); + + factory EtqanGetIncidentRequestResponse.fromRawJson(String str) => EtqanGetIncidentRequestResponse.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory EtqanGetIncidentRequestResponse.fromJson(Map json) => EtqanGetIncidentRequestResponse( + key: json["key"], + keyAr: json["keyAr"], + value: json["value"], + valueAr: json["valueAr"], + subValues: json["subValues"] == null ? [] : List.from(json["subValues"]!.map((x) => x)), + ); + + Map toJson() => { + "key": key, + "keyAr": keyAr, + "value": value, + "valueAr": valueAr, + "subValues": subValues == null ? [] : List.from(subValues!.map((x) => x)), + }; +} diff --git a/lib/models/etqan_ovr/etqan_get_requests_model.dart b/lib/models/etqan_ovr/etqan_get_requests_model.dart new file mode 100644 index 0000000..ac4911b --- /dev/null +++ b/lib/models/etqan_ovr/etqan_get_requests_model.dart @@ -0,0 +1,65 @@ +import 'dart:convert'; + +class EtqanGetEmployeeRequestsModel { + List? etqanGetEmployeeOvrRequestsResponse; + + EtqanGetEmployeeRequestsModel({ + this.etqanGetEmployeeOvrRequestsResponse, + }); + + factory EtqanGetEmployeeRequestsModel.fromRawJson(String str) => EtqanGetEmployeeRequestsModel.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory EtqanGetEmployeeRequestsModel.fromJson(Map json) => EtqanGetEmployeeRequestsModel( + etqanGetEmployeeOvrRequestsResponse: json["ETQAN_GetEmployeeOVRRequests_Response"] == null ? [] : List.from(json["ETQAN_GetEmployeeOVRRequests_Response"]!.map((x) => EtqanGetEmployeeOvrRequestsResponse.fromJson(x))), + ); + + Map toJson() => { + "ETQAN_GetEmployeeOVRRequests_Response": etqanGetEmployeeOvrRequestsResponse == null ? [] : List.from(etqanGetEmployeeOvrRequestsResponse!.map((x) => x.toJson())), + }; +} + +class EtqanGetEmployeeOvrRequestsResponse { + String? createdDate; + String? description; + String? id; + bool? isAnonymous; + String? status; + String? statusAr; + String? ticketNumber; + + EtqanGetEmployeeOvrRequestsResponse({ + this.createdDate, + this.description, + this.id, + this.isAnonymous, + this.status, + this.statusAr, + this.ticketNumber, + }); + + factory EtqanGetEmployeeOvrRequestsResponse.fromRawJson(String str) => EtqanGetEmployeeOvrRequestsResponse.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory EtqanGetEmployeeOvrRequestsResponse.fromJson(Map json) => EtqanGetEmployeeOvrRequestsResponse( + createdDate: json["createdDate"], + description: json["description"], + id: json["id"], + isAnonymous: json["isAnonymous"], + status: json["status"], + statusAr: json["statusAr"], + ticketNumber: json["ticketNumber"], + ); + + Map toJson() => { + "createdDate": createdDate, + "description": description, + "id": id, + "isAnonymous": isAnonymous, + "status": status, + "statusAr": statusAr, + "ticketNumber": ticketNumber, + }; +} diff --git a/lib/models/etqan_ovr/etqan_getprojects_model.dart b/lib/models/etqan_ovr/etqan_getprojects_model.dart new file mode 100644 index 0000000..5584faa --- /dev/null +++ b/lib/models/etqan_ovr/etqan_getprojects_model.dart @@ -0,0 +1,53 @@ +import 'dart:convert'; + +class EtqanGetProjectsModel { + List? etqanGetProjectsResponse; + + EtqanGetProjectsModel({ + this.etqanGetProjectsResponse, + }); + + factory EtqanGetProjectsModel.fromRawJson(String str) => EtqanGetProjectsModel.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory EtqanGetProjectsModel.fromJson(Map json) => EtqanGetProjectsModel( + etqanGetProjectsResponse: json["ETQAN_GetProjects_Response"] == null ? [] : List.from(json["ETQAN_GetProjects_Response"]!.map((x) => EtqanGetProjectsResponse.fromJson(x))), + ); + + Map toJson() => { + "ETQAN_GetProjects_Response": etqanGetProjectsResponse == null ? [] : List.from(etqanGetProjectsResponse!.map((x) => x.toJson())), + }; +} + +class EtqanGetProjectsResponse { + String? projectAr; + String? projectCode; + String? projectEn; + int? projectId; + + EtqanGetProjectsResponse({ + this.projectAr, + this.projectCode, + this.projectEn, + this.projectId, + }); + + factory EtqanGetProjectsResponse.fromRawJson(String str) => EtqanGetProjectsResponse.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory EtqanGetProjectsResponse.fromJson(Map json) => EtqanGetProjectsResponse( + projectAr: json["projectAr"], + projectCode: json["projectCode"], + projectEn: json["projectEn"], + projectId: json["projectId"], + ); + + Map toJson() => { + "projectAr": projectAr, + "projectCode": projectCode, + "projectEn": projectEn, + "projectId": projectId, + }; +} diff --git a/lib/models/get_employee_parking_details_model.dart b/lib/models/get_employee_parking_details_model.dart new file mode 100644 index 0000000..4a385c8 --- /dev/null +++ b/lib/models/get_employee_parking_details_model.dart @@ -0,0 +1,61 @@ +import 'dart:convert'; + +class GetEmployeeParking { + GetEmployeeParkingClass? getEmployeeParking; + + GetEmployeeParking({ + this.getEmployeeParking, + }); + + factory GetEmployeeParking.fromRawJson(String str) => GetEmployeeParking.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory GetEmployeeParking.fromJson(Map json) => GetEmployeeParking( + getEmployeeParking: json["GET_Employee_Parking"] == null ? null : GetEmployeeParkingClass.fromJson(json["GET_Employee_Parking"]), + ); + + Map toJson() => { + "GET_Employee_Parking": getEmployeeParking?.toJson(), + }; +} + +class GetEmployeeParkingClass { + DateTime? createdAt; + String? employeeCode; + String? isNewQrCode; + String? qrCode; + String? qrCodeImageBase64; + bool? success; + + GetEmployeeParkingClass({ + this.createdAt, + this.employeeCode, + this.isNewQrCode, + this.qrCode, + this.qrCodeImageBase64, + this.success, + }); + + factory GetEmployeeParkingClass.fromRawJson(String str) => GetEmployeeParkingClass.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory GetEmployeeParkingClass.fromJson(Map json) => GetEmployeeParkingClass( + createdAt: json["createdAt"] == null ? null : DateTime.parse(json["createdAt"]), + employeeCode: json["employeeCode"], + isNewQrCode: json["isNewQRCode"], + qrCode: json["qrCode"], + qrCodeImageBase64: json["qrCodeImageBase64"], + success: json["success"], + ); + + Map toJson() => { + "createdAt": createdAt?.toIso8601String(), + "employeeCode": employeeCode, + "isNewQRCode": isNewQrCode, + "qrCode": qrCode, + "qrCodeImageBase64": qrCodeImageBase64, + "success": success, + }; +} diff --git a/lib/models/greetings/greeting_card_model.dart b/lib/models/greetings/greeting_card_model.dart new file mode 100644 index 0000000..486aa8f --- /dev/null +++ b/lib/models/greetings/greeting_card_model.dart @@ -0,0 +1,142 @@ +import 'dart:convert'; + +class GetGreetingCardsModelResponse { + List? getGreetingCardsModelResponse; + + GetGreetingCardsModelResponse({ + this.getGreetingCardsModelResponse, + }); + + factory GetGreetingCardsModelResponse.fromRawJson(String str) => GetGreetingCardsModelResponse.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory GetGreetingCardsModelResponse.fromJson(Map json) => GetGreetingCardsModelResponse( + getGreetingCardsModelResponse: json["Get_GreetingCardsModel_Response"] == null ? [] : List.from(json["Get_GreetingCardsModel_Response"]!.map((x) => GetGreetingCardsModelResponseElement.fromJson(x))), + ); + + Map toJson() => { + "Get_GreetingCardsModel_Response": getGreetingCardsModelResponse == null ? [] : List.from(getGreetingCardsModelResponse!.map((x) => x.toJson())), + }; +} + +class GetGreetingCardsModelResponseElement { + String? backgroundImageUrlAr; + String? backgroundImageUrlEn; + String? buttonBgColor; + String? buttonText; + String? buttonTextN; + int? cardType; + int? categoryId; + String? categoryNameAr; + String? categoryNameEn; + int? channel; + String? createdOn; + String? descriptionAr; + String? descriptionEn; + String? endDate; + bool? hasButton; + int? id; + bool? isActive; + int? moduleId; + String? moduleName; + String? moduleNameN; + String? startDate; + String? textColor; + String? titleAr; + String? titleEn; + String? urlAr; + String? urlEn; + + GetGreetingCardsModelResponseElement({ + this.backgroundImageUrlAr, + this.backgroundImageUrlEn, + this.buttonBgColor, + this.buttonText, + this.buttonTextN, + this.cardType, + this.categoryId, + this.categoryNameAr, + this.categoryNameEn, + this.channel, + this.createdOn, + this.descriptionAr, + this.descriptionEn, + this.endDate, + this.hasButton, + this.id, + this.isActive, + this.moduleId, + this.moduleName, + this.moduleNameN, + this.startDate, + this.textColor, + this.titleAr, + this.titleEn, + this.urlAr, + this.urlEn, + }); + + factory GetGreetingCardsModelResponseElement.fromRawJson(String str) => GetGreetingCardsModelResponseElement.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory GetGreetingCardsModelResponseElement.fromJson(Map json) => GetGreetingCardsModelResponseElement( + backgroundImageUrlAr: json["BackgroundImageUrlAr"], + backgroundImageUrlEn: json["BackgroundImageUrlEn"], + buttonBgColor: json["ButtonBgColor"], + buttonText: json["ButtonText"], + buttonTextN: json["ButtonTextN"], + cardType: json["CardType"], + categoryId: json["CategoryID"], + categoryNameAr: json["CategoryNameAr"], + categoryNameEn: json["CategoryNameEn"], + channel: json["Channel"], + createdOn: json["CreatedOn"], + descriptionAr: json["DescriptionAr"], + descriptionEn: json["DescriptionEn"], + endDate: json["EndDate"], + hasButton: json["HasButton"], + id: json["ID"], + isActive: json["IsActive"], + moduleId: json["ModuleID"], + moduleName: json["ModuleName"], + moduleNameN: json["ModuleNameN"], + startDate: json["StartDate"], + textColor: json["TextColor"], + titleAr: json["TitleAr"], + titleEn: json["TitleEn"], + urlAr: json["UrlAr"], + urlEn: json["UrlEn"], + ); + + Map toJson() => { + "BackgroundImageUrlAr": backgroundImageUrlAr, + "BackgroundImageUrlEn": backgroundImageUrlEn, + "ButtonBgColor": buttonBgColor, + "ButtonText": buttonText, + "ButtonTextN": buttonTextN, + "CardType": cardType, + "CategoryID": categoryId, + "CategoryNameAr": categoryNameAr, + "CategoryNameEn": categoryNameEn, + "Channel": channel, + "CreatedOn": createdOn, + "DescriptionAr": descriptionAr, + "DescriptionEn": descriptionEn, + "EndDate": endDate, + "HasButton": hasButton, + "ID": id, + "IsActive": isActive, + "ModuleID": moduleId, + "ModuleName": moduleName, + "ModuleNameN": moduleNameN, + "StartDate": startDate, + "TextColor": textColor, + "TitleAr": titleAr, + "TitleEn": titleEn, + "UrlAr": urlAr, + "UrlEn": urlEn, + }; +} + diff --git a/lib/models/itg/advertisement.dart b/lib/models/itg/advertisement.dart index 002f73d..2379d37 100644 --- a/lib/models/itg/advertisement.dart +++ b/lib/models/itg/advertisement.dart @@ -136,10 +136,21 @@ class ActionButtonsColl { late String btnTextEn; late String btnTextAr; late String actionValue; - late dynamic iconOrImage; + dynamic iconOrImage; + String? actionType; late int orderNo; + String? url; - ActionButtonsColl({required this.actionButtonId, required this.btnTextEn, required this.btnTextAr, required this.actionValue, required this.iconOrImage, required this.orderNo}); + ActionButtonsColl({ + required this.actionButtonId, + required this.btnTextEn, + required this.btnTextAr, + required this.actionValue, + this.iconOrImage, + this.actionType, + required this.orderNo, + this.url, + }); ActionButtonsColl.fromJson(Map json) { actionButtonId = json['actionButtonId']; @@ -147,7 +158,9 @@ class ActionButtonsColl { btnTextAr = json['btnTextAr']; actionValue = json['actionValue']; iconOrImage = json['iconOrImage']; + actionType = json['actionType']; orderNo = json['orderNo']; + url = json['url']; } Map toJson() { @@ -157,7 +170,9 @@ class ActionButtonsColl { data['btnTextAr'] = this.btnTextAr; data['actionValue'] = this.actionValue; data['iconOrImage'] = this.iconOrImage; + data['actionType'] = this.actionType; data['orderNo'] = this.orderNo; + data['url'] = this.url; return data; } } diff --git a/lib/provider/dashboard_provider_model.dart b/lib/provider/dashboard_provider_model.dart index 05c9ebb..2b7a2bb 100644 --- a/lib/provider/dashboard_provider_model.dart +++ b/lib/provider/dashboard_provider_model.dart @@ -19,8 +19,10 @@ import 'package:mohem_flutter_app/models/dashboard/list_menu.dart'; import 'package:mohem_flutter_app/models/dashboard/menu_entries.dart'; import 'package:mohem_flutter_app/models/dashboard/menus.dart'; import 'package:mohem_flutter_app/models/dashboard/mohemm_itg_pending_task_responseitem.dart'; +import 'package:mohem_flutter_app/models/dashboard/courses_response_model.dart'; import 'package:mohem_flutter_app/models/eit/get_eit_transaction_model.dart'; import 'package:mohem_flutter_app/models/generic_response_model.dart'; +import 'package:mohem_flutter_app/models/greetings/greeting_card_model.dart'; import 'package:mohem_flutter_app/models/itg/itg_response_model.dart'; import 'package:mohem_flutter_app/models/offers_and_discounts/get_offers_list.dart'; import 'package:mohem_flutter_app/models/sso_auth_model.dart'; @@ -67,6 +69,15 @@ class DashboardProviderModel with ChangeNotifier, DiagnosticableTreeMixin { bool isOffersLoading = true; List getOffersList = []; + //Courses + bool isCoursesLoading = true; + CoursesResponseModel? coursesResponse; + List coursesList = []; + + bool isGreetingCardsLoading = true; + bool isDisplayEidGreetings = false; + List? greetingCardsList; + //Attendance Tracking API's & Methods Future fetchAttendanceTracking(context) async { try { @@ -115,12 +126,18 @@ class DashboardProviderModel with ChangeNotifier, DiagnosticableTreeMixin { getMenuEntriesList = null; isOffersLoading = true; getOffersList = []; + isGreetingCardsLoading = true; + isDisplayEidGreetings = false; drawerMenuItemList = [ DrawerMenuItem("assets/images/drawer/my_profile.svg", LocaleKeys.myProfile.tr(), AppRoutes.profile), DrawerMenuItem("assets/images/drawer/performance_evaluation.svg", LocaleKeys.performanceEvaluation.tr(), AppRoutes.performanceEvaluation), DrawerMenuItem("assets/images/drawer/mowadhafi.svg", LocaleKeys.mowadhafhi.tr(), AppRoutes.mowadhafhi), + // DrawerMenuItem("assets/images/drawer/mowadhafi.svg", LocaleKeys.etqanOVR.tr(), AppRoutes.etqanOvr), + DrawerMenuItem("assets/images/drawer/mowadhafi.svg", LocaleKeys.keen.tr(), AppRoutes.etqanOvr), + // DrawerMenuItem("assets/images/drawer/car_parking_icon.svg", LocaleKeys.parkingQr.tr(), AppRoutes.parkingQr), DrawerMenuItem("assets/images/drawer/pending_trasactions.svg", LocaleKeys.pendingTransactions.tr(), AppRoutes.pendingTransactions), + // DrawerMenuItem("assets/images/drawer/courses.svg", "courses".tr(), AppRoutes.courses), // DrawerMenuItem("assets/images/drawer/drawer_marathon.svg", LocaleKeys.brainMarathon.tr(), AppRoutes.marathonIntroScreen), DrawerMenuItem("assets/images/drawer/change_password.svg", LocaleKeys.changePassword.tr(), AppRoutes.changePassword), ]; @@ -306,6 +323,28 @@ class DashboardProviderModel with ChangeNotifier, DiagnosticableTreeMixin { } } + void fetchGreetingCards() async { + isGreetingCardsLoading = true; + notifyListeners(); + try { + GetGreetingCardsModelResponse? response = await DashboardApiClient().getGreetingCards(); + greetingCardsList = response?.getGreetingCardsModelResponse ?? []; + if (greetingCardsList != null && greetingCardsList!.isNotEmpty) { + isDisplayEidGreetings = greetingCardsList!.any((card) => card.isActive == true); + } else { + isDisplayEidGreetings = false; + } + isGreetingCardsLoading = false; + notifyListeners(); + } catch (ex) { + logger.wtf(ex); + isDisplayEidGreetings = false; + greetingCardsList = []; + isGreetingCardsLoading = false; + notifyListeners(); + } + } + Future fetchTicketBooking() async { try { GenericResponseModel? genericResponseModel = await DashboardApiClient().getTicketBookingRedirection(); @@ -326,8 +365,6 @@ class DashboardProviderModel with ChangeNotifier, DiagnosticableTreeMixin { void fetchTicketBalance() async { try { GenericResponseModel? ticketResponse = await DashboardApiClient().getTicketBalance(); - logger.d("Ticket Balance Response: ${ticketResponse?.toJson()}"); - print(" ========= Ticket Balance Response ======= "); } catch (ex) { logger.wtf(ex); isEventLoadingLoading = false; @@ -340,8 +377,7 @@ class DashboardProviderModel with ChangeNotifier, DiagnosticableTreeMixin { Future fetchSSOAuthRedirection({String? clientID}) async { try { SSOAuthModel? ssoResponse = await DashboardApiClient().getBookingSSOAuthRedirection(clientID: clientID!); - logger.d("SSO Response: ${ssoResponse!.toJson()}"); - if (ssoResponse.status == "Success") { + if (ssoResponse!.status == "Success") { return ssoResponse; } else { Utils.showToast(ssoResponse.message!.first ?? "Failed to fetch SSO Auth Token"); @@ -361,18 +397,12 @@ class DashboardProviderModel with ChangeNotifier, DiagnosticableTreeMixin { dynamic res = await DashboardApiClient().getBookingSSOFinalRedirection(token: token); logger.d("SSO URL: $res"); return res; - // if (ssoResponse.status == "Success") { - // return ssoResponse; - // } else { - // Utils.showToast(ssoResponse.message!.first ?? "Failed to fetch SSO Auth Token"); - // return null; - // } } catch (ex) { logger.wtf(ex); isEventLoadingLoading = false; notifyListeners(); Utils.handleException(ex, null, null); - return null; // Ensure a return value in case of an exception + return null; } } @@ -409,6 +439,30 @@ class DashboardProviderModel with ChangeNotifier, DiagnosticableTreeMixin { return res; } + //Courses API's & Methods + Future fetchCourses(BuildContext context) async { + try { + isCoursesLoading = true; + notifyListeners(); + + coursesResponse = await DashboardApiClient().fetchAllCoursesStatus(); + + if (coursesResponse != null && coursesResponse!.success == true) { + coursesList = coursesResponse!.coursesStatus ?? []; + } else { + coursesList = []; + } + + isCoursesLoading = false; + notifyListeners(); + } catch (ex) { + isCoursesLoading = false; + coursesList = []; + notifyListeners(); + Utils.handleException(ex, context, null); + } + } + void notify() { notifyListeners(); } diff --git a/lib/provider/etqan_ovr_provider.dart b/lib/provider/etqan_ovr_provider.dart new file mode 100644 index 0000000..b4f2eb1 --- /dev/null +++ b/lib/provider/etqan_ovr_provider.dart @@ -0,0 +1,262 @@ +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:mohem_flutter_app/api/etqan_ovr_api_client.dart'; +import 'package:mohem_flutter_app/classes/utils.dart'; +import 'package:mohem_flutter_app/models/etqan_ovr/etqan_get_employee_incident_report.dart'; +import 'package:mohem_flutter_app/models/etqan_ovr/etqan_get_requests_model.dart'; +import 'package:mohem_flutter_app/models/etqan_ovr/etqan_getprojects_model.dart'; +import 'package:speech_to_text/speech_recognition_result.dart'; +import 'package:speech_to_text/speech_to_text.dart'; + +class EtqanOvrProviderModel with ChangeNotifier, DiagnosticableTreeMixin { + // Home screen state + List? getEtqanProjectsList; + List? getEtqanEmployeeRequestsList; + List? getEtqanEmployeeIncidnetRequest; + bool isLoading = false; + String? _ticketId; + String? _currentStatus; + String? _currentStatusAr; + bool? _currentIsAnonymous; + + // Speech to text state + final SpeechToText _speechToText = SpeechToText(); + bool _speechEnabled = false; + bool _isListening = false; + String _currentLocaleId = ''; + TextEditingController? _currentController; + String _baseText = ''; // Text that was there when we started listening + String _currentSessionText = ''; // Text captured in current listening session + + // Getters for speech state + bool get speechEnabled => _speechEnabled; + + bool get isListening => _isListening; + + // Getter for ticketId + String get getTicketId => _ticketId ?? ''; + String get getCurrentStatus => _currentStatus ?? ''; + String get getCurrentStatusAr => _currentStatusAr ?? ''; + bool get getCurrentIsAnonymous => _currentIsAnonymous ?? false; + + // Setter for ticketId with status and isAnonymous + void setTicketId(String ticketId, {String? status, String? statusAr, bool? isAnonymous}) { + _ticketId = ticketId; + _currentStatus = status; + _currentStatusAr = statusAr; + _currentIsAnonymous = isAnonymous; + notifyListeners(); + } + + // Create request screen state + EtqanGetProjectsResponse? selectedProject; + String? selectedEmployeeNumber; + File? attachment; + String? attachmentBase64; + String description = ''; + bool isAnonymous = false; + + /// Initialize speech recognition - call this once when screen loads + Future initSpeech() async { + _speechEnabled = await _speechToText.initialize( + onStatus: (String status) { + print('Speech status: $status, isListening: $_isListening'); + // If user wants to keep listening but speech stopped, restart it + if ((status == 'done' || status == 'notListening') && _isListening && _currentController != null) { + // Save the finalized text before restarting + if (_currentSessionText.isNotEmpty) { + _baseText = _currentController!.text.trim(); + _currentSessionText = ''; + } + + // Automatically restart listening after a brief pause + Future.delayed(const Duration(milliseconds: 300), () { + if (_isListening && _currentController != null) { + _startListeningInternal(_currentLocaleId, _currentController!); + } + }); + } + }, + ); + notifyListeners(); + } + + /// Internal method to start listening + Future _startListeningInternal(String localeId, TextEditingController controller) async { + if (!_speechEnabled) return; + + await _speechToText.listen( + onResult: (SpeechRecognitionResult result) { + _currentSessionText = result.recognizedWords; + + // Combine base text with current session text + String combinedText = _baseText.isEmpty + ? _currentSessionText + : _baseText + (_baseText.endsWith(' ') ? '' : ' ') + _currentSessionText; + + description = combinedText; + controller.text = combinedText; + controller.selection = TextSelection.fromPosition(TextPosition(offset: controller.text.length)); + notifyListeners(); + }, + localeId: localeId, + listenFor: const Duration(seconds: 60), + pauseFor: const Duration(seconds: 10), + listenOptions: SpeechListenOptions( + partialResults: true, + listenMode: ListenMode.confirmation, + cancelOnError: false, + ), + ); + } + + /// Start listening for speech input + Future startListening(String localeId, TextEditingController controller) async { + _isListening = true; + _currentLocaleId = localeId; + _currentController = controller; + _baseText = controller.text.trim(); // Save existing text + _currentSessionText = ''; + notifyListeners(); + await _startListeningInternal(localeId, controller); + } + + /// Stop listening for speech input + Future stopListening() async { + _isListening = false; + _currentLocaleId = ''; + _currentController = null; + _baseText = ''; + _currentSessionText = ''; + await _speechToText.stop(); + notifyListeners(); + } + + /// Toggle listening state + Future toggleListening(String localeId, TextEditingController controller) async { + if (_isListening) { + await stopListening(); + } else { + await startListening(localeId, controller); + } + } + + // Fetch Etqan Projects + Future fetchEtqanProjects(BuildContext context) async { + isLoading = true; + notifyListeners(); + Utils.showLoading(context); + getEtqanProjectsList?.clear(); + getEtqanProjectsList = await EtqanApiClient().getEtqanProjects(); + Utils.hideLoading(context); + isLoading = false; + notifyListeners(); + } + + // Fetch Etqan Employee Requests + Future fetchEtqanEmployeeRequests(BuildContext context) async { + isLoading = true; + notifyListeners(); + Utils.showLoading(context); + getEtqanEmployeeRequestsList?.clear(); + getEtqanEmployeeRequestsList = await EtqanApiClient().getEmployeeEtqanRequests(); + Utils.hideLoading(context); + isLoading = false; + notifyListeners(); + } + + // Fetch Etqan Employee Incident Report + Future fetchEtqanEmployeeIncidentRequests(BuildContext context, String ticketNo) async { + isLoading = true; + notifyListeners(); + Utils.showLoading(context); + getEtqanEmployeeIncidnetRequest?.clear(); + getEtqanEmployeeIncidnetRequest = await EtqanApiClient().getEtqanIncidentRequests(ticketNo); + Utils.hideLoading(context); + isLoading = false; + notifyListeners(); + } + + // Set selected project + void setSelectedProject(EtqanGetProjectsResponse? project) { + selectedProject = project; + notifyListeners(); + } + + // Set selected employee number + void setSelectedEmployeeNumber(String? employeeNumber) { + selectedEmployeeNumber = employeeNumber; + notifyListeners(); + } + + // Set attachment with base64 + void setAttachment(File file, String base64) { + attachment = file; + attachmentBase64 = base64; + notifyListeners(); + } + + // Remove attachment + void removeAttachment() { + attachment = null; + attachmentBase64 = null; + notifyListeners(); + } + + // Set description + void setDescription(String value) { + description = value; + notifyListeners(); + } + + // Validate description + bool isDescriptionValid() { + return description.trim().isNotEmpty; + } + + // Set isAnonymous + void setIsAnonymous(bool value) { + isAnonymous = value; + notifyListeners(); + } + + // Clear create request form + void clearCreateRequestForm() { + selectedProject = null; + selectedEmployeeNumber = null; + attachment = null; + attachmentBase64 = null; + description = ''; + isAnonymous = false; + notifyListeners(); + } + + // Submit request + Future?> submitRequest(BuildContext context) async { + try { + Utils.showLoading(context); + Map? response = await EtqanApiClient().createRequest(description, selectedProject?.projectId, attachmentBase64, isAnonymous); + Utils.hideLoading(context); + if (response != null) { + getEtqanEmployeeRequestsList = await EtqanApiClient().getEmployeeEtqanRequests(); + notifyListeners(); + return response; + } + return null; + } catch (ex) { + Utils.hideLoading(context); + Utils.handleException(ex, context, null); + return null; + } + } + + @override + void dispose() { + if (_isListening) { + _speechToText.stop(); + } + super.dispose(); + } +} diff --git a/lib/ui/etqan_ovr/etqan_create_request.dart b/lib/ui/etqan_ovr/etqan_create_request.dart new file mode 100644 index 0000000..a1d64af --- /dev/null +++ b/lib/ui/etqan_ovr/etqan_create_request.dart @@ -0,0 +1,355 @@ +import 'dart:io'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:mohem_flutter_app/app_state/app_state.dart'; +import 'package:mohem_flutter_app/classes/colors.dart'; +import 'package:mohem_flutter_app/classes/utils.dart'; +import 'package:mohem_flutter_app/config/routes.dart'; +import 'package:mohem_flutter_app/extensions/int_extensions.dart'; +import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; +import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; +import 'package:mohem_flutter_app/models/etqan_ovr/etqan_getprojects_model.dart'; +import 'package:mohem_flutter_app/provider/etqan_ovr_provider.dart'; +import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; +import 'package:mohem_flutter_app/widgets/bottom_sheet.dart'; +import 'package:mohem_flutter_app/widgets/button/default_button.dart'; +import 'package:mohem_flutter_app/widgets/image_picker.dart'; +import 'package:provider/provider.dart'; + +class EtqanOvrCreateRequest extends StatefulWidget { + const EtqanOvrCreateRequest({Key? key}) : super(key: key); + + @override + _EtqanOvrCreateRequestState createState() => _EtqanOvrCreateRequestState(); +} + +class _EtqanOvrCreateRequestState extends State { + final TextEditingController _descriptionController = TextEditingController(); + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + var provider = context.read(); + provider.initSpeech(); + provider.fetchEtqanProjects(context); + provider.clearCreateRequestForm(); + }); + } + + @override + void dispose() { + // Stop listening if active + context.read().stopListening(); + _descriptionController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBarWidget(context, title: LocaleKeys.reportIncident.tr(), showHomeButton: false), + body: Consumer( + builder: (BuildContext context, EtqanOvrProviderModel provider, Widget? child) { + return Column( + children: [ + (ListView( + shrinkWrap: true, + physics: const BouncingScrollPhysics(), + padding: const EdgeInsets.all(21), + children: [ + _buildDropdownField( + label: LocaleKeys.project.tr(), + hint: LocaleKeys.selectProject.tr(), + value: _getProjectDisplayName(provider.selectedProject), + onTap: () => _showProjectSelectionSheet(context, provider), + isMandatory: true, + ), + 16.height, + // _buildDropdownField( + // label: 'Employee Number', + // hint: 'Write a message', + // value: provider.selectedEmployeeNumber, + // onTap: () { + // // TODO: Show employee selection dialog + // }, + // ), + // 16.height, + _buildAttachmentSection(provider), + 16.height, + _buildDescriptionField(provider), + 8.height, + _buildAnonymousCheckbox(provider), + 24.height, + ], + )).expanded, + DefaultButton(LocaleKeys.submit.tr(), () async { + provider.setDescription(_descriptionController.text); + if (provider.selectedProject == null) { + Utils.showErrorDialog( + context: context, + message: LocaleKeys.pleaseSelectProject.tr(), + onOkTapped: () { + Navigator.of(context).pop(); + }, + ); + return; + } + if (!provider.isDescriptionValid()) { + Utils.showErrorDialog( + context: context, + message: LocaleKeys.pleaseEnterDescription.tr(), + onOkTapped: () { + Navigator.of(context).pop(); + }, + ); + return; + } + Map? ticketInfo = await provider.submitRequest(context); + if (ticketInfo != null) { + String ticketNumber = ticketInfo['ticketNumber'] ?? ''; + String ticketId = ticketInfo['id'] ?? ''; + String successMessage = '${LocaleKeys.requestCreatedSuccessfully.tr()}\n\n${LocaleKeys.ticketNumber.tr()}: $ticketNumber\n${LocaleKeys.ticketId.tr()}: $ticketId'; + Utils.showErrorDialog( + context: context, + message: successMessage, + onOkTapped: () { + Navigator.popAndPushNamed(context, AppRoutes.etqanOvr); + }, + onCloseTap: () { + Navigator.popAndPushNamed(context, AppRoutes.etqanOvr); + }, + ); + } + }).insideContainer, + ], + ); + }, + ), + ); + } + + String? _getProjectDisplayName(EtqanGetProjectsResponse? project) { + if (project == null) return null; + return AppState().isArabic(context) ? project.projectAr : project.projectEn; + } + + void _showProjectSelectionSheet(BuildContext context, EtqanOvrProviderModel provider) { + if (provider.getEtqanProjectsList == null || provider.getEtqanProjectsList!.isEmpty) { + return; + } + showMyBottomSheet( + context, + callBackFunc: () {}, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Text(LocaleKeys.selectProject.tr(), style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xff2B353E))), + ), + const Divider(), + ListView.separated( + shrinkWrap: true, + physics: const BouncingScrollPhysics(), + itemCount: provider.getEtqanProjectsList!.length, + separatorBuilder: (BuildContext context, int index) => const Divider(height: 1), + itemBuilder: (BuildContext context, int index) { + EtqanGetProjectsResponse project = provider.getEtqanProjectsList![index]; + bool isSelected = provider.selectedProject?.projectId == project.projectId; + return ListTile( + title: Text( + AppState().isArabic(context) ? (project.projectAr ?? '') : (project.projectEn ?? ''), + style: TextStyle(fontSize: 14, fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400, color: isSelected ? MyColors.gradiantEndColor : const Color(0xff2B353E)), + ), + trailing: isSelected ? const Icon(Icons.check, color: MyColors.gradiantEndColor) : null, + onTap: () { + provider.setSelectedProject(project); + Navigator.pop(context); + }, + ); + }, + ), + 16.height, + ], + ), + ); + } + + Widget _buildDropdownField({required String label, required String hint, String? value, required VoidCallback onTap, bool isMandatory = false}) { + return InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(15), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 15), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(15), color: Colors.white, border: Border.all(color: const Color(0xffefefef), width: 1)), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text(label, style: const TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: Color(0xff2B353E), letterSpacing: -0.44)), + if (isMandatory) const Text(' *', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: Colors.red, letterSpacing: -0.44)), + ], + ), + const SizedBox(height: 4), + Text(value ?? hint, style: TextStyle(fontSize: 14, fontWeight: FontWeight.w400, color: value != null ? const Color(0xff2B353E) : const Color(0xff575757), letterSpacing: -0.56)), + ], + ), + ), + const Icon(Icons.keyboard_arrow_down, color: Color(0xff575757)), + ], + ), + ), + ); + } + + Widget _buildAttachmentSection(EtqanOvrProviderModel provider) { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(15), color: Colors.white, border: Border.all(color: const Color(0xffefefef), width: 1)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(LocaleKeys.supportingDocument.tr(), style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xff2B353E))), + const SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(LocaleKeys.attachments.tr(), style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Color(0xff2B353E))), + if (provider.attachment == null) + InkWell( + onTap: () { + ImageOptions.showImageOptionsNew(context, true, (String base64, File file) { + provider.setAttachment(file, base64); + Navigator.of(context).pop(); + }); + }, + borderRadius: BorderRadius.circular(8), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8), + decoration: BoxDecoration(color: MyColors.gradiantEndColor, borderRadius: BorderRadius.circular(8)), + child: Text(LocaleKeys.add.tr(), style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Colors.white)), + ), + ), + ], + ), + if (provider.attachment != null) ...[ + const SizedBox(height: 12), + Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Row( + children: [ + const Icon(Icons.attach_file, size: 18, color: Color(0xff575757)), + const SizedBox(width: 8), + Expanded(child: Text(provider.attachment!.path.split('/').last, style: const TextStyle(fontSize: 12, color: Color(0xff575757)), overflow: TextOverflow.ellipsis)), + InkWell( + onTap: () { + provider.removeAttachment(); + }, + child: const Icon(Icons.close, size: 18, color: Colors.red), + ), + ], + ), + ), + ], + ], + ), + ); + } + + Widget _buildDescriptionField(EtqanOvrProviderModel provider) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 15), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(15), color: Colors.white, border: Border.all(color: const Color(0xffefefef), width: 1)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Text(LocaleKeys.description.tr(), style: const TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: Color(0xff2B353E), letterSpacing: -0.44)), + const Text(' *', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: Colors.red, letterSpacing: -0.44)), + ], + ), + GestureDetector( + onTap: + provider.speechEnabled + ? () { + String localeId = AppState().isArabic(context) ? 'ar_SA' : 'en_US'; + provider.toggleListening(localeId, _descriptionController); + } + : null, + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration(color: provider.isListening ? MyColors.gradiantEndColor.withOpacity(0.1) : Colors.transparent, borderRadius: BorderRadius.circular(20)), + child: Icon( + provider.isListening ? Icons.mic : Icons.mic_none, + color: provider.isListening ? MyColors.gradiantEndColor : (provider.speechEnabled ? MyColors.grey57Color : Colors.grey.shade300), + size: 24, + ), + ), + ), + ], + ), + if (provider.isListening) + Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Row( + children: [ + Container(width: 8, height: 8, decoration: BoxDecoration(color: Colors.red, borderRadius: BorderRadius.circular(4))), + const SizedBox(width: 8), + const Text('Listening...', style: TextStyle(fontSize: 12, color: MyColors.gradiantEndColor, fontWeight: FontWeight.w500)), + ], + ), + ), + TextField( + controller: _descriptionController, + maxLines: 4, + onChanged: (String value) => provider.setDescription(value), + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w400, color: Color(0xff2B353E), letterSpacing: -0.44), + decoration: InputDecoration( + isDense: true, + hintText: LocaleKeys.writeAMessage.tr(), + hintStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w400, color: Color(0xff575757), letterSpacing: -0.56), + contentPadding: EdgeInsets.zero, + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + ), + ), + ], + ), + ); + } + + Widget _buildAnonymousCheckbox(EtqanOvrProviderModel provider) { + return InkWell( + onTap: () { + provider.setIsAnonymous(!provider.isAnonymous); + }, + child: Row( + children: [ + Checkbox( + value: provider.isAnonymous, + onChanged: (bool? value) { + if (value != null) { + provider.setIsAnonymous(value); + } + }, + activeColor: MyColors.gradiantEndColor, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), + ), + + Expanded(child: Text(LocaleKeys.submitAsAnonymous.tr(), style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Color(0xff2B353E), letterSpacing: -0.44))), + ], + ), + ); + } +} diff --git a/lib/ui/etqan_ovr/etqan_ovr_home.dart b/lib/ui/etqan_ovr/etqan_ovr_home.dart new file mode 100644 index 0000000..b55b4c0 --- /dev/null +++ b/lib/ui/etqan_ovr/etqan_ovr_home.dart @@ -0,0 +1,165 @@ +import 'package:easy_localization/easy_localization.dart' as lclize; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:mohem_flutter_app/app_state/app_state.dart'; +import 'package:mohem_flutter_app/classes/colors.dart'; +import 'package:mohem_flutter_app/config/routes.dart'; +import 'package:mohem_flutter_app/extensions/int_extensions.dart'; +import 'package:mohem_flutter_app/extensions/string_extensions.dart'; +import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; +import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; +import 'package:mohem_flutter_app/models/etqan_ovr/etqan_get_requests_model.dart'; +import 'package:mohem_flutter_app/provider/etqan_ovr_provider.dart'; +import 'package:mohem_flutter_app/classes/utils.dart'; +import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; +import 'package:mohem_flutter_app/widgets/button/default_button.dart'; +import 'package:provider/provider.dart'; +import 'package:intl/src/intl/text_direction.dart' as intl; +import 'package:sizer/sizer.dart'; + +class EtqanOvrHome extends StatefulWidget { + const EtqanOvrHome({Key? key}) : super(key: key); + + @override + _EtqanOvrHomeState createState() => _EtqanOvrHomeState(); +} + +class _EtqanOvrHomeState extends State { + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + context.read().fetchEtqanEmployeeRequests(context); + }); + } + + Color _getStatusColor(String status) { + String statusLower = status.toLowerCase(); + if (statusLower.contains('open') || statusLower.contains('مفتوح')) { + return const Color(0xff2196F3); // Blue for open + } else if (statusLower.contains('progress') || statusLower.contains('قيد التنفيذ')) { + return const Color(0xffFF9800); // Orange for in progress + } else if (statusLower.contains('closed') || statusLower.contains('مغلق')) { + return const Color(0xff4CAF50); // Green for closed + } + return MyColors.grey70Color; // Default gray + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBarWidget(context, title: LocaleKeys.myIncidents.tr()), + body: Consumer( + builder: (BuildContext context, EtqanOvrProviderModel provider, Widget? child) { + return Column( + children: [ + (provider.getEtqanEmployeeRequestsList == null || provider.getEtqanEmployeeRequestsList!.isEmpty + ? Utils.getNoDataWidget(context) + : ListView.separated( + physics: const BouncingScrollPhysics(), + padding: const EdgeInsets.all(20), + itemBuilder: (BuildContext context, int index) { + EtqanGetEmployeeOvrRequestsResponse data = provider.getEtqanEmployeeRequestsList![index]; + return InkWell( + onTap: () { + context.read().setTicketId(data.ticketNumber ?? '', status: data.status, statusAr: data.statusAr, isAnonymous: data.isAnonymous); + Navigator.pushNamed(context, AppRoutes.etqanGetIncidientRequest); + }, + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(15), border: Border.all(color: const Color(0xffefefef), width: 1)), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Wrap( + spacing: 8, + runSpacing: 8, + children: [ + if (data.status != null && data.status!.isNotEmpty) + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: _getStatusColor(data.status ?? "").withOpacity(0.1), + borderRadius: BorderRadius.circular(6), + border: Border.all(color: _getStatusColor(data.status ?? "").withOpacity(0.3), width: 1), + ), + child: (AppState().isArabic(context) ? (data.statusAr ?? "") : (data.status ?? "")).toText11( + color: _getStatusColor(data.status ?? ""), + isBold: true, + ), + ), + if (data.isAnonymous == true) + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: MyColors.grey70Color.withOpacity(0.15), + borderRadius: BorderRadius.circular(6), + border: Border.all(color: MyColors.grey70Color.withOpacity(0.3), width: 1), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.visibility_off, size: 12, color: MyColors.grey70Color), + const SizedBox(width: 4), + (AppState().isArabic(context) ? "مجهول" : "Anonymous").toText11(color: MyColors.grey70Color, isBold: true), + ], + ), + ), + ], + ), + ), + const SizedBox(width: 8), + ], + ), + 8.height, + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + (data.ticketNumber ?? "").toText14(isBold: true, color: MyColors.darkTextColor), + Directionality(textDirection: TextDirection.ltr, child: (data.createdDate!.split(" ").first ?? "").toText12(color: MyColors.grey70Color)), + ], + ), + 8.height, + Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [(data.description ?? "").toText12(color: MyColors.grey57Color).expanded]), + 3.height, + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + RotatedBox( + quarterTurns: AppState().isArabic(context) ? 2 : 0, + child: SvgPicture.asset("assets/images/arrow_next.svg", color: MyColors.grey70Color, width: 16, height: 16), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ); + }, + separatorBuilder: (BuildContext context, int index) => 12.height, + itemCount: provider.getEtqanEmployeeRequestsList!.length, + )) + .expanded, + DefaultButton(LocaleKeys.reportIncident.tr(), () async { + await Navigator.pushNamed(context, AppRoutes.etqanCreateRequest); + // getOpenTickets(); + }).insideContainer, + ], + ); + }, + ), + ); + } +} diff --git a/lib/ui/etqan_ovr/etqan_request_detailed.dart b/lib/ui/etqan_ovr/etqan_request_detailed.dart new file mode 100644 index 0000000..41d11c6 --- /dev/null +++ b/lib/ui/etqan_ovr/etqan_request_detailed.dart @@ -0,0 +1,235 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:mohem_flutter_app/app_state/app_state.dart'; +import 'package:mohem_flutter_app/classes/colors.dart'; +import 'package:mohem_flutter_app/extensions/int_extensions.dart'; +import 'package:mohem_flutter_app/extensions/string_extensions.dart'; +import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; +import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; +import 'package:mohem_flutter_app/models/etqan_ovr/etqan_get_employee_incident_report.dart'; +import 'package:mohem_flutter_app/provider/etqan_ovr_provider.dart'; +import 'package:mohem_flutter_app/classes/utils.dart'; +import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; +import 'package:provider/provider.dart'; +import 'package:open_filex/open_filex.dart'; +import 'package:path_provider/path_provider.dart'; + +class EtqanOvrRequestDetailed extends StatefulWidget { + const EtqanOvrRequestDetailed({Key? key}) : super(key: key); + + @override + _EtqanOvrRequestDetailedState createState() => _EtqanOvrRequestDetailedState(); +} + +class _EtqanOvrRequestDetailedState extends State { + List get fieldOrder => ['Status', 'Project', 'Created', 'Is Anonymous', 'Occurrence Time', 'OVR Reference No', 'Involved Employee Number', 'Description']; + + List get hiddenFields => ['OVR Id']; + + Color _getStatusColor(String status) { + String statusLower = status.toLowerCase(); + if (statusLower.contains('open') || statusLower.contains('مفتوح')) { + return const Color(0xff2196F3); // Blue for open + } else if (statusLower.contains('progress') || statusLower.contains('قيد التنفيذ')) { + return const Color(0xffFF9800); // Orange for in progress + } else if (statusLower.contains('closed') || statusLower.contains('مغلق')) { + return const Color(0xff4CAF50); // Green for closed + } + return MyColors.grey70Color; // Default gray + } + + Future _viewAttachment(BuildContext context, String base64Data) async { + try { + Uint8List bytes = base64.decode(base64Data); + String dir = (await getApplicationDocumentsDirectory()).path; + String ext = 'pdf'; + if (base64Data.startsWith('/9j/')) { + ext = 'jpg'; + } else if (base64Data.startsWith('iVBORw0KGgo')) { + ext = 'png'; + } + File file = File("$dir/${DateTime.now().millisecondsSinceEpoch}.$ext"); + await file.writeAsBytes(bytes); + OpenFilex.open(file.path); + } catch (e) { + Utils.showToast("Cannot open attachment."); + } + } + + List _getOrderedList(List list, EtqanOvrProviderModel provider) { + List orderedList = []; + + List filteredList = list.where((EtqanGetIncidentRequestResponse item) => !hiddenFields.any((f) => f.toLowerCase() == item.key?.toLowerCase())).toList(); + + for (String fieldName in fieldOrder) { + // Check if this is Status or Is Anonymous - inject from provider + if (fieldName.toLowerCase() == 'status' && provider.getCurrentStatus.isNotEmpty) { + orderedList.add(EtqanGetIncidentRequestResponse( + key: 'Status', + keyAr: 'الحالة', + value: provider.getCurrentStatus, + valueAr: provider.getCurrentStatusAr.isNotEmpty ? provider.getCurrentStatusAr : provider.getCurrentStatus, + )); + } else if (fieldName.toLowerCase() == 'is anonymous') { + orderedList.add(EtqanGetIncidentRequestResponse( + key: 'Is Anonymous', + keyAr: 'مجهول', + value: provider.getCurrentIsAnonymous.toString(), + valueAr: provider.getCurrentIsAnonymous.toString(), + )); + } else { + // Regular field from API + var item = filteredList.firstWhere((e) => e.key?.toLowerCase() == fieldName.toLowerCase(), orElse: () => EtqanGetIncidentRequestResponse()); + if (item.key != null) { + orderedList.add(item); + } + } + } + + for (var item in filteredList) { + if (!fieldOrder.any((f) => f.toLowerCase() == item.key?.toLowerCase())) { + orderedList.add(item); + } + } + + return orderedList; + } + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + EtqanOvrProviderModel provider = context.read(); + provider.fetchEtqanEmployeeIncidentRequests(context, provider.getTicketId); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBarWidget(context, title: LocaleKeys.incidentDetails.tr()), + body: Consumer( + builder: (BuildContext context, EtqanOvrProviderModel provider, Widget? child) { + if (provider.getEtqanEmployeeIncidnetRequest == null || provider.getEtqanEmployeeIncidnetRequest!.isEmpty) { + return Utils.getNoDataWidget(context); + } + + List orderedList = _getOrderedList(provider.getEtqanEmployeeIncidnetRequest!, provider); + + return SingleChildScrollView( + physics: const BouncingScrollPhysics(), + padding: const EdgeInsets.all(16), + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(15), + border: Border.all(color: const Color(0xffefefef), width: 1), + boxShadow: [BoxShadow(color: Colors.black.withValues(alpha: 0.05), blurRadius: 10, offset: const Offset(0, 2))], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: + orderedList.asMap().entries.map((MapEntry entry) { + int index = entry.key; + EtqanGetIncidentRequestResponse data = entry.value; + bool isLast = index == orderedList.length - 1; + bool isAttachment = data.key?.toLowerCase() == 'attachment'; + bool isStatus = data.key?.toLowerCase() == 'status'; + bool isAnonymous = data.key?.toLowerCase() == 'is anonymous'; + + String displayKey = context.locale.languageCode == "ar" ? (data.keyAr ?? data.key ?? "") : (data.key ?? ""); + String displayValue = context.locale.languageCode == "ar" ? (data.valueAr ?? data.value ?? "") : (data.value ?? ""); + + bool hasSubValues = data.subValues != null && data.subValues!.isNotEmpty; + + return Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(flex: 2, child: displayKey.toText12(isBold: true, color: MyColors.grey57Color)), + 8.width, + Expanded( + flex: 3, + child: isStatus + ? displayValue.toText14( + color: _getStatusColor(displayValue), + isBold: true, + ) + : isAnonymous + ? (displayValue.toLowerCase() == 'true' || displayValue.toLowerCase() == 'yes' || displayValue == '1' + ? (context.locale.languageCode == "ar" ? "نعم" : "Yes") + : (context.locale.languageCode == "ar" ? "لا" : "No") + ).toText14(color: MyColors.darkTextColor) + : isAttachment && hasSubValues + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: data.subValues!.asMap().entries.map((entry) { + int subIndex = entry.key; + String base64Data = entry.value; + return Padding( + padding: EdgeInsets.only(bottom: subIndex < data.subValues!.length - 1 ? 8.0 : 0), + child: GestureDetector( + onTap: () => _viewAttachment(context, base64Data), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: context.locale.languageCode == "ar" ? MainAxisAlignment.end : MainAxisAlignment.start, + children: [ + Text( + data.subValues!.length > 1 ? '${LocaleKeys.viewAttachment.tr()} ${subIndex + 1}' : LocaleKeys.viewAttachment.tr(), + style: const TextStyle( + fontSize: 14, + color: MyColors.green9CColor, + decoration: TextDecoration.underline, + decorationColor: MyColors.green9CColor, + ), + ), + ], + ), + ), + ); + }).toList(), + ) + : hasSubValues + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (displayValue.isNotEmpty) displayValue.toText14(color: MyColors.darkTextColor), + if (displayValue.isNotEmpty) 8.height, + ...data.subValues! + .map( + (subValue) => Padding( + padding: const EdgeInsets.only(bottom: 4), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [const Text("• ", style: TextStyle(fontSize: 14)), Expanded(child: subValue.toText14(color: MyColors.darkTextColor))], + ), + ), + ) + .toList(), + ], + ) + : (displayValue.isNotEmpty ? displayValue : "-").toText14(color: MyColors.darkTextColor), + ), + ], + ), + ), + if (!isLast) const Padding(padding: EdgeInsets.only(left: 14, right: 14), child: Divider(height: 1, thickness: 1, color: Color(0xffefefef))), + ], + ); + }).toList(), + ), + ), + ); + }, + ).paddingOnly(bottom: 20), + ); + } +} diff --git a/lib/ui/landing/dashboard_screen.dart b/lib/ui/landing/dashboard_screen.dart index 3122d17..eac7890 100644 --- a/lib/ui/landing/dashboard_screen.dart +++ b/lib/ui/landing/dashboard_screen.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:io'; import 'dart:ui' as ui; - import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_countdown_timer/flutter_countdown_timer.dart'; @@ -34,6 +33,7 @@ import 'package:mohem_flutter_app/widgets/shimmer/offers_shimmer_widget.dart'; import 'package:provider/provider.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart'; import 'package:signalr_netcore/signalr_client.dart'; +import 'package:url_launcher/url_launcher.dart'; late HubConnection chatHubConnection; @@ -155,6 +155,7 @@ class _DashboardScreenState extends State with WidgetsBindingOb data.fetchLeaveTicketBalance(context, DateTime.now()); data.fetchMenuEntries(); data.fetchEventActivity(); + data.fetchGreetingCards(); data.getCategoryOffersListAPI(context); marathonProvider.getMarathonDetailsFromApi(); marathonProvider.getMarathonTutorial(); @@ -167,8 +168,9 @@ class _DashboardScreenState extends State with WidgetsBindingOb // await data.fetchTicketBooking(); // if (data.ticketBookingResponse != null && !data.ticketBookingResponse!.success) { - // // data.fetchTicketBalance(); + // data.fetchTicketBalance(); // } + //continue here } @@ -315,103 +317,103 @@ class _DashboardScreenState extends State with WidgetsBindingOb child: Consumer( builder: (BuildContext context, DashboardProviderModel model, Widget? child) { return (model.isAttendanceTrackingLoading - ? GetAttendanceTrackingShimmer() - : Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(15), - gradient: const LinearGradient( - transform: GradientRotation(.46), - begin: Alignment.topRight, - end: Alignment.bottomLeft, - colors: [MyColors.gradiantEndColor, MyColors.gradiantStartColor], - ), - ), - child: Stack( - alignment: Alignment.center, - children: [ - if (model.isTimeRemainingInSeconds == 0) SvgPicture.asset("assets/images/thumb.svg"), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.markAttendance.tr().toText14(color: Colors.white, isBold: true), - if (model.isTimeRemainingInSeconds == 0) DateTime.now().toString().split(" ")[0].toText12(color: Colors.white), - if (model.isTimeRemainingInSeconds != 0) - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, + ? GetAttendanceTrackingShimmer() + : Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15), + gradient: const LinearGradient( + transform: GradientRotation(.46), + begin: Alignment.topRight, + end: Alignment.bottomLeft, + colors: [MyColors.gradiantEndColor, MyColors.gradiantStartColor], + ), + ), + child: Stack( + alignment: Alignment.center, + children: [ + if (model.isTimeRemainingInSeconds == 0) SvgPicture.asset("assets/images/thumb.svg"), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.markAttendance.tr().toText14(color: Colors.white, isBold: true), + if (model.isTimeRemainingInSeconds == 0) DateTime.now().toString().split(" ")[0].toText12(color: Colors.white), + if (model.isTimeRemainingInSeconds != 0) + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 9.height, + Directionality( + textDirection: ui.TextDirection.ltr, + child: CountdownTimer( + endTime: model.endTime, + onEnd: null, + endWidget: "00:00:00".toText14(color: Colors.white, isBold: true), + textStyle: const TextStyle(color: Colors.white, fontSize: 14, letterSpacing: -0.48, fontWeight: FontWeight.bold), + ), + ), + LocaleKeys.timeLeftToday.tr().toText12(color: Colors.white), + 9.height, + ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(20)), + child: LinearProgressIndicator( + value: model.progress, + minHeight: 8, + valueColor: const AlwaysStoppedAnimation(Colors.white), + backgroundColor: const Color(0xff196D73), + ), + ), + ], + ), + ], + ).paddingOnly(top: 12, right: 15, left: 12), + ), + Row( children: [ - 9.height, - Directionality( - textDirection: ui.TextDirection.ltr, - child: CountdownTimer( - endTime: model.endTime, - onEnd: null, - endWidget: "00:00:00".toText14(color: Colors.white, isBold: true), - textStyle: const TextStyle(color: Colors.white, fontSize: 14, letterSpacing: -0.48, fontWeight: FontWeight.bold), - ), + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.checkIn.tr().toText12(color: Colors.white), + (model.attendanceTracking!.pSwipeIn == null ? "--:--" : model.attendanceTracking!.pSwipeIn).toString().toText14( + color: Colors.white, + isBold: true, + ), + 4.height, + ], + ).paddingOnly(left: 12, right: 12), ), - LocaleKeys.timeLeftToday.tr().toText12(color: Colors.white), - 9.height, - ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(20)), - child: LinearProgressIndicator( - value: model.progress, - minHeight: 8, - valueColor: const AlwaysStoppedAnimation(Colors.white), - backgroundColor: const Color(0xff196D73), + Container( + margin: EdgeInsets.only(top: AppState().isArabic(context) ? 6 : 0), + width: 45, + height: 45, + padding: const EdgeInsets.only(left: 10, right: 10), + decoration: BoxDecoration( + color: const Color(0xff259EA4), + borderRadius: BorderRadius.only( + bottomRight: AppState().isArabic(context) ? const Radius.circular(0) : const Radius.circular(15), + bottomLeft: AppState().isArabic(context) ? const Radius.circular(15) : const Radius.circular(0), + ), ), - ), + child: SvgPicture.asset(model.isTimeRemainingInSeconds == 0 ? "assets/images/biometrics.svg" : "assets/images/biometrics.svg"), + ).onPress(() { + showMyBottomSheet(context, callBackFunc: () {}, child: MarkAttendanceWidget(model, isFromDashboard: true)); + }), ], ), - ], - ).paddingOnly(top: 12, right: 15, left: 12), - ), - Row( - children: [ - Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.checkIn.tr().toText12(color: Colors.white), - (model.attendanceTracking!.pSwipeIn == null ? "--:--" : model.attendanceTracking!.pSwipeIn).toString().toText14( - color: Colors.white, - isBold: true, - ), - 4.height, - ], - ).paddingOnly(left: 12, right: 12), + ], ), - Container( - margin: EdgeInsets.only(top: AppState().isArabic(context) ? 6 : 0), - width: 45, - height: 45, - padding: const EdgeInsets.only(left: 10, right: 10), - decoration: BoxDecoration( - color: const Color(0xff259EA4), - borderRadius: BorderRadius.only( - bottomRight: AppState().isArabic(context) ? const Radius.circular(0) : const Radius.circular(15), - bottomLeft: AppState().isArabic(context) ? const Radius.circular(15) : const Radius.circular(0), - ), - ), - child: SvgPicture.asset(model.isTimeRemainingInSeconds == 0 ? "assets/images/biometrics.svg" : "assets/images/biometrics.svg"), - ).onPress(() { - showMyBottomSheet(context, callBackFunc: () {}, child: MarkAttendanceWidget(model, isFromDashboard: true)); - }), ], ), - ], - ), - ], - ), - ).onPress(() { - Navigator.pushNamed(context, AppRoutes.todayAttendance); - })) + ).onPress(() { + Navigator.pushNamed(context, AppRoutes.todayAttendance); + })) .animatedSwither(); }, ), @@ -423,8 +425,102 @@ class _DashboardScreenState extends State with WidgetsBindingOb ), ], ).paddingOnly(left: 21, right: 21, top: 7, bottom: 21), - eventActivityWidget(context), + Consumer( + builder: (BuildContext context, DashboardProviderModel model, Widget? child) { + if (model.isGreetingCardsLoading) { + return const GreetingCardShimmer(); + } else if (model.isDisplayEidGreetings && model.greetingCardsList != null && model.greetingCardsList!.isNotEmpty) { + return SizedBox( + height: 120, + child: ListView.separated( + shrinkWrap: true, + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.symmetric(horizontal: 21), + itemCount: model.greetingCardsList!.length, + separatorBuilder: (context, index) => const SizedBox(width: 12), + itemBuilder: (context, index) { + var card = model.greetingCardsList![index]; + return SizedBox( + width: MediaQuery.of(context).size.width - 42, + child: Directionality( + textDirection: AppState().isArabic(context) ? ui.TextDirection.rtl : ui.TextDirection.ltr, + child: Stack( + children: [ + Transform.flip( + flipX: !AppState().isArabic(context), + child: SvgPicture.network( + AppState().isArabic(context) + ? (card.backgroundImageUrlAr ?? "https://www.hmg.com/Lists/CS_Greeting_Cards/Attachments/5/CS_Ramadan_26.jpg") + : (card.backgroundImageUrlEn ?? "https://www.hmg.com/Lists/CS_Greeting_Cards/Attachments/5/CS_Ramadan_26.jpg"), + fit: BoxFit.contain, + placeholderBuilder: (context) => ClipRRect(borderRadius: BorderRadius.circular(18), child: Container(height: 80, color: Colors.white).toShimmer()), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(width: AppState().isArabic(context) ? 20 : 50), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + AppState().isArabic(context) ? card.titleAr ?? '' : card.titleEn ?? '', + style: TextStyle( + fontSize: AppState().isArabic(context) ? 20 : 18, + letterSpacing: -0.2, + fontFamily: AppState().isArabic(context) ? 'Cairo' : 'Poppins', + fontWeight: FontWeight.w700, + height: 24 / 16, + color: const Color(0xFF3B3E4F), + ), + ), + const SizedBox(height: 4), + Text( + AppState().isArabic(context) ? card.descriptionAr ?? '' : card.descriptionEn ?? '', + style: TextStyle( + fontSize: AppState().isArabic(context) ? 16 : 14, + letterSpacing: -0.2, + fontFamily: AppState().isArabic(context) ? 'Cairo' : 'Poppins', + fontWeight: FontWeight.w500, + color: const Color(0xFF3B3E4F), + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + (AppState().isArabic(context) + ? card.buttonTextN!.toText12(isUnderLine: true, color: const Color(0xFF3B3D4A)) + : card.buttonText!.toText12(isUnderLine: true, color: const Color(0xFF3B3D4A))) + .onPress(() { + launchUrl(Uri.parse(AppState().isArabic(context) ? card.urlAr! : card.urlEn!)); + }), + ], + ).paddingOnly(bottom: 10, right: 15, left: 15), + ], + ).paddingOnly(left: AppState().isArabic(context) ? 10 : 36, right: AppState().isArabic(context) ? 66 : 10, top: 6, bottom: 40), + ], + ), + ), + ); + }, + ), + ).paddingOnly(bottom: 0); + } else { + return const SizedBox(); + } + }, + ), + // eventActivityWidget(context), if (isDisplayMazaya) ...[ Column( mainAxisSize: MainAxisSize.min, @@ -460,70 +556,70 @@ class _DashboardScreenState extends State with WidgetsBindingOb flex: 2, child: RichText( text: - AppState().isArabic(context) - ? TextSpan( - children: [ - TextSpan( - text: 'اطلع على مميزات', - style: TextStyle( - fontSize: 16, - letterSpacing: -0.2, - fontFamily: AppState().isArabic(context) ? 'Cairo' : 'Poppins', - fontWeight: FontWeight.w700, - height: 24 / 16, - color: Color(0xFF5D5E5E), - ), - ), - TextSpan( - text: ' مزايا', - style: TextStyle( - fontSize: 16, - fontFamily: AppState().isArabic(context) ? 'Cairo' : 'Poppins', - fontWeight: FontWeight.w700, - letterSpacing: -0.2, - height: 24 / 16, - color: MyColors.mazayaRedColor, // Use your MAZAYA red color here if defined, e.g. MyColors.mazayaRed - ), - ), - ], - ) - : TextSpan( - children: [ - TextSpan( - text: LocaleKeys.explore.tr() + ' ', - style: const TextStyle( - fontSize: 16, - letterSpacing: -0.2, - fontFamily: 'Poppins', - fontWeight: FontWeight.w700, - height: 24 / 16, - color: Color(0xFF5D5E5E), - ), - ), - TextSpan( - text: LocaleKeys.mazaya.tr(), - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w700, - fontFamily: 'Poppins', - letterSpacing: -0.2, - height: 24 / 16, - color: MyColors.mazayaRedColor, // Use your MAZAYA red color here if defined, e.g. MyColors.mazayaRed - ), - ), - TextSpan( - text: ' ' + LocaleKeys.benefits.tr(), - style: const TextStyle( - fontSize: 16, - letterSpacing: -0.2, - fontFamily: 'Poppins', - fontWeight: FontWeight.w700, - height: 24 / 16, - color: Color(0xFF5D5E5E), - ), - ), - ], - ), + AppState().isArabic(context) + ? TextSpan( + children: [ + TextSpan( + text: 'اطلع على مميزات', + style: TextStyle( + fontSize: 16, + letterSpacing: -0.2, + fontFamily: AppState().isArabic(context) ? 'Cairo' : 'Poppins', + fontWeight: FontWeight.w700, + height: 24 / 16, + color: const Color(0xFF5D5E5E), + ), + ), + TextSpan( + text: ' مزايا', + style: TextStyle( + fontSize: 16, + fontFamily: AppState().isArabic(context) ? 'Cairo' : 'Poppins', + fontWeight: FontWeight.w700, + letterSpacing: -0.2, + height: 24 / 16, + color: MyColors.mazayaRedColor, // Use your MAZAYA red color here if defined, e.g. MyColors.mazayaRed + ), + ), + ], + ) + : TextSpan( + children: [ + TextSpan( + text: LocaleKeys.explore.tr() + ' ', + style: const TextStyle( + fontSize: 16, + letterSpacing: -0.2, + fontFamily: 'Poppins', + fontWeight: FontWeight.w700, + height: 24 / 16, + color: Color(0xFF5D5E5E), + ), + ), + TextSpan( + text: LocaleKeys.mazaya.tr(), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + fontFamily: 'Poppins', + letterSpacing: -0.2, + height: 24 / 16, + color: MyColors.mazayaRedColor, // Use your MAZAYA red color here if defined, e.g. MyColors.mazayaRed + ), + ), + TextSpan( + text: ' ' + LocaleKeys.benefits.tr(), + style: const TextStyle( + fontSize: 16, + letterSpacing: -0.2, + fontFamily: 'Poppins', + fontWeight: FontWeight.w700, + height: 24 / 16, + color: Color(0xFF5D5E5E), + ), + ), + ], + ), ), ), const Expanded(flex: 1, child: SizedBox()), @@ -576,12 +672,10 @@ class _DashboardScreenState extends State with WidgetsBindingOb LocaleKeys.discounts.tr().toText24(isBold: true), 6.width, Container( - padding: const EdgeInsets.only(left: 8, right: 8), - decoration: BoxDecoration( - color: MyColors.yellowColor, - borderRadius: BorderRadius.circular(10), - ), - child: LocaleKeys.newString.tr().toText10(isBold: true)), + padding: const EdgeInsets.only(left: 8, right: 8), + decoration: BoxDecoration(color: MyColors.yellowColor, borderRadius: BorderRadius.circular(10)), + child: LocaleKeys.newString.tr().toText10(isBold: true), + ), ], ), ], @@ -589,7 +683,7 @@ class _DashboardScreenState extends State with WidgetsBindingOb ), LocaleKeys.viewAllOffers.tr().toText12(isUnderLine: true).onPress(() { Navigator.pushNamed(context, AppRoutes.offersAndDiscounts); - }) + }), ], ).paddingOnly(left: 21, right: 21), Consumer( @@ -597,59 +691,54 @@ class _DashboardScreenState extends State with WidgetsBindingOb return SizedBox( height: 103 + 33, child: ListView.separated( - shrinkWrap: true, - physics: const BouncingScrollPhysics(), - padding: const EdgeInsets.only(left: 21, right: 21, top: 13), - scrollDirection: Axis.horizontal, - itemBuilder: (BuildContext cxt, int index) { - return model.isOffersLoading - ? const OffersShimmerWidget() - : InkWell( - onTap: () { - navigateToDetails(data.getOffersList[index]); - }, - child: SizedBox( - width: 73, - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Container( - width: 73, - height: 73, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: const BorderRadius.all( - Radius.circular(100), - ), - border: Border.all(color: MyColors.lightGreyE3Color, width: 1), - ), - child: ClipRRect( - borderRadius: const BorderRadius.all( - Radius.circular(50), + shrinkWrap: true, + physics: const BouncingScrollPhysics(), + padding: const EdgeInsets.only(left: 21, right: 21, top: 13), + scrollDirection: Axis.horizontal, + itemBuilder: (BuildContext cxt, int index) { + return model.isOffersLoading + ? const OffersShimmerWidget() + : InkWell( + onTap: () { + navigateToDetails(data.getOffersList[index]); + }, + child: SizedBox( + width: 73, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: 73, + height: 73, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(100)), + border: Border.all(color: MyColors.lightGreyE3Color, width: 1), ), - child: Hero( - tag: "ItemImage" + data.getOffersList[index].offersDiscountId.toString()!, - transitionOnUserGestures: true, - child: Image.network( - data.getOffersList[index].logo ?? "", - fit: BoxFit.contain, + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(50)), + child: Hero( + tag: "ItemImage" + data.getOffersList[index].offersDiscountId.toString()!, + transitionOnUserGestures: true, + child: Image.network(data.getOffersList[index].logo ?? "", fit: BoxFit.contain), ), ), ), - ), - 4.height, - Expanded( - child: AppState().isArabic(context) - ? data.getOffersList[index].titleAr!.toText12(isCenter: true, maxLine: 1) - : data.getOffersList[index].titleEn!.toText12(isCenter: true, maxLine: 1), - ), - ], + 4.height, + Expanded( + child: + AppState().isArabic(context) + ? data.getOffersList[index].titleAr!.toText12(isCenter: true, maxLine: 1) + : data.getOffersList[index].titleEn!.toText12(isCenter: true, maxLine: 1), + ), + ], + ), ), - ), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => 8.width, - itemCount: 9), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => 8.width, + itemCount: 9, + ), ); }, ), @@ -772,28 +861,28 @@ class _DashboardScreenState extends State with WidgetsBindingOb SvgPicture.asset( "assets/icons/chat/chat.svg", color: - !checkIfPrivilegedForChat() - ? MyColors.lightGreyE3Color - : currentIndex == 4 - ? MyColors.grey3AColor - : cProvider.disbaleChatForThisUser - ? MyColors.lightGreyE3Color - : MyColors.grey98Color, + !checkIfPrivilegedForChat() + ? MyColors.lightGreyE3Color + : currentIndex == 4 + ? MyColors.grey3AColor + : cProvider.disbaleChatForThisUser + ? MyColors.lightGreyE3Color + : MyColors.grey98Color, ).paddingAll(4), Consumer( builder: (BuildContext cxt, ChatProviderModel data, Widget? child) { return !checkIfPrivilegedForChat() ? const SizedBox() : Positioned( - right: 0, - top: 0, - child: Container( - padding: const EdgeInsets.only(left: 4, right: 4), - alignment: Alignment.center, - decoration: BoxDecoration(color: cProvider.disbaleChatForThisUser ? MyColors.pinkDarkColor : MyColors.redColor, borderRadius: BorderRadius.circular(17)), - child: data.chatUConvCounter.toString().toText10(color: Colors.white), - ), - ); + right: 0, + top: 0, + child: Container( + padding: const EdgeInsets.only(left: 4, right: 4), + alignment: Alignment.center, + decoration: BoxDecoration(color: cProvider.disbaleChatForThisUser ? MyColors.pinkDarkColor : MyColors.redColor, borderRadius: BorderRadius.circular(17)), + child: data.chatUConvCounter.toString().toText10(color: Colors.white), + ), + ); }, ), ], @@ -864,4 +953,4 @@ class _DashboardScreenState extends State with WidgetsBindingOb } return false; } -} \ No newline at end of file +} diff --git a/lib/ui/landing/itg/its_add_screen_video_image.dart b/lib/ui/landing/itg/its_add_screen_video_image.dart index 2076d7c..23bfc66 100644 --- a/lib/ui/landing/itg/its_add_screen_video_image.dart +++ b/lib/ui/landing/itg/its_add_screen_video_image.dart @@ -6,14 +6,19 @@ import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/classes/colors.dart'; import 'package:mohem_flutter_app/classes/lottie_consts.dart'; import 'package:mohem_flutter_app/classes/utils.dart'; +import 'package:mohem_flutter_app/config/routes.dart'; import 'package:mohem_flutter_app/extensions/int_extensions.dart'; import 'package:mohem_flutter_app/extensions/string_extensions.dart'; import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; import 'package:mohem_flutter_app/main.dart'; +import 'package:mohem_flutter_app/models/dashboard/menu_entries.dart'; import 'package:mohem_flutter_app/models/itg/advertisement.dart' as ads; +import 'package:mohem_flutter_app/provider/dashboard_provider_model.dart'; +import 'package:mohem_flutter_app/ui/my_attendance/services_menu_list_screen.dart'; import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; import 'package:mohem_flutter_app/widgets/button/default_button.dart'; import 'package:mohem_flutter_app/widgets/my_video_progress_indicator.dart'; +import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:video_player/video_player.dart'; @@ -26,7 +31,7 @@ class ITGAdsScreen extends StatefulWidget { class _ITGAdsScreenState extends State { late Future _futureController; - late VideoPlayerController _controller; + VideoPlayerController? _controller; bool skip = false; bool isVideo = false; bool isImage = false; @@ -125,7 +130,7 @@ class _ITGAdsScreenState extends State { @override void dispose() { - if (_controller != null) _controller.dispose(); + _controller?.dispose(); super.dispose(); } @@ -166,11 +171,11 @@ class _ITGAdsScreenState extends State { child: isAudio ? Lottie.asset(MyLottieConsts.audioPlaybackLottie) : AspectRatio( - aspectRatio: _controller.value.aspectRatio, + aspectRatio: _controller!.value.aspectRatio, child: Stack( alignment: Alignment.bottomCenter, children: [ - VideoPlayer(_controller), + VideoPlayer(_controller!), if (advertisementData?.isOptional ?? false) Positioned( top: 4, @@ -230,7 +235,7 @@ class _ITGAdsScreenState extends State { alignment: Alignment.center, children: [ MyVideoProgressIndicator( - _controller, + _controller!, allowScrubbing: false, //padding: EdgeInsets.only(left: 60, right: 60), ), @@ -386,16 +391,39 @@ class _ITGAdsScreenState extends State { if (advertisementData!.actionButtonsColl![index].actionValue.toLowerCase() == "skip") { Navigator.pop(context); } else { - Uri uri = Uri.parse(advertisementData!.actionButtonsColl![index].iconOrImage); - // if (await canLaunchUrl(uri)) { - await launchUrl(uri, mode: LaunchMode.externalApplication).catchError((err) { - // print(err); - Utils.showToast('Could not launch'); - }); - Navigator.pop(context); - // } else { - // Utils.showToast('Could not launch'); - // } + if (advertisementData!.actionButtonsColl![index].actionType!.toLowerCase() == 'redirect' && advertisementData!.actionButtonsColl![index].actionValue!.toLowerCase() == + 'external') { + Uri uri = Uri.parse(advertisementData!.actionButtonsColl![index].url!); + await launchUrl(uri, mode: LaunchMode.externalApplication).catchError((err) { + // print(err); + Utils.showToast('Could not launch'); + }); + Navigator.pop(context); + } else if (advertisementData!.actionButtonsColl![index].actionType!.toLowerCase() == 'redirect' && advertisementData!.actionButtonsColl![index].actionValue!.toLowerCase() == + 'internal') { + + await Navigator.pushNamed(context, advertisementData!.actionButtonsColl![index].url!); + + } else if(advertisementData!.actionButtonsColl![index].actionType!.toLowerCase() == 'eit' && advertisementData!.actionButtonsColl![index].actionValue!.toLowerCase() == + 'internal'){ + var pro = Provider.of(context, listen: false); + List _menuList = + pro.getMenuEntriesList?.where((element) => element.parentMenuName == advertisementData!.actionButtonsColl![index].url && (element.menuEntryType == "FUNCTION" || element.menuEntryType == "MENU")).toList() ?? []; + Navigator.pushNamed(context, AppRoutes.servicesMenuListScreen, arguments: ServicesMenuListScreenParams(advertisementData!.advertisementTitle!, _menuList)); + + } + else{ + Uri uri = Uri.parse(advertisementData!.actionButtonsColl![index].iconOrImage); + // if (await canLaunchUrl(uri)) { + await launchUrl(uri, mode: LaunchMode.externalApplication).catchError((err) { + // print(err); + Utils.showToast('Could not launch'); + }); + Navigator.pop(context); + // } else { + // Utils.showToast('Could not launch'); + // } + } } }, ), diff --git a/lib/ui/landing/widget/menus_widget.dart b/lib/ui/landing/widget/menus_widget.dart index bc5dceb..e764dca 100644 --- a/lib/ui/landing/widget/menus_widget.dart +++ b/lib/ui/landing/widget/menus_widget.dart @@ -1,13 +1,17 @@ import 'package:easy_localization/src/public_ext.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:mohem_flutter_app/api/eit_api_client.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart'; +import 'package:mohem_flutter_app/classes/utils.dart'; import 'package:mohem_flutter_app/config/routes.dart'; import 'package:mohem_flutter_app/extensions/string_extensions.dart'; import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; +import 'package:mohem_flutter_app/models/sso_auth_model.dart'; import 'package:mohem_flutter_app/provider/dashboard_provider_model.dart'; import 'package:mohem_flutter_app/ui/my_attendance/dynamic_screens/dynamic_listview_screen.dart'; +import 'package:mohem_flutter_app/ui/screens/ticket/ticket_detailed_screen.dart'; import 'package:mohem_flutter_app/widgets/shimmer/dashboard_shimmer_widget.dart'; import 'package:provider/provider.dart'; @@ -117,8 +121,29 @@ class MenusWidget extends StatelessWidget { ), ], ).paddingOnly(left: 10, right: 10, bottom: 6, top: 6), - ).onPress(() { + ).onPress(() async { Navigator.pushNamed(context, AppRoutes.dynamicScreen, arguments: DynamicListViewParams(LocaleKeys.ticketBalance.tr(), "HMG_TKT_NEW_EIT_SS")); + + // var pro = Provider.of(context, listen: false); + // + // // if (menuEntry.menuName == "HMG_TICKET_REQUESTS") { + // Utils.showLoading(context); + // //Ticket Work + // if (pro.ticketBookingResponse != null && pro.ticketBookingResponse!.success) { + // SSOAuthModel? ssoToken = await pro.fetchSSOAuthRedirection(clientID: pro.ticketBookingResponse!.clientId); + // if (ssoToken != null) { + // dynamic url = await pro.fetchURLRedirection(token: ssoToken.data!.accessToken!); + // await pro.fetchTicketAccuralBalance(context, DateTime.now()); + // pro.ticketHistoryTransactionList = await EITApiClient().getEITTransactions("HMG_TICKET_ITENARY_HR_EIT_SS", isCompleteList: true); + // + // Utils.hideLoading(context); + // // Here Need Work + // Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) => TicketDetailedScreen(url: url, jwtToken: ssoToken.data!.accessToken))); + // } + // //} + // } else { + // Navigator.pushNamed(context, AppRoutes.dynamicScreen, arguments: DynamicListViewParams(LocaleKeys.ticketBalance.tr(), "HMG_TKT_NEW_EIT_SS")); + // } }), ], ); diff --git a/lib/ui/landing/widget/services_widget.dart b/lib/ui/landing/widget/services_widget.dart index 879173b..0a30857 100644 --- a/lib/ui/landing/widget/services_widget.dart +++ b/lib/ui/landing/widget/services_widget.dart @@ -92,7 +92,7 @@ class ServicesWidget extends StatelessWidget { aspectRatio: 105 / 105, child: data.isServicesMenusLoading - ? ServicesMenuShimmer() + ? const ServicesMenuShimmer() : Container( decoration: BoxDecoration( color: Colors.white, @@ -266,7 +266,6 @@ class ServicesWidget extends StatelessWidget { await pro.fetchTicketAccuralBalance(context, DateTime.now()); pro.ticketHistoryTransactionList = await EITApiClient().getEITTransactions("HMG_TICKET_ITENARY_HR_EIT_SS", isCompleteList: true); - Utils.hideLoading(context); // Here Need Work Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) => TicketDetailedScreen(url: url, jwtToken: ssoToken.data!.accessToken))); @@ -311,7 +310,7 @@ class ServicesWidget extends StatelessWidget { padding: const EdgeInsets.only(left: 21, right: 21, top: 13, bottom: 13), scrollDirection: Axis.horizontal, itemBuilder: (cxt, index) { - return AspectRatio(aspectRatio: 105 / 105, child: ServicesMenuShimmer()); + return AspectRatio(aspectRatio: 105 / 105, child: const ServicesMenuShimmer()); }, separatorBuilder: (cxt, index) => 9.width, itemCount: 4, diff --git a/lib/ui/parking_qr/parking_qr.dart b/lib/ui/parking_qr/parking_qr.dart new file mode 100644 index 0000000..641f84a --- /dev/null +++ b/lib/ui/parking_qr/parking_qr.dart @@ -0,0 +1,106 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:mohem_flutter_app/api/dashboard_api_client.dart'; +import 'package:mohem_flutter_app/classes/colors.dart'; +import 'package:mohem_flutter_app/classes/utils.dart'; +import 'package:mohem_flutter_app/extensions/int_extensions.dart'; +import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; +import 'package:mohem_flutter_app/models/get_employee_parking_details_model.dart'; +import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; + +class ParkingQrScreen extends StatefulWidget { + const ParkingQrScreen({Key? key}) : super(key: key); + + @override + _RequestDetailsState createState() => _RequestDetailsState(); +} + +class _RequestDetailsState extends State { + GetEmployeeParking? qrData; + + @override + void initState() { + super.initState(); + getTicketDetails(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBarWidget(context, title: LocaleKeys.parkingQr.tr()), + body: + qrData?.getEmployeeParking == null + ? Center(child: Text(LocaleKeys.noDataAvailable.tr())) + : SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Center( + child: Container( + padding: const EdgeInsets.all(20.0), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + boxShadow: [BoxShadow(color: Colors.grey.withValues(alpha: 0.2), spreadRadius: 5, blurRadius: 10, offset: const Offset(0, 3))], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // QR Code Image + if (qrData!.getEmployeeParking!.qrCodeImageBase64 != null && qrData!.getEmployeeParking!.qrCodeImageBase64!.isNotEmpty) + Image.memory(Utils.dataFromBase64String(qrData!.getEmployeeParking!.qrCodeImageBase64!), width: 300, height: 300, fit: BoxFit.contain) + else + Container( + width: 300, + height: 300, + color: MyColors.lightGreyEFColor, + child: Center(child: Text(LocaleKeys.noDataAvailable.tr(), style: const TextStyle(color: MyColors.grey67Color))), + ), + 10.height, + Container(height: 1, color: MyColors.lightGreyE3Color, margin: const EdgeInsets.symmetric(vertical: 10)), + 20.height, + SizedBox( + width: double.infinity, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Employee Code + if (qrData!.getEmployeeParking!.employeeCode != null && qrData!.getEmployeeParking!.employeeCode!.isNotEmpty) ...[ + Text(LocaleKeys.employeeNumber.tr(), style: const TextStyle(fontSize: 14, color: MyColors.grey67Color, fontWeight: FontWeight.w400)), + 8.height, + Text(qrData!.getEmployeeParking!.employeeCode!, style: const TextStyle(fontSize: 18, color: MyColors.darkTextColor, fontWeight: FontWeight.w600)), + 8.height, + ], + if (qrData!.getEmployeeParking!.createdAt != null) ...[ + Text(LocaleKeys.creationDate.tr(), style: const TextStyle(fontSize: 14, color: MyColors.grey67Color, fontWeight: FontWeight.w400)), + 8.height, + Text( + DateFormat('dd MMM yyyy, hh:mm a').format(qrData!.getEmployeeParking!.createdAt!), + style: const TextStyle(fontSize: 16, color: MyColors.darkTextColor, fontWeight: FontWeight.w500), + ), + ], + ], + ), + ), + ], + ), + ), + ), + ), + ), + ); + } + + void getTicketDetails() async { + try { + Utils.showLoading(context); + qrData = await DashboardApiClient().getEmployeeParkingQrCode(); + Utils.hideLoading(context); + setState(() {}); + } catch (ex) { + Utils.hideLoading(context); + Utils.handleException(ex, context, null); + } + } +} diff --git a/lib/ui/screens/courses/courses_screen.dart b/lib/ui/screens/courses/courses_screen.dart new file mode 100644 index 0000000..598b6cf --- /dev/null +++ b/lib/ui/screens/courses/courses_screen.dart @@ -0,0 +1,333 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_inappwebview/flutter_inappwebview.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:mohem_flutter_app/app_state/app_state.dart'; +import 'package:mohem_flutter_app/classes/colors.dart'; +import 'package:mohem_flutter_app/classes/inAppWebView.dart'; +import 'package:mohem_flutter_app/classes/utils.dart'; +import 'package:mohem_flutter_app/extensions/int_extensions.dart'; +import 'package:mohem_flutter_app/extensions/string_extensions.dart'; +import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; +import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; +import 'package:mohem_flutter_app/models/dashboard/courses_response_model.dart'; +import 'package:mohem_flutter_app/provider/dashboard_provider_model.dart'; +import 'package:mohem_flutter_app/theme/colors.dart' as theme_colors; +import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; +import 'package:mohem_flutter_app/widgets/sso_webview_widget.dart'; +import 'package:provider/provider.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; + +class CoursesScreen extends StatefulWidget { + const CoursesScreen({Key? key}) : super(key: key); + + @override + _CoursesScreenState createState() => _CoursesScreenState(); +} + +class _CoursesScreenState extends State { + late DashboardProviderModel data; + final RefreshController _refreshController = RefreshController(initialRefresh: false); + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + data = Provider.of(context, listen: false); + _onRefresh(); + }); + } + + void _onRefresh() async { + await data.fetchCourses(context); + _refreshController.refreshCompleted(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBarWidget( + context, + title: LocaleKeys.courses.tr(), + ), + body: Consumer( + builder: (context, provider, child) { + return SmartRefresher( + enablePullDown: true, + enablePullUp: false, + controller: _refreshController, + onRefresh: _onRefresh, + child: provider.isCoursesLoading + ? _buildShimmerLoading() + : provider.coursesList.isEmpty + ? _buildEmptyState() + : _buildCoursesList(provider.coursesList), + ); + }, + ), + ); + } + + Widget _buildShimmerLoading() { + return ListView.builder( + padding: const EdgeInsets.all(21), + itemCount: 5, + itemBuilder: (context, index) { + return Container( + margin: const EdgeInsets.only(bottom: 12), + height: 150, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(15), + boxShadow: [ + BoxShadow( + color: const Color(0xff000000).withOpacity(.05), + blurRadius: 26, + offset: const Offset(0, -3), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 20, + width: 200, + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(4), + ), + ).toShimmer(), + 12.height, + Container( + height: 14, + width: 100, + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(4), + ), + ).toShimmer(), + const Spacer(), + Container( + height: 40, + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(6), + ), + ).toShimmer(), + ], + ).paddingAll(14), + ); + }, + ); + } + + Widget _buildEmptyState() { + return Utils.getNoDataWidget(context); + } + + Widget _buildCoursesList(List courses) { + return ListView.separated( + physics: const BouncingScrollPhysics(), + shrinkWrap: true, + padding: const EdgeInsets.all(21), + itemBuilder: (BuildContext context, int index) { + return _buildCourseCard(courses[index]); + }, + separatorBuilder: (BuildContext context, int index) => 12.height, + itemCount: courses.length, + ); + } + + Widget _buildCourseCard(CourseStatus course) { + bool isArabic = AppState().isArabic(context); + String courseName = isArabic && course.cNameAr != null && course.cNameAr!.isNotEmpty + ? course.cNameAr! + : course.cFullname ?? ''; + + String status = course.cStatus ?? 'Unknown'; + Color statusColor = _getStatusColor(status); + + return InkWell( + onTap: () => _openCourse(course.cUrl, course.token), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Course Title + Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: MyColors.lightGreyEFColor, + borderRadius: BorderRadius.circular(8), + ), + child: const Icon( + Icons.school_rounded, + color: MyColors.gradiantStartColor, + size: 20, + ), + ), + 12.width, + Expanded( + child: courseName.toText14(isBold: true, color: MyColors.darkTextColor), + ), + ], + ), + 12.height, + // Status Badge + Row( + children: [ + Icon( + _getStatusIcon(status), + size: 16, + color: statusColor, + ), + 6.width, + LocaleKeys.status.tr().toText11(color: MyColors.grey98Color), + 8.width, + Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + decoration: BoxDecoration( + color: statusColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: statusColor.withOpacity(0.3), + width: 1, + ), + ), + child: status.toText10(color: statusColor, isBold: true), + ), + ], + ), + 10.height, + // Start Date + if (course.cEnddate != null && course.cEnddate!.isNotEmpty) + Row( + children: [ + const Icon( + Icons.calendar_month_rounded, + size: 16, + color: MyColors.darkIconColor, + ), + 6.width, + LocaleKeys.endDateT.tr().toText11(color: MyColors.grey98Color), + 8.width, + Expanded( + child: _formatDate(course.cEnddate!).toText11( + color: MyColors.darkTextColor, + isBold: true, + ), + ), + ], + ), + if (course.cStartdate != null && course.cStartdate!.isNotEmpty) 14.height, + // Open Button + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + LocaleKeys.open.tr().toText12(color: MyColors.gradiantStartColor, isUnderLine: true), + 6.width, + RotatedBox( + quarterTurns: AppState().isArabic(context) ? 2 : 4, + child: SvgPicture.asset( + "assets/images/arrow_next.svg", + color: MyColors.gradiantStartColor, + width: 16, + height: 16, + ), + ), + ], + ), + ], + ).objectContainerView(), + ); + } + + + IconData _getStatusIcon(String status) { + if (status.toLowerCase().contains('passed')) { + return Icons.check_circle_rounded; + } else if (status.toLowerCase().contains('not passed') || status.toLowerCase().contains('failed')) { + return Icons.cancel_rounded; + } else if (status.toLowerCase().contains('in progress')) { + return Icons.pending_rounded; + } else { + return Icons.info_rounded; + } + } + + Color _getStatusColor(String status) { + if (status.toLowerCase().contains('passed') && !status.toLowerCase().contains('not passed')) { + return Colors.green; + } else if (status.toLowerCase().contains('not passed') || status.toLowerCase().contains('failed')) { + return Colors.red; + } else if (status.toLowerCase().contains('in progress')) { + return Colors.yellow[700]!; + } else { + return MyColors.grey98Color; + } + } + + DateTime? _parseDate(String? date) { + if (date == null || date.isEmpty) return null; + try { + return DateTime.parse(date.replaceAll(' ', 'T')); + } catch (e) { + try { + DateFormat format = DateFormat('yyyy-MM-dd HH:mm:ss'); + return format.parse(date); + } catch (e) { + return null; + } + } + } + + String _formatDate(String date) { + DateTime? dateTime = _parseDate(date); + if (dateTime == null) return date; + + bool isArabic = AppState().isArabic(context); + DateFormat format = DateFormat('dd MMM yyyy, hh:mm a', isArabic ? 'ar' : 'en'); + return format.format(dateTime); + } + + void _openCourse(String? url, String? jwtToken) { + + + Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) => SsoLoginWebView(url: url, jwtToken: jwtToken))); + //Utils.showToast(LocaleKeys.courseUrlNotAvailable.tr()); + + + // return; + // } + // + // MyInAppBrowser browser = MyInAppBrowser( + // onExitCallback: () {}, + // onLoadStartCallback: (String url) {}, + // ); + // + // var settings = InAppBrowserClassSettings( + // webViewSettings: InAppWebViewSettings( + // useShouldOverrideUrlLoading: false, + // transparentBackground: false, + // ), + // browserSettings: InAppBrowserSettings( + // hideUrlBar: true, + // hideTitleBar: false, + // toolbarTopBackgroundColor: theme_colors.primaryColor, + // ), + // ); + // + // browser.openUrlRequest( + // urlRequest: URLRequest(url: WebUri(url)), + // settings: settings, + // ); + } + + @override + void dispose() { + _refreshController.dispose(); + super.dispose(); + } +} diff --git a/lib/ui/screens/mowadhafhi/mowadhafhi_hr_request.dart b/lib/ui/screens/mowadhafhi/mowadhafhi_hr_request.dart index 44b3833..e7d2fde 100644 --- a/lib/ui/screens/mowadhafhi/mowadhafhi_hr_request.dart +++ b/lib/ui/screens/mowadhafhi/mowadhafhi_hr_request.dart @@ -150,7 +150,9 @@ class _MowadhafhiHRRequestState extends State { isReadOnly: false, onChange: (String value) { debugPrint(value); - description = value; + setState(() { + description = value; + }); }, ), ], @@ -171,7 +173,7 @@ class _MowadhafhiHRRequestState extends State { } bool checkValidation() { - if (selectedServiceType == null || selectedDepartment == null || selectedSection == null || selectedTopic == null) { + if (selectedServiceType == null || selectedDepartment == null || selectedSection == null || selectedTopic == null || description == "") { return false; } else { return true; diff --git a/lib/ui/screens/ticket/ticket_detailed_screen.dart b/lib/ui/screens/ticket/ticket_detailed_screen.dart index d406153..c4d232f 100644 --- a/lib/ui/screens/ticket/ticket_detailed_screen.dart +++ b/lib/ui/screens/ticket/ticket_detailed_screen.dart @@ -9,6 +9,7 @@ import 'package:mohem_flutter_app/classes/utils.dart'; import 'package:mohem_flutter_app/extensions/int_extensions.dart'; import 'package:mohem_flutter_app/extensions/string_extensions.dart'; import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; +import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; import 'package:mohem_flutter_app/models/dashboard/get_accural_ticket_balance_model.dart'; import 'package:mohem_flutter_app/provider/dashboard_provider_model.dart'; import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; @@ -55,7 +56,7 @@ class _TicketDetailedScreenState extends State { Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, - appBar: AppBarWidget(context, title: "Ticket Details"), + appBar: AppBarWidget(context, title:LocaleKeys.ticketDetails.tr()), body: Column( children: [ ListView( @@ -74,7 +75,7 @@ class _TicketDetailedScreenState extends State { children: [ Row( children: [ - "Current Ticket Balance".toText20().expanded, + LocaleKeys.currentTicketBalance.tr().toText20().expanded, Row( children: [ const Icon(Icons.calendar_month_rounded, color: MyColors.darkIconColor, size: 16), @@ -118,7 +119,7 @@ class _TicketDetailedScreenState extends State { shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), children: [ - "Tickets History".toText20().expanded, + LocaleKeys.ticketHistory.tr().toText20().expanded, 12.height, ListView.separated( physics: const NeverScrollableScrollPhysics(), @@ -175,7 +176,7 @@ class _TicketDetailedScreenState extends State { ], ], ).expanded, - DefaultButton("Proceed For Booking", () { + DefaultButton(LocaleKeys.proceedBooking.tr(), () { if (widget.url != null && widget.jwtToken != null) { Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) => SsoLoginWebView(url: widget.url, jwtToken: widget.jwtToken))); } diff --git a/lib/ui/work_list/itg_fragments/approval_level_fragment.dart b/lib/ui/work_list/itg_fragments/approval_level_fragment.dart index d7d9c7e..9d7e94a 100644 --- a/lib/ui/work_list/itg_fragments/approval_level_fragment.dart +++ b/lib/ui/work_list/itg_fragments/approval_level_fragment.dart @@ -21,7 +21,11 @@ class ApprovalLevelfragment extends StatelessWidget { @override Widget build(BuildContext context) { - if (wFHistory.isNotEmpty) wFHistory = wFHistory.reversed.toList(); + if (wFHistory.isNotEmpty) { + //TODO: This COde is Written For Future. If Needed Enable to Filter The Empty Action Requests. + // wFHistory = wFHistory.where((item) => item.action != null && item.action!.isNotEmpty).toList(); + wFHistory = wFHistory.reversed.toList(); + } return Container( width: double.infinity, height: double.infinity, @@ -164,8 +168,8 @@ class ApprovalLevelfragment extends StatelessWidget { if (wFHistory[index].date!.isEmpty || wFHistory[index].action!.toLowerCase() == "NO ACTION" || wFHistory[index + 1].date! == "") { return ""; } else { - DateTime dateTimeTo = DateUtil.convertSimpleStringDateToDate(wFHistory[index].date!); - DateTime dateTimeFrom = DateUtil.convertSimpleStringDateToDate(wFHistory[index + 1].date!); + DateTime dateTimeTo = DateUtil.convertSimpleStringDateToDate(wFHistory[index].date!, isITG: true); + DateTime dateTimeFrom = DateUtil.convertSimpleStringDateToDate(wFHistory[index + 1].date!, isITG: true); Duration duration = dateTimeTo.difference(dateTimeFrom); return "Action duration: " + DateUtil.formatDuration(duration); } diff --git a/lib/widgets/image_picker.dart b/lib/widgets/image_picker.dart index a5d346f..984d6ad 100644 --- a/lib/widgets/image_picker.dart +++ b/lib/widgets/image_picker.dart @@ -141,7 +141,7 @@ void galleryImageAndroid(Function(String, File) image) async { } void cameraImageAndroid(Function(String, File) image) async { - File _image = File(( await picker.pickMedia())?.path ?? ""); + File _image = File((await picker.pickImage(source: ImageSource.camera, imageQuality: 20))?.path ?? ""); String fileName = _image.path; var bytes = File(fileName).readAsBytesSync(); String base64Encode = base64.encode(bytes); @@ -159,7 +159,7 @@ class _BottomSheet extends StatelessWidget { Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(vertical: 12.0), - decoration: BoxDecoration( borderRadius: const BorderRadius.only(topLeft: Radius.circular(16.0), topRight: Radius.circular(16.0))), + decoration: const BoxDecoration( borderRadius: BorderRadius.only(topLeft: Radius.circular(16.0), topRight: Radius.circular(16.0))), child: SafeArea( top: false, child: Column( diff --git a/lib/widgets/shimmer/dashboard_shimmer_widget.dart b/lib/widgets/shimmer/dashboard_shimmer_widget.dart index c11cf16..7d59899 100644 --- a/lib/widgets/shimmer/dashboard_shimmer_widget.dart +++ b/lib/widgets/shimmer/dashboard_shimmer_widget.dart @@ -8,6 +8,8 @@ import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; import 'package:shimmer/shimmer.dart'; class GetAttendanceTrackingShimmer extends StatelessWidget { + const GetAttendanceTrackingShimmer({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { return Container( @@ -85,6 +87,8 @@ class GetAttendanceTrackingShimmer extends StatelessWidget { } class MenuShimmer extends StatelessWidget { + const MenuShimmer({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { return Container( @@ -122,6 +126,8 @@ class MenuShimmer extends StatelessWidget { } class ServicesHeaderShimmer extends StatelessWidget { + const ServicesHeaderShimmer({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { return Row( @@ -145,6 +151,8 @@ class ServicesHeaderShimmer extends StatelessWidget { } class ServicesMenuShimmer extends StatelessWidget { + const ServicesMenuShimmer({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { return Container( @@ -188,6 +196,75 @@ class ServicesMenuShimmer extends StatelessWidget { } } +class GreetingCardShimmer extends StatelessWidget { + const GreetingCardShimmer({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + height: 100, + decoration: BoxDecoration( + color: const Color(0xFFFAF4E7), + border: Border.all(width: 2, color: const Color(0xFFE7E7E7)), + borderRadius: BorderRadius.circular(20), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 65, + height: 81, + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + ).toShimmer(), + const SizedBox(width: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 150, + height: 16, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(4), + ), + ).toShimmer(), + const SizedBox(height: 8), + Container( + width: 120, + height: 16, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(4), + ), + ).toShimmer(), + ], + ).expanded, + Column( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Container( + width: 60, + height: 12, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(4), + ), + ).toShimmer(), + ], + ), + ], + ).paddingOnly(left: 8, right: 8, top: 6, bottom: 12), + ).paddingOnly(left: 21, right: 21, top: 14, bottom: 14); + } +} + + class MarathonBannerShimmer extends StatelessWidget { const MarathonBannerShimmer({Key? key}) : super(key: key); @@ -305,3 +382,4 @@ class ChatHomeShimmer extends StatelessWidget { )); } } + diff --git a/lib/widgets/sso_webview_widget.dart b/lib/widgets/sso_webview_widget.dart index 5877a63..ee4ed7a 100644 --- a/lib/widgets/sso_webview_widget.dart +++ b/lib/widgets/sso_webview_widget.dart @@ -15,10 +15,13 @@ class SsoLoginWebView extends StatefulWidget { class _SsoLoginWebViewState extends State { late final WebViewController _controller; - + String? url; @override void initState() { // TODO: implement initState + + url = widget.url ?? "https://Paxes-release.techmaster.in/SSO/HMG"; + super.initState(); _controller = WebViewController() @@ -46,7 +49,7 @@ class _SsoLoginWebViewState extends State { -
+

Redirecting...

@@ -56,6 +59,6 @@ class _SsoLoginWebViewState extends State { @override Widget build(BuildContext context) { - return Scaffold(appBar: AppBar(title: Text('Logging in...')), body: WebViewWidget(controller: _controller)); + return Scaffold(appBar: null, body:SafeArea(child: WebViewWidget(controller: _controller))); } } diff --git a/pubspec.yaml b/pubspec.yaml index b5c81d3..1e91825 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,8 +17,8 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -#version: 3.6.8+300078 -version: 3.9.2+1 +version: 3.6.8+300078 +#version: 3.9.2+1 environment: sdk: ^3.7.0 @@ -105,6 +105,9 @@ dependencies: audio_waveforms: ^1.3.0 rxdart: ^0.27.7 + #Speech to Text + speech_to_text: ^7.0.0 + #Encryption flutter_des: ^2.1.0