Merge branch 'development_mirza' into 'main'
structure 1.0 See merge request Cloud_Solution/hmg_nurses!1merge-requests/2/merge
commit
241a42f02e
@ -0,0 +1,502 @@
|
||||
{
|
||||
"mohemm": "Mohemm",
|
||||
"english": "English",
|
||||
"arabic": "عربي",
|
||||
"login": "Login",
|
||||
"pleaseEnterLoginDetails": "Please enter the detail below to login",
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
"welcomeBack": "Welcome back",
|
||||
"wouldYouLikeToLoginWithCurrentUsername": "Would you like to login with current Username?",
|
||||
"lastLoginDetails": "Last Login Details:",
|
||||
"verificationType": "Verification Type:",
|
||||
"pleaseVerify": "Please Verify",
|
||||
"pleaseVerifyForBio": "Please verify login with one of the following options",
|
||||
"verifyThroughFace": "Verify Through Face",
|
||||
"verifyThroughFingerprint": "Verify Through Fingerprint",
|
||||
"verifyThroughSMS": "Verify Through SMS",
|
||||
"verifyThroughWhatsapp": "Verify Through Whatsapp",
|
||||
"useAnotherAccount": "Use Another Account",
|
||||
"pleaseEnterTheVerificationCodeSentTo": "Please enter the verification code sent to ",
|
||||
"theVerificationCodeWillExpireIn": "The verification code will expire in ",
|
||||
"goodMorning": "Good Morning",
|
||||
"markAttendance": "Mark Attendance",
|
||||
"timeLeftToday": "Time Left Today",
|
||||
"checkIn": "Check In",
|
||||
"workList": "Work List",
|
||||
"leaveBalance": "Leave Balance",
|
||||
"missingSwipes": "Missing Swipes",
|
||||
"ticketBalance": "Ticket Balance",
|
||||
"other": "Other",
|
||||
"services": "Services",
|
||||
"viewAllServices": "View All Services",
|
||||
"monthlyAttendance": "Monthly Attendance",
|
||||
"vacationRule": "Vacation Rule",
|
||||
"vacationType": "Vacation Type",
|
||||
"startDateT": "Start Date",
|
||||
"endDateT": "End Date",
|
||||
"workFromHome": "Work From Home",
|
||||
"ticketRequest": "Ticket Request",
|
||||
"viewAllOffers": "View All Offers",
|
||||
"offers": "Offers & ",
|
||||
"discounts": "Discounts",
|
||||
"newString": "New",
|
||||
"setTheNewPassword": "Set the new password",
|
||||
"typeYourNewPasswordBelow": "Type your new password below",
|
||||
"confirmPassword": "Confirm Password",
|
||||
"update": "Update",
|
||||
"title": "Title",
|
||||
"home": "Home",
|
||||
"mySalary": "My Salary",
|
||||
"createRequest": "Create Request",
|
||||
"forgotPassword": "Forgot Password",
|
||||
"employeeId": "Employee ID",
|
||||
"loginCodeWillSentToMobileNumber": "Please Enter your Employee ID, A login code will be sent to your mobile number",
|
||||
"changePassword": "Change Password",
|
||||
"ok": "OK",
|
||||
"confirm": "Confirm",
|
||||
"passwordChangedSuccessfully": "Password changed successfully",
|
||||
"itemsForSale": "Items for Sale",
|
||||
"attendanceDetails": "Attendance Details",
|
||||
"order": "order",
|
||||
"earlyOut": "Early Out",
|
||||
"shortage": "Shortage",
|
||||
"excess": "Excess",
|
||||
"lateIn": "Late In",
|
||||
"approvedCheckOut": "Approved Check Out",
|
||||
"approvedCheckIn": "Approved Check In",
|
||||
"actualCheckOut": "Actual Check Out",
|
||||
"actualCheckIn": "Actual Check In",
|
||||
"present": "PRESENT",
|
||||
"pres": "present",
|
||||
"shiftTime": "Shift Time",
|
||||
"absent": "ABSENT",
|
||||
"attendance": "Attendance",
|
||||
"scheduleDays": "Schedule\nDays",
|
||||
"offDays": "Off\nDays",
|
||||
"nonAnalyzed": "Non\nAnalyzed",
|
||||
"shortageHour": "Shortage\nHour",
|
||||
"stats": "Stats",
|
||||
"completed": "Completed",
|
||||
"doNotUseRecentPassword": "Do not use recent password",
|
||||
"atLeastOneLowercase": "At least one lowercase",
|
||||
"atLeastOneUppercase": "At least one uppercase",
|
||||
"atLeastOneNumeric": "At least one numeric",
|
||||
"minimum8Characters": "Minimum 8 characters",
|
||||
"doNotAddRepeatingLetters": "Do not add repeating letters",
|
||||
"itShouldContainSpecialCharacter": "It should contain special character",
|
||||
"confirmPasswordMustMatch": "Confirm password must match",
|
||||
"sms": "SMS",
|
||||
"fingerPrint": "Fingerprint",
|
||||
"face": "Face",
|
||||
"whatsapp": "Whatsapp",
|
||||
"reject": "Reject",
|
||||
"approve": "Approve",
|
||||
"cancel": "Cancel",
|
||||
"requestedItems": "Requested Items",
|
||||
"request": "Request",
|
||||
"myRequest": "My Request",
|
||||
"actions": "Actions",
|
||||
"delegate": "Delegate",
|
||||
"request_info": "Request Info",
|
||||
"attachments": "Attachments",
|
||||
"info": "Info.",
|
||||
"employeeNumber": "Employee Number",
|
||||
"assignmentNumber": "Assignment Number",
|
||||
"employeeName": "Employee Name",
|
||||
"scheduleDate": "Schedule Date",
|
||||
"shiftType": "Shift Type",
|
||||
"shift": "Shift",
|
||||
"breakText": "Break",
|
||||
"actualSwipeStart": "Actual Swipe Start",
|
||||
"actualSwipeEnd": "Actual Swipe End",
|
||||
"approvedSwipeStart": "Approved Swipe Start",
|
||||
"approvedSwipeStartReason": "Approved Swipe Start Reason",
|
||||
"approvedSwipeEnd": "Approved Swipe End",
|
||||
"approvedSwipeEndReason": "Approved Swipe End Reason",
|
||||
"from": "From",
|
||||
"to": "To",
|
||||
"sent": "Sent",
|
||||
"closed": "Closed",
|
||||
"id": "ID",
|
||||
"responder": "Responder",
|
||||
"jobTitle": "Job Title",
|
||||
"grade": "Grade",
|
||||
"jobCategory": "Job Category",
|
||||
"category": "Category",
|
||||
"employeeEmailAddress": "Employee Email Address",
|
||||
"payrollBranch": "Payroll Branch",
|
||||
"yourChangeHasBeenSavedSuccessfully": "Your change has been saved successfully",
|
||||
"code": "Code",
|
||||
"unit": "Unit",
|
||||
"quantity": "Quantity",
|
||||
"dateRequired": "Date Required",
|
||||
"lineStatus": "Line Status",
|
||||
"statusDate": "Status Date",
|
||||
"transactionType": "Transaction Type",
|
||||
"operatingUnit": "Operating Unit",
|
||||
"organizationCode": "Organization Code",
|
||||
"organization": "Organization",
|
||||
"fromSubInventory": "From Sub Inventory",
|
||||
"fromLocator": "From Locator",
|
||||
"toSubInventory": "To Sub Inventory",
|
||||
"toLocator": "To Locator",
|
||||
"shipToLocator": "Ship To Locator",
|
||||
"itemHistory": "Item History",
|
||||
"mfg": "MFG",
|
||||
"lineType": "Line Type",
|
||||
"price": "Price",
|
||||
"lineAmount": "Line Amount",
|
||||
"lineDiscount": "Line Discount %",
|
||||
"needByDate": "Need By Date",
|
||||
"promisedDate": "Promised Date",
|
||||
"deliverToLocation": "Deliver To Location",
|
||||
"requisitionNumber": "Requisition Number",
|
||||
"requester": "Requester",
|
||||
"subject": "Subject",
|
||||
"quotationAnalysis": "Quotation Analysis",
|
||||
"description": "Description",
|
||||
"supplier": "Supplier",
|
||||
"site": "Site",
|
||||
"buyer": "Buyer",
|
||||
"preparer": "Preparer",
|
||||
"creationDate": "Creation Date",
|
||||
"shipToLocation": "Ship To Location",
|
||||
"quotationNumber": "Quotation Number",
|
||||
"quotationDate": "Quotation Date",
|
||||
"paymentTerms": "Payment Terms",
|
||||
"currency": "Currency",
|
||||
"grossAmount": "Gross Amount",
|
||||
"discountAmount": "Discount Amount",
|
||||
"customDuty": "Custom Duty",
|
||||
"shipHandle": "Ship Handle",
|
||||
"otherCharges": "Other Charges",
|
||||
"totalPOAmountWithVAT": "Total PO Amount With VAT",
|
||||
"totalPOAmountInWords": "Total PO Amount In Words",
|
||||
"requestNumber": "Request Number",
|
||||
"uom": "UOM",
|
||||
"operatingCode": "Operating Code",
|
||||
"poNumber": "PO Number",
|
||||
"revision": "Revision",
|
||||
"quantityOrdered": "Quantity Ordered",
|
||||
"quantityReceived": "Quantity Received",
|
||||
"bonusQuantity": "Bonus Quantity",
|
||||
"purchasePrice": "Purchase Price",
|
||||
"discountPer": "Discount %",
|
||||
"balanceQuantity": "Balance Quantity",
|
||||
"netPrice": "Net Price",
|
||||
"closureStatus": "Closure Status",
|
||||
"quotationNetPrice": "Quotation Net Price",
|
||||
"quotationUOM": "Quotation UOM",
|
||||
"quotationQty": "Quotation Qty",
|
||||
"itemCode": "item Code",
|
||||
"vendorName": "Vendor Name",
|
||||
"quotationMFGPartNumber": "Quotation MFG Part Number",
|
||||
"quotationDeliveryDate": "Quotation Delivery Date",
|
||||
"quotationBonusQuantity": "Quotation Bonus Quantity",
|
||||
"quotationLineTotal": "Quotation Line Total",
|
||||
"rfqUOM": "RFQ UOM",
|
||||
"rfqQty": "RFQ Qty",
|
||||
"rfqNumber": "RFQ Number",
|
||||
"msg": "Hello {} in the {} world ",
|
||||
"msg_named": "{} are written in the {lang} language",
|
||||
"clickMe": "Click me",
|
||||
"human": "Human",
|
||||
"resources": "Resources",
|
||||
"details": "Details",
|
||||
"noDataAvailable": "No Data Available",
|
||||
"productName": "Product Name",
|
||||
"productDescription": "Product Description",
|
||||
"unitPrice": "Unit Price",
|
||||
"manufacturerName": "Manufacturer Name",
|
||||
"manufacturerPartName": "Manufacturer Part Name",
|
||||
"supplierName": "Supplier Name",
|
||||
"supplierContact": "Supplier Contact",
|
||||
"chargeToPatient": "Charge To Patient",
|
||||
"justification": "Justification",
|
||||
"itemDescription": "Item Description",
|
||||
"groupCode": "Group Code",
|
||||
"primaryUOM": "Primary UOM",
|
||||
"subgroupDescription": "Subgroup Description",
|
||||
"subgroupCode": "Subgroup Code",
|
||||
"groupDescription": "Group Description",
|
||||
"templateName": "Template Name",
|
||||
"itemCreationStatus": "Item Creation Status",
|
||||
"standardizationApprovalStatus": "Standardization Approval Status",
|
||||
"standardizationApprovalRejectionReason": "Standardization Approval Rejection Reason",
|
||||
"analyzedBy": "Analyzed By",
|
||||
"approvedDate": "Approved Date",
|
||||
"itemType": "Item Type",
|
||||
"relatedTo": "Related To",
|
||||
"requestDate": "Request Date",
|
||||
"analyzedDate": "Analyzed Date",
|
||||
"urgent": "Urgent",
|
||||
"requestDetails": "Request Details",
|
||||
"approvalLevel": "Approval Level",
|
||||
"requesterDetails": "Requester Details",
|
||||
"myAttendance": "My Attendance",
|
||||
"workOnBreak": "Work On Break",
|
||||
"next": "Next",
|
||||
"apply": "Apply",
|
||||
"mobile": "Mobile",
|
||||
"year": "Year",
|
||||
"month": "Month",
|
||||
"day": "Day",
|
||||
"completingYear": "We appreciate you for completing the service of",
|
||||
"address": "Address",
|
||||
"phoneNumber": "Phone Number",
|
||||
"businessGroup": "Business",
|
||||
"Payroll": "Payroll",
|
||||
"civilIdentityNumber": "Civil Identity Number",
|
||||
"dateOfBirth": "Date of Birth",
|
||||
"maritalStatus ": "Marital Status ",
|
||||
"fullName": "Full Name",
|
||||
"remove": "Remove",
|
||||
"Attendance": "Attendance",
|
||||
"submit": "Submit",
|
||||
"areYouSureYouWantToSubmit": "Are you sure you want to submit?",
|
||||
"comments": "Comments",
|
||||
"writeComment": "Write a comment",
|
||||
"approversList": "Approvers List",
|
||||
"yourRequestHasBeenSubmittedForApprovals": "Your request has been submitted for approvals",
|
||||
"monthlyPaySlip": "Monthly Pay Slip",
|
||||
"particular": "Particular",
|
||||
"earnings": "Earnings",
|
||||
"deductions": "Deductions",
|
||||
"paymentMethodName": "Payment Method Name",
|
||||
"bankName": "Bank Name",
|
||||
"branchCode": "Branch Code",
|
||||
"accountNo": "Account No",
|
||||
"summaryOfInformation": "Summary of Information",
|
||||
"totalPayAmount": "Total Pay Amount",
|
||||
"paymentInformation": "Payment Information",
|
||||
"amount": "Amount",
|
||||
"correctCurrentDatails": "correct or complete the current details",
|
||||
"selectType": "Select the type of change you want to make",
|
||||
"enterNewInfo": "Enter new Information because of a real change to the current details (e.g because of a change in marital status)",
|
||||
"endDate": "*End Date",
|
||||
"removeThisMember": "Are You Sure You Want to Remove this Member?",
|
||||
"wantUpdateThisMember": "Are You Sure You Want to Update this Member?",
|
||||
"addNewFamilyMember": "Add New Family Member",
|
||||
"addRow": "Add new row",
|
||||
"pleaseSelect": "Please Select *",
|
||||
"delete": "delete",
|
||||
"add": "Add",
|
||||
"edit": "Edit",
|
||||
"myProfile": "My Profile",
|
||||
"mowadhafhi": "Mowadhafi",
|
||||
"searchAnnouncements": "Search Announcements",
|
||||
"announcements": "Announcements",
|
||||
"swipeRequest": "Swipe Request",
|
||||
"serviceType": "Service Type",
|
||||
"departmentName": "Department Name",
|
||||
"selectDepartment": "Select Department",
|
||||
"relatedSection": "Related Section",
|
||||
"selectSection": "Select Section",
|
||||
"relatedTopic": "Related Topic",
|
||||
"selectTopic": "Select Topic",
|
||||
"supportingDocument": "Supporting Document",
|
||||
"mowadhafhiRequest": "Mowadhafi Request",
|
||||
"ticketReference": "Ticket Reference",
|
||||
"section": "Section",
|
||||
"topic": "Topic",
|
||||
"actionBy": "Action By",
|
||||
"pendingTransactions": "Pending Transactions",
|
||||
"selectRequestType": "Please select request type",
|
||||
"dateFrom": "Date From",
|
||||
"dateTo": "Date To",
|
||||
"requestName": "Request Name",
|
||||
"createdFor": "Created For",
|
||||
"requestType": "Request Type",
|
||||
"requestCreatedSuccessfully": "Request created successfully",
|
||||
"search": "Search",
|
||||
"wantToReject": "Are you sure want to reject?",
|
||||
"employeeDigitalID": "Employee Digital ID",
|
||||
"businessCard": "Business Card",
|
||||
"checkOut": "Check Out",
|
||||
"regular": "Regular",
|
||||
"mark": "Mark",
|
||||
"performance": "Performance Evaluation",
|
||||
"performanceEvaluationIn": "Your performance Evaluation in",
|
||||
"valuationIn": "Performance Evaluation in",
|
||||
"viewBusinessCard": "View Business Card",
|
||||
"performanceEvaluation": "Performance Evaluation",
|
||||
"logout": "Logout",
|
||||
"selectMethodOfAttendance": "Select the method to mark the attendance",
|
||||
"comeNearHMGWifi": "Please come near to HMG wifi",
|
||||
"deliverNotificationToMeRegardless": "Deliver notifications to me regardless of any general rules",
|
||||
"close": "Close",
|
||||
"respond": "Respond",
|
||||
"vacationRuleAdded": "Vacation rule added",
|
||||
"selectTypeT": "Select Type",
|
||||
"notification": "Notification",
|
||||
"selectNotification": "Select Notification",
|
||||
"ifAllSelectedYouWillSkip": "*If All is selected, you will skip to step 3",
|
||||
"applyForVacationRule": "Apply for Vacation Rule",
|
||||
"step1": "Step 1",
|
||||
"step2": "Step 2",
|
||||
"step3": "Step 3",
|
||||
"message": "Message",
|
||||
"writeAMessage": "Write a message",
|
||||
"notificationReassign": "Notification Reassign",
|
||||
"selectEmployee": "Select Employee",
|
||||
"searchEmployeeForReplacement": "Search employee for replacement",
|
||||
"searchForEmployee": "Search for Employee",
|
||||
"pleaseSpecifyEndTime": "Please specify End Time",
|
||||
"pleaseSelectNotificationReassign": "Please select notification reassign",
|
||||
"pleaseSelectEmployeeForReplacement": "Please select employee for replacement",
|
||||
"pleaseSelectAction": "Please select action",
|
||||
"pleaseSelectDate": "Please select date",
|
||||
"todayAttendance": "Today's Attendance",
|
||||
"viewAttendance": "View Attendance",
|
||||
"teamMembers": "Team Members",
|
||||
"profileDetails": "Profile Details",
|
||||
"noResultsFound": "No Results Found",
|
||||
"searchBy": "Search by",
|
||||
"myTeamMembers": "My Team Members",
|
||||
"save": "Save",
|
||||
"TurnNotificationsFor": "Turn on notifications for",
|
||||
"worklistSettings": "Worklist Settings",
|
||||
"absenceType": "Absence Type",
|
||||
"absenceCategory": "Absence Category",
|
||||
"days": "Days",
|
||||
"hours": "Hours",
|
||||
"approvalStatus": "Approval Status",
|
||||
"absenceStatus": "Absence Status",
|
||||
"poweredBy": "Powered By",
|
||||
"cloudSolutions": "Cloud Solutions",
|
||||
"subordinateLeave": "Subordinate Leave",
|
||||
"numberDays": "Number of days",
|
||||
"selectTemplate": "Select Template",
|
||||
"myPostedAds": "My posted ads",
|
||||
"browseCategories": "Browse Categories",
|
||||
"searchItems": "Search Items",
|
||||
"offerAndDiscounts": "Offer & Discounts",
|
||||
"offerValid": "Offer Valid",
|
||||
"offerExpired": "Offer Expired",
|
||||
"whatAreYouOffering": "What are you offering?",
|
||||
"selectCategory": "Select Category",
|
||||
"inProgress": "InProgress",
|
||||
"locked": "Locked",
|
||||
"addDetails": "Add Details",
|
||||
"reviewAndSell": "Review & Sell",
|
||||
"itemTitle": "Item Title",
|
||||
"itemCondition": "Item Condition",
|
||||
"used": "Used",
|
||||
"region": "Region",
|
||||
"selectRegion": "Select Region",
|
||||
"itemPrice": "Item Price",
|
||||
"itemPhotos": "Item Photos",
|
||||
"itemInfo": "Item Info",
|
||||
"uploadAttachment": "Upload Attachment",
|
||||
"selectFromGalleryOrOpenCamera": "Select from gallery or open camera",
|
||||
"openCamera": "Open\nCamera",
|
||||
"uploadFromGallery": "Upload from\nGallery",
|
||||
"name": "Name",
|
||||
"email": "Email",
|
||||
"noHistoryAvailable": "No History Available",
|
||||
"purchaseRequisition": "Purchase Requisition",
|
||||
"moveOrder": "Move Order",
|
||||
"humanResource": "Human Resource",
|
||||
"purchaseOrder": "Purchase Order",
|
||||
"ITGForms": "ITG Forms",
|
||||
"itemCreation": "Item Creation",
|
||||
"stamp": "Stamp",
|
||||
"addFavoriteList": "Do you want to add {name} in your favorite list",
|
||||
"feedbackUserExperience": "This is to get the feedback about the user experience",
|
||||
"rateUI": "1. How would you rate this UI?",
|
||||
"submitSurvey": "Submit Survey",
|
||||
"typeHere": "Type here",
|
||||
"infoDetail": "Info Detail",
|
||||
"amount_detail": "Amount Detail",
|
||||
"currentBalance": "Current Balance",
|
||||
"currentLeaveBalance": "Current Leave Balance",
|
||||
"calculatedDays": "Calculated Days",
|
||||
"totalDays": "Total Days",
|
||||
"usedBalance": "Used",
|
||||
"infants": "Infants",
|
||||
"child": "Child",
|
||||
"adult": "Adult",
|
||||
"updateMember": "Are You Sure You Want to Update this Member?",
|
||||
"fieldIsEmpty": "'{data}' Field is empty. Please select",
|
||||
"pleaseEnterComments": "Please enter comments",
|
||||
"skip": "Skip",
|
||||
"typeCurrentPasswordBelow": "Type Your Current password below",
|
||||
"currentPassword": "Current password",
|
||||
"concurrentReports": "Concurrent Reports",
|
||||
"profile": {
|
||||
"reset_password": {
|
||||
"label": "Reset Password",
|
||||
"username": "Username",
|
||||
"password": "password"
|
||||
},
|
||||
"profileCompletionPer": "Profile Completion",
|
||||
"completeProfile": "Complete Profile",
|
||||
"personalInformation": "Personal Information",
|
||||
"basicDetails": "Basic Details",
|
||||
"address": "Address",
|
||||
"contactDetails": "Contact Details",
|
||||
"familyDetails": "Family Members",
|
||||
"effectiveDate": "Effective Date",
|
||||
"country": "Country"
|
||||
},
|
||||
"clicked": {
|
||||
"zero": "You clicked {} times!",
|
||||
"one": "You clicked {} time!",
|
||||
"two": "You clicked {} times!",
|
||||
"few": "You clicked {} times!",
|
||||
"many": "You clicked {} times!",
|
||||
"other": "You clicked {} times!"
|
||||
},
|
||||
"gender": {
|
||||
"male": "Hi man ;) ",
|
||||
"female": "Hello girl :)",
|
||||
"with_arg": {
|
||||
"male": "Hi man ;) {}",
|
||||
"female": "Hello girl :) {}"
|
||||
}
|
||||
},
|
||||
"reset_locale": "Reset Language",
|
||||
"chat": "Chat",
|
||||
"mychats": "My Chats",
|
||||
"createNewChat": "Create New Chat",
|
||||
"brainMarathon": "Brain Marathon",
|
||||
"contestTopicAbout": "Contest Topic will be about:",
|
||||
"gameDate": "Game Date:",
|
||||
"gameTime": "Game Time:",
|
||||
"joinMarathon": "Join Marathon",
|
||||
"joinDemoMarathon": "Join Demo Marathon",
|
||||
"minutes": "Minutes",
|
||||
"seconds": "Seconds",
|
||||
"note": "Note:",
|
||||
"demoMarathonNoteP1": "You can play the demo Marathon to learn how it works. You can join the Marathon",
|
||||
"demoMarathonNoteP2": "5 Minutes",
|
||||
"demoMarathonNoteP3": "before the actual time.",
|
||||
"sponsoredBy": "Sponsored By:",
|
||||
"question": "Question",
|
||||
"marathoners": "Marathoners",
|
||||
"prize": "Prize:",
|
||||
"advancedSearch": "Advanced Search",
|
||||
"openNot": "Open Notifications",
|
||||
"fyi": "FYI Notifications",
|
||||
"toDo": "To Do Notifications",
|
||||
"all": "All Notifications",
|
||||
"meNot": "Notifications from Me",
|
||||
"view": "View",
|
||||
"fromUserName": "From User Name",
|
||||
"sentDate": "Sent Date",
|
||||
"itemTypeDisplayName": "Item Type Display Name",
|
||||
"none": "None",
|
||||
"winnerSelection": "Winner Selection",
|
||||
"qualifiers": "Qualifiers",
|
||||
"getReadyForContest": "Get Ready for the coming contest:",
|
||||
"winnerSelectedRandomly": "The winner will be selected randomly among the qualifiers.",
|
||||
"fingersCrossed": "Fingers Crossed!!!",
|
||||
"congrats": "Congratulations!!!",
|
||||
"otp": "OTP",
|
||||
"verification": "Verification",
|
||||
"resend": "Resend",
|
||||
"codeExpire": "The verification code has been expired",
|
||||
"allQuestionsCorrect": "You have answered all questions correct",
|
||||
"typeheretoreply": "Type here to reply"
|
||||
}
|
||||
@ -0,0 +1,321 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:http/io_client.dart';
|
||||
import 'package:mohem_flutter_app/app_state/app_state.dart';
|
||||
import 'package:mohem_flutter_app/exceptions/api_exception.dart';
|
||||
import 'package:mohem_flutter_app/main.dart';
|
||||
// ignore_for_file: avoid_annotating_with_dynamic
|
||||
|
||||
typedef FactoryConstructor<U> = U Function(dynamic);
|
||||
|
||||
class APIError {
|
||||
int? errorCode;
|
||||
String? errorMessage;
|
||||
|
||||
APIError(this.errorCode, this.errorMessage);
|
||||
|
||||
Map<String, dynamic> toJson() =>
|
||||
{'errorCode': errorCode, 'errorMessage': errorMessage};
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
}
|
||||
|
||||
APIException _throwAPIException(Response response) {
|
||||
switch (response.statusCode) {
|
||||
case 200:
|
||||
APIError? apiError;
|
||||
if (response.body != null && response.body.isNotEmpty) {
|
||||
var jsonError = jsonDecode(response.body);
|
||||
print(jsonError);
|
||||
apiError = APIError(jsonError['ErrorCode'], jsonError['ErrorMessage']);
|
||||
}
|
||||
return APIException(APIException.BAD_REQUEST, error: apiError);
|
||||
case 400:
|
||||
APIError? apiError;
|
||||
if (response.body != null && response.body.isNotEmpty) {
|
||||
var jsonError = jsonDecode(response.body);
|
||||
apiError = APIError(jsonError['ErrorCode'], jsonError['ErrorMessage']);
|
||||
}
|
||||
return APIException(APIException.BAD_REQUEST, error: apiError);
|
||||
case 401:
|
||||
return APIException(APIException.UNAUTHORIZED);
|
||||
case 403:
|
||||
return APIException(APIException.FORBIDDEN);
|
||||
case 404:
|
||||
return APIException(APIException.NOT_FOUND);
|
||||
case 500:
|
||||
return APIException(APIException.INTERNAL_SERVER_ERROR);
|
||||
case 444:
|
||||
var downloadUrl = response.headers["location"];
|
||||
return APIException(APIException.UPGRADE_REQUIRED,
|
||||
arguments: downloadUrl);
|
||||
default:
|
||||
return APIException(APIException.OTHER);
|
||||
}
|
||||
}
|
||||
|
||||
class ApiClient {
|
||||
static final ApiClient _instance = ApiClient._internal();
|
||||
|
||||
ApiClient._internal();
|
||||
|
||||
factory ApiClient() => _instance;
|
||||
|
||||
Future<U> postJsonForObject<T, U>(
|
||||
FactoryConstructor<U> factoryConstructor, String url, T jsonObject,
|
||||
{String? token,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
Map<String, String>? headers,
|
||||
int retryTimes = 0,
|
||||
bool isFormData = false}) async {
|
||||
var _headers = {'Accept': 'application/json'};
|
||||
if (headers != null && headers.isNotEmpty) {
|
||||
_headers.addAll(headers);
|
||||
}
|
||||
if (!kReleaseMode) {
|
||||
print("Url:$url");
|
||||
var bodyJson = json.encode(jsonObject);
|
||||
print("body:$bodyJson");
|
||||
}
|
||||
var response = await postJsonForResponse(url, jsonObject,
|
||||
token: token,
|
||||
queryParameters: queryParameters,
|
||||
headers: _headers,
|
||||
retryTimes: retryTimes,
|
||||
isFormData: isFormData);
|
||||
// try {
|
||||
if (!kReleaseMode) {
|
||||
logger.i("res: " + response.body);
|
||||
}
|
||||
var jsonData = jsonDecode(response.body);
|
||||
if (jsonData["IsAuthenticated"] != null) {
|
||||
AppState().setIsAuthenticated = jsonData["IsAuthenticated"];
|
||||
}
|
||||
if (jsonData["ErrorMessage"] == null) {
|
||||
return factoryConstructor(jsonData);
|
||||
} else {
|
||||
APIError? apiError;
|
||||
apiError =
|
||||
APIError(jsonData['ErrorCode'], jsonData['ErrorEndUserMessage']);
|
||||
throw APIException(APIException.BAD_REQUEST, error: apiError);
|
||||
}
|
||||
// } catch (ex) {
|
||||
// if (ex is APIException) {
|
||||
// rethrow;
|
||||
// } else {
|
||||
// throw APIException(APIException.BAD_RESPONSE_FORMAT, arguments: ex);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
Future<Response> postJsonForResponse<T>(String url, T jsonObject,
|
||||
{String? token,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
Map<String, String>? headers,
|
||||
int retryTimes = 0,
|
||||
bool isFormData = false}) async {
|
||||
String? requestBody;
|
||||
late Map<String, String> stringObj;
|
||||
if (jsonObject != null) {
|
||||
requestBody = jsonEncode(jsonObject);
|
||||
if (headers == null) {
|
||||
headers = {'Content-Type': 'application/json'};
|
||||
} else {
|
||||
headers['Content-Type'] = 'application/json';
|
||||
}
|
||||
}
|
||||
|
||||
if (isFormData) {
|
||||
headers = {'Content-Type': 'application/x-www-form-urlencoded'};
|
||||
stringObj = ((jsonObject ?? {}) as Map<String, dynamic>)
|
||||
.map((key, value) => MapEntry(key, value?.toString() ?? ""));
|
||||
}
|
||||
|
||||
return await _postForResponse(url, isFormData ? stringObj : requestBody,
|
||||
token: token,
|
||||
queryParameters: queryParameters,
|
||||
headers: headers,
|
||||
retryTimes: retryTimes);
|
||||
}
|
||||
|
||||
Future<Response> _postForResponse(String url, requestBody,
|
||||
{String? token,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
Map<String, String>? headers,
|
||||
int retryTimes = 0}) async {
|
||||
try {
|
||||
var _headers = <String, String>{};
|
||||
if (token != null) {
|
||||
_headers['Authorization'] = 'Bearer $token';
|
||||
}
|
||||
|
||||
if (headers != null && headers.isNotEmpty) {
|
||||
_headers.addAll(headers);
|
||||
}
|
||||
|
||||
if (queryParameters != null) {
|
||||
var queryString = new Uri(queryParameters: queryParameters).query;
|
||||
url = url + '?' + queryString;
|
||||
}
|
||||
var response =
|
||||
await _post(Uri.parse(url), body: requestBody, headers: _headers)
|
||||
.timeout(Duration(seconds: 120));
|
||||
|
||||
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||
return response;
|
||||
} else {
|
||||
throw _throwAPIException(response);
|
||||
}
|
||||
} on SocketException catch (e) {
|
||||
if (retryTimes > 0) {
|
||||
print('will retry after 3 seconds...');
|
||||
await Future.delayed(Duration(seconds: 3));
|
||||
return await _postForResponse(url, requestBody,
|
||||
token: token,
|
||||
queryParameters: queryParameters,
|
||||
headers: headers,
|
||||
retryTimes: retryTimes - 1);
|
||||
} else {
|
||||
throw APIException(APIException.OTHER, arguments: e);
|
||||
}
|
||||
} on HttpException catch (e) {
|
||||
if (retryTimes > 0) {
|
||||
print('will retry after 3 seconds...');
|
||||
await Future.delayed(Duration(seconds: 3));
|
||||
return await _postForResponse(url, requestBody,
|
||||
token: token,
|
||||
queryParameters: queryParameters,
|
||||
headers: headers,
|
||||
retryTimes: retryTimes - 1);
|
||||
} else {
|
||||
throw APIException(APIException.OTHER, arguments: e);
|
||||
}
|
||||
} on TimeoutException catch (e) {
|
||||
throw APIException(APIException.TIMEOUT, arguments: e);
|
||||
} on ClientException catch (e) {
|
||||
if (retryTimes > 0) {
|
||||
print('will retry after 3 seconds...');
|
||||
await Future.delayed(Duration(seconds: 3));
|
||||
return await _postForResponse(url, requestBody,
|
||||
token: token,
|
||||
queryParameters: queryParameters,
|
||||
headers: headers,
|
||||
retryTimes: retryTimes - 1);
|
||||
} else {
|
||||
throw APIException(APIException.OTHER, arguments: e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<Response> getJsonForResponse<T>(String url,
|
||||
{String? token,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
Map<String, String>? headers,
|
||||
int retryTimes = 0}) async {
|
||||
if (headers == null) {
|
||||
headers = {'Content-Type': 'application/json'};
|
||||
} else {
|
||||
headers['Content-Type'] = 'application/json';
|
||||
}
|
||||
return await _getForResponse(url,
|
||||
token: token,
|
||||
queryParameters: queryParameters,
|
||||
headers: headers,
|
||||
retryTimes: retryTimes);
|
||||
}
|
||||
|
||||
Future<Response> _getForResponse(String url,
|
||||
{String? token,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
Map<String, String>? headers,
|
||||
int retryTimes = 0}) async {
|
||||
try {
|
||||
var _headers = <String, String>{};
|
||||
if (token != null) {
|
||||
_headers['Authorization'] = 'Bearer $token';
|
||||
}
|
||||
|
||||
if (headers != null && headers.isNotEmpty) {
|
||||
_headers.addAll(headers);
|
||||
}
|
||||
|
||||
if (queryParameters != null) {
|
||||
var queryString = new Uri(queryParameters: queryParameters).query;
|
||||
url = url + '?' + queryString;
|
||||
}
|
||||
var response = await _get(Uri.parse(url), headers: _headers)
|
||||
.timeout(Duration(seconds: 60));
|
||||
|
||||
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||
return response;
|
||||
} else {
|
||||
throw _throwAPIException(response);
|
||||
}
|
||||
} on SocketException catch (e) {
|
||||
if (retryTimes > 0) {
|
||||
print('will retry after 3 seconds...');
|
||||
await Future.delayed(Duration(seconds: 3));
|
||||
return await _getForResponse(url,
|
||||
token: token,
|
||||
queryParameters: queryParameters,
|
||||
headers: headers,
|
||||
retryTimes: retryTimes - 1);
|
||||
} else {
|
||||
throw APIException(APIException.OTHER, arguments: e);
|
||||
}
|
||||
} on HttpException catch (e) {
|
||||
if (retryTimes > 0) {
|
||||
print('will retry after 3 seconds...');
|
||||
await Future.delayed(Duration(seconds: 3));
|
||||
return await _getForResponse(url,
|
||||
token: token,
|
||||
queryParameters: queryParameters,
|
||||
headers: headers,
|
||||
retryTimes: retryTimes - 1);
|
||||
} else {
|
||||
throw APIException(APIException.OTHER, arguments: e);
|
||||
}
|
||||
} on TimeoutException catch (e) {
|
||||
throw APIException(APIException.TIMEOUT, arguments: e);
|
||||
} on ClientException catch (e) {
|
||||
if (retryTimes > 0) {
|
||||
print('will retry after 3 seconds...');
|
||||
await Future.delayed(Duration(seconds: 3));
|
||||
return await _getForResponse(url,
|
||||
token: token,
|
||||
queryParameters: queryParameters,
|
||||
headers: headers,
|
||||
retryTimes: retryTimes - 1);
|
||||
} else {
|
||||
throw APIException(APIException.OTHER, arguments: e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<Response> _get(url, {Map<String, String>? headers}) =>
|
||||
_withClient((client) => client.get(url, headers: headers));
|
||||
|
||||
bool _certificateCheck(X509Certificate cert, String host, int port) => true;
|
||||
|
||||
Future<T> _withClient<T>(Future<T> Function(Client) fn) async {
|
||||
var httpClient = HttpClient()..badCertificateCallback = _certificateCheck;
|
||||
var client = IOClient(httpClient);
|
||||
try {
|
||||
return await fn(client);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
Future<Response> _post(url,
|
||||
{Map<String, String>? headers, body, Encoding? encoding}) =>
|
||||
_withClient((client) =>
|
||||
client.post(url, headers: headers, body: body, encoding: encoding));
|
||||
}
|
||||
@ -0,0 +1,148 @@
|
||||
import 'dart:async';
|
||||
|
||||
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/basic_member_information_model.dart';
|
||||
import 'package:mohem_flutter_app/models/check_mobile_app_version_model.dart';
|
||||
import 'package:mohem_flutter_app/models/generic_response_model.dart';
|
||||
import 'package:mohem_flutter_app/models/get_mobile_login_info_list_model.dart';
|
||||
import 'package:mohem_flutter_app/models/member_login_list_model.dart';
|
||||
|
||||
class LoginApiClient {
|
||||
static final LoginApiClient _instance = LoginApiClient._internal();
|
||||
|
||||
LoginApiClient._internal();
|
||||
|
||||
factory LoginApiClient() => _instance;
|
||||
|
||||
Future<GetMobileLoginInfoListModel?> getMobileLoginInfoNEW(String deviceToken, String deviceType) async {
|
||||
String url = "${ApiConsts.erpRest}Mohemm_GetMobileLoginInfo_NEW";
|
||||
Map<String, dynamic> postParams = {};
|
||||
postParams["DeviceToken"] = deviceToken;
|
||||
postParams["DeviceType"] = deviceType;
|
||||
return await ApiClient().postJsonForObject((json) {
|
||||
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
|
||||
return (responseData.mohemmGetMobileLoginInfoList?.length ?? 0) > 0 ? (responseData.mohemmGetMobileLoginInfoList!.first) : null;
|
||||
}, url, postParams);
|
||||
}
|
||||
|
||||
Future<GenericResponseModel?> insertMobileLoginInfoNEW(
|
||||
String email, int sessionId, String employeeName, int loginType, String mobileNumber, String userName, String deviceToken, String deviceType) async {
|
||||
String url = "${ApiConsts.erpRest}Mohemm_InsertMobileLoginInfo";
|
||||
Map<String, dynamic> postParams = {
|
||||
"MobileNumber": mobileNumber,
|
||||
"P_USER_NAME": userName,
|
||||
"UserName": userName,
|
||||
"CompanyID": 1, // todo 'sikander' @discuss umer for companyID
|
||||
"DeviceToken": deviceToken,
|
||||
"LoginType": loginType,
|
||||
"EmployeeName": employeeName,
|
||||
"P_SESSION_ID": sessionId,
|
||||
"P_EMAIL_ADDRESS": email
|
||||
};
|
||||
postParams["DeviceToken"] = deviceToken;
|
||||
postParams["DeviceType"] = deviceType;
|
||||
postParams.addAll(AppState().postParamsJson);
|
||||
return await ApiClient().postJsonForObject((json) {
|
||||
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
|
||||
AppState().setLogged = true;
|
||||
return responseData;
|
||||
}, url, postParams);
|
||||
}
|
||||
|
||||
Future<CheckMobileAppVersionModel> checkMobileAppVersion() async {
|
||||
String url = "${ApiConsts.utilitiesRest}CheckMobileAppVersion";
|
||||
Map<String, dynamic> postParams = {};
|
||||
postParams.addAll(AppState().postParamsJson);
|
||||
return await ApiClient().postJsonForObject((json) => CheckMobileAppVersionModel.fromJson(json), url, postParams);
|
||||
}
|
||||
|
||||
Future<MemberLoginListModel?> memberLogin(String username, String password) async {
|
||||
String url = "${ApiConsts.erpRest}MemberLogin";
|
||||
Map<String, dynamic> postParams = {"P_APP_VERSION": "CS", "P_LANGUAGE": "US", "P_PASSWORD": password, "P_USER_NAME": username};
|
||||
postParams.addAll(AppState().postParamsJson);
|
||||
return await ApiClient().postJsonForObject((json) {
|
||||
GenericResponseModel responseData = GenericResponseModel.fromJson(json);
|
||||
AppState().postParamsObject?.setLogInTokenID = responseData.logInTokenID;
|
||||
return responseData.memberLoginList;
|
||||
}, url, postParams);
|
||||
}
|
||||
|
||||
Future<BasicMemberInformationModel?> mohemmSendActivationCodeByOTPNotificationType(int isMobileFingerPrint, String? mobileNumber, int optSendType, String? pUserName) async {
|
||||
String url = "${ApiConsts.erpRest}Mohemm_SendActivationCodebyOTPNotificationType";
|
||||
Map<String, dynamic> postParams = {"IsMobileFingerPrint": isMobileFingerPrint, "MobileNumber": mobileNumber, "OTP_SendType": optSendType, "P_USER_NAME": pUserName};
|
||||
postParams.addAll(AppState().postParamsJson);
|
||||
return await ApiClient().postJsonForObject((json) => GenericResponseModel.fromJson(json).basicMemberInformation, url, postParams);
|
||||
}
|
||||
|
||||
Future<GenericResponseModel?> checkActivationCode(bool isDeviceNFC, String? mobileNumber, String activationCode, String? pUserName) async {
|
||||
String url = "${ApiConsts.erpRest}CheckActivationCode";
|
||||
Map<String, dynamic> postParams = {"IsDeviceNFC": isDeviceNFC, "MobileNumber": mobileNumber, "activationCode": activationCode, "P_USER_NAME": pUserName};
|
||||
postParams.addAll(AppState().postParamsJson);
|
||||
return await ApiClient().postJsonForObject((json) {
|
||||
GenericResponseModel responseData = GenericResponseModel.fromJson(json);
|
||||
AppState().postParamsObject?.setTokenID = responseData.tokenID;
|
||||
AppState().postParamsObject?.mobileNumber = responseData.basicMemberInformation!.pMOBILENUMBER;
|
||||
AppState().postParamsObject?.userName = AppState().getUserName;
|
||||
AppState().postParamsObject?.pEmailAddress = responseData.basicMemberInformation!.pEMAILADDRESS;
|
||||
AppState().postParamsObject?.pSessionId = responseData.pSESSIONID;
|
||||
AppState().postParamsObject?.pUserName = AppState().getUserName;
|
||||
AppState().postParamsObject?.pSelectedEmployeeNumber = AppState().getUserName;
|
||||
AppState().postParamsObject?.setPLegislationCode = responseData.basicMemberInformation!.pLEGISLATIONCODE;
|
||||
AppState().postParamsObject?.setPayrollCodeStr = responseData.memberInformationList!.first.pAYROLLCODE;
|
||||
AppState().setBusinessCardPrivilege = responseData.businessCardPrivilege ?? false;
|
||||
return responseData;
|
||||
}, url, postParams);
|
||||
}
|
||||
|
||||
Future<BasicMemberInformationModel?> getBasicUserInformation(String pAppVersion, String pUsername) async {
|
||||
String url = "${ApiConsts.erpRest}Get_BasicUserInformation";
|
||||
Map<String, dynamic> postParams = {"P_APP_VERSION": pAppVersion, "P_USER_NAME": pUsername};
|
||||
postParams.addAll(AppState().postParamsJson);
|
||||
return await ApiClient().postJsonForObject((json) => GenericResponseModel.fromJson(json).basicMemberInformation, url, postParams);
|
||||
}
|
||||
|
||||
Future<GenericResponseModel?> sendPublicActivationCode(String? mobileNumber, String? pUsername) async {
|
||||
String url = "${ApiConsts.erpRest}SendPublicActivationCode";
|
||||
Map<String, dynamic> postParams = {"MobileNumber": mobileNumber, "P_MOBILE_NUMBER": mobileNumber, "P_USER_NAME": pUsername};
|
||||
postParams.addAll(AppState().postParamsJson);
|
||||
return await ApiClient().postJsonForObject((json) {
|
||||
GenericResponseModel responseData = GenericResponseModel.fromJson(json);
|
||||
AppState().postParamsObject?.setLogInTokenID = responseData.logInTokenID;
|
||||
return responseData;
|
||||
}, url, postParams);
|
||||
}
|
||||
|
||||
Future<GenericResponseModel?> checkPublicActivationCode(String activationCode, String? pUserName) async {
|
||||
String url = "${ApiConsts.erpRest}checkPublicActivationCode";
|
||||
Map<String, dynamic> postParams = {"activationCode": activationCode, "P_USER_NAME": pUserName};
|
||||
postParams.addAll(AppState().postParamsJson);
|
||||
return await ApiClient().postJsonForObject((json) {
|
||||
GenericResponseModel responseData = GenericResponseModel.fromJson(json);
|
||||
AppState().setForgetPasswordTokenID = responseData.forgetPasswordTokenID;
|
||||
return responseData;
|
||||
}, url, postParams);
|
||||
}
|
||||
|
||||
Future<GenericResponseModel?> changePasswordForget(String forgetPasswordTokenID, String pNewPassword, String pConfirmNewPassword, String? pUserName) async {
|
||||
String url = "${ApiConsts.erpRest}ChangePassword_Forget";
|
||||
Map<String, dynamic> postParams = {"P_USER_NAME": pUserName, "ForgetPasswordTokenID": forgetPasswordTokenID, "P_Confirm_NEW_PASSWORD": pConfirmNewPassword, "P_NEW_PASSWORD": pNewPassword};
|
||||
postParams.addAll(AppState().postParamsJson);
|
||||
return await ApiClient().postJsonForObject((json) {
|
||||
GenericResponseModel responseData = GenericResponseModel.fromJson(json);
|
||||
|
||||
return responseData;
|
||||
}, url, postParams);
|
||||
}
|
||||
|
||||
Future<GenericResponseModel?> changePasswordFromActiveSession(String pOldPassword, String pNewPassword, String pConfirmNewPassword) async {
|
||||
String url = "${ApiConsts.erpRest}ChangePassword_FromActiveSession";
|
||||
Map<String, dynamic> postParams = {"P_OLD_PASSWORD": pOldPassword, "P_Confirm_NEW_PASSWORD": pConfirmNewPassword, "P_NEW_PASSWORD": pNewPassword};
|
||||
postParams.addAll(AppState().postParamsJson);
|
||||
return await ApiClient().postJsonForObject((json) {
|
||||
GenericResponseModel responseData = GenericResponseModel.fromJson(json);
|
||||
return responseData;
|
||||
}, url, postParams);
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,48 @@
|
||||
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
|
||||
import 'package:mohem_flutter_app/widgets/dialogs/confirm_dialog.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
class AppPermissions {
|
||||
static void location(Function(bool) completion, BuildContext context) {
|
||||
Permission.location.isGranted.then((isGranted){
|
||||
if(!isGranted){
|
||||
Permission.location.request().then((granted){
|
||||
completion(granted == PermissionStatus.granted);
|
||||
});
|
||||
}
|
||||
completion(isGranted);
|
||||
});
|
||||
}
|
||||
|
||||
static void showErrorLocationDialog(bool isPermissionError, BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (cxt) => ConfirmDialog(
|
||||
message: "Please provide location permission",
|
||||
onTap: () {
|
||||
if (isPermissionError) {
|
||||
Geolocator.openAppSettings();
|
||||
} else {
|
||||
Geolocator.openLocationSettings();
|
||||
}
|
||||
Navigator.pop(context);
|
||||
// createVacationRule(list);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static void checkAll(Function(bool) completion) {
|
||||
[Permission.location].request().then((value) {
|
||||
bool allGranted = false;
|
||||
value.values.forEach((element) {
|
||||
allGranted = allGranted && element == PermissionStatus.granted;
|
||||
});
|
||||
|
||||
completion(allGranted);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
class MyColors {
|
||||
static const Color kWhiteColor = Color(0xffffffff);
|
||||
static const Color darkIconColor = Color(0xff28323A);
|
||||
static const Color darkTextColor = Color(0xff2B353E);
|
||||
static const Color normalTextColor = Color(0xff5A5A5A);
|
||||
static const Color lightTextColor = Color(0xffBFBFBF);
|
||||
static const Color gradiantStartColor = Color(0xff33c0a5);
|
||||
static const Color gradiantEndColor = Color(0xff259db7);
|
||||
static const Color textMixColor = Color(0xff2BB8A6);
|
||||
static const Color backgroundColor = Color(0xffF8F8F8);
|
||||
static const Color grey41Color = Color(0xff414141);
|
||||
static const Color grey57Color = Color(0xff575757);
|
||||
static const Color grey67Color = Color(0xff676767);
|
||||
static const Color grey77Color = Color(0xff777777);
|
||||
static const Color greyF7Color = Color(0xffF7F7F7);
|
||||
static const Color grey80Color = Color(0xff808080);
|
||||
static const Color grey70Color = Color(0xff707070);
|
||||
static const Color greyACColor = Color(0xffACACAC);
|
||||
static const Color grey98Color = Color(0xff989898);
|
||||
static const Color lightGreyEFColor = Color(0xffEFEFEF);
|
||||
static const Color lightGreyEDColor = Color(0xffEDEDED);
|
||||
static const Color lightGreyDeColor = Color(0xffDEDEDE);
|
||||
static const Color lightGreyE3Color = Color(0xffE3E3E3);
|
||||
static const Color lightGreyE6Color = Color(0xffE6E6E6);
|
||||
static const Color lightGreyEAColor = Color(0xffEAEAEA);
|
||||
static const Color lightGreyE5Color = Color(0xffE5E5E5);
|
||||
static const Color darkWhiteColor = Color(0xffE0E0E0);
|
||||
static const Color redColor = Color(0xffD02127);
|
||||
static const Color pinkColor = Color(0xffEBA9A9);
|
||||
static const Color pinkDarkColor = Color(0xffe3797d);
|
||||
static const Color yellowColor = Color(0xffF4E31C);
|
||||
static const Color orange = Color(0xFFCC9B14);
|
||||
static const Color yellowFavColor = Color(0xffEAC321);
|
||||
static const Color yellowColorII = Color(0xffEAA118);
|
||||
static const Color backgroundBlackColor = Color(0xff202529);
|
||||
static const Color black = Color(0xff000000);
|
||||
static const Color white = Color(0xffffffff);
|
||||
static const Color borderColor = Color(0xffE8E8E8);
|
||||
static const Color borderE3Color = Color(0xffE3E3E3);
|
||||
static const Color borderCEColor = Color(0xffCECECE);
|
||||
//static const Color grey67Color = Color(0xff676767);
|
||||
static const Color whiteColor = Color(0xFFEEEEEE);
|
||||
static const Color greenColor = Color(0xff1FA269);
|
||||
static const Color lightGreenColor = Color(0xff2AB2AB);
|
||||
static const Color darkGreyColor = Color(0xff464646);
|
||||
static const Color greyA5Color = Color(0xffA5A5A5);
|
||||
static const Color blackColor = Color(0xff000014);
|
||||
static const Color grey3AColor = Color(0xff2E303A);
|
||||
static const Color darkColor = Color(0xff000015);
|
||||
static const Color lightGrayColor = Color(0xff808080);
|
||||
static const Color darkRedColor = Color(0xffD02127);
|
||||
static const Color lightGreyColor = Color(0xffC7C7C7);
|
||||
static const Color green69Color = Color(0xff1FA169);
|
||||
static const Color redA3Color = Color(0xffCA3332);
|
||||
static const Color green9CColor = Color(0xff259CB8);
|
||||
static const Color green2DColor = Color(0xff32D892);
|
||||
static const Color greyC4Color = Color(0xffC4C4C4);
|
||||
static const Color grey35Color = Color(0xff535353);
|
||||
static const Color grey9DColor = Color(0xff9D9D9D);
|
||||
static const Color darkDigitColor = Color(0xff2D2F39);
|
||||
static const Color grey71Color = Color(0xff717171);
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
class ApiConsts {
|
||||
//static String baseUrl = "http://10.200.204.20:2801/"; // Local server
|
||||
static String baseUrl = "https://uat.hmgwebservices.com"; // UAT server
|
||||
// static String baseUrl = "https://hmgwebservices.com"; // Live server
|
||||
static String baseUrlServices = baseUrl + "/Services/"; // server
|
||||
// static String baseUrlServices = "https://api.cssynapses.com/tangheem/"; // Live server
|
||||
static String utilitiesRest = baseUrlServices + "Utilities.svc/REST/";
|
||||
static String erpRest = baseUrlServices + "ERP.svc/REST/";
|
||||
static String swpRest = baseUrlServices + "SWP.svc/REST/";
|
||||
static String user = baseUrlServices + "api/User/";
|
||||
static String cocRest = baseUrlServices + "COCWS.svc/REST/";
|
||||
|
||||
//Chat
|
||||
static String chatServerBaseUrl = "https://apiderichat.hmg.com";
|
||||
static String chatServerBaseApiUrl = "https://apiderichat.hmg.com/api/";
|
||||
static String chatHubConnectionUrl = chatServerBaseUrl + "/ConnectionChatHub";
|
||||
static String chatSearchMember = "user/getUserWithStatusAndFavAsync/";
|
||||
static String chatRecentUrl = "UserChatHistory/getchathistorybyuserid"; //For a Mem
|
||||
static String chatSingleUserHistoryUrl = "UserChatHistory/GetUserChatHistory";
|
||||
static String chatMediaImageUploadUrl = "shared/upload";
|
||||
static String chatFavoriteUsers = "FavUser/getFavUserById/";
|
||||
}
|
||||
|
||||
class SharedPrefsConsts {
|
||||
static String isRememberMe = "remember_me";
|
||||
static String username = "username";
|
||||
static String password = "password";
|
||||
static String privilegeList = "privilegeList";
|
||||
static String firebaseToken = "firebaseToken";
|
||||
static String memberInformation = "memberInformation";
|
||||
static String welcomeVideoUrl = "welcomeVideoUrl";
|
||||
static String doNotShowWelcomeVideo = "doNotShowWelcomeVideo";
|
||||
static String mohemmWifiSSID = "mohemmWifiSSID";
|
||||
static String mohemmWifiPassword = "mohemmWifiPassword";
|
||||
static String editItemForSale = "editItemForSale";
|
||||
}
|
||||
@ -0,0 +1,460 @@
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class DateUtil {
|
||||
/// convert String To Date function
|
||||
/// [date] String we want to convert
|
||||
static DateTime convertStringToDate(String date) {
|
||||
// /Date(1585774800000+0300)/
|
||||
if (date != null) {
|
||||
const start = "/Date(";
|
||||
const end = "+0300)";
|
||||
int startIndex = date.indexOf(start);
|
||||
int endIndex = date.indexOf(end, startIndex + start.length);
|
||||
return DateTime.fromMillisecondsSinceEpoch(
|
||||
int.parse(
|
||||
date.substring(startIndex + start.length, endIndex),
|
||||
),
|
||||
);
|
||||
} else
|
||||
return DateTime.now();
|
||||
}
|
||||
|
||||
static DateTime convertSimpleStringDateToDate(String date) {
|
||||
return DateFormat("MM/dd/yyyy hh:mm:ss").parse(date);
|
||||
}
|
||||
|
||||
static DateTime convertSimpleStringDateToDateddMMyyyy(String date) {
|
||||
return DateFormat("MM/dd/yyyy hh:mm:ss").parse(date);
|
||||
}
|
||||
|
||||
static DateTime convertStringToDateNoTimeZone(String date) {
|
||||
// /Date(1585774800000+0300)/
|
||||
if (date != null) {
|
||||
const start = "/Date(";
|
||||
const end = ")";
|
||||
int startIndex = date.indexOf(start);
|
||||
int endIndex = date.indexOf(end, startIndex + start.length);
|
||||
return DateTime.fromMillisecondsSinceEpoch(
|
||||
int.parse(
|
||||
date.substring(startIndex + start.length, endIndex),
|
||||
),
|
||||
);
|
||||
} else
|
||||
return DateTime.now();
|
||||
}
|
||||
|
||||
static DateTime convertStringToDateTime(String date) {
|
||||
if (date != null) {
|
||||
try {
|
||||
var dateT = date.split('/');
|
||||
var year = dateT[2].substring(0, 4);
|
||||
var dateP = DateTime(int.parse(year), int.parse(dateT[1]), int.parse(dateT[0]));
|
||||
return dateP;
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
|
||||
return DateTime.now();
|
||||
} else
|
||||
return DateTime.now();
|
||||
}
|
||||
|
||||
static String convertDateToString(DateTime date) {
|
||||
const start = "/Date(";
|
||||
const end = "+0300)/";
|
||||
int milliseconds = date.millisecondsSinceEpoch;
|
||||
|
||||
return start + "$milliseconds" + end;
|
||||
}
|
||||
|
||||
static String convertDateToStringLocation(DateTime date) {
|
||||
const start = "/Date(";
|
||||
const end = ")/";
|
||||
int milliseconds = date.millisecondsSinceEpoch;
|
||||
|
||||
return start + "$milliseconds" + end;
|
||||
}
|
||||
|
||||
static String formatDateToDate(DateTime date, bool isArabic) {
|
||||
return DateFormat('dd MMM yyy', isArabic ? "ar_SA" : "en_US").format(date);
|
||||
}
|
||||
|
||||
static String formatDateToTime(DateTime date) {
|
||||
return DateFormat('hh:mm a').format(date);
|
||||
}
|
||||
|
||||
static String yearMonthDay(DateTime dateTime) {
|
||||
String dateFormat = '${dateTime.year}-${dateTime.month}-${dateTime.day}';
|
||||
return dateFormat;
|
||||
}
|
||||
|
||||
static String time(DateTime dateTime) {
|
||||
String dateFormat = '${dateTime.hour}:${dateTime.minute}:00';
|
||||
return dateFormat;
|
||||
}
|
||||
|
||||
static String convertDateMSToJsonDate(utc) {
|
||||
var dt = new DateTime.fromMicrosecondsSinceEpoch(utc);
|
||||
|
||||
return "/Date(" + (dt.millisecondsSinceEpoch * 1000).toString() + '+0300' + ")/";
|
||||
}
|
||||
|
||||
/// check Date
|
||||
/// [dateString] String we want to convert
|
||||
static String checkDate(DateTime checkedTime) {
|
||||
DateTime currentTime = DateTime.now();
|
||||
if ((currentTime.year == checkedTime.year) && (currentTime.month == checkedTime.month) && (currentTime.day == checkedTime.day)) {
|
||||
return "Today";
|
||||
} else if ((currentTime.year == checkedTime.year) && (currentTime.month == checkedTime.month)) {
|
||||
if ((currentTime.day - checkedTime.day) == 1) {
|
||||
return "YESTERDAY";
|
||||
} else if ((currentTime.day - checkedTime.day) == -1) {
|
||||
return "Tomorrow";
|
||||
}
|
||||
|
||||
if ((currentTime.day - checkedTime.day) <= -2) {
|
||||
return "Next Week";
|
||||
} else {
|
||||
return "Old Date";
|
||||
}
|
||||
}
|
||||
return "Old Date";
|
||||
}
|
||||
|
||||
static String getDateFormatted(String date) {
|
||||
DateTime dateObj = DateUtil.convertStringToDate(date);
|
||||
return DateUtil.getWeekDay(dateObj.weekday) + ", " + dateObj.day.toString() + " " + DateUtil.getMonth(dateObj.month) + " " + dateObj.year.toString();
|
||||
}
|
||||
|
||||
static String getISODateFormat(DateTime dateTime) {
|
||||
// 2020-04-30T00:00:00.000
|
||||
return dateTime.toIso8601String();
|
||||
}
|
||||
|
||||
/// get month by
|
||||
/// [month] convert month number in to month name
|
||||
static String getMonth(int month) {
|
||||
switch (month) {
|
||||
case 1:
|
||||
return "January";
|
||||
case 2:
|
||||
return "February";
|
||||
case 3:
|
||||
return "March";
|
||||
case 4:
|
||||
return "April";
|
||||
case 5:
|
||||
return "May";
|
||||
case 6:
|
||||
return "June";
|
||||
case 7:
|
||||
return "July";
|
||||
case 8:
|
||||
return "August";
|
||||
case 9:
|
||||
return "September";
|
||||
case 10:
|
||||
return "October";
|
||||
case 11:
|
||||
return "November";
|
||||
case 12:
|
||||
return "December";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/// get month by
|
||||
/// [month] convert month number in to month name in Arabic
|
||||
static String getMonthArabic(int month) {
|
||||
switch (month) {
|
||||
case 1:
|
||||
return "يناير";
|
||||
case 2:
|
||||
return " فبراير";
|
||||
case 3:
|
||||
return "مارس";
|
||||
case 4:
|
||||
return "أبريل";
|
||||
case 5:
|
||||
return "مايو";
|
||||
case 6:
|
||||
return "يونيو";
|
||||
case 7:
|
||||
return "يوليو";
|
||||
case 8:
|
||||
return "أغسطس";
|
||||
case 9:
|
||||
return "سبتمبر";
|
||||
case 10:
|
||||
return " اكتوبر";
|
||||
case 11:
|
||||
return " نوفمبر";
|
||||
case 12:
|
||||
return "ديسمبر";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
static int getMonthByName(String month) {
|
||||
switch (month.toLowerCase()) {
|
||||
case 'january':
|
||||
return 1;
|
||||
case 'february':
|
||||
return 2;
|
||||
case 'march':
|
||||
return 3;
|
||||
case 'april':
|
||||
return 4;
|
||||
case 'may':
|
||||
return 5;
|
||||
case 'june':
|
||||
return 6;
|
||||
case 'july':
|
||||
return 7;
|
||||
case 'august':
|
||||
return 8;
|
||||
case 'september':
|
||||
return 9;
|
||||
case 'october':
|
||||
return 10;
|
||||
case 'november':
|
||||
return 11;
|
||||
case 'december':
|
||||
return 12;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static DateTime getMonthDateTime(String month, yearName) {
|
||||
DateTime? date;
|
||||
try {
|
||||
date = DateTime(int.parse(yearName), getMonthByName(month));
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
return date ?? DateTime.now();
|
||||
}
|
||||
|
||||
/// get month by
|
||||
/// [weekDay] convert week day in int to week day name
|
||||
static String getWeekDay(int weekDay) {
|
||||
switch (weekDay) {
|
||||
case 1:
|
||||
return "Monday";
|
||||
case 2:
|
||||
return "Tuesday";
|
||||
case 3:
|
||||
return "Wednesday";
|
||||
case 4:
|
||||
return "Thursday";
|
||||
case 5:
|
||||
return "Friday";
|
||||
case 6:
|
||||
return "Saturday ";
|
||||
case 7:
|
||||
return "Sunday";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/// get month by
|
||||
/// [weekDay] convert week day in int to week day name arabic
|
||||
static String getWeekDayArabic(int weekDay) {
|
||||
switch (weekDay) {
|
||||
case 1:
|
||||
return "الاثنين";
|
||||
case 2:
|
||||
return "الثلاثاء";
|
||||
case 3:
|
||||
return "الاربعاء";
|
||||
case 4:
|
||||
return "الخميس";
|
||||
case 5:
|
||||
return "الجمعه";
|
||||
case 6:
|
||||
return "السبت ";
|
||||
case 7:
|
||||
return "الاحد";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
static String getWeekDayEnglish(int weekDay) {
|
||||
switch (weekDay) {
|
||||
case 1:
|
||||
return "Monday";
|
||||
case 2:
|
||||
return "Tuesday";
|
||||
case 3:
|
||||
return "Wednesday";
|
||||
case 4:
|
||||
return "Thursday";
|
||||
case 5:
|
||||
return "Friday";
|
||||
case 6:
|
||||
return "Saturday ";
|
||||
case 7:
|
||||
return "Sunday";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/// get data formatted like Apr 26,2020
|
||||
/// [dateTime] convert DateTime to data formatted
|
||||
static String getMonthDayYearDateFormatted(DateTime dateTime) {
|
||||
if (dateTime != null)
|
||||
return getMonth(dateTime.month) + " " + dateTime.day.toString() + ", " + dateTime.year.toString();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
/// get data formatted like Apr 26,2020
|
||||
/// [dateTime] convert DateTime to data formatted Arabic
|
||||
static String getMonthDayYearDateFormattedAr(DateTime dateTime) {
|
||||
if (dateTime != null)
|
||||
return getMonthArabic(dateTime.month) + " " + dateTime.day.toString() + ", " + dateTime.year.toString();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
/// get data formatted like Thursday, Apr 26,2020
|
||||
/// [dateTime] convert DateTime to date formatted
|
||||
static String getWeekDayMonthDayYearDateFormatted(DateTime dateTime, String lang) {
|
||||
// print(dateTime);
|
||||
// print(dateTime.weekday);
|
||||
// print(dateTime.weekday.getDayOfWeekEnumValue.value);
|
||||
if (dateTime != null)
|
||||
return lang == 'en'
|
||||
? getWeekDayEnglish(dateTime.weekday) + ", " + getMonth(dateTime.month) + " " + dateTime.day.toString() + " " + dateTime.year.toString()
|
||||
: getWeekDayArabic(dateTime.weekday) + ", " + dateTime.day.toString() + " " + getMonthArabic(dateTime.month) + " " + dateTime.year.toString();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
static String getMonthDayYearLangDateFormatted(DateTime dateTime, String lang) {
|
||||
if (dateTime != null)
|
||||
return lang == 'en'
|
||||
? getMonth(dateTime.month) + " " + dateTime.day.toString() + " " + dateTime.year.toString()
|
||||
: dateTime.day.toString() + " " + getMonthArabic(dateTime.month) + " " + dateTime.year.toString();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
/// get data formatted like 26/4/2020
|
||||
static String getDayMonthYearLangDateFormatted(DateTime dateTime, String lang) {
|
||||
if (dateTime != null)
|
||||
return lang == 'en'
|
||||
? dateTime.day.toString() + " " + getMonth(dateTime.month) + " " + dateTime.year.toString()
|
||||
: dateTime.day.toString() + " " + getMonthArabic(dateTime.month) + " " + dateTime.year.toString();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
static String getMonthYearLangDateFormatted(DateTime dateTime, String lang) {
|
||||
if (dateTime != null)
|
||||
return lang == 'en' ? getMonth(dateTime.month) + " " + dateTime.year.toString() : getMonthArabic(dateTime.month) + " " + dateTime.year.toString();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
/// get data formatted like 26/4/2020
|
||||
/// [dateTime] convert DateTime to data formatted
|
||||
static String getDayMonthYearDateFormatted(DateTime dateTime) {
|
||||
if (dateTime != null)
|
||||
return dateTime.day.toString() + "/" + dateTime.month.toString() + "/" + dateTime.year.toString();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
/// get data formatted like 26/4/2020
|
||||
/// [dateTime] convert DateTime to data formatted
|
||||
static String getDayMonthDateFormatted(DateTime dateTime) {
|
||||
if (dateTime != null)
|
||||
return DateFormat('dd/MM').format(dateTime);
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
/// get data formatted like 26/4/2020
|
||||
/// [dateTime] convert DateTime to data formatted according to language
|
||||
static String getDayMonthYearDateFormattedLang(DateTime dateTime, bool isArabic) {
|
||||
if (dateTime != null)
|
||||
return DateFormat('dd/MM/yyyy', isArabic ? "ar_SA" : "en_US").format(dateTime);
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
/// get data formatted like 10:30 according to lang
|
||||
static String formatDateToTimeLang(DateTime date, bool isArabic) {
|
||||
return DateFormat('HH:mm', isArabic ? "ar_SA" : "en_US").format(date);
|
||||
}
|
||||
|
||||
/// get data formatted like 26/4/2020 10:30
|
||||
/// [dateTime] convert DateTime to data formatted
|
||||
static String getDayMonthYearHourMinuteDateFormatted(DateTime dateTime) {
|
||||
if (dateTime != null)
|
||||
return dateTime.day.toString() + "/" + dateTime.month.toString() + "/" + dateTime.year.toString() + " " + DateFormat('HH:mm').format(dateTime);
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
/// get data formatted like 2020-8-13 09:43:00
|
||||
/// [dateTime] convert DateTime to data formatted
|
||||
static String getYearMonthDayHourMinSecDateFormatted(DateTime dateTime) {
|
||||
if (dateTime != null)
|
||||
return dateTime.year.toString() +
|
||||
"-" +
|
||||
dateTime.month.toString() +
|
||||
"-" +
|
||||
dateTime.day.toString() +
|
||||
" " +
|
||||
dateTime.hour.toString() +
|
||||
":" +
|
||||
dateTime.minute.toString() +
|
||||
":" +
|
||||
dateTime.second.toString();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
static String getFormattedDate(DateTime dateTime, String formattedString) {
|
||||
return DateFormat(formattedString).format(dateTime);
|
||||
}
|
||||
|
||||
static String convertISODateToJsonDate(String isoDate) {
|
||||
return "/Date(" + DateFormat('mm-dd-yyy').parse(isoDate).millisecondsSinceEpoch.toString() + ")/";
|
||||
}
|
||||
|
||||
// static String getDay(DayOfWeek dayOfWeek) {
|
||||
// switch (dayOfWeek) {
|
||||
// case DayOfWeek.Monday:
|
||||
// return "Monday";
|
||||
// break;
|
||||
// case DayOfWeek.Tuesday:
|
||||
// return "Tuesday";
|
||||
// break;
|
||||
// case DayOfWeek.Wednesday:
|
||||
// return "Wednesday";
|
||||
// break;
|
||||
// case DayOfWeek.Thursday:
|
||||
// return "Thursday";
|
||||
// break;
|
||||
// case DayOfWeek.Friday:
|
||||
// return "Friday";
|
||||
// break;
|
||||
// case DayOfWeek.Saturday:
|
||||
// return "Saturday";
|
||||
// break;
|
||||
// case DayOfWeek.Sunday:
|
||||
// return "Sunday";
|
||||
// break;
|
||||
// }
|
||||
// return "";
|
||||
// }
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mohem_flutter_app/classes/colors.dart';
|
||||
|
||||
class MyDecorations {
|
||||
static Decoration shadowDecoration = BoxDecoration(
|
||||
color: MyColors.kWhiteColor,
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: const Color(0xff000000).withOpacity(.05),
|
||||
blurRadius: 26,
|
||||
offset: const Offset(0, -3),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
static Decoration getContainersDecoration(Color color) {
|
||||
Decoration answerContainerDecoration = BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: color,
|
||||
border: Border.all(width: 1, color: MyColors.lightGreyEFColor),
|
||||
);
|
||||
return answerContainerDecoration;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:open_file/open_file.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
class FileProcess {
|
||||
static bool isFolderCreated = false;
|
||||
static Directory? directory;
|
||||
|
||||
static Future checkDocumentFolder() async {
|
||||
try {
|
||||
if (!isFolderCreated) {
|
||||
directory = await getApplicationDocumentsDirectory();
|
||||
await directory!.exists().then((value) {
|
||||
if (value) directory!.create();
|
||||
isFolderCreated = true;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
static void openFile(String fileName) {
|
||||
String dir = directory!.path + "/$fileName.pdf";
|
||||
OpenFile.open(dir);
|
||||
}
|
||||
|
||||
static Future<File> downloadFile(String base64Content, String fileName) async {
|
||||
Uint8List bytes = base64.decode(base64Content);
|
||||
await checkDocumentFolder();
|
||||
String dir = directory!.path + "/" + fileName + ".pdf";
|
||||
File file = File(dir);
|
||||
if (!file.existsSync()) file.create();
|
||||
await file.writeAsBytes(bytes);
|
||||
return file;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
class MyLottieConsts {
|
||||
static const String hourGlassLottie = "assets/lottie/hourglass.json";
|
||||
static const String celebrate1Lottie = "assets/lottie/celebrate1.json";
|
||||
static const String celebrate2Lottie = "assets/lottie/celebrate2.json";
|
||||
static const String winnerLottie = "assets/lottie/winner3.json";
|
||||
static const String allQuestions = "assets/lottie/all_questions.json";
|
||||
}
|
||||
@ -0,0 +1,346 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:fluttertoast/fluttertoast.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/exceptions/api_exception.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/widgets/dialogs/confirm_dialog.dart';
|
||||
import 'package:mohem_flutter_app/widgets/loading_dialog.dart';
|
||||
import 'package:nfc_manager/nfc_manager.dart';
|
||||
import 'package:nfc_manager/platform_tags.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
// ignore_for_file: avoid_annotating_with_dynamic
|
||||
|
||||
class Utils {
|
||||
static bool _isLoadingVisible = false;
|
||||
|
||||
static bool get isLoading => _isLoadingVisible;
|
||||
|
||||
static void showToast(String message, {bool longDuration = true}) {
|
||||
Fluttertoast.showToast(
|
||||
msg: message,
|
||||
toastLength: longDuration ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT,
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
timeInSecForIosWeb: 1,
|
||||
backgroundColor: Colors.black54,
|
||||
textColor: Colors.white,
|
||||
fontSize: 16.0);
|
||||
}
|
||||
|
||||
static dynamic getNotNullValue(List<dynamic> list, int index) {
|
||||
try {
|
||||
return list[index];
|
||||
} catch (ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static int stringToHex(String colorCode) {
|
||||
try {
|
||||
return int.parse(colorCode.replaceAll("#", "0xff"));
|
||||
} catch (ex) {
|
||||
return (0xff000000);
|
||||
}
|
||||
}
|
||||
|
||||
static Future delay(int millis) async {
|
||||
return await Future.delayed(Duration(milliseconds: millis));
|
||||
}
|
||||
|
||||
static void showLoading(BuildContext context) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_isLoadingVisible = true;
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierColor: Colors.black.withOpacity(0.5),
|
||||
builder: (BuildContext context) => LoadingDialog(),
|
||||
).then((value) {
|
||||
_isLoadingVisible = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static void hideLoading(BuildContext context) {
|
||||
if (_isLoadingVisible) {
|
||||
_isLoadingVisible = false;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
_isLoadingVisible = false;
|
||||
}
|
||||
|
||||
static Future<String> getStringFromPrefs(String key) async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
return prefs.getString(key) ?? "";
|
||||
}
|
||||
|
||||
static Future<bool> saveStringFromPrefs(String key, String value) async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
return await prefs.setString(key, value);
|
||||
}
|
||||
|
||||
static void handleException(dynamic exception, cxt, Function(String)? onErrorMessage) {
|
||||
String errorMessage;
|
||||
if (exception is APIException) {
|
||||
if (exception.message == APIException.UNAUTHORIZED) {
|
||||
return;
|
||||
} else {
|
||||
errorMessage = exception.error?.errorMessage ?? exception.message;
|
||||
}
|
||||
} else {
|
||||
errorMessage = APIException.UNKNOWN;
|
||||
}
|
||||
if (onErrorMessage != null) {
|
||||
onErrorMessage(errorMessage);
|
||||
} else {
|
||||
if (!AppState().isAuthenticated) {
|
||||
showDialog(
|
||||
context: cxt,
|
||||
builder: (cxt) => ConfirmDialog(
|
||||
message: errorMessage,
|
||||
onTap: () {
|
||||
Navigator.pushNamedAndRemoveUntil(cxt, AppRoutes.login, (Route<dynamic> route) => false);
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
showToast(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void confirmDialog(cxt, String message) {
|
||||
showDialog(
|
||||
context: cxt,
|
||||
builder: (cxt) => ConfirmDialog(
|
||||
message: message,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static Widget getNoDataWidget(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SvgPicture.asset('assets/images/not_found.svg', width: 110.0, height: 110.0),
|
||||
LocaleKeys.noDataAvailable.tr().toText16().paddingOnly(top: 15),
|
||||
],
|
||||
).center;
|
||||
}
|
||||
|
||||
static Widget getNoChatWidget(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SvgPicture.asset('assets/images/not_found.svg', width: 110.0, height: 110.0),
|
||||
LocaleKeys.noDataAvailable.tr().toText16().paddingOnly(top: 15),
|
||||
],
|
||||
).center;
|
||||
}
|
||||
|
||||
static Uint8List getPostBytes(img) {
|
||||
try {
|
||||
var b64 = img.replaceFirst('data:image/png;base64,', '');
|
||||
if (img != null && Utils.isBase64(b64)) return Utils.dataFromBase64String(b64);
|
||||
} catch (e) {}
|
||||
return Uint8List.fromList([]);
|
||||
}
|
||||
|
||||
static String getBase64FromJpeg(img) {
|
||||
try {
|
||||
var b64 = img.replaceFirst('data:image/jpeg;base64,', '');
|
||||
return b64;
|
||||
} catch (e) {}
|
||||
return "";
|
||||
}
|
||||
|
||||
static bool isBase64(String str) {
|
||||
RegExp _base64 = RegExp(r'^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=|[A-Za-z0-9+\/]{4})$');
|
||||
return _base64.hasMatch(str);
|
||||
}
|
||||
|
||||
static Uint8List dataFromBase64String(String base64String) {
|
||||
return base64Decode(base64String);
|
||||
}
|
||||
|
||||
static Widget tableColumnTitle(String? text, {bool showDivider = true, bool alignCenter = false}) {
|
||||
text ??= "";
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
6.height,
|
||||
alignCenter ? text.toText12().center : text.toText12(),
|
||||
5.height,
|
||||
if (showDivider)
|
||||
const Divider(
|
||||
height: 1,
|
||||
color: Color(0xff2E303A),
|
||||
thickness: 1,
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
static Decoration containerRadius(Color background, double radius) {
|
||||
return BoxDecoration(
|
||||
color: background,
|
||||
border: Border.all(
|
||||
width: 1, //
|
||||
color: background // <--- border width here
|
||||
),
|
||||
borderRadius: BorderRadius.circular(radius),
|
||||
);
|
||||
}
|
||||
|
||||
static Widget mHeight(double h) {
|
||||
return Container(
|
||||
height: h,
|
||||
);
|
||||
}
|
||||
|
||||
static Widget mDivider(Color color) {
|
||||
return Divider(
|
||||
// width: double.infinity,
|
||||
height: 1,
|
||||
color: color,
|
||||
);
|
||||
}
|
||||
|
||||
static Widget tableColumnValue(String text, {bool isCapitable = true, bool alignCenter = false}) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
12.height,
|
||||
if (alignCenter)
|
||||
(isCapitable ? text.toLowerCase().capitalizeFirstofEach : text).toText12(color: MyColors.normalTextColor).center
|
||||
else
|
||||
(isCapitable ? text.toLowerCase().capitalizeFirstofEach : text).toText12(color: MyColors.normalTextColor),
|
||||
12.height,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// EIT Forms date formats
|
||||
|
||||
static String getMonthNamedFormat(DateTime date) {
|
||||
/// it will return like "29-Sep-2022"
|
||||
return DateFormat('dd-MMM-yyyy').format(date);
|
||||
}
|
||||
|
||||
static String reverseFormatDate(String date) {
|
||||
String formattedDate;
|
||||
if (date.isNotEmpty) {
|
||||
formattedDate = date.replaceAll('/', '-');
|
||||
formattedDate = formattedDate.replaceAll(' 00:00:00', '');
|
||||
} else {
|
||||
formattedDate = date;
|
||||
}
|
||||
return formattedDate;
|
||||
}
|
||||
|
||||
static String formatStandardDate(String date) {
|
||||
String formattedDate;
|
||||
if (date.isNotEmpty) {
|
||||
formattedDate = date.replaceAll('-', '/');
|
||||
} else {
|
||||
formattedDate = date;
|
||||
}
|
||||
return formattedDate;
|
||||
}
|
||||
|
||||
static String reverseFormatStandardDate(String date) {
|
||||
String formattedDate;
|
||||
if (date.isNotEmpty) {
|
||||
formattedDate = date.replaceAll('/', '-');
|
||||
} else {
|
||||
formattedDate = date;
|
||||
}
|
||||
return formattedDate;
|
||||
}
|
||||
|
||||
static String formatDate(String date) {
|
||||
String formattedDate;
|
||||
|
||||
if (date.isNotEmpty) {
|
||||
date = date.substring(0, 10);
|
||||
formattedDate = date.replaceAll('-', '/');
|
||||
formattedDate = formattedDate + ' 00:00:00';
|
||||
} else {
|
||||
formattedDate = date;
|
||||
}
|
||||
return formattedDate;
|
||||
}
|
||||
|
||||
static String formatDateNew(String date) {
|
||||
String formattedDate;
|
||||
if (date.isNotEmpty) {
|
||||
formattedDate = date.split('T')[0];
|
||||
if(!formattedDate.contains("00:00:00")) {
|
||||
formattedDate = formattedDate + ' 00:00:00';
|
||||
}
|
||||
} else {
|
||||
formattedDate = date;
|
||||
}
|
||||
return formattedDate;
|
||||
}
|
||||
|
||||
static Future<DateTime> selectDate(BuildContext context, DateTime selectedDate) async {
|
||||
if (!Platform.isIOS) {
|
||||
await showCupertinoModalPopup(
|
||||
context: context,
|
||||
builder: (cxt) => Container(
|
||||
height: 250,
|
||||
color: Colors.white,
|
||||
child: CupertinoDatePicker(
|
||||
backgroundColor: Colors.white,
|
||||
mode: CupertinoDatePickerMode.date,
|
||||
onDateTimeChanged: (value) {
|
||||
if (value != null && value != selectedDate) {
|
||||
selectedDate = value;
|
||||
}
|
||||
},
|
||||
initialDateTime: selectedDate,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
DateTime? picked = await showDatePicker(context: context, initialDate: selectedDate, initialEntryMode: DatePickerEntryMode.calendarOnly, firstDate: DateTime(2015, 8), lastDate: DateTime(2101));
|
||||
if (picked != null && picked != selectedDate) {
|
||||
selectedDate = picked;
|
||||
}
|
||||
}
|
||||
return selectedDate;
|
||||
}
|
||||
|
||||
static void readNFc({required Function(String) onRead}) {
|
||||
|
||||
NfcManager.instance.startSession(onDiscovered: (NfcTag tag) async {
|
||||
print(tag.data);
|
||||
var f;
|
||||
if (Platform.isAndroid) {
|
||||
f = MifareUltralight(tag: tag, identifier: tag.data["nfca"]["identifier"], type: 2, maxTransceiveLength: 252, timeout: 22);
|
||||
} else {
|
||||
f = MifareUltralight(tag: tag, identifier: tag.data["mifare"]["identifier"], type: 2, maxTransceiveLength: 252, timeout: 22);
|
||||
}
|
||||
String identifier = f.identifier.map((e) => e.toRadixString(16).padLeft(2, '0')).join('');
|
||||
NfcManager.instance.stopSession();
|
||||
onRead(identifier);
|
||||
}).catchError((err) {
|
||||
print(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class AppProvider extends StatelessWidget {
|
||||
final Widget child;
|
||||
|
||||
AppProvider({required this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return child;
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
// ChangeNotifierProvider(create: (_) => Counter()),
|
||||
],
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,300 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mohem_flutter_app/ui/attendance/add_vacation_rule_screen.dart';
|
||||
import 'package:mohem_flutter_app/ui/attendance/monthly_attendance_screen.dart';
|
||||
import 'package:mohem_flutter_app/ui/attendance/vacation_rule_screen.dart';
|
||||
import 'package:mohem_flutter_app/ui/bottom_sheets/attendence_details_bottom_sheet.dart';
|
||||
import 'package:mohem_flutter_app/ui/chat/chat_detailed_screen.dart';
|
||||
import 'package:mohem_flutter_app/ui/chat/chat_home.dart';
|
||||
import 'package:mohem_flutter_app/ui/landing/dashboard_screen.dart';
|
||||
import 'package:mohem_flutter_app/ui/landing/itg/survey_screen.dart';
|
||||
import 'package:mohem_flutter_app/ui/landing/today_attendance_screen.dart';
|
||||
import 'package:mohem_flutter_app/ui/landing/today_attendance_screen2.dart';
|
||||
import 'package:mohem_flutter_app/ui/leave_balance/add_leave_balance_screen.dart';
|
||||
import 'package:mohem_flutter_app/ui/leave_balance/leave_balance_screen.dart';
|
||||
import 'package:mohem_flutter_app/ui/login/change_password_screen.dart';
|
||||
import 'package:mohem_flutter_app/ui/login/forgot_password_screen.dart';
|
||||
import 'package:mohem_flutter_app/ui/login/login_screen.dart';
|
||||
import 'package:mohem_flutter_app/ui/login/new_password_screen.dart';
|
||||
import 'package:mohem_flutter_app/ui/login/verify_last_login_screen.dart';
|
||||
import 'package:mohem_flutter_app/ui/login/verify_login_screen.dart';
|
||||
import 'package:mohem_flutter_app/ui/marathon/marathon_intro_screen.dart';
|
||||
import 'package:mohem_flutter_app/ui/marathon/marathon_screen.dart';
|
||||
import 'package:mohem_flutter_app/ui/marathon/marathon_winner_selection.dart';
|
||||
import 'package:mohem_flutter_app/ui/marathon/winner_screen.dart';
|
||||
import 'package:mohem_flutter_app/ui/misc/request_submit_screen.dart';
|
||||
import 'package:mohem_flutter_app/ui/my_attendance/dynamic_screens/dynamic_input_screen.dart';
|
||||
import 'package:mohem_flutter_app/ui/my_attendance/dynamic_screens/dynamic_listview_screen.dart';
|
||||
import 'package:mohem_flutter_app/ui/my_attendance/services_menu_list_screen.dart';
|
||||
|
||||
// import 'package:mohem_flutter_app/ui/my_attendance/my_attendance_screen.dart';
|
||||
import 'package:mohem_flutter_app/ui/my_team/create_request.dart';
|
||||
import 'package:mohem_flutter_app/ui/my_team/employee_details.dart';
|
||||
import 'package:mohem_flutter_app/ui/my_team/my_team.dart';
|
||||
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/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';
|
||||
import 'package:mohem_flutter_app/ui/profile/basic_details.dart';
|
||||
import 'package:mohem_flutter_app/ui/profile/contact_details.dart';
|
||||
import 'package:mohem_flutter_app/ui/profile/delete_family_member.dart';
|
||||
import 'package:mohem_flutter_app/ui/profile/dynamic_screens/dynamic_input_address_screen.dart';
|
||||
import 'package:mohem_flutter_app/ui/profile/dynamic_screens/dynamic_input_basic_details_screen.dart';
|
||||
import 'package:mohem_flutter_app/ui/profile/family_members.dart';
|
||||
import 'package:mohem_flutter_app/ui/profile/personal_info.dart';
|
||||
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/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/items_for_sale/add_new_item_for_sale.dart';
|
||||
import 'package:mohem_flutter_app/ui/screens/items_for_sale/item_for_sale_detail.dart';
|
||||
import 'package:mohem_flutter_app/ui/screens/items_for_sale/items_for_sale_home.dart';
|
||||
import 'package:mohem_flutter_app/ui/screens/mowadhafhi/mowadhafhi_home.dart';
|
||||
import 'package:mohem_flutter_app/ui/screens/mowadhafhi/mowadhafhi_hr_request.dart';
|
||||
import 'package:mohem_flutter_app/ui/screens/mowadhafhi/request_details.dart';
|
||||
import 'package:mohem_flutter_app/ui/screens/my_requests/my_requests.dart';
|
||||
import 'package:mohem_flutter_app/ui/screens/my_requests/new_request.dart';
|
||||
import 'package:mohem_flutter_app/ui/screens/offers_and_discounts/offers_and_discounts_details.dart';
|
||||
import 'package:mohem_flutter_app/ui/screens/offers_and_discounts/offers_and_discounts_home.dart';
|
||||
import 'package:mohem_flutter_app/ui/screens/pending_transactions/pending_transactions.dart';
|
||||
import 'package:mohem_flutter_app/ui/screens/pending_transactions/pending_transactions_details.dart';
|
||||
import 'package:mohem_flutter_app/ui/screens/submenu_screen.dart';
|
||||
import 'package:mohem_flutter_app/ui/termination/end_employement.dart';
|
||||
import 'package:mohem_flutter_app/ui/work_list/item_history_screen.dart';
|
||||
import 'package:mohem_flutter_app/ui/work_list/itg_detail_screen.dart';
|
||||
import 'package:mohem_flutter_app/ui/work_list/work_list_screen.dart';
|
||||
import 'package:mohem_flutter_app/ui/work_list/worklist_detail_screen.dart';
|
||||
import 'package:mohem_flutter_app/ui/work_list/worklist_settings.dart';
|
||||
|
||||
class AppRoutes {
|
||||
static const String splash = "/splash";
|
||||
static const String registerSelection = "/registerSelection";
|
||||
static const String loginVerifyAccount = "/loginVerifyAccount";
|
||||
static const String login = "/login";
|
||||
static const String verifyLogin = "/verifyLogin";
|
||||
static const String verifyLastLogin = "/verifyLastLogin";
|
||||
static const String forgotPassword = "/forgotPassword";
|
||||
static const String newPassword = "/newPassword";
|
||||
static const String loginVerification = "/loginVerification";
|
||||
static const String dashboard = "/dashboard";
|
||||
static const String todayAttendance = "/todayAttendance";
|
||||
static const String subMenuScreen = "/submenuScreen";
|
||||
static const String addEitScreen = "/addeitScreen";
|
||||
static const String initialRoute = login;
|
||||
static const String survey = "/survey";
|
||||
|
||||
//Work List
|
||||
static const String workList = "/workList";
|
||||
static const String workListDetail = "/workListDetail";
|
||||
static const String itgDetail = "/itgDetail";
|
||||
static const String itemHistory = "/itemHistory";
|
||||
static const String worklistSettings = "/worklistSettings";
|
||||
|
||||
// Leave Balance
|
||||
static const String leaveBalance = "/leaveBalance";
|
||||
static const String addLeaveBalance = "/addLeaveBalance";
|
||||
|
||||
static const String servicesMenuListScreen = "/servicesMenuListScreen";
|
||||
static const String dynamicScreen = "/dynamicScreen";
|
||||
static const String addDynamicInput = "/addDynamicInput";
|
||||
static const String requestSubmitScreen = "/requestSubmitScreen";
|
||||
|
||||
//profile
|
||||
|
||||
static const String addDynamicInputProfile = 'addDynamicInputProfile';
|
||||
static const String addDynamicAddressScreen = 'addDynamicAddressProfile';
|
||||
|
||||
//Attendance
|
||||
static const String attendance = "/attendance";
|
||||
static const String monthlyAttendance = "/monthlyAttendance";
|
||||
static const String vacationRule = "/vacationRule";
|
||||
static const String addVacationRule = "/addVacationRule";
|
||||
|
||||
//Bottom Sheet
|
||||
static const String attendanceDetailsBottomSheet =
|
||||
"/attendanceDetailsBottomSheet";
|
||||
|
||||
//Profile
|
||||
static const String profile = "/profile";
|
||||
static const String personalInfo = "/personalInfo";
|
||||
static const String basicDetails = "/basicDetails";
|
||||
static const String contactDetails = "/contactDetails";
|
||||
static const String familyMembers = "/familyMembers";
|
||||
static const String deleteFamilyMember = "/deleteFamilyMember";
|
||||
static const String addUpdateFamilyMember = "/addUpdateFamilyMember";
|
||||
|
||||
// Mowadhafhi
|
||||
static const String mowadhafhi = "/mowadhafhi";
|
||||
static const String mowadhafhiDetails = "/mowadhafhiDetails";
|
||||
static const String mowadhafhiHRRequest = "/mowadhafhiHRRequest";
|
||||
|
||||
// Pending Transactions
|
||||
static const String pendingTransactions = "/pendingTransactions";
|
||||
static const String pendingTransactionsDetails =
|
||||
"/pendingTransactionsDetails";
|
||||
|
||||
// Announcements
|
||||
static const String announcements = "/announcements";
|
||||
static const String announcementsDetails = "/announcementsDetails";
|
||||
|
||||
// My Requests
|
||||
static const String myRequests = "/myRequests";
|
||||
static const String newRequest = "/newRequests";
|
||||
|
||||
// Items For Sale
|
||||
static const String itemsForSale = "/itemsForSale";
|
||||
static const String itemsForSaleDetail = "/itemsForSaleDetail";
|
||||
static const String addNewItemForSale = "/addNewItemForSale";
|
||||
|
||||
// Offers & Discounts
|
||||
static const String offersAndDiscounts = "/offersAndDiscounts";
|
||||
static const String offersAndDiscountsDetails = "/offersAndDiscountsDetails";
|
||||
|
||||
//Pay slip
|
||||
static const String monthlyPaySlip = "/monthlyPaySlip";
|
||||
|
||||
//performance evaluation
|
||||
|
||||
static const String performanceEvaluation = "/performanceEvaluation";
|
||||
|
||||
static const String endEmploymentScreen = "/endEmploymentScreen";
|
||||
|
||||
//My Team
|
||||
static const String myTeam = "/myTeam";
|
||||
static const String employeeDetails = "/employeeDetails";
|
||||
static const String profileDetails = "/profileDetails";
|
||||
static const String viewAttendance = "/viewAttendance";
|
||||
static const String teamMembers = "/teamMembers";
|
||||
static const String createRequest = "/createRequest";
|
||||
static const String subordinateLeave = "/subordinateLeave";
|
||||
|
||||
static const String changePassword = "/changePassword";
|
||||
|
||||
//Chat
|
||||
static const String chat = "/chat";
|
||||
static const String chatDetailed = "/chatDetailed";
|
||||
|
||||
//Marathon
|
||||
static const String marathonIntroScreen = "/marathonIntroScreen";
|
||||
static const String marathonScreen = "/marathonScreen";
|
||||
static const String marathonWinnerSelection = "/marathonWinnerSelection";
|
||||
static const String marathonWinnerScreen = "/marathonWinnerScreen";
|
||||
|
||||
static final Map<String, WidgetBuilder> routes = {
|
||||
login: (BuildContext context) => LoginScreen(),
|
||||
verifyLogin: (BuildContext context) => VerifyLoginScreen(),
|
||||
verifyLastLogin: (BuildContext context) => VerifyLastLoginScreen(),
|
||||
dashboard: (BuildContext context) => DashboardScreen(),
|
||||
survey: (BuildContext context) => SurveyScreen(),
|
||||
|
||||
subMenuScreen: (BuildContext context) => SubMenuScreen(),
|
||||
newPassword: (BuildContext context) => NewPasswordScreen(),
|
||||
forgotPassword: (BuildContext context) => ForgotPasswordScreen(),
|
||||
todayAttendance: (BuildContext context) => TodayAttendanceScreen2(),
|
||||
//eit
|
||||
|
||||
addEitScreen: (BuildContext context) => AddEITScreen(),
|
||||
|
||||
//Work List
|
||||
workList: (BuildContext context) => WorkListScreen(),
|
||||
workListDetail: (BuildContext context) => WorkListDetailScreen(),
|
||||
itgDetail: (BuildContext context) => ItgDetailScreen(),
|
||||
itemHistory: (BuildContext context) => ItemHistoryScreen(),
|
||||
worklistSettings: (BuildContext context) => WorklistSettings(),
|
||||
|
||||
// Leave Balance
|
||||
|
||||
leaveBalance: (BuildContext context) => LeaveBalance(),
|
||||
addLeaveBalance: (BuildContext context) => AddLeaveBalanceScreen(),
|
||||
|
||||
servicesMenuListScreen: (BuildContext context) => ServicesMenuListScreen(),
|
||||
// workFromHome: (BuildContext context) => WorkFromHomeScreen(),
|
||||
// addWorkFromHome: (BuildContext context) => AddWorkFromHomeScreen(),
|
||||
profile: (BuildContext context) => ProfileScreen(),
|
||||
//Attendance
|
||||
monthlyAttendance: (BuildContext context) => MonthlyAttendanceScreen(),
|
||||
vacationRule: (BuildContext context) => VacationRuleScreen(),
|
||||
addVacationRule: (BuildContext context) => AddVacationRuleScreen(),
|
||||
|
||||
//Bottom Sheet
|
||||
attendanceDetailsBottomSheet: (BuildContext context) =>
|
||||
AttendenceDetailsBottomSheet(),
|
||||
|
||||
//Profile
|
||||
//profile: (BuildContext context) => Profile(),
|
||||
//profile: (BuildContext context) => Profile(),
|
||||
personalInfo: (BuildContext context) => PersonalInfo(),
|
||||
basicDetails: (BuildContext context) => BasicDetails(),
|
||||
contactDetails: (BuildContext context) => ContactDetails(),
|
||||
familyMembers: (BuildContext context) => FamilyMembers(),
|
||||
dynamicScreen: (BuildContext context) => DynamicListViewScreen(),
|
||||
addDynamicInput: (BuildContext context) => DynamicInputScreen(),
|
||||
addDynamicInputProfile: (BuildContext context) =>
|
||||
DynamicInputScreenProfile(),
|
||||
addDynamicAddressScreen: (BuildContext context) =>
|
||||
DynamicInputScreenAddress(),
|
||||
|
||||
deleteFamilyMember: (BuildContext context) =>
|
||||
DeleteFamilyMember(ModalRoute.of(context)!.settings.arguments as int),
|
||||
requestSubmitScreen: (BuildContext context) => RequestSubmitScreen(),
|
||||
addUpdateFamilyMember: (BuildContext context) => AddUpdateFamilyMember(),
|
||||
|
||||
//mowadhafhi
|
||||
mowadhafhi: (BuildContext context) => MowadhafhiHome(),
|
||||
mowadhafhiDetails: (BuildContext context) => MowadhafhiRequestDetails(),
|
||||
mowadhafhiHRRequest: (BuildContext context) => MowadhafhiHRRequest(),
|
||||
|
||||
pendingTransactions: (BuildContext context) => PendingTransactions(),
|
||||
pendingTransactionsDetails: (BuildContext context) =>
|
||||
PendingTransactionsDetails(),
|
||||
|
||||
announcements: (BuildContext context) => Announcements(),
|
||||
announcementsDetails: (BuildContext context) => AnnouncementDetails(),
|
||||
|
||||
//My Requests
|
||||
myRequests: (BuildContext context) => MyRequests(),
|
||||
newRequest: (BuildContext context) => NewRequest(),
|
||||
|
||||
// Items for sale
|
||||
itemsForSale: (BuildContext context) => ItemsForSale(),
|
||||
itemsForSaleDetail: (BuildContext context) => ItemForSaleDetailPage(),
|
||||
addNewItemForSale: (BuildContext context) => AddNewItemForSale(),
|
||||
|
||||
// Offers & Discounts
|
||||
offersAndDiscounts: (BuildContext context) => OffersAndDiscountsHome(),
|
||||
offersAndDiscountsDetails: (BuildContext context) =>
|
||||
OffersAndDiscountsDetails(),
|
||||
|
||||
//pay slip
|
||||
monthlyPaySlip: (BuildContext context) => MonthlyPaySlipScreen(),
|
||||
performanceEvaluation: (BuildContext context) => PerformanceAppraisal(),
|
||||
|
||||
endEmploymentScreen: (BuildContext context) => EndEmploymentScreen(),
|
||||
|
||||
//My Team
|
||||
myTeam: (BuildContext context) => MyTeam(),
|
||||
employeeDetails: (BuildContext context) => EmployeeDetails(),
|
||||
profileDetails: (BuildContext context) => ProfileDetails(),
|
||||
viewAttendance: (BuildContext context) => ViewAttendance(),
|
||||
teamMembers: (BuildContext context) => TeamMembers(),
|
||||
createRequest: (BuildContext context) => CreateRequest(),
|
||||
subordinateLeave: (BuildContext context) => SubordinateLeave(),
|
||||
|
||||
changePassword: (BuildContext context) => ChangePasswordScreen(),
|
||||
|
||||
//Chat
|
||||
chat: (BuildContext context) => ChatHomeScreen(),
|
||||
chatDetailed: (BuildContext context) => ChatDetailScreen(),
|
||||
|
||||
// Marathon
|
||||
marathonIntroScreen: (BuildContext context) => MarathonIntroScreen(),
|
||||
marathonScreen: (BuildContext context) => MarathonScreen(),
|
||||
marathonWinnerSelection: (BuildContext context) =>
|
||||
MarathonWinnerSelection(),
|
||||
marathonWinnerScreen: (BuildContext context) => WinnerScreen(),
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,274 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:easy_localization/src/public_ext.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.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/generated/locale_keys.g.dart';
|
||||
import 'package:mohem_flutter_app/widgets/button/default_button.dart';
|
||||
import 'package:mohem_flutter_app/widgets/otp_widget.dart';
|
||||
import 'package:sizer/sizer.dart';
|
||||
|
||||
final ValueNotifier<String> otpFieldClear = ValueNotifier<String>("");
|
||||
|
||||
class OtpDialog {
|
||||
final int type;
|
||||
final int? mobileNo;
|
||||
final Function(String, TextEditingController _pinPutController) onSuccess;
|
||||
final Function onFailure;
|
||||
final BuildContext context;
|
||||
final Function onResendCode;
|
||||
|
||||
int remainingTime = 120;
|
||||
|
||||
Future<Null>? timer;
|
||||
|
||||
static BuildContext? _context;
|
||||
|
||||
static bool? _loading;
|
||||
|
||||
OtpDialog(this.context, this.type, this.mobileNo, this.onSuccess, this.onFailure, {required this.onResendCode});
|
||||
|
||||
GlobalKey? verifyAccountForm = GlobalKey<FormState>();
|
||||
|
||||
final TextEditingController _pinPutController = TextEditingController();
|
||||
|
||||
TextEditingController digit1 = TextEditingController(text: "");
|
||||
TextEditingController digit2 = TextEditingController(text: "");
|
||||
TextEditingController digit3 = TextEditingController(text: "");
|
||||
TextEditingController digit4 = TextEditingController(text: "");
|
||||
|
||||
Map verifyAccountFormValue = {
|
||||
'digit1': '',
|
||||
'digit2': '',
|
||||
'digit3': '',
|
||||
'digit4': '',
|
||||
};
|
||||
final focusD1 = FocusNode();
|
||||
final focusD2 = FocusNode();
|
||||
final focusD3 = FocusNode();
|
||||
final focusD4 = FocusNode();
|
||||
String? errorMsg;
|
||||
|
||||
// ProjectViewModel projectProvider;
|
||||
String displayTime = '';
|
||||
String? _code;
|
||||
dynamic setState;
|
||||
bool stopTimer = false;
|
||||
|
||||
// static String signature;
|
||||
|
||||
void displayDialog(BuildContext context) async {
|
||||
return showDialog(
|
||||
context: context,
|
||||
barrierColor: Colors.black.withOpacity(0.63),
|
||||
builder: (context) {
|
||||
// projectProvider = Provider.of(context);
|
||||
return Dialog(
|
||||
backgroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||
insetPadding: const EdgeInsets.only(left: 21, right: 21),
|
||||
child: StatefulBuilder(builder: (context, setState) {
|
||||
if (displayTime == '') {
|
||||
startTimer(setState);
|
||||
}
|
||||
|
||||
return Container(
|
||||
padding: EdgeInsets.all(21),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
type == 1
|
||||
? "assets/images/login/verify_sms.svg"
|
||||
: type == 2
|
||||
? "assets/images/login/verify_whatsapp.svg"
|
||||
: type == 3
|
||||
? "assets/images/login/verify_face.svg"
|
||||
: 'assets/images/login/verify_thumb.svg',
|
||||
height: 50,
|
||||
width: 50,
|
||||
),
|
||||
12.height,
|
||||
LocaleKeys.otp.tr().toText14(),
|
||||
LocaleKeys.verification.tr().toText24(isBold: true),
|
||||
6.height,
|
||||
(LocaleKeys.pleaseEnterTheVerificationCodeSentTo.tr() + ' xxxxxxxx' + mobileNo.toString().substring(mobileNo.toString().length - 3)).toText16(),
|
||||
18.height,
|
||||
ValueListenableBuilder<String>(
|
||||
builder: (BuildContext context, String value, Widget? child) {
|
||||
// This builder will only get called when the _counter
|
||||
// is updated.
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Center(
|
||||
child: OTPWidget(
|
||||
autoFocus: true,
|
||||
controller: _pinPutController,
|
||||
defaultBorderColor: const Color(0xffD8D8D8),
|
||||
maxLength: 4,
|
||||
onTextChanged: (text) {},
|
||||
pinBoxColor: Colors.white,
|
||||
onDone: (code) => _onOtpCallBack(code, null),
|
||||
textBorderColor: const Color(0xffD8D8D8),
|
||||
pinBoxWidth: 60,
|
||||
pinBoxHeight: 60,
|
||||
pinTextStyle: const TextStyle(fontSize: 24.0, color: MyColors.darkTextColor),
|
||||
pinTextAnimatedSwitcherTransition: ProvidedPinBoxTextAnimation.scalingTransition,
|
||||
pinTextAnimatedSwitcherDuration: const Duration(milliseconds: 300),
|
||||
pinBoxRadius: 10,
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
),
|
||||
),
|
||||
10.height,
|
||||
stopTimer
|
||||
? Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: LocaleKeys.codeExpire.tr().toText16(
|
||||
color: MyColors.redColor,
|
||||
),
|
||||
),
|
||||
12.width,
|
||||
Image.asset(
|
||||
"assets/icons/ic_alarm.png",
|
||||
width: 20,
|
||||
height: 20,
|
||||
color: MyColors.redColor,
|
||||
),
|
||||
],
|
||||
)
|
||||
: RichText(
|
||||
text: TextSpan(
|
||||
text: LocaleKeys.theVerificationCodeWillExpireIn.tr() + '\n',
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: MyColors.darkTextColor, letterSpacing: -0.48),
|
||||
children: <TextSpan>[
|
||||
TextSpan(
|
||||
text: displayTime,
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: MyColors.textMixColor, letterSpacing: -0.48),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
valueListenable: otpFieldClear,
|
||||
),
|
||||
18.height,
|
||||
DefaultButton(
|
||||
stopTimer ? LocaleKeys.resend.tr() : LocaleKeys.cancel.tr(),
|
||||
() async {
|
||||
if (stopTimer) {
|
||||
hideSMSBox(context);
|
||||
onResendCode();
|
||||
} else {
|
||||
stopTimer = true;
|
||||
// onFailure();
|
||||
hideSMSBox(context);
|
||||
}
|
||||
},
|
||||
colors: stopTimer
|
||||
? null
|
||||
: [
|
||||
MyColors.pinkDarkColor,
|
||||
MyColors.pinkDarkColor,
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
InputDecoration buildInputDecoration(BuildContext context) {
|
||||
return InputDecoration(
|
||||
counterText: " ",
|
||||
enabledBorder: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||
borderSide: BorderSide(color: Colors.black),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(10.0)),
|
||||
borderSide: BorderSide(color: Theme.of(context).primaryColor),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(10.0)),
|
||||
borderSide: BorderSide(color: Theme.of(context).errorColor),
|
||||
),
|
||||
focusedErrorBorder: OutlineInputBorder(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(10.0)),
|
||||
borderSide: BorderSide(color: Theme.of(context).errorColor),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// String validateCodeDigit(value) {
|
||||
// if (value.isEmpty) {
|
||||
// return ' ';
|
||||
// } else if (value.length == 3) {
|
||||
// print(value);
|
||||
// } else {
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
|
||||
String getSecondsAsDigitalClock(int inputSeconds) {
|
||||
int secNum = int.parse(inputSeconds.toString()); // don't forget the second param
|
||||
int hours = (secNum / 3600).floor();
|
||||
int minutes = ((secNum - hours * 3600) / 60).floor();
|
||||
double seconds = secNum - hours * 3600 - minutes * 60;
|
||||
String minutesString = "";
|
||||
String secondsString = "";
|
||||
minutesString = minutes < 10 ? "0" + minutes.toString() : minutes.toString();
|
||||
secondsString = seconds < 10 ? "0" + seconds.toStringAsFixed(0) : seconds.toStringAsFixed(0);
|
||||
return minutesString + ":" + secondsString;
|
||||
}
|
||||
|
||||
void startTimer(setState) {
|
||||
remainingTime--;
|
||||
if (stopTimer) return;
|
||||
setState(() {
|
||||
displayTime = getSecondsAsDigitalClock(remainingTime);
|
||||
});
|
||||
|
||||
timer = Future.delayed(const Duration(seconds: 1), () {
|
||||
if (remainingTime > 0) {
|
||||
startTimer(setState);
|
||||
} else {
|
||||
setState(() {
|
||||
stopTimer = true;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void hideSMSBox(context) {
|
||||
onFailure();
|
||||
}
|
||||
|
||||
void _onOtpCallBack(String otpCode, bool? isAutofill) {
|
||||
if (otpCode.length == 4) {
|
||||
// stopTimer = true;
|
||||
otpFieldClear.value = otpCode;
|
||||
onSuccess(otpCode, _pinPutController);
|
||||
}
|
||||
}
|
||||
|
||||
// static getSignature() async {
|
||||
// // if (Platform.isAndroid) {
|
||||
// // return await SmsRetriever.getAppSignature();
|
||||
// // } else {
|
||||
// // return null;
|
||||
// // }
|
||||
// }
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:mohem_flutter_app/api/api_client.dart';
|
||||
|
||||
class APIException implements Exception {
|
||||
static const String BAD_REQUEST = 'api_common_bad_request';
|
||||
static const String UNAUTHORIZED = 'api_common_unauthorized';
|
||||
static const String FORBIDDEN = 'api_common_forbidden';
|
||||
static const String NOT_FOUND = 'api_common_not_found';
|
||||
static const String INTERNAL_SERVER_ERROR = 'api_common_internal_server_error';
|
||||
static const String UPGRADE_REQUIRED = 'api_common_upgrade_required';
|
||||
static const String BAD_RESPONSE_FORMAT = 'api_common_bad_response_format';
|
||||
static const String OTHER = 'api_common_http_error';
|
||||
static const String TIMEOUT = 'api_common_http_timeout';
|
||||
static const String UNKNOWN = 'unexpected_error';
|
||||
|
||||
final String message;
|
||||
final APIError? error;
|
||||
final arguments;
|
||||
|
||||
const APIException(this.message, {this.arguments, this.error});
|
||||
|
||||
Map<String, dynamic> toJson() => {'message': message, 'error': error, 'arguments': '$arguments'};
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mohem_flutter_app/classes/colors.dart';
|
||||
|
||||
extension IntExtensions on int {
|
||||
Widget get height => SizedBox(height: toDouble());
|
||||
|
||||
Widget get width => SizedBox(width: toDouble());
|
||||
|
||||
Widget get divider => Divider(height: toDouble(), thickness: toDouble(), color: MyColors.lightGreyEFColor);
|
||||
|
||||
Widget get makeItSquare => SizedBox(width: toDouble(), height: toDouble());
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,234 @@
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:mohem_flutter_app/classes/colors.dart';
|
||||
|
||||
extension CapExtension on String {
|
||||
String get toCamelCase => "${this[0].toUpperCase()}${this.substring(1)}";
|
||||
|
||||
String get inCaps => '${this[0].toUpperCase()}${this.substring(1)}';
|
||||
|
||||
String get allInCaps => this.toUpperCase();
|
||||
|
||||
String get capitalizeFirstofEach => this.trim().length > 0 ? this.trim().toLowerCase().split(" ").map((str) => str.inCaps).join(" ") : "";
|
||||
}
|
||||
|
||||
extension EmailValidator on String {
|
||||
Widget get toWidget => Text(this);
|
||||
|
||||
Widget toText10({Color? color, bool isBold = false, int? maxlines, FontStyle? fontStyle}) => Text(
|
||||
this,
|
||||
maxLines: maxlines,
|
||||
style: TextStyle(fontSize: 10, fontStyle: fontStyle ?? FontStyle.normal, fontWeight: isBold ? FontWeight.bold : FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -0.4),
|
||||
);
|
||||
|
||||
Widget toText11({Color? color, FontWeight? weight, bool isUnderLine = false, bool isBold = false}) => Text(
|
||||
this,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600),
|
||||
color: color ?? MyColors.darkTextColor,
|
||||
letterSpacing: -0.33,
|
||||
decoration: isUnderLine ? TextDecoration.underline : null,
|
||||
),
|
||||
);
|
||||
|
||||
Widget toText12({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int maxLine = 0}) => Text(
|
||||
this,
|
||||
textAlign: isCenter ? TextAlign.center : null,
|
||||
maxLines: (maxLine > 0) ? maxLine : null,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: isBold ? FontWeight.bold : FontWeight.w600,
|
||||
color: color ?? MyColors.darkTextColor,
|
||||
letterSpacing: -0.72,
|
||||
decoration: isUnderLine ? TextDecoration.underline : null,
|
||||
),
|
||||
);
|
||||
|
||||
Widget toText12Auto({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int maxLine = 0}) => AutoSizeText(
|
||||
this,
|
||||
textAlign: isCenter ? TextAlign.center : null,
|
||||
maxLines: (maxLine > 0) ? maxLine : null,
|
||||
minFontSize: 8,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: isBold ? FontWeight.bold : FontWeight.w600,
|
||||
color: color ?? MyColors.darkTextColor,
|
||||
letterSpacing: -0.72,
|
||||
decoration: isUnderLine ? TextDecoration.underline : null,
|
||||
),
|
||||
);
|
||||
|
||||
Widget toTextAuto({
|
||||
Color? color,
|
||||
bool isUnderLine = false,
|
||||
bool isBold = false,
|
||||
bool isCenter = false,
|
||||
int maxLine = 0,
|
||||
double fontSize = 12,
|
||||
double letterSpacing = -0.72,
|
||||
double height = 1,
|
||||
}) =>
|
||||
AutoSizeText(
|
||||
this,
|
||||
textAlign: isCenter ? TextAlign.center : null,
|
||||
maxLines: (maxLine > 0) ? maxLine : null,
|
||||
minFontSize: 5,
|
||||
style: TextStyle(
|
||||
fontSize: fontSize,
|
||||
fontWeight: isBold ? FontWeight.bold : FontWeight.w600,
|
||||
color: color ?? MyColors.darkTextColor,
|
||||
letterSpacing: letterSpacing,
|
||||
decoration: isUnderLine ? TextDecoration.underline : null,
|
||||
),
|
||||
);
|
||||
|
||||
Widget toText13({Color? color, bool isUnderLine = false}) => Text(
|
||||
this,
|
||||
style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -0.52, decoration: isUnderLine ? TextDecoration.underline : null),
|
||||
);
|
||||
|
||||
Widget toText14({Color? color, bool isUnderLine = false, bool isBold = false, FontWeight? weight, int? maxlines}) => Text(
|
||||
this,
|
||||
maxLines: maxlines,
|
||||
style: TextStyle(
|
||||
color: color ?? MyColors.darkTextColor,
|
||||
fontSize: 14,
|
||||
letterSpacing: -0.48,
|
||||
fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600),
|
||||
decoration: isUnderLine ? TextDecoration.underline : null),
|
||||
);
|
||||
|
||||
Widget toText16({Color? color, bool isUnderLine = false, bool isBold = false, int? maxlines}) => Text(
|
||||
this,
|
||||
maxLines: maxlines,
|
||||
style: TextStyle(
|
||||
color: color ?? MyColors.darkTextColor,
|
||||
fontSize: 16,
|
||||
letterSpacing: -0.64,
|
||||
fontWeight: isBold ? FontWeight.bold : FontWeight.w600,
|
||||
decoration: isUnderLine ? TextDecoration.underline : null,
|
||||
),
|
||||
);
|
||||
|
||||
Widget toText17({Color? color, bool isBold = false}) => Text(
|
||||
this,
|
||||
style: TextStyle(color: color ?? MyColors.darkTextColor, fontSize: 17, letterSpacing: -0.68, fontWeight: isBold ? FontWeight.bold : FontWeight.w600),
|
||||
);
|
||||
|
||||
Widget toText18({Color? color, bool isBold = false}) => Text(
|
||||
this,
|
||||
style: TextStyle(fontSize: 18, fontWeight: isBold ? FontWeight.bold : FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -1.08),
|
||||
);
|
||||
|
||||
Widget toText19({Color? color, bool isBold = false}) => Text(
|
||||
this,
|
||||
style: TextStyle(fontSize: 19, fontWeight: isBold ? FontWeight.bold : FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -1.14),
|
||||
);
|
||||
|
||||
Widget toText20({Color? color, bool isBold = false}) => Text(
|
||||
this,
|
||||
style: TextStyle(fontSize: 20, fontWeight: isBold ? FontWeight.bold : FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -0.4),
|
||||
);
|
||||
|
||||
Widget toText21({Color? color, bool isBold = false, FontWeight? weight, int? maxlines}) => Text(
|
||||
this,
|
||||
maxLines: maxlines,
|
||||
style: TextStyle(color: color ?? MyColors.grey3AColor, fontSize: 21, letterSpacing: -0.31, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600)),
|
||||
);
|
||||
|
||||
Widget toText22({Color? color, bool isBold = false}) => Text(
|
||||
this,
|
||||
style: TextStyle(height: 1, color: color ?? MyColors.darkTextColor, fontSize: 22, letterSpacing: -1.44, fontWeight: isBold ? FontWeight.bold : FontWeight.w600),
|
||||
);
|
||||
|
||||
Widget toText24({Color? color, bool isBold = false}) => Text(
|
||||
this,
|
||||
style: TextStyle(height: 23 / 24, color: color ?? MyColors.darkTextColor, fontSize: 24, letterSpacing: -1.44, fontWeight: isBold ? FontWeight.bold : FontWeight.w600),
|
||||
);
|
||||
|
||||
Widget toText32({Color? color, bool isBold = false}) => Text(
|
||||
this,
|
||||
style: TextStyle(height: 32 / 32, color: color ?? MyColors.darkTextColor, fontSize: 32, letterSpacing: -1.92, fontWeight: isBold ? FontWeight.bold : FontWeight.w600),
|
||||
);
|
||||
|
||||
Widget toText44({Color? color, bool isBold = false}) => Text(
|
||||
this,
|
||||
style: TextStyle(height: 32 / 32, color: color ?? MyColors.darkTextColor, fontSize: 44, letterSpacing: -2.64, fontWeight: isBold ? FontWeight.bold : FontWeight.w600),
|
||||
);
|
||||
|
||||
Widget toSectionHeading({String upperHeading = "", String lowerHeading = ""}) {
|
||||
String upper = "";
|
||||
String lower = "";
|
||||
String heading = this;
|
||||
if (heading.isNotEmpty) {
|
||||
List<String> data = heading.split(" ");
|
||||
|
||||
if (data.length > 1) {
|
||||
upper = data[0];
|
||||
data.removeAt(0);
|
||||
lower = data.join(" ");
|
||||
} else {
|
||||
lower = data[0];
|
||||
}
|
||||
}
|
||||
if (upperHeading.isNotEmpty) {
|
||||
upper = upperHeading;
|
||||
}
|
||||
if (lowerHeading.isNotEmpty) {
|
||||
lower = lowerHeading;
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (upper.isNotEmpty) upper.toText12(),
|
||||
lower.toText24(isBold: true),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
bool isValidEmail() {
|
||||
return RegExp(r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$').hasMatch(this);
|
||||
}
|
||||
|
||||
String toFormattedDate() {
|
||||
String date = this.split("T")[0];
|
||||
String time = this.split("T")[1];
|
||||
var dates = date.split("-");
|
||||
return "${dates[2]} ${getMonth(int.parse(dates[1]))} ${dates[0]} ${DateFormat('hh:mm a').format(DateFormat('hh:mm:ss').parse(time))}";
|
||||
}
|
||||
|
||||
String getMonth(int month) {
|
||||
switch (month) {
|
||||
case 1:
|
||||
return "January";
|
||||
case 2:
|
||||
return "February";
|
||||
case 3:
|
||||
return "March";
|
||||
case 4:
|
||||
return "April";
|
||||
case 5:
|
||||
return "May";
|
||||
case 6:
|
||||
return "June";
|
||||
case 7:
|
||||
return "July";
|
||||
case 8:
|
||||
return "August";
|
||||
case 9:
|
||||
return "September";
|
||||
case 10:
|
||||
return "October";
|
||||
case 11:
|
||||
return "November";
|
||||
case 12:
|
||||
return "December";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,107 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.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:shimmer/shimmer.dart';
|
||||
|
||||
extension WidgetExtensions on Widget {
|
||||
Widget onPress(VoidCallback onTap) => InkWell(onTap: onTap, child: this);
|
||||
|
||||
Widget get expanded => Expanded(child: this);
|
||||
|
||||
Widget get center => Center(child: this);
|
||||
|
||||
Widget circle(double _value) => ClipRRect(borderRadius: BorderRadius.circular(_value), child: this);
|
||||
|
||||
Widget paddingAll(double _value) => Padding(padding: EdgeInsets.all(_value), child: this);
|
||||
|
||||
Widget paddingOnly({double left = 0.0, double right = 0.0, double top = 0.0, double bottom = 0.0}) =>
|
||||
Padding(padding: EdgeInsets.only(left: left, right: right, top: top, bottom: bottom), child: this);
|
||||
|
||||
Widget toExpanded({int flex = 1}) => Expanded(flex: flex, child: this);
|
||||
|
||||
Widget toShimmer({bool isShow = true}) => isShow
|
||||
? Shimmer.fromColors(
|
||||
baseColor: Color(0xffe8eff0),
|
||||
highlightColor: Colors.white,
|
||||
child: Container(
|
||||
color: Colors.white,
|
||||
child: this,
|
||||
),
|
||||
)
|
||||
: Container(
|
||||
child: this,
|
||||
);
|
||||
|
||||
Widget animatedSwither() => AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
// transitionBuilder: (Widget child, Animation<double> animation) {
|
||||
// return ScaleTransition(scale: animation, child: child);
|
||||
// },
|
||||
switchInCurve: Curves.linearToEaseOut,
|
||||
switchOutCurve: Curves.linearToEaseOut,
|
||||
child: this,
|
||||
);
|
||||
|
||||
Widget objectContainerView({String title = "", String note = "", bool disablePadding = false, double radius = 15}) {
|
||||
return Container(
|
||||
padding: disablePadding ? EdgeInsets.zero : const EdgeInsets.only(top: 15, bottom: 15, left: 14, right: 14),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(radius),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: const Color(0xff000000).withOpacity(.05),
|
||||
blurRadius: 26,
|
||||
offset: const Offset(0, -3),
|
||||
),
|
||||
],
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (title.isNotEmpty) title.toText16(),
|
||||
if (title.isNotEmpty) 12.height,
|
||||
this,
|
||||
if (note.isNotEmpty) note.toText11(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget objectContainerBorderView(
|
||||
{String title = "",
|
||||
String note = "",
|
||||
bool disablePadding = false,
|
||||
double radius = 15,
|
||||
Color? color,
|
||||
Color borderColor = MyColors.lightGreyEFColor,
|
||||
bool disableWidth = false,
|
||||
bool isAlignment = false}) {
|
||||
return Container(
|
||||
padding: disablePadding ? EdgeInsets.zero : const EdgeInsets.only(top: 15, bottom: 15, left: 14, right: 14),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(radius),
|
||||
color: color,
|
||||
border: Border.all(
|
||||
color: borderColor,
|
||||
width: disableWidth ? 2 : 1,
|
||||
),
|
||||
),
|
||||
alignment: isAlignment ? Alignment.center : null,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (title.isNotEmpty) title.toText16(),
|
||||
if (title.isNotEmpty) 12.height,
|
||||
this,
|
||||
if (note.isNotEmpty) note.toText11(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,274 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mohem_flutter_app/api/dashboard_api_client.dart';
|
||||
import 'package:mohem_flutter_app/api/offers_and_discounts_api_client.dart';
|
||||
import 'package:mohem_flutter_app/classes/utils.dart';
|
||||
import 'package:mohem_flutter_app/config/routes.dart';
|
||||
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
|
||||
import 'package:mohem_flutter_app/main.dart';
|
||||
import 'package:mohem_flutter_app/models/dashboard/drawer_menu_item_model.dart';
|
||||
import 'package:mohem_flutter_app/models/dashboard/get_accrual_balances_list_model.dart';
|
||||
import 'package:mohem_flutter_app/models/dashboard/get_attendance_tracking_list_model.dart';
|
||||
import 'package:mohem_flutter_app/models/dashboard/get_open_notifications_list.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/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/generic_response_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/widgets/dialogs/confirm_dialog.dart';
|
||||
|
||||
/// Mix-in [DiagnosticableTreeMixin] to have access to [debugFillProperties] for the devtool
|
||||
// ignore: prefer_mixin
|
||||
class DashboardProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
|
||||
//Attendance Tracking
|
||||
bool isAttendanceTrackingLoading = true;
|
||||
int endTime = 0, isTimeRemainingInSeconds = 0;
|
||||
double progress = 0.0;
|
||||
GetAttendanceTracking? attendanceTracking;
|
||||
|
||||
//Work List
|
||||
bool isWorkListLoading = true;
|
||||
int workListCounter = 0;
|
||||
|
||||
//Misssing Swipe
|
||||
bool isMissingSwipeLoading = true;
|
||||
int missingSwipeCounter = 0;
|
||||
|
||||
//Leave and Ticket Balance
|
||||
bool isLeaveTicketBalanceLoading = true;
|
||||
List<GetAccrualBalancesList>? accrualList;
|
||||
GetAccrualBalancesList? leaveBalanceAccrual;
|
||||
|
||||
double get leaveBalance => leaveBalanceAccrual?.accrualNetEntitlement ?? 0;
|
||||
double ticketBalance = 0;
|
||||
|
||||
//Menu Entries
|
||||
bool isServicesMenusLoading = true;
|
||||
List<Menus>? homeMenus;
|
||||
List<GetMenuEntriesList>? getMenuEntriesList;
|
||||
|
||||
//Offers And Discounts
|
||||
bool isOffersLoading = true;
|
||||
List<OffersListModel> getOffersList = [];
|
||||
|
||||
//Attendance Tracking API's & Methods
|
||||
Future<bool> fetchAttendanceTracking(context) async {
|
||||
try {
|
||||
attendanceTracking = await DashboardApiClient().getAttendanceTracking();
|
||||
isAttendanceTrackingLoading = false;
|
||||
// isTimeRemainingInSeconds = calculateSeconds( "00:00:00");
|
||||
if (attendanceTracking?.pSwipeIn != null) {
|
||||
isTimeRemainingInSeconds = calculateSeconds(attendanceTracking!.pRemainingHours ?? "00:00:00");
|
||||
int totalShiftTimeInSeconds = calculateSeconds(attendanceTracking!.pScheduledHours ?? "00:00:00");
|
||||
progress = (isTimeRemainingInSeconds / totalShiftTimeInSeconds);
|
||||
endTime = DateTime.now().millisecondsSinceEpoch + Duration(seconds: isTimeRemainingInSeconds).inMilliseconds;
|
||||
}
|
||||
notifyListeners();
|
||||
} catch (ex) {
|
||||
Utils.handleException(ex, context, null);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void initProvider() {
|
||||
isAttendanceTrackingLoading = true;
|
||||
endTime = 0;
|
||||
isTimeRemainingInSeconds = 0;
|
||||
progress = 0.0;
|
||||
attendanceTracking = null;
|
||||
|
||||
isWorkListLoading = true;
|
||||
workListCounter = 0;
|
||||
|
||||
isMissingSwipeLoading = true;
|
||||
missingSwipeCounter = 0;
|
||||
|
||||
isLeaveTicketBalanceLoading = true;
|
||||
accrualList = null;
|
||||
leaveBalanceAccrual = null;
|
||||
|
||||
ticketBalance = 0;
|
||||
isServicesMenusLoading = true;
|
||||
homeMenus = null;
|
||||
getMenuEntriesList = null;
|
||||
isOffersLoading = true;
|
||||
getOffersList = [];
|
||||
|
||||
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/pending_trasactions.svg", LocaleKeys.pendingTransactions.tr(), AppRoutes.pendingTransactions),
|
||||
DrawerMenuItem("assets/images/drawer/drawer_marathon.svg", LocaleKeys.brainMarathon.tr(), AppRoutes.marathonIntroScreen),
|
||||
DrawerMenuItem("assets/images/drawer/change_password.svg", LocaleKeys.changePassword.tr(), AppRoutes.changePassword),
|
||||
];
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
int calculateSeconds(String time) {
|
||||
int hour = int.parse(time.split(":")[0]);
|
||||
int mints = int.parse(time.split(":")[1]);
|
||||
int seconds = int.parse(time.split(":")[2]);
|
||||
return ((hour * 60 * 60) + (mints * 60) + seconds);
|
||||
}
|
||||
|
||||
void update(context) {
|
||||
fetchAttendanceTracking(context);
|
||||
}
|
||||
|
||||
ItgFormsModel? itgFormsModel;
|
||||
List<GetOpenNotificationsList>? getOpenNotificationsList;
|
||||
MohemmITGPendingTaskResponseItem? cocCount;
|
||||
int cocFinalCount = 0;
|
||||
|
||||
//Work List API's & Methods
|
||||
Future fetchWorkListCounter(context, {bool showLoading = false}) async {
|
||||
try {
|
||||
if (showLoading) Utils.showLoading(context);
|
||||
GenericResponseModel? genericResponseModel = await DashboardApiClient().getOpenNotifications();
|
||||
|
||||
isWorkListLoading = false;
|
||||
getOpenNotificationsList = genericResponseModel?.getOpenNotificationsList;
|
||||
|
||||
workListCounter = genericResponseModel?.pOPENNTFNUMBER ?? 0;
|
||||
|
||||
itgFormsModel = await DashboardApiClient().getItgFormsPendingTask();
|
||||
workListCounter = workListCounter + (itgFormsModel?.totalCount ?? 0);
|
||||
GenericResponseModel? cocGenericResponseModel = await DashboardApiClient().getCOCNotifications();
|
||||
cocCount = cocGenericResponseModel?.mohemmITGPendingTaskResponseItem;
|
||||
if (cocCount != null) {
|
||||
cocFinalCount = (cocCount?.escalation ?? 0) + (cocCount?.waitingToClose ?? 0) + (cocCount?.waitingForAcceptance ?? 0) + (cocCount?.extendTATRequest ?? 0);
|
||||
workListCounter += cocFinalCount;
|
||||
}
|
||||
if (showLoading) Utils.hideLoading(context);
|
||||
notifyListeners();
|
||||
} catch (ex) {
|
||||
isWorkListLoading = false;
|
||||
logger.wtf(ex);
|
||||
if (showLoading) Utils.hideLoading(context);
|
||||
notifyListeners();
|
||||
Utils.handleException(ex, context, (err) {
|
||||
Utils.hideLoading(context);
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (cxt) => ConfirmDialog(
|
||||
message: err,
|
||||
onTap: () {
|
||||
Navigator.pushNamedAndRemoveUntil(cxt, AppRoutes.login, (Route<dynamic> route) => false);
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//Missing Siwpe API's & Methods
|
||||
Future fetchMissingSwipe(context) async {
|
||||
try {
|
||||
GenericResponseModel? genericResponseModel = await DashboardApiClient().getOpenMissingSwipes();
|
||||
isMissingSwipeLoading = false;
|
||||
missingSwipeCounter = genericResponseModel!.getOpenMissingSwipesList!.pOpenMissingSwipes ?? 0;
|
||||
notifyListeners();
|
||||
} catch (ex) {
|
||||
isMissingSwipeLoading = false;
|
||||
logger.wtf(ex);
|
||||
notifyListeners();
|
||||
Utils.handleException(ex, context, null);
|
||||
}
|
||||
}
|
||||
|
||||
//Leave and Ticket Balance API's & Methods
|
||||
Future fetchLeaveTicketBalance(context, DateTime date) async {
|
||||
try {
|
||||
accrualList = await DashboardApiClient().getAccrualBalances(DateFormat("MM/dd/yyyy").format(date));
|
||||
isLeaveTicketBalanceLoading = false;
|
||||
leaveBalanceAccrual = accrualList![0];
|
||||
ticketBalance = (accrualList![1].accrualNetEntitlement ?? 0.0) + (accrualList![2].accrualNetEntitlement ?? 0.0);
|
||||
notifyListeners();
|
||||
} catch (ex) {
|
||||
isLeaveTicketBalanceLoading = false;
|
||||
logger.wtf(ex);
|
||||
notifyListeners();
|
||||
Utils.handleException(ex, context, null);
|
||||
}
|
||||
}
|
||||
|
||||
//List Menu API's & Methods
|
||||
|
||||
List<DrawerMenuItem> drawerMenuItemList = [];
|
||||
|
||||
void fetchListMenu() async {
|
||||
try {
|
||||
List<ListMenu> menuList = await DashboardApiClient().getListMenu();
|
||||
List findMyRequest = menuList.where((element) => element.menuType == "E").toList();
|
||||
if (findMyRequest.isNotEmpty) {
|
||||
drawerMenuItemList.insert(3, DrawerMenuItem("assets/images/drawer/my_requests.svg", LocaleKeys.myRequest.tr(), AppRoutes.myRequests));
|
||||
}
|
||||
List findMyTeam = menuList.where((element) => element.menuType == "M").toList();
|
||||
if (findMyTeam.isNotEmpty) {
|
||||
drawerMenuItemList.insert(2, DrawerMenuItem("assets/images/drawer/my_team.svg", LocaleKeys.myTeamMembers.tr(), AppRoutes.myTeam));
|
||||
}
|
||||
} catch (ex) {
|
||||
logger.wtf(ex);
|
||||
Utils.handleException(ex, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
//Menu Entries API's & Methods
|
||||
void fetchMenuEntries() async {
|
||||
try {
|
||||
GenericResponseModel? genericResponseModel = await DashboardApiClient().getGetMenuEntries();
|
||||
getMenuEntriesList = genericResponseModel!.getMenuEntriesList;
|
||||
homeMenus = parseMenus(getMenuEntriesList ?? []);
|
||||
if (homeMenus!.isNotEmpty) {
|
||||
homeMenus!.first.menuEntiesList.insert(0, GetMenuEntriesList(requestType: "MONTHLY_ATTENDANCE", prompt: LocaleKeys.monthlyAttendance.tr()));
|
||||
homeMenus!.first.menuEntiesList.add(GetMenuEntriesList(requestType: "VACATION_RULE", prompt: LocaleKeys.vacationRule.tr()));
|
||||
}
|
||||
isServicesMenusLoading = false;
|
||||
notifyListeners();
|
||||
} catch (ex) {
|
||||
logger.wtf(ex);
|
||||
notifyListeners();
|
||||
Utils.handleException(ex, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
void getCategoryOffersListAPI(BuildContext context) async {
|
||||
try {
|
||||
// Utils.showLoading(context);
|
||||
getOffersList = await OffersAndDiscountsApiClient().getOffersList(0, 6);
|
||||
isOffersLoading = false;
|
||||
notifyListeners();
|
||||
} catch (ex) {
|
||||
// Utils.hideLoading(context);
|
||||
notifyListeners();
|
||||
Utils.handleException(ex, context, null);
|
||||
}
|
||||
}
|
||||
|
||||
List<Menus> parseMenus(List<GetMenuEntriesList> getMenuEntriesList) {
|
||||
List<Menus> menus = [];
|
||||
for (int i = 0; i < getMenuEntriesList.length; i++) {
|
||||
if (getMenuEntriesList[i].parentMenuName!.isEmpty) {
|
||||
menus.add(Menus(getMenuEntriesList[i], getMenuEntriesList.where((element) => getMenuEntriesList[i].menuName == element.parentMenuName).toList()));
|
||||
}
|
||||
}
|
||||
return menus;
|
||||
}
|
||||
|
||||
Future<MohemmItgResponseItem?> getITGNotification() async {
|
||||
MohemmItgResponseItem? res = await DashboardApiClient().getITGPageNotification();
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void notify() {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mohem_flutter_app/classes/colors.dart';
|
||||
import 'package:mohem_flutter_app/theme/colors.dart';
|
||||
|
||||
class AppTheme {
|
||||
static ThemeData getTheme(isArabic) => ThemeData(
|
||||
fontFamily: isArabic ? 'Cairo' : 'Poppins',
|
||||
primarySwatch: Colors.red,
|
||||
visualDensity: VisualDensity.adaptivePlatformDensity,
|
||||
brightness: Brightness.light,
|
||||
pageTransitionsTheme: const PageTransitionsTheme(
|
||||
builders: {
|
||||
TargetPlatform.android: ZoomPageTransitionsBuilder(),
|
||||
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
|
||||
},
|
||||
),
|
||||
hintColor: Colors.grey[400],
|
||||
colorScheme: ColorScheme.fromSwatch(accentColor: MyColors.backgroundColor),
|
||||
disabledColor: Colors.grey[300],
|
||||
errorColor: const Color.fromRGBO(235, 80, 60, 1.0),
|
||||
scaffoldBackgroundColor: MyColors.backgroundColor,
|
||||
textSelectionTheme: const TextSelectionThemeData(cursorColor: Colors.grey, selectionColor: Color.fromRGBO(80, 100, 253, 0.5), selectionHandleColor: Colors.grey),
|
||||
canvasColor: Colors.white,
|
||||
backgroundColor: const Color.fromRGBO(255, 255, 255, 1),
|
||||
highlightColor: Colors.grey[100]!.withOpacity(0.4),
|
||||
splashColor: Colors.transparent,
|
||||
primaryColor: primaryColor,
|
||||
primaryColorDark: primaryColor,
|
||||
buttonColor: Colors.black,
|
||||
toggleableActiveColor: secondaryColor,
|
||||
indicatorColor: secondaryColor,
|
||||
bottomSheetTheme: const BottomSheetThemeData(
|
||||
backgroundColor: Color(0xFFE0E0E0),
|
||||
),
|
||||
primaryTextTheme: const TextTheme(
|
||||
bodyText2: TextStyle(color: Colors.white),
|
||||
),
|
||||
iconTheme: const IconThemeData(color: MyColors.darkIconColor),
|
||||
textTheme: const TextTheme(
|
||||
bodyText1: TextStyle(color: Colors.black, letterSpacing: 0.6),
|
||||
headline1: TextStyle(color: Colors.white, letterSpacing: 0.6),
|
||||
headline2: TextStyle(color: Colors.white, letterSpacing: 0.6),
|
||||
),
|
||||
floatingActionButtonTheme: const FloatingActionButtonThemeData(highlightElevation: 2, disabledElevation: 0, elevation: 2),
|
||||
appBarTheme: AppBarTheme(
|
||||
color: const Color(0xff515A5D),
|
||||
brightness: Brightness.light,
|
||||
elevation: 0.0,
|
||||
actionsIconTheme: IconThemeData(
|
||||
color: Colors.grey[800],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
extension ExtendedRevoCheckTheme on TextTheme {
|
||||
//add custom styles and colors here
|
||||
//taken from https://medium.com/@crizantlai/flutter-how-to-extend-themedata-b5b987a95bb5
|
||||
TextStyle get price => const TextStyle(color: Colors.redAccent);
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
const Color primaryColor = Color(0xffF8F8F8);
|
||||
const Color secondaryColor = Color(0xff2BB8A6);
|
||||
const Color accentColor = Colors.blue;
|
||||
const Color appBackgroundColor = Colors.white;
|
||||
Color? accentColorDark = Colors.green[800];
|
||||
const Color borderColor = Color(0xffE8E8E8);
|
||||
Color? borderLightColor = Colors.blueGrey[50];
|
||||
Color backgroudColor = Colors.blueGrey[50]!.withOpacity(0.5);
|
||||
const Color iconColor = Colors.blueGrey;
|
||||
Color? headingColor = Colors.blueGrey[800];
|
||||
Color? txtColor = Colors.blueGrey[500];
|
||||
@ -0,0 +1,39 @@
|
||||
/* ZiK */
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
typedef ChildProvider<E> = Widget Function(BuildContext context, E? data);
|
||||
|
||||
class Updater<T> extends StatelessWidget{
|
||||
final ChildProvider<T> childProvider;
|
||||
StreamController<T?>? sink;
|
||||
T? initialData;
|
||||
List<T?> _history = [];
|
||||
|
||||
Stream<T?>? _stream;
|
||||
Updater({T? initialData, required this.childProvider}){
|
||||
this.sink = StreamController<T?>();
|
||||
this.initialData = initialData;
|
||||
_stream = this.sink?.stream;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StreamBuilder<T?>(
|
||||
initialData: this.initialData,
|
||||
stream: _stream,
|
||||
builder: (ctx, snapshot){
|
||||
return childProvider(context, snapshot.data);
|
||||
});
|
||||
}
|
||||
|
||||
void pushData(T? data) {
|
||||
_history.add(data);
|
||||
sink?.sink.add(data);
|
||||
}
|
||||
|
||||
List<T?> getDataHistory() => _history;
|
||||
T? getLatestData() => _history.last;
|
||||
|
||||
}
|
||||
@ -0,0 +1,70 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.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';
|
||||
|
||||
AppBar AppBarWidget(BuildContext context,
|
||||
{required String title,
|
||||
bool showHomeButton = true,
|
||||
bool showNotificationButton = false,
|
||||
bool showMemberButton = false,
|
||||
String? image}) {
|
||||
return AppBar(
|
||||
leadingWidth: 0,
|
||||
// leading: GestureDetector(
|
||||
// behavior: HitTestBehavior.opaque,
|
||||
// onTap: Feedback.wrapForTap(() => Navigator.maybePop(context), context),
|
||||
// child: const Icon(Icons.arrow_back_ios, color: MyColors.darkIconColor),
|
||||
// ),
|
||||
//titleSpacing: -1.44,
|
||||
title: Row(
|
||||
children: [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap:
|
||||
Feedback.wrapForTap(() => Navigator.maybePop(context), context),
|
||||
child:
|
||||
const Icon(Icons.arrow_back_ios, color: MyColors.darkIconColor),
|
||||
),
|
||||
4.width,
|
||||
if (image != null) SvgPicture.asset(
|
||||
image,
|
||||
height: 40,
|
||||
width: 40,
|
||||
),
|
||||
if (image != null) 14.width,
|
||||
title.toText24(color: MyColors.darkTextColor, isBold: true).expanded,
|
||||
],
|
||||
),
|
||||
centerTitle: false,
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.white,
|
||||
actions: [
|
||||
if (showHomeButton)
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.popUntil(
|
||||
context, ModalRoute.withName(AppRoutes.dashboard));
|
||||
},
|
||||
icon: const Icon(Icons.home, color: MyColors.darkIconColor),
|
||||
),
|
||||
if (showNotificationButton)
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.pushNamed(context, AppRoutes.worklistSettings);
|
||||
},
|
||||
icon: const Icon(Icons.notifications, color: MyColors.textMixColor),
|
||||
),
|
||||
if (showMemberButton)
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.pushNamed(context, AppRoutes.subordinateLeave);
|
||||
},
|
||||
icon: const Icon(Icons.people, color: MyColors.textMixColor),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:mohem_flutter_app/extensions/int_extensions.dart';
|
||||
import 'package:mohem_flutter_app/extensions/string_extensions.dart';
|
||||
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
|
||||
|
||||
class AppLogo extends StatelessWidget {
|
||||
AppLogo({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
"assets/mohemm_logo.svg",
|
||||
height: 48,
|
||||
width: 48,
|
||||
alignment: Alignment.centerRight,
|
||||
),
|
||||
8.width,
|
||||
LocaleKeys.mohemm.tr().toText14()
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,86 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mohem_flutter_app/extensions/int_extensions.dart';
|
||||
|
||||
void showMyBottomSheet(BuildContext context, {required Widget child, required VoidCallback callBackFunc}) {
|
||||
showModalBottomSheet<String>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (BuildContext context) {
|
||||
return Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: Radius.circular(25),
|
||||
topLeft: Radius.circular(25),
|
||||
),
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
13.height,
|
||||
Container(
|
||||
height: 6,
|
||||
width: 60,
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xff9A9A9A),
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(20),
|
||||
),
|
||||
),
|
||||
),
|
||||
8.height,
|
||||
child,
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
).then((value) {
|
||||
// print("BACK FROM DELEGATE!!!!");
|
||||
// print("value: $value");
|
||||
if (value == "delegate_reload") {
|
||||
callBackFunc();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class BottomSheetItem extends StatelessWidget {
|
||||
final Function onTap;
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final Color color;
|
||||
|
||||
const BottomSheetItem({Key? key, required this.onTap, required this.title, required this.icon, this.color = Colors.black}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
if (onTap != null) {
|
||||
Navigator.pop(context);
|
||||
onTap();
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 18.0, vertical: 18.0),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
if (icon != null)
|
||||
Icon(
|
||||
icon,
|
||||
color: color,
|
||||
size: 18.0,
|
||||
),
|
||||
if (icon != null) SizedBox(width: 24.0),
|
||||
Text(
|
||||
title ?? "",
|
||||
style: TextStyle(color: color),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,139 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:mohem_flutter_app/classes/colors.dart';
|
||||
|
||||
extension WithContainer on Widget {
|
||||
Widget get insideContainer => Container(
|
||||
color: Colors.white,
|
||||
padding:
|
||||
const EdgeInsets.only(top: 16, bottom: 16, right: 21, left: 21),
|
||||
child: this,
|
||||
);
|
||||
}
|
||||
|
||||
class DefaultButton extends StatelessWidget {
|
||||
final String text;
|
||||
final VoidCallback? onPress;
|
||||
final Color textColor;
|
||||
final Color? color;
|
||||
final Color? disabledColor;
|
||||
final IconData? iconData;
|
||||
final String? svgIcon;
|
||||
final double? fontSize;
|
||||
final bool isTextExpanded;
|
||||
final int count;
|
||||
final List<Color>? colors;
|
||||
|
||||
const DefaultButton(
|
||||
this.text,
|
||||
this.onPress, {
|
||||
Key? key,
|
||||
this.color,
|
||||
this.isTextExpanded = true,
|
||||
this.svgIcon,
|
||||
this.disabledColor,
|
||||
this.count = 0,
|
||||
this.textColor = Colors.white,
|
||||
this.iconData,
|
||||
this.fontSize,
|
||||
this.colors,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onPress,
|
||||
child: Container(
|
||||
height: 43,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6.0),
|
||||
gradient: onPress == null
|
||||
? const LinearGradient(
|
||||
colors: <Color>[
|
||||
Color(0xffEAEAEA),
|
||||
Color(0xffEAEAEA),
|
||||
],
|
||||
)
|
||||
: color == MyColors.yellowColorII
|
||||
? const LinearGradient(
|
||||
colors: <Color>[
|
||||
MyColors.yellowColorII,
|
||||
MyColors.yellowColorII,
|
||||
],
|
||||
)
|
||||
: LinearGradient(
|
||||
transform: const GradientRotation(.83),
|
||||
begin: Alignment.topRight,
|
||||
end: Alignment.bottomLeft,
|
||||
colors: colors ??
|
||||
<Color>[
|
||||
MyColors.gradiantEndColor,
|
||||
MyColors.gradiantStartColor,
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
if (iconData != null) Icon(iconData, color: textColor),
|
||||
if (svgIcon != null)
|
||||
SvgPicture.asset(svgIcon ?? "", color: textColor),
|
||||
if (!isTextExpanded)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: (iconData ?? svgIcon) != null ? 6 : 0,
|
||||
),
|
||||
child: Text(
|
||||
text,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: fontSize ?? 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: textColor,
|
||||
letterSpacing: -0.48,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isTextExpanded)
|
||||
Expanded(
|
||||
child: Text(
|
||||
text,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: fontSize ?? 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: textColor,
|
||||
letterSpacing: -0.48,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (count > 0)
|
||||
Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(top: 6, bottom: 6),
|
||||
padding: const EdgeInsets.only(left: 5, right: 5),
|
||||
alignment: Alignment.center,
|
||||
height: 16,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
color: Colors.white,
|
||||
),
|
||||
child: Text(
|
||||
"$count",
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Color(0xffD02127),
|
||||
letterSpacing: -0.6,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mohem_flutter_app/classes/colors.dart';
|
||||
|
||||
class SimpleButton extends StatelessWidget {
|
||||
final String text;
|
||||
final VoidCallback? onPress;
|
||||
final Color textColor;
|
||||
final Color? color;
|
||||
final double? fontSize;
|
||||
final List<Color>? colors;
|
||||
|
||||
SimpleButton(this.text, this.onPress, {this.color, this.textColor = Colors.white, this.fontSize, this.colors});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onPress,
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.only(left: 12, right: 12, top: 4, bottom: 4),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6.0),
|
||||
gradient: onPress == null
|
||||
? const LinearGradient(colors: [Color(0xffEAEAEA), Color(0xffEAEAEA)])
|
||||
: LinearGradient(
|
||||
transform: const GradientRotation(.83),
|
||||
begin: Alignment.topRight,
|
||||
end: Alignment.bottomLeft,
|
||||
colors: colors ??
|
||||
[
|
||||
MyColors.gradiantEndColor,
|
||||
MyColors.gradiantStartColor,
|
||||
]),
|
||||
),
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(fontSize: fontSize ?? 16, fontWeight: FontWeight.w600, color: textColor, letterSpacing: -0.48),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CircularAvatar extends StatelessWidget {
|
||||
final String? url;
|
||||
final double radius;
|
||||
final double width;
|
||||
final double height;
|
||||
final bool isImageBase64;
|
||||
|
||||
CircularAvatar({Key? key, this.radius = 70.0, this.width = 70, this.height = 60, this.url, this.isImageBase64 = false}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: width,
|
||||
height: height,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
image: isImageBase64
|
||||
? null
|
||||
: DecorationImage(
|
||||
fit: BoxFit.cover,
|
||||
image: NetworkImage(url ?? "https://cdn4.iconfinder.com/data/icons/professions-2-2/151/89-512.png"),
|
||||
),
|
||||
),
|
||||
child: (isImageBase64 && url != null) ? imageFromBase64String(url!) : Container(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget imageFromBase64String(String base64String) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(100),
|
||||
child: Image.memory(
|
||||
base64Decode(base64String),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,515 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CircularStepProgressBar extends StatelessWidget {
|
||||
/// Defines if steps grow from
|
||||
/// clockwise [CircularDirection.clockwise] or
|
||||
/// counterclockwise [CircularDirection.counterclockwise]
|
||||
final CircularDirection circularDirection;
|
||||
|
||||
/// Number of steps to underline, all the steps with
|
||||
/// index <= [currentStep] will have [Color] equal to
|
||||
/// [selectedColor]
|
||||
///
|
||||
/// Only used when [customColor] is [null]
|
||||
///
|
||||
/// Default value: 0
|
||||
final int currentStep;
|
||||
|
||||
/// Total number of step of the complete indicator
|
||||
final int totalSteps;
|
||||
|
||||
/// Radial spacing between each step. Remember to
|
||||
/// define the value in radiant units
|
||||
///
|
||||
/// Default value: math.pi / 20
|
||||
final double padding;
|
||||
|
||||
/// Height of the indicator's box container
|
||||
final double? height;
|
||||
|
||||
/// Width of the indicator's box container
|
||||
final double? width;
|
||||
|
||||
/// Assign a custom [Color] for each step
|
||||
///
|
||||
/// Takes a [int], index of the current step starting from 0, and
|
||||
/// must return a [Color]
|
||||
///
|
||||
/// **NOTE**: If provided, it overrides
|
||||
/// [selectedColor] and [unselectedColor]
|
||||
final Color Function(int)? customColor;
|
||||
|
||||
/// [Color] of the selected steps
|
||||
///
|
||||
/// All the steps with index <= [currentStep]
|
||||
///
|
||||
/// Default value: [Colors.blue]
|
||||
final Color? selectedColor;
|
||||
|
||||
/// [Color] of the unselected steps
|
||||
///
|
||||
/// All the steps with index between
|
||||
/// [currentStep] and [totalSteps]
|
||||
///
|
||||
/// Default value: [Colors.grey]
|
||||
final Color? unselectedColor;
|
||||
|
||||
/// The size of a single step in the indicator
|
||||
///
|
||||
/// Default value: 6.0
|
||||
final double stepSize;
|
||||
|
||||
/// Specify a custom size for selected steps
|
||||
///
|
||||
/// Only applicable when not custom setting (customColor, customStepSize) is defined
|
||||
///
|
||||
/// This value will replace the [stepSize] only for selected steps
|
||||
final double? selectedStepSize;
|
||||
|
||||
/// Specify a custom size for unselected steps
|
||||
///
|
||||
/// Only applicable when not custom setting (customColor, customStepSize) is defined
|
||||
///
|
||||
/// This value will replace the [stepSize] only for unselected steps
|
||||
final double? unselectedStepSize;
|
||||
|
||||
/// Assign a custom size [double] for each step
|
||||
///
|
||||
/// Function takes a [int], index of the current step starting from 0, and
|
||||
/// a [bool], which tells if the step is selected based on [currentStep],
|
||||
/// and must return a [double] size of the step
|
||||
///
|
||||
/// **NOTE**: If provided, it overrides [stepSize]
|
||||
final double Function(int, bool)? customStepSize;
|
||||
|
||||
/// [Widget] contained inside the circular indicator
|
||||
final Widget? child;
|
||||
|
||||
/// Height of the indicator container in case no [height] parameter
|
||||
/// given and parent height is [double.infinity]
|
||||
///
|
||||
/// Default value: 100.0
|
||||
final double fallbackHeight;
|
||||
|
||||
/// Height of the indicator container in case no [width] parameter
|
||||
/// given and parent height is [double.infinity]
|
||||
///
|
||||
/// Default value: 100.0
|
||||
final double fallbackWidth;
|
||||
|
||||
/// Angle in radiants in which the first step of the indicator is placed.
|
||||
/// The initial value is on the top of the indicator (- math.pi / 2)
|
||||
/// - 0 => TOP
|
||||
/// - math.pi / 2 => LEFT
|
||||
/// - math.pi => BOTTOM
|
||||
/// - math.pi / 2 * 3 => RIGHT
|
||||
/// - math.pi / 2 => TOP (again)
|
||||
final double startingAngle;
|
||||
|
||||
/// Angle in radiants which represents the size of the arc used to display the indicator.
|
||||
/// It allows you to draw a semi-circle instead of a full 360° (math.pi * 2) circle.
|
||||
final double arcSize;
|
||||
|
||||
/// Adds rounded edges at the beginning and at the end of the circular indicator
|
||||
/// given a [int], index of each step, and a [bool],
|
||||
/// which tells if the step is selected based on [currentStep], and must return a
|
||||
/// [bool] that tells if the edges are rounded or not
|
||||
///
|
||||
/// **NOTE**: For continuous circular indicators (`padding: 0`), to check if to apply
|
||||
/// the rounded edges the packages uses index 0 (for the first arc painted) and
|
||||
/// 1 (for the second arc painted)
|
||||
///
|
||||
/// ```dart
|
||||
/// // Example: Add rounded edges for all the steps
|
||||
/// roundedCap: (index, _) => true
|
||||
/// ```
|
||||
///
|
||||
/// ```dart
|
||||
/// // Example: Add rounded edges for the selected arc of the indicator
|
||||
/// roundedCap: (index, _) => index == 0,
|
||||
/// padding: 0
|
||||
/// ```
|
||||
final bool Function(int, bool)? roundedCap;
|
||||
|
||||
/// Adds a gradient color to the circular indicator
|
||||
///
|
||||
/// **NOTE**: If provided, it overrides [selectedColor], [unselectedColor], and [customColor]
|
||||
final Gradient? gradientColor;
|
||||
|
||||
/// Removes the extra angle caused by [StrokeCap.round] when [roundedCap] is applied
|
||||
final bool removeRoundedCapExtraAngle;
|
||||
|
||||
const CircularStepProgressBar({
|
||||
required this.totalSteps,
|
||||
this.child,
|
||||
this.height,
|
||||
this.width,
|
||||
this.customColor,
|
||||
this.customStepSize,
|
||||
this.selectedStepSize,
|
||||
this.unselectedStepSize,
|
||||
this.roundedCap,
|
||||
this.gradientColor,
|
||||
this.circularDirection = CircularDirection.clockwise,
|
||||
this.fallbackHeight = 100.0,
|
||||
this.fallbackWidth = 100.0,
|
||||
this.currentStep = 0,
|
||||
this.selectedColor = Colors.blue,
|
||||
this.unselectedColor = Colors.grey,
|
||||
this.padding = math.pi / 20,
|
||||
this.stepSize = 6.0,
|
||||
this.startingAngle = 0,
|
||||
this.arcSize = math.pi * 2,
|
||||
this.removeRoundedCapExtraAngle = false,
|
||||
Key? key,
|
||||
}) : assert(totalSteps > 0, "Number of total steps (totalSteps) of the CircularStepProgressBar must be greater than 0"),
|
||||
assert(currentStep >= 0, "Current step (currentStep) of the CircularStepProgressBar must be greater than or equal to 0"),
|
||||
assert(padding >= 0.0, "Padding (padding) of the CircularStepProgressBar must be greater or equal to 0"),
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Print warning when arcSize greater than math.pi * 2 which causes steps to overlap
|
||||
if (arcSize > math.pi * 2) print("WARNING (step_progress_indicator): arcSize of CircularStepProgressBar is greater than 360° (math.pi * 2), this will cause some steps to overlap!");
|
||||
TextDirection textDirection = Directionality.of(context);
|
||||
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) => SizedBox(
|
||||
// Apply fallback for both height and width
|
||||
// if their value is null and no parent size limit
|
||||
height: height != null
|
||||
? height
|
||||
: constraints.maxHeight != double.infinity
|
||||
? constraints.maxHeight
|
||||
: fallbackHeight,
|
||||
width: width != null
|
||||
? width
|
||||
: constraints.maxWidth != double.infinity
|
||||
? constraints.maxWidth
|
||||
: fallbackWidth,
|
||||
child: CustomPaint(
|
||||
painter: _CircularIndicatorPainter(
|
||||
totalSteps: totalSteps,
|
||||
currentStep: currentStep,
|
||||
customColor: customColor,
|
||||
padding: padding,
|
||||
circularDirection: circularDirection,
|
||||
selectedColor: selectedColor,
|
||||
unselectedColor: unselectedColor,
|
||||
arcSize: arcSize,
|
||||
stepSize: stepSize,
|
||||
customStepSize: customStepSize,
|
||||
maxDefinedSize: maxDefinedSize,
|
||||
selectedStepSize: selectedStepSize,
|
||||
unselectedStepSize: unselectedStepSize,
|
||||
startingAngle: startingAngleTopOfIndicator,
|
||||
roundedCap: roundedCap,
|
||||
gradientColor: gradientColor,
|
||||
textDirection: textDirection,
|
||||
removeRoundedCapExtraAngle: removeRoundedCapExtraAngle,
|
||||
),
|
||||
// Padding needed to show the indicator when child is placed on top of it
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(maxDefinedSize),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Compute the maximum possible size of the indicator between
|
||||
/// [stepSize] and [customStepSize]
|
||||
double get maxDefinedSize {
|
||||
if (customStepSize == null) {
|
||||
return math.max(stepSize, math.max(selectedStepSize ?? 0, unselectedStepSize ?? 0));
|
||||
}
|
||||
|
||||
// When customSize defined, compute and return max possible size
|
||||
double currentMaxSize = 0;
|
||||
|
||||
for (int step = 0; step < totalSteps; ++step) {
|
||||
// Consider max between selected and unselected case
|
||||
var customSizeValue = math.max(customStepSize!(step, false), customStepSize!(step, true));
|
||||
if (customSizeValue > currentMaxSize) {
|
||||
currentMaxSize = customSizeValue;
|
||||
}
|
||||
}
|
||||
|
||||
return currentMaxSize;
|
||||
}
|
||||
|
||||
/// Make [startingAngle] to top-center of indicator (0°) by default
|
||||
double get startingAngleTopOfIndicator => startingAngle - math.pi / 2;
|
||||
}
|
||||
|
||||
class _CircularIndicatorPainter implements CustomPainter {
|
||||
final int totalSteps;
|
||||
final int currentStep;
|
||||
final double padding;
|
||||
final Color? selectedColor;
|
||||
final Color? unselectedColor;
|
||||
final double stepSize;
|
||||
final double? selectedStepSize;
|
||||
final double? unselectedStepSize;
|
||||
final double Function(int, bool)? customStepSize;
|
||||
final double maxDefinedSize;
|
||||
final Color Function(int)? customColor;
|
||||
final CircularDirection circularDirection;
|
||||
final double startingAngle;
|
||||
final double arcSize;
|
||||
final bool Function(int, bool)? roundedCap;
|
||||
final Gradient? gradientColor;
|
||||
final TextDirection textDirection;
|
||||
final bool removeRoundedCapExtraAngle;
|
||||
|
||||
_CircularIndicatorPainter({
|
||||
required this.totalSteps,
|
||||
required this.circularDirection,
|
||||
required this.customColor,
|
||||
required this.currentStep,
|
||||
required this.selectedColor,
|
||||
required this.unselectedColor,
|
||||
required this.padding,
|
||||
required this.stepSize,
|
||||
required this.selectedStepSize,
|
||||
required this.unselectedStepSize,
|
||||
required this.customStepSize,
|
||||
required this.startingAngle,
|
||||
required this.arcSize,
|
||||
required this.maxDefinedSize,
|
||||
required this.roundedCap,
|
||||
required this.gradientColor,
|
||||
required this.textDirection,
|
||||
required this.removeRoundedCapExtraAngle,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
var w = size.width;
|
||||
var h = size.height;
|
||||
|
||||
// Step length is user-defined arcSize
|
||||
// divided by the total number of steps (each step same size)
|
||||
var stepLength = arcSize / totalSteps;
|
||||
|
||||
// Define general arc paint
|
||||
Paint paint = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = maxDefinedSize;
|
||||
|
||||
var rect = Rect.fromCenter(
|
||||
// Rect created from the center of the widget
|
||||
center: Offset(w / 2, h / 2),
|
||||
// For both height and width, subtract maxDefinedSize to fit indicator inside the parent container
|
||||
height: h - maxDefinedSize,
|
||||
width: w - maxDefinedSize,
|
||||
);
|
||||
|
||||
if (gradientColor != null) {
|
||||
paint.shader = gradientColor!.createShader(rect, textDirection: textDirection);
|
||||
}
|
||||
|
||||
// Change color selected or unselected based on the circularDirection
|
||||
var isClockwise = circularDirection == CircularDirection.clockwise;
|
||||
|
||||
// Make a continuous arc without rendering all the steps when possible
|
||||
if (padding == 0 && customColor == null && customStepSize == null && roundedCap == null) {
|
||||
_drawContinuousArc(canvas, paint, rect, isClockwise);
|
||||
} else {
|
||||
_drawStepArc(canvas, paint, rect, isClockwise, stepLength);
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw a series of arcs, each composing the full steps of the indicator
|
||||
void _drawStepArc(Canvas canvas, Paint paint, Rect rect, bool isClockwise, double stepLength) {
|
||||
// Draw a series of circular arcs to compose the indicator
|
||||
// Starting based on startingAngle attribute
|
||||
//
|
||||
// When clockwise:
|
||||
// - Start drawing counterclockwise so to have the selected steps on top of the unselected
|
||||
int step = isClockwise ? totalSteps - 1 : 0;
|
||||
double stepAngle = isClockwise ? startingAngle - stepLength : startingAngle;
|
||||
for (; isClockwise ? step >= 0 : step < totalSteps; isClockwise ? stepAngle -= stepLength : stepAngle += stepLength, isClockwise ? --step : ++step) {
|
||||
// Check if the current step is selected or unselected
|
||||
var isSelectedColor = _isSelectedColor(step, isClockwise);
|
||||
|
||||
// Size of the step
|
||||
var indexStepSize = customStepSize != null
|
||||
// Consider step index inverted when counterclockwise
|
||||
? customStepSize!(_indexOfStep(step, isClockwise), isSelectedColor)
|
||||
: isSelectedColor
|
||||
? selectedStepSize ?? stepSize
|
||||
: unselectedStepSize ?? stepSize;
|
||||
|
||||
// Use customColor if defined
|
||||
var stepColor = customColor != null
|
||||
// Consider step index inverted when counterclockwise
|
||||
? customColor!(_indexOfStep(step, isClockwise))
|
||||
: isSelectedColor
|
||||
? selectedColor!
|
||||
: unselectedColor!;
|
||||
|
||||
// Apply stroke cap to each step
|
||||
var hasStrokeCap = roundedCap != null ? roundedCap!(_indexOfStep(step, isClockwise), isSelectedColor) : false;
|
||||
var strokeCap = hasStrokeCap ? StrokeCap.round : StrokeCap.butt;
|
||||
|
||||
// Remove extra size caused by rounded stroke cap
|
||||
// https://github.com/SandroMaglione/step-progress-indicator/issues/20#issue-786114745
|
||||
var extraCapSize = indexStepSize / 2;
|
||||
var extraCapAngle = extraCapSize / (rect.width / 2);
|
||||
var extraCapRemove = hasStrokeCap && removeRoundedCapExtraAngle;
|
||||
|
||||
// Draw arc steps of the indicator
|
||||
_drawArcOnCanvas(
|
||||
canvas: canvas,
|
||||
rect: rect,
|
||||
startingAngle: stepAngle + (extraCapRemove ? extraCapAngle : 0),
|
||||
sweepAngle: stepLength - padding - (extraCapRemove ? extraCapAngle * 2 : 0),
|
||||
paint: paint,
|
||||
color: stepColor,
|
||||
strokeWidth: indexStepSize,
|
||||
strokeCap: strokeCap,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw optimized continuous indicator instead of multiple steps
|
||||
void _drawContinuousArc(Canvas canvas, Paint paint, Rect rect, bool isClockwise) {
|
||||
// Compute color of the selected and unselected bars
|
||||
var firstStepColor = isClockwise ? selectedColor : unselectedColor;
|
||||
var secondStepColor = !isClockwise ? selectedColor : unselectedColor;
|
||||
|
||||
// Selected and unselected step sizes if defined, otherwise use stepSize
|
||||
var firstStepSize = isClockwise ? selectedStepSize ?? stepSize : unselectedStepSize ?? stepSize;
|
||||
var secondStepSize = !isClockwise ? selectedStepSize ?? stepSize : unselectedStepSize ?? stepSize;
|
||||
|
||||
// Compute length and starting angle of the selected and unselected bars
|
||||
var firstArcLength = arcSize * (currentStep / totalSteps);
|
||||
var secondArcLength = arcSize - firstArcLength;
|
||||
|
||||
// firstArcStartingAngle = startingAngle
|
||||
var secondArcStartingAngle = startingAngle + firstArcLength;
|
||||
|
||||
// Apply stroke cap to both arcs
|
||||
// NOTE: For continuous circular indicator, it uses 0 and 1 as index to
|
||||
// apply the rounded cap
|
||||
var firstArcStrokeCap = roundedCap != null
|
||||
? isClockwise
|
||||
? roundedCap!(0, true)
|
||||
: roundedCap!(1, false)
|
||||
: false;
|
||||
var secondArcStrokeCap = roundedCap != null
|
||||
? isClockwise
|
||||
? roundedCap!(1, false)
|
||||
: roundedCap!(0, true)
|
||||
: false;
|
||||
var firstCap = firstArcStrokeCap ? StrokeCap.round : StrokeCap.butt;
|
||||
var secondCap = secondArcStrokeCap ? StrokeCap.round : StrokeCap.butt;
|
||||
|
||||
// When clockwise, draw the second arc first and the first on top of it
|
||||
// Required when stroke cap is rounded to make the selected step on top of the unselected
|
||||
if (circularDirection == CircularDirection.clockwise) {
|
||||
// Second arc, selected when counterclockwise, unselected otherwise
|
||||
_drawArcOnCanvas(
|
||||
canvas: canvas,
|
||||
rect: rect,
|
||||
paint: paint,
|
||||
startingAngle: secondArcStartingAngle,
|
||||
sweepAngle: secondArcLength,
|
||||
strokeWidth: secondStepSize,
|
||||
color: secondStepColor!,
|
||||
strokeCap: secondCap,
|
||||
);
|
||||
|
||||
// First arc, selected when clockwise, unselected otherwise
|
||||
_drawArcOnCanvas(
|
||||
canvas: canvas,
|
||||
rect: rect,
|
||||
paint: paint,
|
||||
startingAngle: startingAngle,
|
||||
sweepAngle: firstArcLength,
|
||||
strokeWidth: firstStepSize,
|
||||
color: firstStepColor!,
|
||||
strokeCap: firstCap,
|
||||
);
|
||||
} else {
|
||||
// First arc, selected when clockwise, unselected otherwise
|
||||
_drawArcOnCanvas(
|
||||
canvas: canvas,
|
||||
rect: rect,
|
||||
paint: paint,
|
||||
startingAngle: startingAngle,
|
||||
sweepAngle: firstArcLength,
|
||||
strokeWidth: firstStepSize,
|
||||
color: firstStepColor!,
|
||||
strokeCap: firstCap,
|
||||
);
|
||||
|
||||
// Second arc, selected when counterclockwise, unselected otherwise
|
||||
_drawArcOnCanvas(
|
||||
canvas: canvas,
|
||||
rect: rect,
|
||||
paint: paint,
|
||||
startingAngle: secondArcStartingAngle,
|
||||
sweepAngle: secondArcLength,
|
||||
strokeWidth: secondStepSize,
|
||||
color: secondStepColor!,
|
||||
strokeCap: secondCap,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw the actual arc for a continuous indicator
|
||||
void _drawArcOnCanvas({
|
||||
required Canvas canvas,
|
||||
required Rect rect,
|
||||
required double startingAngle,
|
||||
required double sweepAngle,
|
||||
required Paint paint,
|
||||
required Color color,
|
||||
required double strokeWidth,
|
||||
required StrokeCap strokeCap,
|
||||
}) =>
|
||||
canvas.drawArc(
|
||||
rect,
|
||||
startingAngle,
|
||||
sweepAngle,
|
||||
false /*isRadial*/,
|
||||
paint
|
||||
..color = color
|
||||
..strokeWidth = strokeWidth
|
||||
..strokeCap = strokeCap,
|
||||
);
|
||||
|
||||
bool _isSelectedColor(int step, bool isClockwise) => isClockwise ? step < currentStep : (step + 1) > (totalSteps - currentStep);
|
||||
|
||||
/// Start counting indexes from the right if clockwise and on the left if counterclockwise
|
||||
int _indexOfStep(int step, bool isClockwise) => isClockwise ? step : totalSteps - step - 1;
|
||||
|
||||
@override
|
||||
bool shouldRepaint(CustomPainter oldDelegate) => oldDelegate != this;
|
||||
|
||||
@override
|
||||
bool hitTest(Offset position) => false;
|
||||
|
||||
@override
|
||||
void addListener(listener) {}
|
||||
|
||||
@override
|
||||
void removeListener(listener) {}
|
||||
|
||||
@override
|
||||
Null get semanticsBuilder => null;
|
||||
|
||||
@override
|
||||
bool shouldRebuildSemantics(CustomPainter oldDelegate) => false;
|
||||
}
|
||||
|
||||
/// Used to define the [circularDirection] attribute of the [CircularStepProgressBar]
|
||||
enum CircularDirection {
|
||||
clockwise,
|
||||
counterclockwise,
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
import 'package:easy_localization/src/public_ext.dart';
|
||||
import 'package:flutter/material.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/widgets/button/default_button.dart';
|
||||
|
||||
class ConfirmDialog extends StatelessWidget {
|
||||
final String? title;
|
||||
final String message;
|
||||
final String? okTitle;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const ConfirmDialog({Key? key, this.title, required this.message, this.okTitle, this.onTap}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Dialog(
|
||||
backgroundColor: Colors.white,
|
||||
shape: const RoundedRectangleBorder(),
|
||||
insetPadding: const EdgeInsets.only(left: 21, right: 21),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 20, right: 20, top: 18, bottom: 28),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
title ?? LocaleKeys.confirm.tr(),
|
||||
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.w600, color: MyColors.darkTextColor, height: 35 / 24, letterSpacing: -0.96),
|
||||
).paddingOnly(top: 16),
|
||||
),
|
||||
IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
icon: const Icon(Icons.close),
|
||||
color: MyColors.darkTextColor,
|
||||
constraints: const BoxConstraints(),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
message.toText16(color: MyColors.lightGrayColor),
|
||||
28.height,
|
||||
DefaultButton(
|
||||
okTitle ?? LocaleKeys.ok.tr(),
|
||||
onTap ?? () => Navigator.pop(context),
|
||||
textColor: Colors.white,
|
||||
//color: Ap.green,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void showMDialog(context, {Widget? child,Color? backgroundColor,bool isDismissable=true}) async {
|
||||
return showDialog(
|
||||
context: context,
|
||||
barrierDismissible: isDismissable,
|
||||
builder: (context) {
|
||||
return Dialog(
|
||||
backgroundColor: backgroundColor,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,142 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mohem_flutter_app/classes/colors.dart';
|
||||
import 'package:mohem_flutter_app/extensions/int_extensions.dart';
|
||||
import 'package:mohem_flutter_app/extensions/widget_extensions.dart';
|
||||
|
||||
class InputWidget extends StatefulWidget {
|
||||
final String labelText;
|
||||
final String hintText;
|
||||
final TextEditingController controller;
|
||||
final VoidCallback? suffixTap;
|
||||
final bool isEnable;
|
||||
final bool hasSelection;
|
||||
final int? lines;
|
||||
final bool isInputTypeNum;
|
||||
final bool isTextIsPassword;
|
||||
final bool isBackgroundEnable;
|
||||
final bool isEnableBorder;
|
||||
final double verticalPadding;
|
||||
final double horizontalPadding;
|
||||
final Function(String)? onChange;
|
||||
|
||||
InputWidget(
|
||||
this.labelText,
|
||||
this.hintText,
|
||||
this.controller, {
|
||||
Key? key,
|
||||
this.isTextIsPassword = false,
|
||||
this.suffixTap,
|
||||
this.isEnable = true,
|
||||
this.hasSelection = false,
|
||||
this.isEnableBorder = true,
|
||||
this.lines = 1,
|
||||
this.onChange,
|
||||
this.isInputTypeNum = false,
|
||||
this.isBackgroundEnable = false,
|
||||
this.verticalPadding = 15,
|
||||
this.horizontalPadding = 16,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_InputWidgetState createState() {
|
||||
return _InputWidgetState();
|
||||
}
|
||||
}
|
||||
|
||||
class _InputWidgetState extends State<InputWidget> {
|
||||
late bool isObscureText;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
isObscureText = widget.isTextIsPassword;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(left: widget.horizontalPadding, right: widget.horizontalPadding, bottom: widget.verticalPadding, top: widget.verticalPadding),
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
color: widget.isBackgroundEnable ? Color(0xffF7F7F7) : Colors.white,
|
||||
border: Border.all(
|
||||
color: widget.isEnableBorder ? Color(0xffefefef) : Colors.transparent,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: widget.hasSelection ? () {} : null,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
widget.labelText,
|
||||
style: const TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xff2B353E),
|
||||
letterSpacing: -0.44,
|
||||
),
|
||||
),
|
||||
TextField(
|
||||
enabled: widget.isEnable,
|
||||
scrollPadding: EdgeInsets.zero,
|
||||
keyboardType: widget.isInputTypeNum ? TextInputType.number : TextInputType.text,
|
||||
controller: widget.controller,
|
||||
maxLines: widget.lines,
|
||||
obscuringCharacter: "*",
|
||||
obscureText: isObscureText,
|
||||
onChanged: widget.onChange,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
height: 21 / 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Color(0xff2B353E),
|
||||
letterSpacing: -0.44,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
hintText: widget.hintText,
|
||||
hintStyle: const TextStyle(
|
||||
fontSize: 14,
|
||||
height: 21 / 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Color(0xff575757),
|
||||
letterSpacing: -0.56,
|
||||
),
|
||||
suffixIconConstraints: const BoxConstraints(minWidth: 50),
|
||||
suffixIcon: widget.suffixTap == null ? null : IconButton(icon: const Icon(Icons.mic, color: MyColors.darkTextColor), onPressed: widget.suffixTap),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
border: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (widget.isTextIsPassword) ...[
|
||||
16.width,
|
||||
Icon(isObscureText ? Icons.visibility_rounded : Icons.visibility_off_rounded).onPress(() {
|
||||
setState(() {
|
||||
isObscureText = !isObscureText;
|
||||
});
|
||||
})
|
||||
],
|
||||
if (widget.hasSelection) Icon(Icons.keyboard_arrow_down_outlined),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,150 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.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/models/itg_forms_models/itg_worklist_table_model.dart';
|
||||
|
||||
class ItemDetailView extends StatelessWidget {
|
||||
final String title;
|
||||
final String value;
|
||||
|
||||
const ItemDetailView(this.title, this.value, {Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
"$title:".toText12(isBold: true, color: const Color(0xff2D3238)),
|
||||
6.width,
|
||||
(value.isEmpty ? "--" : value).toText12(color: MyColors.normalTextColor).expanded,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ItemDetailViewCol extends StatelessWidget {
|
||||
final String title;
|
||||
final String value;
|
||||
|
||||
const ItemDetailViewCol(this.title, this.value, {Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
"$title:".toText12(isBold: true, color: const Color(0xff2BB8A6)),
|
||||
4.width,
|
||||
(value.isEmpty ? "--" : value).toText12(color: MyColors.normalTextColor),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ItemDetailViewGridItem extends StatelessWidget {
|
||||
int index;
|
||||
final String? title;
|
||||
final String? value;
|
||||
final String? type;
|
||||
final bool isNeedToShowEmptyDivider;
|
||||
|
||||
ItemDetailViewGridItem(this.index, this.title, this.value, {Key? key, this.isNeedToShowEmptyDivider = false, this.type = ""}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(left: index % 2 != 0 ? 12 : 0),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
border: Border(
|
||||
left: BorderSide(
|
||||
//MyColors.lightGreyEFColor
|
||||
// <--- left side
|
||||
color: index % 2 != 0 ? MyColors.lightGreyEFColor : Colors.transparent,
|
||||
width: 1.5,
|
||||
),
|
||||
top: BorderSide(
|
||||
// <--- left side
|
||||
color: index > 1 ? MyColors.lightGreyEFColor : Colors.transparent,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: isNeedToShowEmptyDivider
|
||||
? Container()
|
||||
: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
title != null ? Flexible(child: "$title:".toText12Auto(isBold: true, color: const Color(0xff2BB8A6))) : Container(),
|
||||
4.width,
|
||||
type != null
|
||||
? type!.toLowerCase() == "table"
|
||||
? getStringFromJSON(value!)
|
||||
: Flexible(child: (value!.isEmpty ? "--" : value).toString().toText12Auto(color: MyColors.normalTextColor))
|
||||
: Container(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ItemDetailGrid extends StatelessWidget {
|
||||
Widget child1, child2;
|
||||
|
||||
bool isItLast;
|
||||
|
||||
ItemDetailGrid(this.child1, this.child2, {this.isItLast = false});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IntrinsicHeight(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: child1,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: 1,
|
||||
height: double.infinity,
|
||||
color: MyColors.lightGreyEFColor,
|
||||
margin: EdgeInsets.symmetric(horizontal: 8),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: child2,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (!isItLast) 1.divider,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget getStringFromJSON(String jsonString) {
|
||||
var body = json.decode(jsonString);
|
||||
ITGWorkListTableModel itgWorkListTableModel = ITGWorkListTableModel();
|
||||
if (body.length != 0) {
|
||||
itgWorkListTableModel = ITGWorkListTableModel.fromJson(body[0][0]);
|
||||
return Flexible(child: (itgWorkListTableModel.textvalue).toString().toText12Auto(color: MyColors.normalTextColor));
|
||||
} else {
|
||||
return Flexible(child: ("-").toString().toText12Auto(color: MyColors.normalTextColor));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
class LoadingDialog extends StatefulWidget {
|
||||
LoadingDialog({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_LoadingDialogState createState() {
|
||||
return _LoadingDialogState();
|
||||
}
|
||||
}
|
||||
|
||||
class _LoadingDialogState extends State<LoadingDialog> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Dialog(
|
||||
insetPadding: const EdgeInsets.symmetric(horizontal: 60.0, vertical: 24.0),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.rtl,
|
||||
child: Center(
|
||||
child: Image.asset(
|
||||
"assets/images/logos/loading_mohemm_logo.gif",
|
||||
height: 96.0,
|
||||
width: 96.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,373 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/animation.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
typedef OnDone = void Function(String text);
|
||||
|
||||
class ProvidedPinBoxTextAnimation {
|
||||
static AnimatedSwitcherTransitionBuilder scalingTransition = (child, animation) {
|
||||
return ScaleTransition(
|
||||
child: child,
|
||||
scale: animation,
|
||||
);
|
||||
};
|
||||
|
||||
static AnimatedSwitcherTransitionBuilder defaultNoTransition = (Widget child, Animation<double> animation) {
|
||||
return child;
|
||||
};
|
||||
}
|
||||
|
||||
class OTPWidget extends StatefulWidget {
|
||||
final int maxLength;
|
||||
final TextEditingController? controller;
|
||||
|
||||
final Color defaultBorderColor;
|
||||
final Color pinBoxColor;
|
||||
final double pinBoxBorderWidth;
|
||||
final double pinBoxRadius;
|
||||
final bool hideDefaultKeyboard;
|
||||
|
||||
final TextStyle? pinTextStyle;
|
||||
final double pinBoxHeight;
|
||||
final double pinBoxWidth;
|
||||
final OnDone? onDone;
|
||||
final bool hasError;
|
||||
final Color errorBorderColor;
|
||||
final Color textBorderColor;
|
||||
final Function(String)? onTextChanged;
|
||||
final bool autoFocus;
|
||||
final FocusNode? focusNode;
|
||||
final AnimatedSwitcherTransitionBuilder? pinTextAnimatedSwitcherTransition;
|
||||
final Duration pinTextAnimatedSwitcherDuration;
|
||||
final TextDirection textDirection;
|
||||
final TextInputType keyboardType;
|
||||
final EdgeInsets pinBoxOuterPadding;
|
||||
|
||||
const OTPWidget({
|
||||
Key? key,
|
||||
this.maxLength: 4,
|
||||
this.controller,
|
||||
this.pinBoxWidth: 70.0,
|
||||
this.pinBoxHeight: 70.0,
|
||||
this.pinTextStyle,
|
||||
this.onDone,
|
||||
this.defaultBorderColor: Colors.black,
|
||||
this.textBorderColor: Colors.black,
|
||||
this.pinTextAnimatedSwitcherTransition,
|
||||
this.pinTextAnimatedSwitcherDuration: const Duration(),
|
||||
this.hasError: false,
|
||||
this.errorBorderColor: Colors.red,
|
||||
this.onTextChanged,
|
||||
this.autoFocus: false,
|
||||
this.focusNode,
|
||||
this.textDirection: TextDirection.ltr,
|
||||
this.keyboardType: TextInputType.number,
|
||||
this.pinBoxOuterPadding = const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
this.pinBoxColor = Colors.white,
|
||||
this.pinBoxBorderWidth = 2.0,
|
||||
this.pinBoxRadius = 0,
|
||||
this.hideDefaultKeyboard = false,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return OTPWidgetState();
|
||||
}
|
||||
}
|
||||
|
||||
class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixin {
|
||||
AnimationController? _highlightAnimationController;
|
||||
FocusNode? focusNode;
|
||||
String text = "";
|
||||
int currentIndex = 0;
|
||||
List<String> strList = [];
|
||||
bool hasFocus = false;
|
||||
|
||||
@override
|
||||
void didUpdateWidget(OTPWidget oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
focusNode = widget.focusNode ?? focusNode;
|
||||
|
||||
if (oldWidget.maxLength < widget.maxLength) {
|
||||
setState(() {
|
||||
currentIndex = text.length;
|
||||
});
|
||||
widget.controller?.text = text;
|
||||
widget.controller?.selection = TextSelection.collapsed(offset: text.length);
|
||||
} else if (oldWidget.maxLength > widget.maxLength && widget.maxLength > 0 && text.length > 0 && text.length > widget.maxLength) {
|
||||
setState(() {
|
||||
text = text.substring(0, widget.maxLength);
|
||||
currentIndex = text.length;
|
||||
});
|
||||
widget.controller?.text = text;
|
||||
widget.controller?.selection = TextSelection.collapsed(offset: text.length);
|
||||
}
|
||||
}
|
||||
|
||||
void calculateStrList() {
|
||||
if (strList.length > widget.maxLength) {
|
||||
strList.length = widget.maxLength;
|
||||
}
|
||||
while (strList.length < widget.maxLength) {
|
||||
strList.add("");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
focusNode = widget.focusNode ?? FocusNode();
|
||||
|
||||
_initTextController();
|
||||
calculateStrList();
|
||||
widget.controller?.addListener(_controllerListener);
|
||||
focusNode?.addListener(_focusListener);
|
||||
}
|
||||
|
||||
void _controllerListener() {
|
||||
if (mounted == true) {
|
||||
setState(() {
|
||||
_initTextController();
|
||||
});
|
||||
var onTextChanged = widget.onTextChanged;
|
||||
if (onTextChanged != null) {
|
||||
onTextChanged(widget.controller?.text ?? "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _focusListener() {
|
||||
if (mounted == true) {
|
||||
setState(() {
|
||||
hasFocus = focusNode?.hasFocus ?? false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _initTextController() {
|
||||
if (widget.controller == null) {
|
||||
return;
|
||||
}
|
||||
strList.clear();
|
||||
var text = widget.controller?.text ?? "";
|
||||
if (text.isNotEmpty) {
|
||||
if (text.length > widget.maxLength) {
|
||||
throw Exception("TextEditingController length exceeded maxLength!");
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
strList.add(text[i]);
|
||||
}
|
||||
}
|
||||
|
||||
double get _width {
|
||||
var width = 0.0;
|
||||
for (var i = 0; i < widget.maxLength; i++) {
|
||||
width += widget.pinBoxWidth;
|
||||
if (i == 0) {
|
||||
width += widget.pinBoxOuterPadding.left;
|
||||
} else if (i + 1 == widget.maxLength) {
|
||||
width += widget.pinBoxOuterPadding.right;
|
||||
} else {
|
||||
width += widget.pinBoxOuterPadding.left;
|
||||
}
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (widget.focusNode == null) {
|
||||
focusNode?.dispose();
|
||||
} else {
|
||||
focusNode?.removeListener(_focusListener);
|
||||
}
|
||||
_highlightAnimationController?.dispose();
|
||||
widget.controller?.removeListener(_controllerListener);
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: <Widget>[
|
||||
_otpTextInput(),
|
||||
_touchPinBoxRow(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _touchPinBoxRow() {
|
||||
return widget.hideDefaultKeyboard
|
||||
? _pinBoxRow(context)
|
||||
: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
if (hasFocus) {
|
||||
FocusScope.of(context).requestFocus(FocusNode());
|
||||
Future.delayed(Duration(milliseconds: 100), () {
|
||||
FocusScope.of(context).requestFocus(focusNode);
|
||||
});
|
||||
} else {
|
||||
FocusScope.of(context).requestFocus(focusNode);
|
||||
}
|
||||
},
|
||||
child: _pinBoxRow(context),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _otpTextInput() {
|
||||
var transparentBorder = OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Colors.transparent,
|
||||
width: 0.0,
|
||||
),
|
||||
);
|
||||
return Container(
|
||||
width: _width,
|
||||
height: widget.pinBoxHeight,
|
||||
child: TextField(
|
||||
autofocus: !kIsWeb ? widget.autoFocus : false,
|
||||
enableInteractiveSelection: false,
|
||||
focusNode: focusNode,
|
||||
controller: widget.controller,
|
||||
keyboardType: widget.keyboardType,
|
||||
inputFormatters: widget.keyboardType == TextInputType.number ? <TextInputFormatter>[FilteringTextInputFormatter.digitsOnly] : null,
|
||||
style: TextStyle(
|
||||
height: 0.1,
|
||||
color: Colors.transparent,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
contentPadding: EdgeInsets.all(0),
|
||||
focusedErrorBorder: transparentBorder,
|
||||
errorBorder: transparentBorder,
|
||||
disabledBorder: transparentBorder,
|
||||
enabledBorder: transparentBorder,
|
||||
focusedBorder: transparentBorder,
|
||||
counterText: null,
|
||||
counterStyle: null,
|
||||
helperStyle: TextStyle(
|
||||
height: 0.0,
|
||||
color: Colors.transparent,
|
||||
),
|
||||
labelStyle: TextStyle(height: 0.1),
|
||||
fillColor: Colors.transparent,
|
||||
border: InputBorder.none,
|
||||
),
|
||||
cursorColor: Colors.transparent,
|
||||
showCursor: false,
|
||||
maxLength: widget.maxLength,
|
||||
onChanged: _onTextChanged,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onTextChanged(text) {
|
||||
var onTextChanged = widget.onTextChanged;
|
||||
if (onTextChanged != null) {
|
||||
onTextChanged(text);
|
||||
}
|
||||
setState(() {
|
||||
this.text = text;
|
||||
if (text.length >= currentIndex) {
|
||||
for (int i = currentIndex; i < text.length; i++) {
|
||||
strList[i] = text[i];
|
||||
}
|
||||
}
|
||||
currentIndex = text.length;
|
||||
});
|
||||
if (text.length == widget.maxLength) {
|
||||
FocusScope.of(context).requestFocus(FocusNode());
|
||||
var onDone = widget.onDone;
|
||||
if (onDone != null) {
|
||||
onDone(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Widget _pinBoxRow(BuildContext context) {
|
||||
calculateStrList();
|
||||
List<Widget> pinCodes = List.generate(widget.maxLength, (int i) {
|
||||
return _buildPinCode(i, context);
|
||||
});
|
||||
return Row(children: pinCodes, mainAxisSize: MainAxisSize.min);
|
||||
}
|
||||
|
||||
Widget _buildPinCode(int i, BuildContext context) {
|
||||
Color borderColor;
|
||||
Color pinBoxColor = widget.pinBoxColor;
|
||||
|
||||
if (widget.hasError) {
|
||||
borderColor = widget.errorBorderColor;
|
||||
} else if (i < text.length) {
|
||||
borderColor = widget.textBorderColor;
|
||||
} else {
|
||||
borderColor = widget.defaultBorderColor;
|
||||
pinBoxColor = widget.pinBoxColor;
|
||||
}
|
||||
|
||||
EdgeInsets insets;
|
||||
if (i == 0) {
|
||||
insets = EdgeInsets.only(
|
||||
left: 0,
|
||||
top: widget.pinBoxOuterPadding.top,
|
||||
right: widget.pinBoxOuterPadding.right,
|
||||
bottom: widget.pinBoxOuterPadding.bottom,
|
||||
);
|
||||
} else if (i == strList.length - 1) {
|
||||
insets = EdgeInsets.only(
|
||||
left: widget.pinBoxOuterPadding.left,
|
||||
top: widget.pinBoxOuterPadding.top,
|
||||
right: 0,
|
||||
bottom: widget.pinBoxOuterPadding.bottom,
|
||||
);
|
||||
} else {
|
||||
insets = widget.pinBoxOuterPadding;
|
||||
}
|
||||
return Container(
|
||||
key: ValueKey<String>("container$i"),
|
||||
alignment: Alignment.center,
|
||||
padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 1.0),
|
||||
margin: insets,
|
||||
child: _animatedTextBox(strList[i], i),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: borderColor,
|
||||
width: widget.pinBoxBorderWidth,
|
||||
),
|
||||
color: pinBoxColor,
|
||||
borderRadius: BorderRadius.circular(widget.pinBoxRadius),
|
||||
),
|
||||
width: widget.pinBoxWidth,
|
||||
height: widget.pinBoxHeight,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _animatedTextBox(String text, int i) {
|
||||
if (widget.pinTextAnimatedSwitcherTransition != null) {
|
||||
return AnimatedSwitcher(
|
||||
duration: widget.pinTextAnimatedSwitcherDuration,
|
||||
transitionBuilder: widget.pinTextAnimatedSwitcherTransition ??
|
||||
(Widget child, Animation<double> animation) {
|
||||
return child;
|
||||
},
|
||||
child: Text(
|
||||
text,
|
||||
key: ValueKey<String>("$text$i"),
|
||||
style: widget.pinTextStyle,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Text(
|
||||
text,
|
||||
key: ValueKey<String>("${strList[i]}$i"),
|
||||
style: widget.pinTextStyle,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
import 'package:flutter/material.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';
|
||||
|
||||
class ShowRadio extends StatelessWidget {
|
||||
String title, value, groupValue;
|
||||
Color selectedColor;
|
||||
|
||||
ShowRadio({
|
||||
required this.title,
|
||||
required this.value,
|
||||
required this.groupValue,
|
||||
this.selectedColor = Colors.black,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 24,
|
||||
height: 24,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
border: Border.all(color: MyColors.grey98Color, width: 0.5),
|
||||
borderRadius: BorderRadius.all(Radius.circular(100)),
|
||||
),
|
||||
padding: EdgeInsets.all(4),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: value == groupValue ? selectedColor : Colors.transparent,
|
||||
borderRadius: BorderRadius.all(Radius.circular(100)),
|
||||
),
|
||||
),
|
||||
),
|
||||
12.width,
|
||||
title.toText12(maxLine: 2)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,256 @@
|
||||
import 'package:easy_localization/src/public_ext.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.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:shimmer/shimmer.dart';
|
||||
|
||||
class GetAttendanceTrackingShimmer extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
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: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
// SvgPicture.asset("assets/images/"),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
LocaleKeys.markAttendance.tr().toText14(color: Colors.white, isBold: true).toShimmer(),
|
||||
16.height,
|
||||
"07:55:12".toText10(color: Colors.white, isBold: true).toShimmer(),
|
||||
3.height,
|
||||
LocaleKeys.timeLeftToday.tr().toText10(color: Colors.white).toShimmer(),
|
||||
9.height,
|
||||
const ClipRRect(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(20),
|
||||
),
|
||||
child: LinearProgressIndicator(
|
||||
value: 0.7,
|
||||
minHeight: 8,
|
||||
valueColor: const AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
backgroundColor: const Color(0xff196D73),
|
||||
),
|
||||
).toShimmer(),
|
||||
],
|
||||
).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).toShimmer(),
|
||||
],
|
||||
).paddingOnly(left: 12),
|
||||
),
|
||||
Container(
|
||||
width: 45,
|
||||
height: 45,
|
||||
// color: Colors.blue,
|
||||
padding: const EdgeInsets.only(left: 14, right: 14),
|
||||
).toShimmer(),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MenuShimmer extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
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(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
LocaleKeys.workList.tr().toText12(color: Colors.white).toShimmer(),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: 123.toString().toText10(color: Colors.white, isBold: true).toShimmer(),
|
||||
),
|
||||
12.width,
|
||||
SvgPicture.asset("assets/images/arrow_next.svg", color: Colors.white).toShimmer()
|
||||
],
|
||||
)
|
||||
],
|
||||
).paddingOnly(left: 10, right: 10, bottom: 6, top: 6),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ServicesHeaderShimmer extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
"Other".tr().toText10().toShimmer(),
|
||||
6.height,
|
||||
LocaleKeys.services.tr().toText12(isBold: true).toShimmer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
LocaleKeys.viewAllServices.tr().toText12(isUnderLine: true).toShimmer(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ServicesMenuShimmer extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
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(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SvgPicture.asset("assets/images/monthly_attendance.svg").toShimmer(),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
"Attendan".toText11(isBold: false).toShimmer(),
|
||||
5.height,
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Expanded(
|
||||
child: LocaleKeys.attendance.tr().toText11(isBold: false).toShimmer(),
|
||||
),
|
||||
6.width,
|
||||
SvgPicture.asset("assets/images/arrow_next.svg").paddingOnly(bottom: 4).toShimmer()
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
).paddingOnly(left: 10, right: 10, bottom: 10, top: 12),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ChatHomeShimmer extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Shimmer.fromColors(
|
||||
baseColor: Colors.white,
|
||||
highlightColor: Colors.grey.shade100,
|
||||
child: ListView.builder(
|
||||
itemBuilder: (_, __) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
width: 48.0,
|
||||
height: 48.0,
|
||||
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(40))),
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8.0),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
width: double.infinity,
|
||||
height: 8.0,
|
||||
color: Colors.white,
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 2.0),
|
||||
),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
height: 8.0,
|
||||
color: Colors.white,
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 2.0),
|
||||
),
|
||||
Container(
|
||||
width: 40.0,
|
||||
height: 8.0,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
itemCount: 6,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,194 @@
|
||||
// import 'dart:async';
|
||||
//
|
||||
// import 'package:flutter/material.dart';
|
||||
//
|
||||
//
|
||||
// void showNfcReader(BuildContext context, {Function onNcfScan}) {
|
||||
// showModalBottomSheet(
|
||||
// context: context,
|
||||
// enableDrag: false,
|
||||
// isDismissible: false,
|
||||
// shape: RoundedRectangleBorder(
|
||||
// borderRadius: BorderRadius.only(topLeft: Radius.circular(12), topRight: Radius.circular(12)),
|
||||
// ),
|
||||
// backgroundColor: Colors.white,
|
||||
// builder: (context) {
|
||||
// return NfcLayout(
|
||||
// onNcfScan: onNcfScan,
|
||||
// );
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// class NfcLayout extends StatefulWidget {
|
||||
// Function onNcfScan;
|
||||
//
|
||||
// NfcLayout({this.onNcfScan});
|
||||
//
|
||||
// @override
|
||||
// _NfcLayoutState createState() => _NfcLayoutState();
|
||||
// }
|
||||
//
|
||||
// class _NfcLayoutState extends State<NfcLayout> {
|
||||
// StreamSubscription<NDEFMessage> _stream;
|
||||
// bool _reading = false;
|
||||
// Widget mainWidget;
|
||||
// String nfcId;
|
||||
//
|
||||
// @override
|
||||
// void initState() {
|
||||
// super.initState();
|
||||
//
|
||||
// setState(() {
|
||||
// // _reading = true;
|
||||
// // Start reading using NFC.readNDEF()
|
||||
// _stream = NFC.readNDEF(once: false, throwOnUserCancel: false, readerMode: NFCDispatchReaderMode()).listen((NDEFMessage message) {
|
||||
// setState(() {
|
||||
// _reading = true;
|
||||
// mainWidget = doneNfc();
|
||||
// });
|
||||
// Future.delayed(const Duration(milliseconds: 500), () {
|
||||
// _stream?.cancel();
|
||||
// widget.onNcfScan(nfcId);
|
||||
// Navigator.pop(context);
|
||||
// });
|
||||
// print("read NDEF id: ${message.id}");
|
||||
// print("NFC Record " + message.payload);
|
||||
// print("NFC Record Lenght " + message.records.length.toString());
|
||||
// print("NFC Record " + message.records.first.id);
|
||||
// print("NFC Record " + message.records.first.payload);
|
||||
// print("NFC Record " + message.records.first.data);
|
||||
// print("NFC Record " + message.records.first.type);
|
||||
// // widget.onNcfScan(message.id);
|
||||
// nfcId = message.id;
|
||||
// }, onError: (e) {
|
||||
// // Check error handling guide below
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// (mainWidget == null && !_reading) ? mainWidget = scanNfc() : mainWidget = doneNfc();
|
||||
// return AnimatedSwitcher(duration: Duration(milliseconds: 500), child: mainWidget);
|
||||
// }
|
||||
//
|
||||
// Widget scanNfc() {
|
||||
// return Container(
|
||||
// key: ValueKey(1),
|
||||
// child: Column(
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
// children: <Widget>[
|
||||
// SizedBox(
|
||||
// height: 30,
|
||||
// ),
|
||||
// Text(
|
||||
// "Ready To Scan",
|
||||
// style: TextStyle(
|
||||
// fontWeight: FontWeight.bold,
|
||||
// fontSize: 24,
|
||||
// ),
|
||||
// ),
|
||||
// SizedBox(
|
||||
// height: 30,
|
||||
// ),
|
||||
// Image.asset(
|
||||
// "assets/images/nfc/ic_nfc.png",
|
||||
// height: MediaQuery.of(context).size.width / 3,
|
||||
// ),
|
||||
// SizedBox(
|
||||
// height: 30,
|
||||
// ),
|
||||
// Text(
|
||||
// "Approach an NFC Tag",
|
||||
// style: TextStyle(
|
||||
// fontSize: 18,
|
||||
// ),
|
||||
// ),
|
||||
// SizedBox(
|
||||
// height: 30,
|
||||
// ),
|
||||
// ButtonTheme(
|
||||
// minWidth: MediaQuery.of(context).size.width / 1.2,
|
||||
// height: 45.0,
|
||||
// buttonColor: Colors.grey[300],
|
||||
// shape: RoundedRectangleBorder(
|
||||
// borderRadius: BorderRadius.circular(6),
|
||||
// ),
|
||||
// child: RaisedButton(
|
||||
// onPressed: () {
|
||||
// _stream?.cancel();
|
||||
// Navigator.pop(context);
|
||||
// },
|
||||
// elevation: 0,
|
||||
// child: Text("CANCEL"),
|
||||
// ),
|
||||
// ),
|
||||
// SizedBox(
|
||||
// height: 30,
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// Widget doneNfc() {
|
||||
// return Container(
|
||||
// key: ValueKey(2),
|
||||
// child: Column(
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
// children: <Widget>[
|
||||
// SizedBox(
|
||||
// height: 30,
|
||||
// ),
|
||||
// Text(
|
||||
// "Successfully Scanned",
|
||||
// style: TextStyle(
|
||||
// fontWeight: FontWeight.bold,
|
||||
// fontSize: 24,
|
||||
// ),
|
||||
// ),
|
||||
// SizedBox(
|
||||
// height: 30,
|
||||
// ),
|
||||
// Image.asset(
|
||||
// "assets/images/nfc/ic_done.png",
|
||||
// height: MediaQuery.of(context).size.width / 3,
|
||||
// ),
|
||||
// SizedBox(
|
||||
// height: 30,
|
||||
// ),
|
||||
// Text(
|
||||
// "Approach an NFC Tag",
|
||||
// style: TextStyle(
|
||||
// fontSize: 18,
|
||||
// ),
|
||||
// ),
|
||||
// SizedBox(
|
||||
// height: 30,
|
||||
// ),
|
||||
// ButtonTheme(
|
||||
// minWidth: MediaQuery.of(context).size.width / 1.2,
|
||||
// height: 45.0,
|
||||
// buttonColor: Colors.grey[300],
|
||||
// shape: RoundedRectangleBorder(
|
||||
// borderRadius: BorderRadius.circular(6),
|
||||
// ),
|
||||
// child: RaisedButton(
|
||||
// // onPressed: () {
|
||||
// // _stream?.cancel();
|
||||
// // widget.onNcfScan(nfcId);
|
||||
// // Navigator.pop(context);
|
||||
// // },
|
||||
// onPressed: null,
|
||||
// elevation: 0,
|
||||
// child: Text("DONE"),
|
||||
// ),
|
||||
// ),
|
||||
// SizedBox(
|
||||
// height: 30,
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
Loading…
Reference in New Issue