Merge branch 'master' of http://34.17.182.140/Haroon6138/mohemm-flutter-app into master_mohemm_flutter

master
haroon amjad 13 hours ago
commit bd6ead1873

@ -4,6 +4,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.CAMERA" />

@ -4,5 +4,5 @@
<external-files-path name="external-files-path" path="."/>
<files-path name="files_path" path="."/>
<cache-path name="cache-path" path="."/>
<root-path name="root" path="."/>
<!-- <root-path name="root" path="."/>-->
</paths>

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="system" />
</trust-anchors>

@ -66,6 +66,20 @@ allprojects {
jvmTarget = "21"
}
}
if (project.name == 'nfc_manager') {
project.tasks.matching { it.name.startsWith("compile") && it.name.endsWith("Kotlin") }.configureEach {
doFirst {
def translatorFile = project.file("src/main/kotlin/io/flutter/plugins/nfcmanager/Translator.kt")
if (translatorFile.exists()) {
def content = translatorFile.text
if (content.contains("toLowerCase(Locale.ROOT)")) {
translatorFile.text = content.replace("toLowerCase(Locale.ROOT)", "lowercase(Locale.ROOT)")
}
}
}
}
}
}
}
}

@ -0,0 +1,4 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.3315 5.33146C10.5145 5.1484 10.5145 4.8516 10.3315 4.66854C10.1484 4.48549 9.8516 4.48549 9.66854 4.66854L7.50009 6.83699L5.95644 5.29352C5.77337 5.11048 5.47657 5.11049 5.29352 5.29356C5.11048 5.47663 5.11049 5.77343 5.29356 5.95648L6.83718 7.49991L6.54354 7.79354C6.36049 7.9766 6.36049 8.2734 6.54354 8.45646C6.7266 8.63951 7.0234 8.63951 7.20646 8.45646L7.50013 8.16278L7.79364 8.45625C7.97671 8.6393 8.27351 8.63928 8.45655 8.45621C8.6396 8.27314 8.63958 7.97634 8.45651 7.79329L8.16305 7.49987L10.3315 5.33146Z" fill="#047857"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.53573 1.09375H7.46427C6.09508 1.09374 5.01965 1.09374 4.18001 1.20662C3.3196 1.3223 2.63694 1.5642 2.10057 2.10057C1.5642 2.63694 1.3223 3.3196 1.20662 4.18001C1.09374 5.01965 1.09374 6.09508 1.09375 7.46427V7.53573C1.09374 8.90492 1.09374 9.98035 1.20662 10.82C1.3223 11.6804 1.5642 12.3631 2.10057 12.8994C2.63694 13.4358 3.3196 13.6777 4.18001 13.7934C5.01965 13.9063 6.09507 13.9063 7.46425 13.9062H7.53573C8.90491 13.9063 9.98035 13.9063 10.82 13.7934C11.6804 13.6777 12.3631 13.4358 12.8994 12.8994C13.4358 12.3631 13.6777 11.6804 13.7934 10.82C13.9063 9.98035 13.9063 8.90493 13.9062 7.53575V7.46427C13.9063 6.09509 13.9063 5.01965 13.7934 4.18001C13.6777 3.3196 13.4358 2.63694 12.8994 2.10057C12.3631 1.5642 11.6804 1.3223 10.82 1.20662C9.98035 1.09374 8.90492 1.09374 7.53573 1.09375ZM2.76348 2.76348C3.09664 2.43033 3.54848 2.23746 4.30493 2.13576C5.07487 2.03225 6.08727 2.03125 7.5 2.03125C8.91273 2.03125 9.92513 2.03225 10.6951 2.13576C11.4515 2.23746 11.9034 2.43033 12.2365 2.76348C12.5697 3.09664 12.7625 3.54848 12.8642 4.30493C12.9678 5.07487 12.9688 6.08727 12.9688 7.5C12.9688 8.91273 12.9678 9.92513 12.8642 10.6951C12.7625 11.4515 12.5697 11.9034 12.2365 12.2365C11.9034 12.5697 11.4515 12.7625 10.6951 12.8642C9.92513 12.9678 8.91273 12.9688 7.5 12.9688C6.08727 12.9688 5.07487 12.9678 4.30493 12.8642C3.54848 12.7625 3.09664 12.5697 2.76348 12.2365C2.43033 11.9034 2.23746 11.4515 2.13576 10.6951C2.03225 9.92513 2.03125 8.91273 2.03125 7.5C2.03125 6.08727 2.03225 5.07487 2.13576 4.30493C2.23746 3.54848 2.43033 3.09664 2.76348 2.76348Z" fill="#047857"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.8331 11.6668C5.83305 11.7752 5.86321 11.8815 5.9202 11.9737C5.97718 12.066 6.05874 12.1405 6.15572 12.1889L7.3225 12.7723C7.41146 12.8167 7.51031 12.8377 7.60967 12.8332C7.70902 12.8287 7.80557 12.7989 7.89015 12.7466C7.97473 12.6943 8.04454 12.6212 8.09293 12.5343C8.14133 12.4474 8.16671 12.3496 8.16666 12.2502V8.16678C8.16679 7.87766 8.27427 7.5989 8.46827 7.38452L12.6821 2.72418C12.7576 2.64051 12.8073 2.53675 12.8251 2.42544C12.8429 2.31414 12.828 2.20007 12.7824 2.09702C12.7367 1.99396 12.6622 1.90635 12.5678 1.84477C12.4733 1.78318 12.3631 1.75027 12.2504 1.75H1.74938C1.63655 1.75004 1.52616 1.78279 1.43158 1.84429C1.33699 1.90579 1.26226 1.99339 1.21645 2.09648C1.17064 2.19958 1.15571 2.31375 1.17347 2.42516C1.19123 2.53656 1.24091 2.64043 1.3165 2.72418L5.53149 7.38452C5.72549 7.5989 5.83297 7.87766 5.8331 8.16678V11.6668Z" stroke-linecap="round" stroke="#202B33"/>
</svg>

After

Width:  |  Height:  |  Size: 1001 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 559 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 B

@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11 6C11 7.65685 9.65685 9 8 9C6.34315 9 5 7.65685 5 6C5 4.34315 6.34315 3 8 3C9.65685 3 11 4.34315 11 6Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke="#26343D"/>
<path d="M2 20V16C1.9946 14.8394 2.32594 13.7021 2.95386 12.726C3.58179 11.7499 4.47934 10.9768 5.53775 10.5006C6.59616 10.0243 7.76999 9.86518 8.917 10.0426C10.064 10.22 11.1349 10.7262 12 11.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke="#26343D"/>
<path d="M19.5 11C19.5 12.3807 18.3807 13.5 17 13.5C15.6193 13.5 14.5 12.3807 14.5 11C14.5 9.61929 15.6193 8.5 17 8.5C18.3807 8.5 19.5 9.61929 19.5 11Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke="#26343D"/>
<path d="M12 21V18.5C12 17.1739 12.5268 15.9021 13.4645 14.9645C14.4021 14.0268 15.6739 13.5 17 13.5C18.3261 13.5 19.5979 14.0268 20.5355 14.9645C21.4732 15.9021 22 17.1739 22 18.5V21H12Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke="#26343D"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,5 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 9.1875C7.656 9.1875 6.5625 8.094 6.5625 6.75C6.5625 5.406 7.656 4.3125 9 4.3125C10.344 4.3125 11.4375 5.406 11.4375 6.75C11.4375 8.094 10.344 9.1875 9 9.1875ZM9 5.4375C8.27625 5.4375 7.6875 6.02625 7.6875 6.75C7.6875 7.47375 8.27625 8.0625 9 8.0625C9.72375 8.0625 10.3125 7.47375 10.3125 6.75C10.3125 6.02625 9.72375 5.4375 9 5.4375Z" fill="#737373"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.66722 13.5263C8.02722 13.872 8.50047 14.0625 8.99997 14.0625C9.49947 14.0625 9.97272 13.872 10.3327 13.5255C10.5082 13.3568 10.6882 13.1858 10.8705 13.0133C13.1174 10.8818 15.9149 8.22811 14.3692 4.51199C13.4805 2.37374 11.322 0.9375 8.99997 0.9375C6.67797 0.9375 4.52021 2.37374 3.63146 4.51199C2.09096 8.21474 4.86521 10.8555 7.09421 12.9773L7.11578 12.9978C7.30192 13.1753 7.48597 13.3508 7.66722 13.5263ZM4.67022 4.94399C5.38647 3.22049 7.12647 2.0625 8.99997 2.0625C10.8735 2.0625 12.6142 3.22049 13.3312 4.94399C14.5792 7.94549 12.3007 10.107 10.0965 12.1972C10.0547 12.237 10.0129 12.2766 9.97134 12.3162C9.83004 12.4504 9.69004 12.5834 9.55273 12.7155C9.40348 12.8588 9.20697 12.9375 8.99997 12.9375C8.79297 12.9375 8.59646 12.858 8.44871 12.7163C8.25971 12.5333 8.06623 12.3487 7.86973 12.162C5.68581 10.0831 3.42677 7.93263 4.67022 4.94399Z" fill="#737373"/>
<path d="M11.0385 17.0625H6.96142C4.53817 17.0625 3.32169 17.0625 2.72319 16.1745C2.66544 16.0882 2.61517 15.999 2.57392 15.909C2.11666 14.93 2.85295 13.8806 3.87139 12.429L3.87295 12.4267C4.05145 12.1725 4.40243 12.111 4.65668 12.2895C4.91093 12.468 4.97243 12.819 4.79393 13.0733C3.98468 14.2268 3.42443 15.072 3.59543 15.4373C3.61343 15.4763 3.63367 15.5115 3.65692 15.5468C3.92017 15.9375 5.12991 15.9375 6.96138 15.9375H11.0385C12.8699 15.9375 14.0797 15.9375 14.3437 15.546C14.3669 15.5115 14.3872 15.4762 14.4029 15.441C14.5754 15.072 14.0152 14.2268 13.2059 13.0733C13.0274 12.819 13.0889 12.468 13.3432 12.2895C13.5974 12.111 13.9484 12.1725 14.1269 12.4267C15.1462 13.8795 15.8827 14.9295 15.4244 15.9127C15.3847 15.999 15.3352 16.0882 15.2775 16.1737C14.6782 17.0625 13.4617 17.0625 11.0385 17.0625Z" fill="#737373"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@ -0,0 +1,5 @@
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="arrow-right">
<path id="Vector" d="M3.1245 7.5H11.8755M7.5 11.8755L11.8755 7.5L7.5 3.1245" stroke-width="1.7" stroke-linecap="round" stroke="#147D6B"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 337 B

@ -0,0 +1,5 @@
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="arrow-up-right">
<path id="Vector" d="M14.166 14.166V5.834H5.834M14.166 5.834L5.834 14.166" stroke-width="1.7" stroke-linecap="round" stroke="#143F36"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 338 B

@ -0,0 +1,5 @@
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="chevron-down">
<path id="Vector" d="M3 4.5L6 7.5L9 4.5" stroke-width="1.7" stroke-linecap="round" stroke="#147D6B"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 302 B

@ -0,0 +1,5 @@
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="chevron-left">
<path id="Vector" d="M12.5 15L7.5 10L12.5 5" stroke-width="1.7" stroke-linecap="round" stroke="#202F32"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 306 B

@ -0,0 +1,10 @@
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="compass" clip-path="url(#clip0_0_24)">
<path id="Vector" d="M20.1674 11C20.1674 16.063 16.063 20.1674 11 20.1674C5.93698 20.1674 1.8326 16.063 1.8326 11C1.8326 5.93698 5.93698 1.8326 11 1.8326C16.063 1.8326 20.1674 5.93698 20.1674 11ZM14.8868 7.11281L13.233 12.0733C13.143 12.3433 12.9913 12.5887 12.79 12.79C12.5887 12.9913 12.3433 13.143 12.0733 13.233L7.11281 14.8868L8.76661 9.92628C8.85661 9.65623 9.00827 9.41084 9.20955 9.20955C9.41084 9.00827 9.65623 8.85661 9.92628 8.76661L14.8868 7.11281Z" stroke-width="1.7" stroke-linecap="round" stroke="#147D6B"/>
</g>
<defs>
<clipPath id="clip0_0_24">
<rect width="22" height="22" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 845 B

@ -0,0 +1,5 @@
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="heart">
<path id="Vector" d="M1.2495 5.93763C1.24951 5.24212 1.46051 4.56298 1.85463 3.9899C2.24875 3.41682 2.80744 2.97676 3.45693 2.72785C4.10641 2.47893 4.81612 2.43287 5.49233 2.59575C6.16855 2.75863 6.77944 3.12278 7.24434 3.64011C7.27708 3.67512 7.31667 3.70303 7.36064 3.72211C7.40462 3.74119 7.45204 3.75104 7.49998 3.75104C7.54792 3.75104 7.59534 3.74119 7.63932 3.72211C7.68329 3.70303 7.72288 3.67512 7.75563 3.64011C8.21906 3.11941 8.8301 2.7522 9.50741 2.58735C10.1847 2.42249 10.8962 2.46781 11.5471 2.71728C12.198 2.96674 12.7575 3.40851 13.1511 3.9838C13.5447 4.55909 13.7538 5.2406 13.7505 5.93763C13.7505 7.36889 12.8129 8.43765 11.8753 9.37516L8.44255 12.6958C8.32609 12.8296 8.18249 12.937 8.0213 13.011C7.86011 13.085 7.68502 13.1239 7.50766 13.125C7.3303 13.1261 7.15473 13.0895 6.99262 13.0175C6.83051 12.9456 6.68556 12.84 6.56741 12.7077L3.12464 9.37516C2.18707 8.43765 1.2495 7.37514 1.2495 5.93763Z" stroke-width="1.7" stroke-linecap="round" stroke="#202F32"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,5 @@
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="heart">
<path id="Vector" d="M1.8326 8.70852C1.83262 7.68845 2.14208 6.69237 2.72012 5.85185C3.29816 5.01134 4.11758 4.36592 5.07016 4.00084C6.02273 3.63577 7.06365 3.56821 8.05542 3.8071C9.0472 4.04598 9.94318 4.58007 10.625 5.33882C10.6731 5.39017 10.7311 5.43111 10.7956 5.45909C10.8601 5.48708 10.9297 5.50152 11 5.50152C11.0703 5.50152 11.1398 5.48708 11.2043 5.45909C11.2688 5.43111 11.3269 5.39017 11.3749 5.33882C12.0546 4.57514 12.9508 4.03656 13.9442 3.79478C14.9376 3.55299 15.9811 3.61946 16.9357 3.98534C17.8904 4.35122 18.711 4.99915 19.2883 5.8429C19.8656 6.68666 20.1722 7.68621 20.1673 8.70852C20.1673 10.8077 18.7922 12.3752 17.4171 13.7502L12.3824 18.6205C12.2116 18.8167 12.001 18.9743 11.7646 19.0828C11.5282 19.1913 11.2714 19.2483 11.0112 19.25C10.7511 19.2516 10.4936 19.1979 10.2558 19.0924C10.0181 18.9869 9.80549 18.8319 9.6322 18.638L4.58281 13.7502C3.20771 12.3752 1.8326 10.8169 1.8326 8.70852Z" stroke-width="1.7" stroke-linecap="round" stroke="#78817F"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 114.3 52">
<defs>
<style>
.cls-1 {
fill: #5e5f5f;
}
.cls-2 {
fill: #fff;
}
.cls-3 {
fill: #ec1f2d;
}
</style>
</defs>
<g id="Layer_1-2" data-name="Layer 1">
<g>
<path fill="#5e5f5f" d="M15.74,25.46H3.99c-2.53,0-3.99-1.42-3.99-3.78v-11.01h3.31v9.33c0,1.56.4,2.39,2.84,2.39h9.59v3.07h0Z"/>
<path fill="#5e5f5f" d="M17.2,29.26h-2.93v-2.66h2.93v2.66ZM21.88,21.03c0,1.21-.35,2.22-1.04,3.03-.78.94-1.87,1.4-3.24,1.4h-2.81v-3.07h2.1c1.11,0,1.68-.76,1.68-2.25v-5.07h3.31v5.96ZM21.67,29.26h-2.95v-2.66h2.95v2.66Z"/>
<path fill="#5e5f5f" d="M27.6,25.46h-3.31v-14.79h3.31v14.79Z"/>
<path fill="#5e5f5f" d="M39.98,25.46h-3.66c-.54,2.52-2.67,3.78-6.43,3.78v-2.59c2.17,0,3.26-1.01,3.26-3.03v-8.55h3.33v7.32h3.5v3.07h0ZM36.27,13.32h-2.93v-2.64h2.93v2.64Z"/>
<path fill="#5e5f5f" d="M53.17,22.2c0,3.07-2.13,5.23-5.44,5.23-2.01,0-3.52-.67-4.49-1.97h-4.25v-3.07h3.28c0-3.28,1.94-5.37,5.34-5.37s5.55,2.04,5.55,5.18ZM49.89,22.31c0-1.81-.71-2.73-2.1-2.73h-.24c-1.21,0-2.03,1.08-2.03,2.59s.8,2.64,2.22,2.64c1.3,0,2.15-1.01,2.15-2.5h0Z"/>
<path fill="#ec1f2d" d="M104.69,0h-34.38c-5.31,0-9.61,4.17-9.61,9.32v33.36c0,5.15,4.3,9.32,9.61,9.32h34.38c5.31,0,9.61-4.17,9.61-9.32V9.32c0-5.15-4.3-9.32-9.61-9.32Z"/>
<path fill="#ffffff" d="M107.74,34.57l-.03,10.61-14.28-.04c-1.28,0-2.16-1.23-2.22-3.11,0-2.95-.9-5.86-2.69-8.25-3.37-4.51-8.12-8.12-14.29-10.8-1.52-.66-3.18-1-4.85-.99-1.32-.08-2.2-1.54-2.2-3.37v-10.57s14.09,0,14.09,0c1.27,0,2.28,1.27,2.34,3.15,0,2.05.58,4.07,1.72,5.79,3.85,5.77,8.76,10.4,15.33,13.26,1.52.66,3.18.99,4.85,1,1.32.08,2.22,1.5,2.22,3.32Z"/>
<path fill="#ffffff" d="M83.66,32.91c-.01.15-.02.3-.02.46v7.34s-.01,1.27-.01,1.27c-.07,1.88-1.12,3.19-2.39,3.19l-10.64-.04c-1.4,0-3.39.01-3.39.01l-.02-10.65c0-1.82.89-3.27,2.21-3.36h3.95s1.51,0,1.51,0h6.83c.92,0,1.7.75,1.98,1.78h0Z"/>
<path fill="#ffffff" d="M107.81,5.37l-.02,13.27c0,1.83-.85,3.33-2.17,3.41h-4s-1.34-.01-1.34-.01c-.09-.02-.19-.02-.28-.02h-6.81c-.9,0-1.67-.72-1.96-1.72.02-.16.03-.33.04-.51v-8.51c.06-1.89,1.12-3.22,2.39-3.22h9.73c2.94-.14,4.43-2.68,4.43-2.68h0Z"/>
<path fill="#5e5f5f" d="M8.84,38.19c-.04-.99-.08-2.18-.08-3.37h-.04c-.26,1.05-.61,2.22-.94,3.18l-1.03,3.2h-1.49l-.9-3.18c-.28-.96-.57-2.13-.77-3.2h-.03c-.05,1.11-.09,2.38-.15,3.4l-.15,3.12h-1.77l.54-8.21h2.55l.83,2.74c.26.95.53,1.97.72,2.94h.04c.24-.95.53-2.03.8-2.95l.9-2.73h2.5l.46,8.21h-1.87l-.12-3.14h0Z"/>
<path fill="#5e5f5f" d="M14.49,39.23l-.6,2.11h-1.98l2.59-8.21h2.51l2.62,8.21h-2.06l-.65-2.11h-2.42ZM16.63,37.84l-.53-1.74c-.15-.49-.3-1.1-.43-1.58h-.03c-.13.49-.25,1.11-.39,1.58l-.5,1.74h1.87Z"/>
<path fill="#5e5f5f" d="M21.45,40.34l4.13-5.63v-.05h-4.13v-1.53h6.67v1.07l-4.04,5.55v.05h4.1v1.53h-6.74v-1h0Z"/>
<path fill="#5e5f5f" d="M32.58,39.23l-.6,2.11h-1.98l2.59-8.21h2.51l2.62,8.21h-2.06l-.65-2.11h-2.42ZM34.73,37.84l-.53-1.74c-.15-.49-.3-1.1-.43-1.58h-.03c-.13.49-.25,1.11-.39,1.58l-.5,1.74h1.87Z"/>
<path fill="#5e5f5f" d="M39.86,41.34v-3.36l-2.67-4.85h2.18l.87,1.97c.26.6.44,1.04.65,1.57h.03c.19-.51.39-.99.64-1.57l.85-1.97h2.15l-2.79,4.79v3.42h-1.91,0Z"/>
<path fill="#5e5f5f" d="M46.54,39.23l-.6,2.11h-1.98l2.59-8.21h2.51l2.62,8.21h-2.06l-.65-2.11h-2.42ZM48.69,37.84l-.53-1.74c-.15-.49-.3-1.1-.43-1.58h-.03c-.13.49-.25,1.11-.39,1.58l-.5,1.74h1.87Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 55.02 94.61">
<defs>
<style>
.cls-1 {
fill: #5e5f5f;
}
.cls-2 {
fill: #ec222e;
}
.cls-3 {
fill: #fff;
}
</style>
</defs>
<g id="Layer_1-2" data-name="Layer 1">
<g>
<g>
<path fill="#5e5f5f" d="M16.59,77.8H4.53c-2.6,0-4.1-1.5-4.1-4v-11.65h3.4v9.88c0,1.65.41,2.52,2.91,2.52h9.85v3.25Z"/>
<path fill="#5e5f5f" d="M18.09,81.83h-3.01v-2.81h3.01v2.81ZM22.89,73.12c0,1.29-.36,2.35-1.07,3.2-.8.99-1.92,1.48-3.32,1.48h-2.89v-3.25h2.16c1.14,0,1.72-.8,1.72-2.38v-5.36h3.4v6.31ZM22.67,81.83h-3.03v-2.81h3.03v2.81Z"/>
<path fill="#5e5f5f" d="M28.76,77.8h-3.4v-15.65h3.4v15.65Z"/>
<path fill="#5e5f5f" d="M41.48,77.8h-3.76c-.56,2.67-2.74,4-6.6,4v-2.74c2.23,0,3.35-1.07,3.35-3.2v-9.05h3.42v7.74h3.59v3.25ZM37.67,64.97h-3.01v-2.79h3.01v2.79Z"/>
<path fill="#5e5f5f" d="M55.02,74.36c0,3.25-2.18,5.53-5.58,5.53-2.06,0-3.62-.7-4.61-2.09h-4.37v-3.25h3.37c0-3.47,1.99-5.68,5.48-5.68s5.7,2.16,5.7,5.48ZM51.64,74.48c0-1.92-.73-2.89-2.16-2.89h-.24c-1.24,0-2.09,1.14-2.09,2.74s.82,2.79,2.28,2.79c1.33,0,2.21-1.07,2.21-2.64Z"/>
</g>
<g>
<rect fill="#ffffff" y="0" width="55.02" height="55.02" rx="9.86" ry="9.86"/>
<g>
<path fill="#ec1f2d" d="M48.28,36.58l-.03,11.22-14.66-.04c-1.31,0-2.21-1.3-2.27-3.29h0c0-3.13-.93-6.2-2.76-8.73-3.46-4.77-8.33-8.59-14.67-11.43-1.56-.7-3.26-1.06-4.98-1.05h0c-1.35-.09-2.26-1.63-2.26-3.56v-11.19s14.46,0,14.46,0c1.31,0,2.34,1.34,2.4,3.33h0c0,2.17.59,4.31,1.77,6.13,3.95,6.11,9,11,15.74,14.03,1.56.7,3.26,1.05,4.98,1.06h0c1.35.09,2.28,1.58,2.28,3.52Z"/>
<path fill="#ec1f2d" d="M23.56,34.82c-.01.16-.02.32-.02.48v7.77s-.02,1.34-.02,1.34c-.07,1.99-1.15,3.37-2.45,3.37l-10.92-.05c-1.44,0-3.48.01-3.48.01l-.02-11.27c0-1.93.91-3.46,2.26-3.55h4.05s.26,0,.26,0h1.3s7.01,0,7.01,0c.94,0,1.75.79,2.03,1.88Z"/>
<path fill="#ec1f2d" d="M48.36,5.69l-.02,14.04c0,1.93-.88,3.52-2.23,3.61h-4.1s-1.38-.01-1.38-.01c-.1-.02-.19-.02-.29-.02h-6.99c-.93,0-1.72-.76-2.01-1.82.02-.17.03-.35.04-.54v-9.01c.06-2,1.15-3.41,2.45-3.41h9.99c3.02-.15,4.55-2.83,4.55-2.83Z"/>
</g>
</g>
<g>
<path fill="#5e5f5f" d="M9.5,91.28c-.04-1.04-.08-2.31-.08-3.57h-.04c-.27,1.11-.63,2.35-.97,3.36l-1.06,3.39h-1.53l-.93-3.36c-.28-1.02-.58-2.26-.79-3.39h-.03c-.05,1.17-.09,2.51-.15,3.6l-.15,3.3h-1.82l.55-8.69h2.62l.85,2.9c.27,1.01.54,2.09.73,3.11h.04c.24-1.01.54-2.15.82-3.12l.93-2.89h2.56l.48,8.69h-1.92l-.13-3.32Z"/>
<path fill="#5e5f5f" d="M15.3,92.38l-.62,2.23h-2.04l2.66-8.69h2.58l2.69,8.69h-2.11l-.67-2.23h-2.49ZM17.51,90.91l-.54-1.84c-.15-.52-.31-1.16-.44-1.68h-.03c-.13.52-.26,1.17-.4,1.68l-.52,1.84h1.92Z"/>
<path fill="#5e5f5f" d="M22.45,93.55l4.24-5.95v-.05h-4.24v-1.62h6.85v1.13l-4.15,5.88v.05h4.21v1.62h-6.92v-1.06Z"/>
<path fill="#5e5f5f" d="M33.88,92.38l-.62,2.23h-2.04l2.66-8.69h2.58l2.69,8.69h-2.11l-.67-2.23h-2.49ZM36.08,90.91l-.54-1.84c-.15-.52-.31-1.16-.44-1.68h-.03c-.13.52-.26,1.17-.4,1.68l-.52,1.84h1.92Z"/>
<path fill="#5e5f5f" d="M41.35,94.61v-3.56l-2.75-5.13h2.24l.89,2.09c.27.63.45,1.1.67,1.66h.03c.19-.54.4-1.04.66-1.66l.88-2.09h2.2l-2.86,5.07v3.62h-1.96Z"/>
<path fill="#5e5f5f" d="M48.21,92.38l-.62,2.23h-2.04l2.66-8.69h2.58l2.69,8.69h-2.11l-.67-2.23h-2.49ZM50.41,90.91l-.54-1.84c-.15-.52-.31-1.16-.44-1.68h-.03c-.13.52-.26,1.17-.4,1.68l-.52,1.84h1.92Z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

@ -0,0 +1,5 @@
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="map-pin">
<path id="Vector" d="M9.9996 4.99997C9.9996 7.4967 7.23038 10.0969 6.30047 10.9C6.21384 10.9652 6.10839 11.0004 6 11.0004C5.89161 11.0004 5.78616 10.9652 5.69953 10.9C4.76962 10.0969 2.0004 7.4967 2.0004 4.99997C2.0004 3.93901 2.42179 2.9215 3.17186 2.17128C3.92193 1.42107 4.93924 0.9996 6 0.9996C7.06076 0.9996 8.07807 1.42107 8.82814 2.17128C9.57822 2.9215 9.9996 3.93901 9.9996 4.99997ZM7.49985 4.99997C7.49985 5.82847 6.82834 6.50011 6 6.50011C5.17166 6.50011 4.50015 5.82847 4.50015 4.99997C4.50015 4.17147 5.17166 3.49983 6 3.49983C6.82834 3.49983 7.49985 4.17147 7.49985 4.99997Z" stroke-linecap="round" stroke="#147D6B"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 826 B

@ -0,0 +1,5 @@
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="search">
<path id="Vector" d="M15.7501 15.7501L12.4951 12.4951M14.25 8.25C14.25 11.5637 11.5637 14.25 8.25 14.25C4.93629 14.25 2.25 11.5637 2.25 8.25C2.25 4.93629 4.93629 2.25 8.25 2.25C11.5637 2.25 14.25 4.93629 14.25 8.25Z" stroke-width="1.7" stroke-linecap="round" stroke="#78817F"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 472 B

@ -0,0 +1,5 @@
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="user-round">
<path id="Vector" d="M11 11.9167C13.5311 11.9167 15.5829 9.86464 15.5829 7.33333C15.5829 4.80203 13.5311 2.75 11 2.75C8.46895 2.75 6.41712 4.80203 6.41712 7.33333C6.41712 9.86464 8.46895 11.9167 11 11.9167ZM11 11.9167C12.9447 11.9167 14.8098 12.6893 16.1849 14.0646C17.5601 15.4398 18.3326 17.3051 18.3326 19.25M11 11.9167C9.05527 11.9167 7.1902 12.6893 5.81507 14.0646C4.43994 15.4398 3.6674 17.3051 3.6674 19.25" stroke-width="1.7" stroke-linecap="round" stroke="#78817F"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 674 B

@ -0,0 +1,6 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.8848 0.782465C11.6672 0.777969 11.077 0.780422 10.3952 0.783256L10.074 0.784571C9.25489 0.787826 8.36176 0.790261 7.8034 0.782363L3.41832 0.782363C3.30775 0.782363 3.09409 0.784794 2.86663 0.826585C2.65209 0.866 2.34887 0.953285 2.12257 1.17828C2.01295 1.28728 1.92531 1.4256 1.85771 1.5488C1.78696 1.67774 1.72248 1.81951 1.66613 1.95532C1.55744 2.2173 1.46792 2.48443 1.41557 2.64068L1.41079 2.65493L0.986568 3.88476C0.919741 4.06317 0.849387 4.33704 0.811103 4.61007C0.791433 4.75035 0.778628 4.90141 0.781624 5.04747C0.784495 5.18744 0.802226 5.35457 0.864472 5.50929C0.963954 5.75657 1.18158 6.01838 1.36007 6.20866C1.36856 6.21771 1.3771 6.22675 1.38569 6.23576C1.38552 6.24088 1.38544 6.24602 1.38544 6.25118L1.38544 9.41047C1.38543 10.2652 1.38541 10.9542 1.45827 11.496C1.5339 12.0586 1.69571 12.5322 2.0719 12.9084C2.4481 13.2846 2.92177 13.4465 3.48434 13.5221C4.0262 13.595 4.71515 13.5949 5.5699 13.5949L7.47919 13.5949C7.73807 13.5949 7.94794 13.3851 7.94794 13.1262C7.94794 12.8673 7.73807 12.6574 7.47919 12.6574L5.60419 12.6574C4.70705 12.6574 4.08136 12.6564 3.60926 12.593C3.15066 12.5313 2.9078 12.4185 2.73482 12.2455C2.56184 12.0726 2.44906 11.8297 2.38741 11.3711C2.32393 10.899 2.32294 10.2733 2.32294 9.37618L2.32294 6.97328C2.45051 7.03849 2.59529 7.10093 2.75359 7.15501C3.17457 7.29882 3.7203 7.39381 4.3406 7.31265C4.89853 7.23967 5.351 7.00851 5.70918 6.71013C6.09419 7.00458 6.70331 7.30587 7.47811 7.31657C8.04917 7.32446 8.56216 7.12979 8.95868 6.90312C9.08184 6.83271 9.19674 6.75753 9.30188 6.68132C9.77763 7.08237 10.6727 7.56818 11.9991 7.20432C13.5774 6.77137 14.083 5.75144 14.209 5.1425C14.2154 5.11127 14.2187 5.07946 14.2187 5.04757C14.2187 4.75732 14.1289 4.33991 13.9659 3.80861C13.7986 3.26346 13.5405 2.55589 13.1738 1.66657C13.1701 1.65753 13.166 1.6486 13.1618 1.63981C13.0956 1.50407 12.9647 1.3046 12.7653 1.13192C12.5616 0.95548 12.2655 0.790329 11.8848 0.782465ZM7.49106 6.37916C6.97962 6.3721 6.58006 6.18008 6.33319 6.00595C6.55391 5.67199 6.6951 5.33553 6.76663 5.07693C6.83564 4.82742 6.68931 4.5692 6.4398 4.50019C6.19028 4.43117 5.93206 4.5775 5.86305 4.82702C5.70947 5.3823 5.16544 6.25926 4.21899 6.38308C3.76402 6.4426 3.36434 6.37295 3.05665 6.26784C2.73842 6.15914 2.54601 6.02235 2.50231 5.98113C2.48528 5.96506 2.46707 5.9503 2.44784 5.93696C2.37021 5.88311 2.20722 5.74146 2.04383 5.56728C1.87113 5.38316 1.76292 5.23071 1.73421 5.15935C1.73421 5.15935 1.73024 5.14811 1.72656 5.12505C1.72269 5.10085 1.71976 5.0687 1.71893 5.02824C1.71725 4.94644 1.72447 4.84757 1.73952 4.74025C1.77015 4.52178 1.82659 4.31346 1.86579 4.21019C1.86748 4.20572 1.86911 4.20123 1.87067 4.19671L2.29773 2.95872L2.30261 2.94414C2.3555 2.78638 2.43617 2.5457 2.53206 2.3146C2.58103 2.19657 2.63099 2.0884 2.67962 1.99977C2.73141 1.90538 2.7681 1.85849 2.78357 1.8431C2.80747 1.81934 2.88262 1.77684 3.03603 1.74865C3.17652 1.72284 3.32038 1.71986 3.41832 1.71986L7.79347 1.71986C8.36231 1.72785 9.26287 1.7253 10.0778 1.72206L10.3974 1.72076C11.0847 1.71792 11.6606 1.71553 11.8655 1.71977C11.9719 1.72196 12.0657 1.76626 12.1515 1.84057C12.2347 1.91263 12.2907 1.99706 12.314 2.0407C12.6704 2.90597 12.9148 3.57905 13.0696 4.08359C13.214 4.55415 13.2701 4.84632 13.2797 5.00173C13.1969 5.33137 12.8878 5.98842 11.7511 6.30022C10.8759 6.54033 10.2955 6.26948 9.96995 6.01784C10.0887 5.82356 10.2297 5.52285 10.3667 5.09485C10.4456 4.84828 10.3097 4.58443 10.0631 4.50553C9.81653 4.42662 9.55268 4.56254 9.47378 4.8091C9.30754 5.32855 9.16081 5.54992 9.1304 5.58829C9.02102 5.71208 8.79354 5.91764 8.4934 6.08922C8.18993 6.2627 7.84298 6.38402 7.49106 6.37916Z" fill="#C2410C"/>
<path d="M3.88544 10.6262C3.88544 10.3673 4.0953 10.1574 4.35419 10.1574L6.85419 10.1574C7.11307 10.1574 7.32294 10.3673 7.32294 10.6262C7.32294 10.8851 7.11307 11.0949 6.85419 11.0949L4.35419 11.0949C4.0953 11.0949 3.88544 10.8851 3.88544 10.6262Z" fill="#C2410C"/>
<path d="M10.9167 10.6262C10.9167 10.281 11.1965 10.0012 11.5417 10.0012L11.5473 10.0012C11.8925 10.0012 12.1723 10.281 12.1723 10.6262C12.1723 10.9714 11.8925 11.2512 11.5473 11.2512L11.5417 11.2512C11.1965 11.2512 10.9167 10.9714 10.9167 10.6262Z" fill="#C2410C"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.5624 7.96748C10.1037 7.96748 8.90619 9.13055 8.90619 10.5835C8.90619 11.4113 9.32257 12.0297 9.75433 12.5247C9.9082 12.7011 10.0773 12.876 10.2353 13.0394C10.2951 13.1013 10.3536 13.1618 10.4088 13.2199C10.619 13.4408 10.7995 13.6441 10.9391 13.8516C11.0558 14.0251 11.2645 14.2195 11.5661 14.2195C11.8728 14.2195 12.0808 14.0193 12.1939 13.8391C12.3264 13.6278 12.5016 13.425 12.7082 13.2072C12.7757 13.1361 12.85 13.0599 12.9271 12.981C13.0706 12.8339 13.2237 12.677 13.3595 12.525C13.7946 12.038 14.2187 11.4281 14.2187 10.5835C14.2187 9.13055 13.0212 7.96748 11.5624 7.96748ZM9.84369 10.5835C9.84369 9.66465 10.605 8.90498 11.5624 8.90498C12.5199 8.90498 13.2812 9.66465 13.2812 10.5835C13.2812 11.0803 13.0441 11.4709 12.6604 11.9004C12.5358 12.0398 12.4117 12.1667 12.28 12.3015L12.2796 12.302C12.1992 12.3842 12.1159 12.4694 12.028 12.562C11.869 12.7297 11.7058 12.9111 11.5583 13.1097C11.4095 12.9183 11.2464 12.7402 11.0881 12.5738C11.0212 12.5034 10.956 12.436 10.8923 12.3701C10.7408 12.2134 10.5979 12.0656 10.4608 11.9084C10.0786 11.4703 9.84369 11.0692 9.84369 10.5835Z" fill="#C2410C"/>
</svg>

After

Width:  |  Height:  |  Size: 5.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 37 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 29 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 37 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

File diff suppressed because it is too large Load Diff

@ -549,6 +549,15 @@
"manage": "Manage",
"members": "Members",
"areYouSureWantTodelete": "Are you sure want to delete?",
"confirmGrantMazayaAccess": "Are you sure you want to grant Mazaya access to \"{}\"?",
"confirmRevokeMazayaAccess": "Are you sure you want to revoke Mazaya access to \"{}\"?",
"yes": "Yes",
"no": "No",
"mazayaInviteInstructions": "Please download the MoheM app from the above link and use the following credentials to login",
"cameraPermissionRequiredToScan": "Please grant camera permission to scan the QR code",
"cameraPermissionPermanentlyDenied": "Please allow camera permission from the app settings > Permissions",
"openSettings": "Open Settings",
"nationalIdOrIqamaFallback": "Your National ID or Iqama Number",
"groupMembers": "Group Members",
"manageGroup": "Manage Group",
"admin": "Admin",
@ -570,7 +579,6 @@
"pleaseClickButtonToJoinMarathon": "Press the button below to join the Marathon.",
"youCannotJoinTheMarathon": "You cannot join the Marathon because you have exceeded the time limit.",
"requesterOperatingUnit": "Requester Operating Unit",
"generate": "Generate",
"paymentRequest": "Pay Request",
"paymentDetails": "Payment Details",
"requestNo": "Request No",
@ -667,17 +675,212 @@
"incidentDetails": "Incident Details",
"ticketNumber": "Ticket Number",
"ticketId": "Ticket ID",
"pleaseSelectProject": "Please select a project",
"viewAttachment" : "View Attachment",
"parkingQr": "Parking QR",
"startNow": "Start Now",
"destination_type": "Destination Type",
"source_organization_name": "Source Organization Name",
"pleaseSelectProject": "Please select a project",
"viewAttachment": "View Attachment",
"parkingQr": "Parking QR",
"startNow": "Start Now",
"destination_type": "Destination Type",
"source_organization_name": "Source Organization Name",
"destination_organization_name": "Destination Organization Name",
"costCenter": "Cost Center",
"priceSAR": "Price (SAR)",
"amountSAR": "Amount (SAR)",
"amuLastThreeMonths": "AMU (Last 3 months)",
"mazayaTitle": "Mazaya",
"mazayaAccessGranted": "Mazaya access granted",
"mazayaAccessGrantedDesc": "{} now has active access. Share the download link below so they can install the Mohem app and complete setup.",
"accessIsNowActive": "Access is now active",
"accessIsNowActiveDesc": "{} can use the provided link to download the Mazaya app and continue setup on their device.",
"dependentAccessActive": "Dependent access active",
"shareableAppDownloadLink": "Shareable app-download link",
"shareableAppDownloadLinkDesc": "Send this link to {} so they can download the Mazaya app and continue setup.",
"downloadLink": "Download link",
"shareDownloadLink": "Share Download Link",
"copyLink": "Copy Link",
"done": "Done",
"linkCopied": "Link copied to clipboard",
"mazayaAccessRevoked": "Mazaya Access",
"mazayaAccessRevokedDesc": "You are not authorized to access the Mazaya Program.",
"contactYourSponsor": "Contact your sponsor",
"contactYourSponsorDesc": "Ask your sponsor to allow Mazaya access again from Dependent Details. Only the sponsor can restore access.",
"dependentAccessBlocked": "Dependent access blocked",
"returnToSignIn": "Return to Sign In",
"allowMazayaAccess": "Allow Mazaya Access",
"revokeMazayaAccess": "Revoke Mazaya Access",
"active": "Active",
"noAccess": "No access",
"offerDetails": "Offer Details",
"offerIdNotAvailable": "Offer ID not available",
"unableToRedeemOffer": "Unable to redeem offer, please try again later",
"errorRedeemingOffer": "An error occurred while redeeming offer",
"useOffer": "Use Offer",
"online": "Online",
"inStore": "In Store",
"ksa": "KSA",
"riyadh": "Riyadh",
"noInstructionsAvailable": "No instructions available.",
"contactInfo": "Contact Information",
"allCategories": "All",
"noOffersAvailable": "No offers available",
"addDependents": "Add Dependents",
"myDependents": "My Dependents",
"offDiscount": "{} Off",
"usageLabel": "Usage: ",
"areYouSureWantToLogOut": "Are you sure you want to log out?",
"searchDependents": "Search dependents",
"searchFamilyMembers": "Search family members",
"addAnotherUser": "Add another user",
"joinMazaya": "Join Mazaya - Coming Soon",
"joinMazayaSubtitle": "Get the best offers and exclusive deals across dining, shopping, and lifestyle rewards.",
"continueAsEmployee": "Continue as an employee",
"continueAsEmployeeSubtitle": "Sign in with your employee username and password.",
"pleaseEnterNationalId": "Please enter National ID",
"pleaseSelectDob": "Please select Date of Birth",
"unableToRetrieveDependentInfo": "Unable to retrieve dependent information",
"failedToSendOtp": "Failed to send verification code",
"verificationCodeResent": "Verification code resent",
"nationalId": "National ID",
"addDependent": "Add Dependent",
"stepVerify": "Verify",
"stepBasic": "Basic",
"stepAdditional": "Additional",
"findYourDependent": "Find your dependent",
"findYourDependentDesc": "Enter the details exactly as they appear on the National ID. We'll securely retrieve the record from NHIC.",
"enter10DigitNationalId": "Enter 10-digit National ID",
"nationalIdMustBe10Digits": "National ID must be 10 digits",
"dateFormatPlaceholder": "DD / MM / YYYY",
"searchUser": "Search User",
"dependentNotFoundNhic": "Sorry, error while getting dependent info from NHIC",
"basicInfo": "Basic Info",
"basicInfoDesc": "Required identity details fetched from NHIC.",
"additionalInfo": "Additional Info",
"additionalInfoDesc": "Add or review sponsorship and eligibility details.",
"select": "Select",
"enterFieldPlaceholder": "Enter {}",
"requiredField": "Required field",
"pleaseFillField": "Please fill {}",
"reviewAndSubmit": "Review & Submit",
"reviewAndSubmitDesc": "Please review the dependent details carefully before submitting.",
"arabicName": "Arabic Name",
"relationship": "Relationship",
"sponsor": "Sponsor",
"dependentSponsorDetail": "Dependent Sponsor Detail",
"insuranceEligibility": "Insurance Eligibility",
"relationshipStartDate": "Start Date",
"dependentAddedSuccessfully": "Dependent added successfully",
"genderMale": "Male",
"genderFemale": "Female",
"religionIslam": "Islam",
"back": "Back",
"submitRequest": "Submit",
"enterDependentDetailsToContinue": "Enter the dependent details to continue.",
"enterBasicDetailsDesc": "Please enter the dependent's basic identity details.",
"locations": "Locations",
"availableOffers": "Available Offers",
"selectOfferToRedeem": "Select an offer to use",
"viewOnMap": "View on map",
"showAllLocations": "Show all {} locations",
"searchBranches": "Search branches or cities...",
"allCities": "All",
"allCitiesOption": "All Cities",
"noBranchesFound": "No branches found",
"browseAllBranches": "Browse all {} branches across cities",
"branchesCount": "{} Branches",
"kmAway": "{} KM Away",
"dependentDetails": "Dependent Details",
"keyDetails": "Key details",
"verified": "Verified",
"idEnding": "ID ending {}",
"servicesAndActions": "Services & actions",
"startDependentFlowsDesc": "Start dependent flows and manage access from one place.",
"mazayaAccess": "Mazaya access",
"accessOff": "Off",
"allowDependentMazayaOffersDesc": "Allow {} to use Mazaya offers with her verified identity.",
"loginMethod": "Login method",
"nationalIdAndDob": "National ID & Date of Birth",
"sponsorOtp": "Sponsor OTP",
"revokingPreventsAccessDesc": "Revoking prevents all future dependent access until you allow it again.",
"locationPermissionRequiredForAttendance": "You need to give location permission to mark attendance",
"locationServicesRequiredForAttendance": "You need to enable location services to mark attendance",
"pleaseProvideLocationPermission": "Please provide location permission",
"microphoneAccessRequired": "The app needs microphone access to be able to record audio.",
"shareAccessGuide": "Share Access Guide",
"nationalIdOrIqamaNumber": "National ID or Iqama Number",
"multipleCities": "Multi-city",
"filters": "Filters",
"availability": "Availability",
"chooseOneOrBoth": "Choose one or both",
"sortBy": "Sort by",
"recommended": "Recommended",
"nearest": "Nearest",
"highestDiscount": "Highest discount",
"newest": "Newest",
"distance": "Distance",
"fromYourCurrentLocation": "From your current location",
"any": "Any",
"minimumDiscount": "Minimum discount",
"reset": "Reset",
"applyFilters": "Apply filters",
"distanceKm": "{} km",
"discountPercentPlus": "{}%+",
"visitMerchantWebsite": "Visit Merchant Website",
"merchantWebsiteNotAvailable": "Merchant website not available",
"locationServicesRequiredForNearestOffers": "You need to enable location services to show nearest offers",
"locationPermissionRequiredForNearestOffers": "You need to give location permission to show nearest offers",
"mazayaDeals": "Mazaya Deals",
"helloName": "Hello, {}",
"findABrandOrSomething": "Find a brand or a little something",
"exploreYourBenefits": "Explore your benefits",
"theMazayaEdit": "The Mazaya Edit",
"exclusivelyYours": "Exclusively HMG",
"aLittleExtraJustForMazayaMembers": "A little extra, just for Mazaya members.",
"seeAll": "See all",
"latestOnMazaya": "Latest on Mazaya",
"goodFoodGreatCompanyEvenBetterPerks": "Good food. Great company. Even better perks.",
"travelEssentials": "Travel Essentials",
"carServices": "Car Services",
"fromEverydayCareToYourNextAdventure": "From everyday care to your next adventure.",
"homeAndKitchen": "Home & Kitchen",
"curatedPicksForYourHomeAndKitchen": "Curated picks for your home and kitchen.",
"discover": "Discover",
"saved": "Saved",
"myFamily": "My Family",
"educationAndTraining": "Education & Training",
"foodsAndDrinks": "Foods & Drinks",
"travelAndEntertainment": "Travel & Entertainment",
"beautyAndFitness": "Beauty & Fitness",
"healthcare": "Healthcare",
"favoriteAddedSuccess": "Added to favorites successfully",
"favoriteRemovedSuccess": "Removed from favorites successfully",
"favoriteError": "An error occurred while updating favorites",
"favoriteMerchant": "Favorite Offers",
"favoriteOffers": "Favorite Offers",
"goodToKnow": "Good to know",
"goodToKnowDesc": "Keep your Mazaya membership handy. Check the merchant's availability, eligible activities and full terms before you book.",
"termsAndConditions": "Terms & conditions",
"haveAQuestionAboutOffer": "Have a question about this offer?",
"getHelp": "Get help",
"redeemOffer": "Redeem offer",
"merchantContact": "Merchant contact",
"website": "Website",
"phone": "Phone",
"viewAll": "View all",
"moreCitiesCount": "+ {} more cities",
"mazayaDeals": "Mazaya Deals",
"exploreYourBenefits": "Explore your benefits",
"findAnEscapeOrAnExperience": "Find an escape or an experience",
"allOffers": "All offers",
"sampleAvailability": "sample availability",
"offersCount": "{} offers",
"mazayaExclusive": "MAZAYA EXCLUSIVE",
"youAreAllCaughtUp": "You're all caught up. More little extras soon.",
"selectCity": "Select City",
"searchCities": "Search cities",
"amuLastThreeMonths": "AMU (Last 3 months)",
"buyerDetails": "Buyer Details",
"chatBot": "ChatBot",
"helloAskMe": "Hello, ask me",
@ -688,4 +891,5 @@
"initializingChatPleaseWait": "Please wait, initializing chat...",
"am": "AM",
"pm": "PM"
}
}

@ -20,5 +20,7 @@
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>13.0</string>
</dict>
</plist>

@ -7,8 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
0AD79F8B1905C17FADC8B42C /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D8F0CC8EAC13AF3E07BEE567 /* Pods_Runner.framework */; };
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
1CCF895CFB3C95DEBB84A05F /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D8F0CC8EAC13AF3E07BEE567 /* Pods_Runner.framework */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
76AE113C28E078EA00513112 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 76AE113B28E078EA00513112 /* GoogleService-Info.plist */; };
@ -59,8 +59,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
1CCF895CFB3C95DEBB84A05F /* Pods_Runner.framework in Frameworks */,
0AD79F8B1905C17FADC8B42C /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -148,7 +147,7 @@
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
C4CFBC4C5CAC00182015ACD5 /* [CP] Embed Pods Frameworks */,
D9FFC0F21D4146458615A992 /* [CP] Copy Pods Resources */,
EF29CE25BB57F5AA48CE1136 /* [CP] Copy Pods Resources */,
);
buildRules = (
);

@ -3,7 +3,7 @@
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
parallelizeBuildables = "NO"
buildImplicitDependencies = "YES">
<PreActions>
<ExecutionAction
@ -24,6 +24,20 @@
</ExecutionAction>
</PreActions>
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8B74B458B450D74B75744B87BD747314"
BuildableName = "Pods_Runner.framework"
BlueprintName = "Pods-Runner"
ReferencedContainer = "container:Pods/Pods.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"

@ -52,6 +52,11 @@
<string>sms</string>
<string>tel</string>
<string>mailto</string>
<string>https</string>
<string>http</string>
<string>comgooglemaps</string>
<string>googlechromes</string>
<string>waze</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
@ -67,15 +72,17 @@
<true/>
</dict>
<key>NSCameraUsageDescription</key>
<string>This app requires camera access to capture &amp; upload picture as profile image.</string>
<string>This app requires camera access to capture &amp; upload picture as profile image, and scan QR codes.</string>
<key>NSFaceIDUsageDescription</key>
<string>This app requires Face ID to allow biometric authentication for app login.</string>
<key>NSLocalNetworkUsageDescription</key>
<string>This app requires access to local network to connect to company Wi-Fi networks for attendance.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>This App requires access to your location to mark your attendance.</string>
<string>This app requires access to your location to mark your attendance and discover nearby offers and merchants.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>This App requires access to your location to mark your attendance.</string>
<string>This app requires access to your location to mark your attendance and discover nearby offers and merchants.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>This App requires access to your location to mark your attendance.</string>
<string>This app requires access to your location to mark your attendance and discover nearby offers and merchants.</string>
<key>NSMicrophoneUsageDescription</key>
<string>This app requires microphone access for calls and speech-to-text input.</string>
<key>NSPhotoLibraryUsageDescription</key>

@ -20,6 +20,7 @@
<true/>
<key>com.apple.developer.nfc.readersession.formats</key>
<array>
<string>NDEF</string>
<string>TAG</string>
</array>
<key>com.apple.developer.ubiquity-container-identifiers</key>

@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'package:flutter/cupertino.dart';
@ -36,16 +37,24 @@ APIException _throwAPIException(Response response) {
case 200:
APIError? apiError;
if (response.body != null && response.body.isNotEmpty) {
var jsonError = jsonDecode(response.body);
print(jsonError);
apiError = APIError(jsonError['ErrorCode'], jsonError['ErrorMessage'], jsonError['ErrorType'], jsonError['ErrorStatusCode']);
try {
var jsonError = jsonDecode(response.body);
print(jsonError);
apiError = APIError(jsonError['ErrorCode'], jsonError['ErrorMessage'], jsonError['ErrorType'], jsonError['ErrorStatusCode']);
} catch (_) {
log("Non-JSON 200 error body: ${response.body}");
}
}
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'], jsonError['ErrorType'], jsonError['ErrorStatusCode']);
try {
var jsonError = jsonDecode(response.body);
apiError = APIError(jsonError['ErrorCode'], jsonError['ErrorMessage'], jsonError['ErrorType'], jsonError['ErrorStatusCode']);
} catch (_) {
log("Non-JSON 400 error body: ${response.body}");
}
}
return APIException(APIException.BAD_REQUEST, error: apiError);
case 401:
@ -86,15 +95,14 @@ class ApiClient {
_headers.addAll(headers);
}
if (!kReleaseMode) {
print("Url:$url");
log("Url: $url");
var bodyJson = json.encode(jsonObject);
print("body:$bodyJson");
log("body:$bodyJson");
}
var response = await postJsonForResponse(url, jsonObject, token: token, queryParameters: queryParameters, headers: _headers, retryTimes: retryTimes, isFormData: isFormData);
try {
if (!kReleaseMode) {
logger.i("Url: " + url);
logger.i("res: " + response.body);
log("res: " + response.body);
}
var jsonData = jsonDecode(response.body);
@ -112,7 +120,7 @@ class ApiClient {
} else if (jsonData["MessageStatus"] == 2 && jsonData["IsOTPMaxLimitExceed"] == true) {
await Utils.performLogout(AppRoutes.navigatorKey.currentContext, null);
throw const APIException(APIException.UNAUTHORIZED, error: null);
} else {
} else {
APIError? apiError;
apiError = APIError(jsonData['ErrorCode'], jsonData['ErrorEndUserMessage'], jsonData['ErrorType'] ?? 0, jsonData['ErrorStatusCode']);
throw APIException(APIException.BAD_REQUEST, error: apiError);
@ -121,6 +129,7 @@ class ApiClient {
if (ex is APIException) {
rethrow;
} else {
log("BAD_RESPONSE_FORMAT for $url -> body: ${response.body}");
throw APIException(APIException.BAD_RESPONSE_FORMAT, arguments: ex);
}
}

@ -1,13 +1,20 @@
import 'dart:async';
import 'dart:io';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:mohem_flutter_app/api/api_client.dart';
import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:mohem_flutter_app/classes/consts.dart';
import 'package:mohem_flutter_app/classes/utils.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/dependent/get_mobile_login_info_dependent_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';
import 'package:sms_otp_auto_verify/sms_otp_auto_verify.dart';
class LoginApiClient {
static final LoginApiClient _instance = LoginApiClient._internal();
@ -16,33 +23,142 @@ class LoginApiClient {
factory LoginApiClient() => _instance;
Future<GetMobileLoginInfoListModel?> getMobileLoginInfoNEW(String deviceToken, String deviceType) async {
static String? _cachedSignature;
static Future<String?> getSignature() async {
if (!Platform.isAndroid) return null;
if (_cachedSignature != null && _cachedSignature!.isNotEmpty) {
return _cachedSignature;
}
try {
String? sig = await SmsVerification.getAppSignature();
if (sig != null && sig.isNotEmpty) {
_cachedSignature = sig;
}
return sig;
} catch (e) {
debugPrint("Error getting app signature: $e");
return null;
}
}
Future<GetMobileLoginInfoListModel?> getMobileLoginInfoNEW(String deviceToken, String deviceType, {int? memberType}) async {
String url = "${ApiConsts.erpRest}Mohemm_GetMobileLoginInfo_NEW";
Map<String, dynamic> postParams = {};
postParams["DeviceToken"] = deviceToken;
postParams["DeviceType"] = deviceType;
postParams["MemberType"] = memberType ?? ApiConsts.selectedMemberType.value;
return await ApiClient().postJsonForObject((json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
if (responseData != null) {
bool? isEmp = responseData.isEmpLastLoginToDevice;
if (isEmp == null && json is Map) {
dynamic raw = json["IsEmpLastLoginToDevice"] ?? json["isEmpLastLoginToDevice"];
if (raw is bool) {
isEmp = raw;
} else if (raw is num) {
isEmp = raw == 1;
} else if (raw is String) {
isEmp = raw.toLowerCase() == "true" || raw == "1";
}
}
AppState().setIsEmpLastLoginToDevice = isEmp;
}
return (responseData.mohemmGetMobileLoginInfoList?.length ?? 0) > 0 ? (responseData.mohemmGetMobileLoginInfoList!.first) : null;
}, url, postParams);
}
Future<GetMobileLoginInfoDependentModel?> getMobileLoginInfoNEWDependent(String deviceToken, String deviceType, {int? memberType}) async {
String url = "${ApiConsts.erpRest}Mohemm_GetMobileLoginInfo_NEW_Dependent";
String effectiveDeviceToken = deviceToken;
if (effectiveDeviceToken.isEmpty) {
effectiveDeviceToken = AppState().getDeviceToken ?? "";
}
// if (effectiveDeviceToken.isEmpty && !AppState().getIsHuawei) {
// try {
// await Firebase.initializeApp();
// String? tok = await FirebaseMessaging.instance.getToken();
// if (tok != null && tok.isNotEmpty) {
// effectiveDeviceToken = tok;
// AppState().setDeviceToken = tok;
// }
// } catch (_) {}
// }
Map<String, dynamic> postParams = {};
postParams["DeviceToken"] = effectiveDeviceToken;
postParams["DeviceType"] = deviceType;
postParams["MemberType"] = memberType ?? MemberType.dependent.value;
return await ApiClient().postJsonForObject((json) {
if (kDebugMode) {
print("Mohemm_GetMobileLoginInfo_NEW_Dependent Response: $json");
}
List? list = json['Mohemm_GetMobileLoginInfo_NEW_DependentList'] ??
json['Mohemm_GetMobileLoginInfo_NEW_Dependent'] ??
json['Mohemm_GetMobileLoginInfo_DependentList'] ??
json['Mohemm_GetMobileLoginInfoDependentList'] ??
json['Mohemm_GetMobileLoginInfoList'] ??
json['GetMobileLoginInfoList'] ??
json['Response'] ??
json['Data'];
if (list != null && list.isNotEmpty && list.first is Map<String, dynamic>) {
return GetMobileLoginInfoDependentModel.fromJson(list.first as Map<String, dynamic>);
}
if (json is Map<String, dynamic> &&
(json['DependentID'] != null || json['DepedentName'] != null || json['DOB'] != null || json['NationalID'] != null || json['LoginType'] != null)) {
return GetMobileLoginInfoDependentModel.fromJson(json);
}
return null;
}, url, postParams);
}
Future<GenericResponseModel?> insertMobileLoginInfoNEW(
String email, int sessionId, String employeeName, int loginType, String mobileNumber, String userName, String deviceToken, String deviceType) async {
String email, int sessionId, String employeeName, int loginType, String mobileNumber, String userName, String? deviceToken, String deviceType,
{int? memberType}) async {
String url = "${ApiConsts.erpRest}Mohemm_InsertMobileLoginInfo";
String devType = deviceType.isNotEmpty ? deviceType : (Platform.isAndroid ? "android" : "ios");
String devToken = (deviceToken != null && deviceToken.isNotEmpty && deviceToken != "null")
? deviceToken
: (AppState().getIsHuawei ? AppState().getHuaweiPushToken : (AppState().getDeviceToken ?? ""));
if (devToken.isEmpty || devToken == "null") {
try {
String savedToken = await Utils.getStringFromPrefs("P_DEP_DEVICE_TOKEN");
if (savedToken.isNotEmpty && savedToken != "null") {
devToken = savedToken;
}
} catch (_) {}
}
if (devToken.isEmpty || devToken == "null") {
try {
if (!AppState().getIsHuawei) {
String? tok = await FirebaseMessaging.instance.getToken();
if (tok != null && tok.isNotEmpty) {
devToken = tok;
AppState().setDeviceToken = tok;
Utils.saveStringFromPrefs("P_DEP_DEVICE_TOKEN", tok);
}
}
} catch (_) {}
}
Map<String, dynamic> postParams = {
"MobileNumber": mobileNumber,
"P_USER_NAME": userName,
"UserName": userName,
"CompanyID": 1, // todo 'sikander' @discuss umer for companyID
"DeviceToken": deviceToken,
"DeviceToken": devToken,
"LoginType": loginType,
"EmployeeName": employeeName,
"P_SESSION_ID": sessionId,
"P_EMAIL_ADDRESS": email
"P_EMAIL_ADDRESS": email,
"MemberType": memberType ?? ApiConsts.selectedMemberType.value,
};
postParams["DeviceToken"] = deviceToken;
postParams["DeviceType"] = deviceType;
postParams["DeviceToken"] = devToken;
postParams["DeviceType"] = devType;
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject((json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
@ -51,6 +167,93 @@ class LoginApiClient {
}, url, postParams);
}
Future<GenericResponseModel?> insertMobileLoginInfoDependent({
required int loginType,
String? nationalId,
String? dob,
String? dependentName,
String? mobileNumber,
String? logInTokenID,
String? tokenID,
int? sessionId,
String? email,
String? deviceToken,
String? deviceType,
int? companyId,
int? versionId,
int? channel,
int? memberType,
BuildContext? context,
}) async {
String url = "${ApiConsts.erpRest}Mohemm_InsertMobileLoginInfo_Dependent";
String devType = deviceType ?? (Platform.isAndroid ? "android" : "ios");
String devToken = (deviceToken != null && deviceToken.isNotEmpty && deviceToken != "null")
? deviceToken
: (AppState().getIsHuawei ? AppState().getHuaweiPushToken : (AppState().getDeviceToken ?? ""));
if (devToken.isEmpty || devToken == "null") {
try {
String savedToken = await Utils.getStringFromPrefs("P_DEP_DEVICE_TOKEN");
if (savedToken.isNotEmpty && savedToken != "null") {
devToken = savedToken;
}
} catch (_) {}
}
if (devToken.isEmpty || devToken == "null") {
try {
if (!AppState().getIsHuawei) {
String? tok = await FirebaseMessaging.instance.getToken();
if (tok != null && tok.isNotEmpty) {
devToken = tok;
AppState().setDeviceToken = tok;
Utils.saveStringFromPrefs("P_DEP_DEVICE_TOKEN", tok);
}
}
} catch (_) {}
}
String effectiveToken = (tokenID != null && tokenID.isNotEmpty)
? tokenID
: (AppState().postParamsObject?.tokenID ?? AppState().authenticationTokenID ?? "");
Map<String, dynamic> postParams = {
"VersionID": versionId ?? 5,
"Channel": channel,
"LanguageID": context != null ? AppState().getLanguageID(context) : 2,
"MemberType": memberType ?? 1,
"MobileType": devType,
"MobileNumber": mobileNumber ?? "",
"UserName": nationalId ?? "",
"P_USER_NAME": nationalId ?? "",
"P_DEP_NATIONAL_ID": nationalId ?? "",
"P_DEP_BIRTH_DATE": dob ?? "",
"P_DEPENDENT_NAME": dependentName ?? "",
"EmployeeName": dependentName ?? "",
"LogInTokenID": logInTokenID ?? (AppState().postParamsObject?.logInTokenID ?? ""),
"CompanyID": companyId ?? 1,
"DeviceType": devType,
"DeviceToken": devToken,
"LoginType": loginType,
"P_SESSION_ID": sessionId ?? (AppState().postParamsObject?.pSessionId ?? 0),
"TokenID": effectiveToken,
"P_EMAIL_ADDRESS": email ?? "",
};
if (kDebugMode) {
print("Mohemm_InsertMobileLoginInfo (Dependent) Request: $postParams");
}
return await ApiClient().postJsonForObject((json) {
if (kDebugMode) {
print("Mohemm_InsertMobileLoginInfo (Dependent) Response: $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 = {};
@ -69,8 +272,15 @@ class LoginApiClient {
}, url, postParams);
}
Future<BasicMemberInformationModel?> mohemmSendActivationCodeByOTPNotificationType(int isMobileFingerPrint, String? mobileNumber, int optSendType, String? pUserName) async {
Future<BasicMemberInformationModel?> mohemmSendActivationCodeByOTPNotificationType(
int isMobileFingerPrint,
String? mobileNumber,
int optSendType,
String? pUserName, {
String? smsSignature,
}) async {
String url = "${ApiConsts.erpRest}Mohemm_SendActivationCodebyOTPNotificationType";
String? sig = smsSignature ?? await getSignature();
Map<String, dynamic> postParams = {
"IsMobileFingerPrint": isMobileFingerPrint,
"MobileNumber": mobileNumber,
@ -80,12 +290,13 @@ class LoginApiClient {
"NotificationCenterAppSeviceGUID": ApiConsts.notificationCenterLoginServiceGUID,
};
postParams.addAll(AppState().postParamsJson);
postParams["smsSignature"] = sig ?? "";
return await ApiClient().postJsonForObject((json) => GenericResponseModel.fromJson(json).basicMemberInformation, url, postParams);
}
Future<GenericResponseModel?> checkActivationCode(bool isDeviceNFC, String? mobileNumber, String activationCode, String? pUserName) async {
Future<GenericResponseModel?> checkActivationCode(bool isDeviceNFC, String? mobileNumber, String activationCode, String? pUserName, String loginType) async {
String url = "${ApiConsts.erpRest}CheckActivationCode";
Map<String, dynamic> postParams = {"IsDeviceNFC": isDeviceNFC, "MobileNumber": mobileNumber, "activationCode": activationCode, "P_USER_NAME": pUserName};
Map<String, dynamic> postParams = {"IsDeviceNFC": isDeviceNFC, "MobileNumber": mobileNumber, "activationCode": activationCode, "P_USER_NAME": pUserName, "LoginType":loginType};
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject((json) {
GenericResponseModel responseData = GenericResponseModel.fromJson(json);
@ -97,8 +308,10 @@ class LoginApiClient {
AppState().postParamsObject?.pUserName = AppState().getUserName;
AppState().postParamsObject?.pSelectedEmployeeNumber = AppState().getUserName;
AppState().postParamsObject?.setPLegislationCode = responseData.basicMemberInformation!.pLEGISLATIONCODE;
AppState().postParamsInitConfig.setPLegislationCode = responseData.basicMemberInformation!.pLEGISLATIONCODE;
AppState().postParamsObject?.setPayrollCodeStr = responseData.memberInformationList!.first.pAYROLLCODE;
AppState().setBusinessCardPrivilege = responseData.businessCardPrivilege ?? false;
AppState().setPrivilegeListModel = responseData.privilegeList ?? [];
return responseData;
}, url, postParams);
}
@ -110,8 +323,9 @@ class LoginApiClient {
return await ApiClient().postJsonForObject((json) => GenericResponseModel.fromJson(json).basicMemberInformation, url, postParams);
}
Future<GenericResponseModel?> sendPublicActivationCode(String? mobileNumber, String? pUsername) async {
Future<GenericResponseModel?> sendPublicActivationCode(String? mobileNumber, String? pUsername, {String? smsSignature}) async {
String url = "${ApiConsts.erpRest}SendPublicActivationCode";
String? sig = smsSignature ?? await getSignature();
Map<String, dynamic> postParams = {
"MobileNumber": mobileNumber,
"P_MOBILE_NUMBER": mobileNumber,
@ -120,6 +334,8 @@ class LoginApiClient {
"NotificationCenterAppSeviceGUID": ApiConsts.notificationCenterForgotPasswordServiceGUID,
};
postParams.addAll(AppState().postParamsJson);
postParams["smsSignature"] = sig ?? "";
postParams["SMSSignature"] = sig ?? "";
return await ApiClient().postJsonForObject((json) {
GenericResponseModel responseData = GenericResponseModel.fromJson(json);
AppState().postParamsObject?.setLogInTokenID = responseData.logInTokenID;
@ -169,4 +385,144 @@ class LoginApiClient {
return responseData;
}, url, postParams);
}
Future<GenericResponseModel?> memberLoginDependent({
required String nationalId,
required String birthDate,
String? deviceToken,
String? deviceType,
BuildContext? context,
}) async {
String url = "${ApiConsts.erpRest}MemberLogin_Dependent";
String devType = deviceType ?? (Platform.isAndroid ? "android" : "ios");
String devToken = deviceToken ?? "";
if (devToken.isEmpty) {
devToken = AppState().getIsHuawei
? AppState().getHuaweiPushToken
: (AppState().getDeviceToken ?? "");
}
if (devToken.isEmpty && !AppState().getIsHuawei) {
try {
String savedToken = await Utils.getStringFromPrefs("P_DEP_DEVICE_TOKEN");
if (savedToken.isNotEmpty) {
devToken = savedToken;
}
} catch (_) {}
}
Map<String, dynamic> postParams = {
"LanguageID": context != null ? AppState().getLanguageID(context) : 2,
"DeviceToken": devToken,
"DeviceType": devType,
"MobileType": devType,
"P_DEP_NATIONAL_ID": nationalId,
"P_DEP_BIRTH_DATE": birthDate,
};
if (kDebugMode) {
print("MemberLogin_Dependent Request: $postParams");
}
return await ApiClient().postJsonForObject((json) {
if (kDebugMode) {
print("MemberLogin_Dependent Response: $json");
}
GenericResponseModel responseData = GenericResponseModel.fromJson(json);
if (responseData.logInTokenID != null && responseData.logInTokenID!.isNotEmpty) {
AppState().postParamsObject?.setLogInTokenID = responseData.logInTokenID;
}
String? authToken = (responseData.authenticationTokenID != null && responseData.authenticationTokenID!.trim().isNotEmpty)
? responseData.authenticationTokenID!.trim()
: null;
if (authToken != null && authToken.isNotEmpty) {
AppState().postParamsObject?.setTokenID = authToken;
AppState().setAuthenticationTokenID = authToken;
}
if (responseData.getWalaplusDepInfoModel != null) {
AppState().setWalaplusDepInfoModel = responseData.getWalaplusDepInfoModel;
if (responseData.getWalaplusDepInfoModel?.pDependentName != null &&
responseData.getWalaplusDepInfoModel!.pDependentName!.trim().isNotEmpty) {
AppState().setDependentName = responseData.getWalaplusDepInfoModel!.pDependentName!.trim();
}
}
AppState().setUserName = nationalId;
AppState().postParamsObject?.setUserName = nationalId;
AppState().postParamsObject?.setPUserName = nationalId;
return responseData;
}, url, postParams);
}
Future<GenericResponseModel?> mohemmSendOTPDependent({
required String nationalId,
required String mobileNumber,
required String logInTokenID,
BuildContext? context,
String? smsSignature,
}) async {
String url = "${ApiConsts.erpRest}Mohemm_SendOTP_Dependent";
String? sig = smsSignature ?? await getSignature();
Map<String, dynamic> postParams = {
"VersionID": 20,
"LanguageID": context != null ? AppState().getLanguageID(context) : 2,
"MobileType": Platform.isAndroid ? "android" : "ios",
"OTP_SendType": 1,
"MobileNumber": mobileNumber,
"IsMobileFingerPrint": 0,
"P_DEP_NATIONAL_ID": nationalId,
"P_USER_NAME": nationalId,
"LogInTokenID": logInTokenID,
"NotificationCenterAppGUID": ApiConsts.notificationCenterAppGUID,
"NotificationCenterAppSeviceGUID": ApiConsts.notificationCenterLoginServiceGUID,
"smsSignature": (sig != null && sig.isNotEmpty) ? sig : "",
};
return await ApiClient().postJsonForObject((json) {
GenericResponseModel responseData = GenericResponseModel.fromJson(json);
return responseData;
}, url, postParams);
}
Future<GenericResponseModel?> mohemmCheckOTPDependent({
required String logInTokenID,
required String activationCode,
required String nationalId,
required String mobileNumber,
BuildContext? context,
}) async {
String url = "${ApiConsts.erpRest}Mohemm_CheckOTP_Dependent";
Map<String, dynamic> postParams = {
"LogInTokenID": logInTokenID,
"activationCode": activationCode,
"P_USER_NAME": nationalId,
"P_DEP_NATIONAL_ID": nationalId,
"MobileNumber": mobileNumber,
"LanguageID": context != null ? AppState().getLanguageID(context) : 2,
"MobileType": Platform.isAndroid ? "android" : "ios",
};
return await ApiClient().postJsonForObject((json) {
GenericResponseModel responseData = GenericResponseModel.fromJson(json);
String effectiveToken = (responseData.authenticationTokenID != null && responseData.authenticationTokenID!.trim().isNotEmpty)
? responseData.authenticationTokenID!.trim()
: (AppState().postParamsObject?.tokenID ?? AppState().authenticationTokenID ?? "");
AppState().postParamsObject?.setTokenID = effectiveToken;
AppState().setAuthenticationTokenID = effectiveToken;
AppState().postParamsObject?.setLogInTokenID = (responseData.logInTokenID != null && responseData.logInTokenID!.isNotEmpty)
? responseData.logInTokenID!
: logInTokenID;
AppState().postParamsObject?.setUserName = nationalId;
AppState().postParamsObject?.setPUserName = nationalId;
AppState().postParamsObject?.setMobileNumer = mobileNumber;
AppState().setUserName = nationalId;
AppState().isAuthenticated = true;
AppState().setLogged = true;
if (responseData.getWalaplusDepInfoModel != null) {
AppState().setWalaplusDepInfoModel = responseData.getWalaplusDepInfoModel;
if (responseData.getWalaplusDepInfoModel?.pDependentName != null &&
responseData.getWalaplusDepInfoModel!.pDependentName!.trim().isNotEmpty) {
AppState().setDependentName = responseData.getWalaplusDepInfoModel!.pDependentName!.trim();
}
}
return responseData;
}, url, postParams);
}
}

@ -104,24 +104,18 @@ class MarathonApiClient {
}
Future<QuestionModel> getNextQuestion({required String? questionId, required String marathonId}) async {
Map<String, String?> jsonObject = <String, String?>{
"previousQuestionId": questionId,
"marathonId": marathonId,
};
Map<String, String?> jsonObject = <String, String?>{"previousQuestionId": questionId, "marathonId": marathonId};
return await ApiClient().postJsonForObject(
(json) {
MarathonGenericModel marathonModel = MarathonGenericModel.fromJson(json);
if (marathonModel.statusCode == 404 || marathonModel.statusCode == 208 || marathonModel.statusCode == 204 || marathonModel.statusCode == 500) {
Utils.confirmDialog(
AppRoutes.navigatorKey.currentContext,
Utils.showToast(
marathonModel.message!,
onTap: () {
onComplete: () {
AppRoutes.navigatorKey.currentContext!.read<MarathonProvider>().resetValues();
Navigator.of(
AppRoutes.navigatorKey.currentContext!,
).popUntil(ModalRoute.withName(AppRoutes.dashboard));
Navigator.of(AppRoutes.navigatorKey.currentContext!).popUntil(ModalRoute.withName(AppRoutes.dashboard));
},
);
}
@ -214,99 +208,99 @@ class MarathonApiClient {
return null;
}
// late HubConnection hubConnection;
// Future<void> buildHubConnection(BuildContext context, String prizeId) async {
// HttpConnectionOptions httpOptions = HttpConnectionOptions(skipNegotiation: false, logMessageContent: true);
// hubConnection = HubConnectionBuilder()
// .withUrl(
// ApiConsts.marathonHubConnectionUrl + "?employeeNumber=${AppState().memberInformationList!.eMPLOYEENUMBER ?? ""}&employeeName=${AppState().memberInformationList!.eMPLOYEENAME ?? ""}",
// options: httpOptions,
// )
// .withAutomaticReconnect(
// retryDelays: <int>[2000, 5000, 10000, 20000],
// )
// .configureLogging(
// Logger("Logging"),
// )
// .build();
// hubConnection.onclose(
// ({Exception? error}) {
// logger.i("onclose");
// },
// );
// hubConnection.onreconnecting(
// ({Exception? error}) {
// logger.i("onreconnecting");
// },
// );
// hubConnection.onreconnected(
// ({String? connectionId}) {
// logger.i("onreconnected");
// },
// );
// if (hubConnection.state != HubConnectionState.Connected) {
// await hubConnection.start();
// logger.i("Started HubConnection");
//
// await hubConnection.invoke(
// "AddParticipant",
// args: <Object>[
// <String, dynamic>{
// "employeeNumber": AppState().memberInformationList!.eMPLOYEENUMBER ?? "",
// "employeeName": AppState().memberInformationList!.eMPLOYEENAME ?? "",
// "marathonId": AppState().getMarathonProjectId,
// "prizeId": "8577B2E8-5DD7-43F0-10DD-08DACB0AC064",
// }
// ],
// ).catchError((e) {
// logger.i("Error in AddParticipant: $e");
// });
//
// context.read<MarathonProvider>().addItemToList(ApiConsts.dummyQuestion);
//
// await hubConnection.invoke(
// "SendQuestionToParticipant",
// args: <Object>[
// <String, dynamic>{
// "marathonId": "${AppState().getMarathonProjectId}",
// }
// ],
// ).catchError((e) {
// Utils.confirmDialog(context, e.toString());
// logger.i("Error in SendQuestionToParticipant: $e");
// });
//
// try {
// hubConnection.on("OnSendQuestionToParticipant", (List<Object?>? arguments) {
// onSendQuestionToParticipant(arguments, context);
// });
// } catch (e, s) {
// logger.i("Error in OnSendQuestionToParticipant");
// }
//
// try {
// hubConnection.on("OnParticipantJoin", (List<Object?>? arguments) {
// onParticipantJoin(arguments, context);
// });
// } catch (e, s) {
// logger.i("Error in OnParticipantJoin");
// }
// }
// }
//
// Future<void> onSendQuestionToParticipant(List<Object?>? arguments, BuildContext context) async {
// logger.i("onSendQuestionToParticipant arguments: $arguments");
//
// if (arguments != null) {
// Map<dynamic, dynamic> data = arguments.first! as Map<dynamic, dynamic>;
// var json = data["data"];
// QuestionModel newQuestion = QuestionModel.fromJson(json);
// AppRoutes.navigatorKey.currentContext!.read<MarathonProvider>().onNewQuestionReceived(newQuestion);
// }
// }
//
// Future<void> onParticipantJoin(List<Object?>? arguments, BuildContext context) async {
// logger.i("OnParticipantJoin arguments: $arguments");
// context.watch<MarathonProvider>().totalMarathoners++;
// }
// late HubConnection hubConnection;
// Future<void> buildHubConnection(BuildContext context, String prizeId) async {
// HttpConnectionOptions httpOptions = HttpConnectionOptions(skipNegotiation: false, logMessageContent: true);
// hubConnection = HubConnectionBuilder()
// .withUrl(
// ApiConsts.marathonHubConnectionUrl + "?employeeNumber=${AppState().memberInformationList!.eMPLOYEENUMBER ?? ""}&employeeName=${AppState().memberInformationList!.eMPLOYEENAME ?? ""}",
// options: httpOptions,
// )
// .withAutomaticReconnect(
// retryDelays: <int>[2000, 5000, 10000, 20000],
// )
// .configureLogging(
// Logger("Logging"),
// )
// .build();
// hubConnection.onclose(
// ({Exception? error}) {
// logger.i("onclose");
// },
// );
// hubConnection.onreconnecting(
// ({Exception? error}) {
// logger.i("onreconnecting");
// },
// );
// hubConnection.onreconnected(
// ({String? connectionId}) {
// logger.i("onreconnected");
// },
// );
// if (hubConnection.state != HubConnectionState.Connected) {
// await hubConnection.start();
// logger.i("Started HubConnection");
//
// await hubConnection.invoke(
// "AddParticipant",
// args: <Object>[
// <String, dynamic>{
// "employeeNumber": AppState().memberInformationList!.eMPLOYEENUMBER ?? "",
// "employeeName": AppState().memberInformationList!.eMPLOYEENAME ?? "",
// "marathonId": AppState().getMarathonProjectId,
// "prizeId": "8577B2E8-5DD7-43F0-10DD-08DACB0AC064",
// }
// ],
// ).catchError((e) {
// logger.i("Error in AddParticipant: $e");
// });
//
// context.read<MarathonProvider>().addItemToList(ApiConsts.dummyQuestion);
//
// await hubConnection.invoke(
// "SendQuestionToParticipant",
// args: <Object>[
// <String, dynamic>{
// "marathonId": "${AppState().getMarathonProjectId}",
// }
// ],
// ).catchError((e) {
// Utils.confirmDialog(context, e.toString());
// logger.i("Error in SendQuestionToParticipant: $e");
// });
//
// try {
// hubConnection.on("OnSendQuestionToParticipant", (List<Object?>? arguments) {
// onSendQuestionToParticipant(arguments, context);
// });
// } catch (e, s) {
// logger.i("Error in OnSendQuestionToParticipant");
// }
//
// try {
// hubConnection.on("OnParticipantJoin", (List<Object?>? arguments) {
// onParticipantJoin(arguments, context);
// });
// } catch (e, s) {
// logger.i("Error in OnParticipantJoin");
// }
// }
// }
//
// Future<void> onSendQuestionToParticipant(List<Object?>? arguments, BuildContext context) async {
// logger.i("onSendQuestionToParticipant arguments: $arguments");
//
// if (arguments != null) {
// Map<dynamic, dynamic> data = arguments.first! as Map<dynamic, dynamic>;
// var json = data["data"];
// QuestionModel newQuestion = QuestionModel.fromJson(json);
// AppRoutes.navigatorKey.currentContext!.read<MarathonProvider>().onNewQuestionReceived(newQuestion);
// }
// }
//
// Future<void> onParticipantJoin(List<Object?>? arguments, BuildContext context) async {
// logger.i("OnParticipantJoin arguments: $arguments");
// context.watch<MarathonProvider>().totalMarathoners++;
// }
}

@ -55,9 +55,9 @@ class MyAttendanceApiClient {
}, url, postParams);
}
Future<GenericResponseModel> getCEIDffStructure(String pFunctionName, String? empID) async {
Future<GenericResponseModel> getCEIDffStructure(String pFunctionName, String? empID, int? relationID) async {
String url = "${ApiConsts.erpRest}GET_CEI_DFF_STRUCTURE";
Map<String, dynamic> postParams = {"P_FUNCTION_NAME": pFunctionName};
Map<String, dynamic> postParams = {"P_FUNCTION_NAME": pFunctionName,"P_CONTACT_RELATIONSHIP_ID": relationID};
postParams.addAll(AppState().postParamsJson);
if (empID != null) postParams["P_SELECTED_EMPLOYEE_NUMBER"] = empID;
return await ApiClient().postJsonForObject((json) {

@ -1,13 +1,24 @@
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:mohem_flutter_app/api/api_client.dart';
import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:mohem_flutter_app/classes/consts.dart';
import 'package:mohem_flutter_app/models/generic_response_model.dart';
import 'package:mohem_flutter_app/models/offers_and_discounts/get_categories_list.dart';
import 'package:mohem_flutter_app/models/offers_and_discounts/get_offers_list.dart';
import 'package:mohem_flutter_app/models/offers_and_discounts/wala_plus_category_model.dart';
import 'package:mohem_flutter_app/models/offers_and_discounts/wala_plus_city_model.dart';
import 'package:mohem_flutter_app/models/offers_and_discounts/wala_plus_retailer_model.dart';
import 'package:mohem_flutter_app/widgets/location/Location.dart' as location;
class OffersAndDiscountsApiClient {
static final OffersAndDiscountsApiClient _instance = OffersAndDiscountsApiClient._internal();
static final OffersAndDiscountsApiClient _instance =
OffersAndDiscountsApiClient._internal();
static List<WalaPlusCategoryModel>? cachedCategories;
OffersAndDiscountsApiClient._internal();
@ -17,7 +28,12 @@ class OffersAndDiscountsApiClient {
List<GetCategoriesList> getSaleCategoriesList = [];
String url = "${ApiConsts.cocRest}Mohemm_ITG_GetCategories";
Map<String, dynamic> postParams = {"EmployeeNumber": AppState().memberInformationList?.eMPLOYEENUMBER, "ItgPageSize": 100, "ItgPageNo": 1, "ItgIsActive": true};
Map<String, dynamic> postParams = {
"EmployeeNumber": AppState().memberInformationList?.eMPLOYEENUMBER,
"ItgPageSize": 100,
"ItgPageNo": 1,
"ItgIsActive": true,
};
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject(
@ -44,11 +60,19 @@ class OffersAndDiscountsApiClient {
);
}
Future<List<OffersListModel>> getOffersList(int categoryID, int pageSize) async {
Future<List<OffersListModel>> getOffersList(
int categoryID,
int pageSize,
) async {
List<OffersListModel> getSaleCategoriesList = [];
String url = "${ApiConsts.cocRest}GetOfferDiscountsConfigData";
Map<String, dynamic> postParams = {"EmployeeNumber": AppState().memberInformationList?.eMPLOYEENUMBER, "ItgPageSize": pageSize, "ItgPageNo": 1, "ItgCategoryID": categoryID};
Map<String, dynamic> postParams = {
"EmployeeNumber": AppState().memberInformationList?.eMPLOYEENUMBER,
"ItgPageSize": pageSize,
"ItgPageNo": 1,
"ItgCategoryID": categoryID,
};
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject(
@ -68,4 +92,918 @@ class OffersAndDiscountsApiClient {
postParams,
);
}
String _resolveUserName() {
String? pUserName = AppState().getUserName;
if (pUserName == null || pUserName.isEmpty) {
pUserName =
AppState().postParamsObject?.userName ??
AppState().postParamsObject?.pUserName ??
AppState().memberInformationList?.eMPLOYEENUMBER;
}
return pUserName ?? "";
}
// Same Location.isEnabled -> Location.havePermission -> Location.getCurrentLocation pattern
// used by mark_attendance_widget.dart (e.g. Wifi attendance), wrapped as a Future since this
// is a background enrichment of the request, not a blocking user action - any failure (service
// disabled, permission denied, error) just resolves to null so the retailer fetch still proceeds
// without WP_Geo_Location rather than showing permission dialogs here.
Future<Position?> _getCurrentPositionSilently(BuildContext context) async {
Completer<bool> enabledCompleter = Completer<bool>();
location.Location.isEnabled((bool isEnabled) {
if (!enabledCompleter.isCompleted) enabledCompleter.complete(isEnabled);
});
bool isEnabled = await enabledCompleter.future;
debugPrint(
"_getCurrentPositionSilently: location service isEnabled=$isEnabled",
);
if (!isEnabled) return null;
Completer<bool> permissionCompleter = Completer<bool>();
location.Location.havePermission((bool hasPermission) {
if (!permissionCompleter.isCompleted)
permissionCompleter.complete(hasPermission);
});
bool hasPermission = await permissionCompleter.future;
debugPrint("_getCurrentPositionSilently: havePermission=$hasPermission");
if (!hasPermission) return null;
Completer<Position?> positionCompleter = Completer<Position?>();
location.Location.getCurrentLocation(
(Position position, bool isMocked) {
debugPrint(
"_getCurrentPositionSilently: got position lat=${position.latitude}, lng=${position.longitude}, isMocked=$isMocked",
);
if (!positionCompleter.isCompleted)
positionCompleter.complete(position);
},
() {
debugPrint(
"_getCurrentPositionSilently: getCurrentLocation error callback fired",
);
if (!positionCompleter.isCompleted) positionCompleter.complete(null);
},
context,
);
return positionCompleter.future;
}
String _resolveDepNationalId() {
if (!ApiConsts.isDependetLogin) {
return "";
}
return AppState().getUserName ?? "";
}
String _resolveMobileNumber({String? overrideMobile}) {
if (overrideMobile != null && overrideMobile.trim().isNotEmpty) {
return overrideMobile.trim();
}
// postParamsObject.mobileNumber/memberInformationList/memberLoginList are only ever set by
// the employee login flow and never cleared on logout - for a dependent session, the only
// legitimate source is the sponsor's mobile captured at dependent login (walaplusDepInfoModel),
// same reasoning as _resolveUserReference above.
if (ApiConsts.isDependetLogin) {
String? depMobile = AppState().walaplusDepInfoModel?.pEmpMobileNumber;
if (depMobile != null && depMobile.trim().isNotEmpty) {
return depMobile.trim();
}
return "";
}
String? mobile = AppState().postParamsObject?.mobileNumber;
if (mobile != null && mobile.trim().isNotEmpty) {
return mobile.trim();
}
mobile = AppState().memberInformationList?.eMPLOYEEMOBILENUMBER;
if (mobile != null && mobile.trim().isNotEmpty) {
return mobile.trim();
}
mobile = AppState().walaplusDepInfoModel?.pEmpMobileNumber;
if (mobile != null && mobile.trim().isNotEmpty) {
return mobile.trim();
}
mobile = AppState().memberLoginList?.pMOBILENUMBER;
if (mobile != null && mobile.trim().isNotEmpty) {
return mobile.trim();
}
mobile = AppState().memberInformationList?.mobileNumberWithZipCode;
if (mobile != null && mobile.trim().isNotEmpty) {
return mobile.trim();
}
return "";
}
Map<String, dynamic> _getBaseWalaPlusParams() {
Map<String, dynamic> base = {};
base.addAll(AppState().postParamsJson);
if (ApiConsts.isDependetLogin) {
String? token =
AppState().postParamsObject?.tokenID ??
AppState().authenticationTokenID;
if (token != null && token.isNotEmpty) {
base["TokenID"] = token;
}
String? logInToken = AppState().postParamsObject?.logInTokenID;
if (logInToken != null && logInToken.isNotEmpty) {
base["LogInTokenID"] = logInToken;
}
}
_cleanWalaPlusParams(base);
return base;
}
void _cleanWalaPlusParams(Map<String, dynamic> map) {
map.remove("P_EMAIL_ADDRESS");
map.remove("P_SESSION_ID");
map.remove("PayrollCodeStr");
map.remove("LegislationCodeStr");
map.remove("P_SELECTED_EMPLOYEE_NUMBER");
map.removeWhere((key, value) => value == null);
}
// Pass parentId to fetch the subcategories under a given main category (WP_ParentId) -
// omit it (the default) to fetch the top-level main categories, same as before.
Future<List<WalaPlusCategoryModel>> getWalaPlusCategories({
BuildContext? context,
String? mobileNumber,
String? parentId,
}) async {
String url = ApiConsts.walaPlusCategories;
String pUserName = _resolveUserName();
String pMobileNumber = _resolveMobileNumber(overrideMobile: mobileNumber);
String pDepNationalId = _resolveDepNationalId();
Map<String, dynamic> postParams = _getBaseWalaPlusParams();
postParams["UserName"] = pUserName;
postParams["P_USER_NAME"] = pUserName;
postParams["P_DEP_NATIONAL_ID"] = pDepNationalId;
if (pMobileNumber.isNotEmpty) {
postParams["MobileNumber"] = pMobileNumber;
}
postParams["MemberType"] = ApiConsts.isDependetLogin ? 2 : 1;
postParams["CompanyID"] = 1;
if (parentId != null && parentId.trim().isNotEmpty) {
postParams["WP_Parent_Id"] = parentId.trim();
}
_cleanWalaPlusParams(postParams);
List<WalaPlusCategoryModel> list = await ApiClient().postJsonForObject(
(response) {
List<WalaPlusCategoryModel> categories = [];
if (response != null) {
dynamic cats =
response['WalaPlus_Categories'] ??
response['walaPlus_Categories'] ??
response['WalaPlusCategories'] ??
response['result'];
if (cats is String) {
try {
cats = json.decode(cats);
} catch (e) {
debugPrint("Error decoding WalaPlus_Categories: $e");
}
}
if (cats is List) {
cats.forEach((v) {
if (v is Map<String, dynamic>) {
categories.add(WalaPlusCategoryModel.fromJson(v));
} else if (v is Map) {
categories.add(
WalaPlusCategoryModel.fromJson(Map<String, dynamic>.from(v)),
);
}
});
}
}
return categories;
},
url,
postParams,
);
if ((parentId == null || parentId.trim().isEmpty) && list.isNotEmpty) {
cachedCategories = list;
}
return list;
}
// Fetched fresh every time the city picker opens (no caching) - see
// mazaya_city_selector.dart. Only P_USER_NAME/MobileNumber (resolved the same way as every
// other WalaPlus call) and WP_Country vary per call; everything else is the shared default
// WalaPlus params/token from _getBaseWalaPlusParams.
Future<List<WalaPlusCityModel>> getWalaPlusCities({
String? mobileNumber,
String? country,
}) async {
String url = ApiConsts.walaPlusCities;
String pUserName = _resolveUserName();
String pMobileNumber = _resolveMobileNumber(overrideMobile: mobileNumber);
Map<String, dynamic> postParams = _getBaseWalaPlusParams();
postParams["P_USER_NAME"] = pUserName;
if (pMobileNumber.isNotEmpty) {
postParams["MobileNumber"] = pMobileNumber;
}
postParams["WP_Country"] = country ?? "";
_cleanWalaPlusParams(postParams);
return await ApiClient().postJsonForObject(
(response) {
List<WalaPlusCityModel> cities = [];
if (response != null) {
dynamic data =
response['WalaPlus_Cities'] ?? response['walaPlus_Cities'];
if (data is String) {
try {
data = json.decode(data);
} catch (e) {
debugPrint("Error decoding WalaPlus_Cities: $e");
}
}
if (data is List) {
for (var v in data) {
if (v is Map<String, dynamic>) {
cities.add(WalaPlusCityModel.fromJson(v));
} else if (v is Map) {
cities.add(
WalaPlusCityModel.fromJson(Map<String, dynamic>.from(v)),
);
}
}
}
}
return cities;
},
url,
postParams,
);
}
// Confirmed with backend team: WP_Offer_Type accepts "online" and "store".
static const String walaPlusOfferTypeOnline = "online";
static const String walaPlusOfferTypeInStore = "store";
// Maps the screen's internal filter key ("online"/"instore"/"all") to the WP_Offer_Type value
// the backend expects. Returns null for "all" (or anything unrecognized) so callers can skip
// sending WP_Offer_Type entirely.
static String? mapOfferTypeFilter(String internalKey) {
switch (internalKey) {
case "online":
return walaPlusOfferTypeOnline;
case "instore":
return walaPlusOfferTypeInStore;
default:
return null;
}
}
Future<WalaPlusRetailersResponse?> getWalaPlusRetailers({
String? categoryId,
// Every existing caller passes categoryId (even "" for "all") and relies on WP_CategoryID
// always being present in the request - default true preserves that for everyone. Pass
// false only when the category filter must be fully omitted (e.g. a cross-category newest
// feed), not just set to empty.
bool includeCategoryId = true,
int page = 1,
int perPage = 10,
String? geoLocation,
String? offerType,
String? sortBy,
int? radius,
int? minDiscount,
// Confirmed with backend team: WP_City_ID=3 or WP_City_ID=0 both mean "all cities" (no
// filter) - any other id filters to that specific city. Default null -> 0, same "always
// send a concrete int" contract as WP_Radius/WP_Min_Discount below.
int? cityId,
BuildContext? context,
String? mobileNumber,
String? query,
String? userReference,
}) async {
String url = ApiConsts.walaPlusRetailers;
String pUserName = _resolveUserName();
String pMobileNumber = _resolveMobileNumber(overrideMobile: mobileNumber);
String pDepNationalId = _resolveDepNationalId();
String effectiveCatId =
(categoryId == "all" || categoryId == "0" || categoryId == null)
? ""
: categoryId;
Map<String, dynamic> postParams = _getBaseWalaPlusParams();
postParams["UserName"] = pUserName;
postParams["P_USER_NAME"] = pUserName;
postParams["P_DEP_NATIONAL_ID"] = pDepNationalId;
if (pMobileNumber.isNotEmpty) {
postParams["MobileNumber"] = pMobileNumber;
}
postParams["MemberType"] = ApiConsts.isDependetLogin ? 2 : 1;
postParams["CompanyID"] = 1;
if (includeCategoryId) {
postParams["WP_CategoryID"] = effectiveCatId;
}
if (offerType != null && offerType.trim().isNotEmpty) {
postParams["WP_Offer_Type"] = offerType.trim();
}
postParams["WP_Page"] = page;
postParams["WP_Per_page"] = perPage;
if (query != null && query.trim().isNotEmpty) {
postParams["WP_Query"] = query.trim();
}
if (sortBy != null && sortBy.trim().isNotEmpty) {
postParams["WP_Sort_By"] = sortBy.trim();
}
// Backend contract declares these as non-nullable int DataMembers, so "Any"/"All" (null
// here) must be sent as a concrete 0, not omitted.
postParams["WP_Radius"] = radius ?? 0;
postParams["WP_Min_Discount"] = minDiscount ?? 0;
postParams["WP_City_ID"] = cityId ?? 0;
if (geoLocation != null && geoLocation.isNotEmpty) {
postParams["WP_Geo_Location"] = geoLocation;
} else if (context != null) {
try {
Position? position = await _getCurrentPositionSilently(context);
if (position != null) {
postParams["WP_Geo_Location"] =
"${position.latitude},${position.longitude}";
}
} catch (e) {
debugPrint("getWalaPlusRetailers: unable to get current location: $e");
}
}
_cleanWalaPlusParams(postParams);
// Pass WP_User_Reference: if filled then user ID, else null value
if (userReference != null && userReference.trim().isNotEmpty) {
postParams["WP_User_Reference"] = userReference.trim();
} else {
postParams["WP_User_Reference"] = null;
}
return await ApiClient().postJsonForObject(
(response) {
if (response != null) {
dynamic val =
response['WalaPlus_Retailers'] ??
response['walaPlus_Retailers'] ??
response['WalaPlusRetailers'] ??
response['WP_Retailers'] ??
response['result'];
if (val is String) {
try {
val = json.decode(val);
} catch (e) {
debugPrint("Error decoding WalaPlus_Retailers: $e");
}
}
if (val is Map<String, dynamic>) {
return WalaPlusRetailersResponse.fromJson(val);
} else if (val is Map) {
return WalaPlusRetailersResponse.fromJson(
Map<String, dynamic>.from(val),
);
} else if (val is List) {
List<WalaPlusRetailerModel> retailers = [];
for (var v in val) {
if (v is Map<String, dynamic>) {
retailers.add(WalaPlusRetailerModel.fromJson(v));
} else if (v is Map) {
retailers.add(
WalaPlusRetailerModel.fromJson(Map<String, dynamic>.from(v)),
);
}
}
return WalaPlusRetailersResponse(
retailers: retailers,
page: 1,
pagesCount: 1,
perPage: retailers.length,
);
}
}
return null;
},
url,
postParams,
);
}
Future<WalaPlusRetailerModel?> getWalaPlusRetailerById({
required String retailerId,
BuildContext? context,
String? mobileNumber,
String? userReference,
}) async {
String url = ApiConsts.walaPlusRetailersByID;
String pUserName = _resolveUserName();
String pMobileNumber = _resolveMobileNumber(overrideMobile: mobileNumber);
String pDepNationalId = _resolveDepNationalId();
String pUserReference = _resolveUserReference(
overrideUserReference: userReference,
);
Map<String, dynamic> postParams = _getBaseWalaPlusParams();
postParams["WP_RetailerID"] = retailerId;
postParams["RetailerID"] = retailerId;
postParams["UserName"] = pUserName;
postParams["P_USER_NAME"] = pUserName;
postParams["P_DEP_NATIONAL_ID"] = pDepNationalId;
if (pMobileNumber.isNotEmpty) {
postParams["MobileNumber"] = pMobileNumber;
}
postParams["MemberType"] = ApiConsts.isDependetLogin ? 2 : 1;
postParams["CompanyID"] = 1;
if (context != null) {
postParams["LanguageID"] = AppState().getLanguageID(context);
try {
Position? position = await _getCurrentPositionSilently(context);
log("context position: $position");
if (position != null) {
postParams["WP_Geo_Location"] =
"${position.latitude},${position.longitude}";
}
} catch (e) {
debugPrint(
"getWalaPlusRetailerById: unable to get current location: $e",
);
}
}
_cleanWalaPlusParams(postParams);
// Pass WP_User_Reference: if filled then user ID, else null value
if (pUserReference.isNotEmpty) {
postParams["WP_User_Reference"] = pUserReference;
} else {
postParams["WP_User_Reference"] = null;
}
debugPrint(
"getWalaPlusRetailerById: calling $url with params: $postParams",
);
return await ApiClient().postJsonForObject(
(response) {
if (response != null) {
debugPrint(
"getWalaPlusRetailerById response keys: ${response is Map ? response.keys.toList() : 'non-map'}",
);
dynamic val =
response['WalaPlus_RetailersByID'] ??
response['walaPlus_RetailersByID'] ??
response['WalaPlusRetailersByID'] ??
response['WP_RetailersByID'] ??
response['result'];
if (val is String) {
try {
val = json.decode(val);
} catch (e) {
debugPrint(
"Error decoding WalaPlus_RetailersByID JSON string: $e",
);
}
}
WalaPlusRetailerModel? model;
if (val is List && val.isNotEmpty) {
var item = val.first;
if (item is Map<String, dynamic>) {
model = WalaPlusRetailerModel.fromJson(item);
} else if (item is Map) {
model = WalaPlusRetailerModel.fromJson(
Map<String, dynamic>.from(item),
);
}
} else if (val is Map) {
Map<String, dynamic> map =
(val is Map<String, dynamic>)
? val
: Map<String, dynamic>.from(val);
if (map.containsKey('retailer')) {
var byIdResponse = WalaPlusRetailersByIDResponse.fromJson(map);
model =
byIdResponse.primaryRetailer ??
WalaPlusRetailerModel.fromJson(map);
} else {
model = WalaPlusRetailerModel.fromJson(map);
}
if (model?.isFavorite == null) {
if (map.containsKey('isfavorite')) {
model?.isFavorite =
map['isfavorite'] == true ||
map['isfavorite'] == 1 ||
map['isfavorite'] == 'true' ||
map['isfavorite'] == '1';
} else if (map.containsKey('is_favorite')) {
model?.isFavorite =
map['is_favorite'] == true ||
map['is_favorite'] == 1 ||
map['is_favorite'] == 'true' ||
map['is_favorite'] == '1';
}
}
if ((model?.offers == null || model!.offers!.isEmpty) &&
(map.containsKey('offers') || map.containsKey('Offers'))) {
model?.offers = WalaPlusRetailerModel.parseOffersList(
map['offers'] ?? map['Offers'],
);
}
if ((model?.locations == null || model!.locations!.isEmpty) &&
(map.containsKey('locations') ||
map.containsKey('Locations') ||
map.containsKey('location') ||
map.containsKey('Location'))) {
model?.locations = WalaPlusRetailerModel.parseLocationsList(
map['locations'] ??
map['Locations'] ??
map['location'] ??
map['Location'],
);
}
if ((model?.website == null ||
model!.website!.isEmpty ||
model!.website == "null")) {
String? web =
(map['website'] ??
map['merchant_website'] ??
map['website_url'] ??
map['url'] ??
map['merchant_url'])
?.toString();
if (web != null && web.trim().isNotEmpty && web != "null") {
model?.website = web.trim();
}
}
if ((model?.categories == null || model!.categories!.isEmpty) &&
(map.containsKey('categories') ||
map.containsKey('Categories') ||
map.containsKey('category') ||
map.containsKey('Category'))) {
dynamic catData =
map['categories'] ??
map['Categories'] ??
map['category'] ??
map['Category'];
if (catData is List) {
model?.categories =
catData
.map(
(c) =>
c is Map
? WalaPlusCategoryModel.fromJson(
Map<String, dynamic>.from(c),
)
: WalaPlusCategoryModel(name: c.toString()),
)
.toList();
} else if (catData is Map) {
model?.categories = [
WalaPlusCategoryModel.fromJson(
Map<String, dynamic>.from(catData),
),
];
} else if (catData is String && catData.trim().isNotEmpty) {
model?.categories = [
WalaPlusCategoryModel(name: catData.trim()),
];
}
}
} else if (response is Map<String, dynamic> &&
response.containsKey('name')) {
model = WalaPlusRetailerModel.fromJson(response);
}
if (model != null) {
if (response is Map) {
if (model.isFavorite == null) {
if (response.containsKey('isfavorite')) {
model.isFavorite =
response['isfavorite'] == true ||
response['isfavorite'] == 1 ||
response['isfavorite'] == 'true' ||
response['isfavorite'] == '1';
} else if (response.containsKey('is_favorite')) {
model.isFavorite =
response['is_favorite'] == true ||
response['is_favorite'] == 1 ||
response['is_favorite'] == 'true' ||
response['is_favorite'] == '1';
}
}
if (model.website == null ||
model.website!.isEmpty ||
model.website == "null") {
String? web =
(response['website'] ??
response['merchant_website'] ??
response['website_url'] ??
response['url'] ??
response['merchant_url'])
?.toString();
if (web != null && web.trim().isNotEmpty && web != "null") {
model.website = web.trim();
}
}
if ((model.offers == null || model.offers!.isEmpty) &&
(response.containsKey('offers') ||
response.containsKey('Offers'))) {
model.offers = WalaPlusRetailerModel.parseOffersList(
response['offers'] ?? response['Offers'],
);
}
if ((model.locations == null || model.locations!.isEmpty) &&
(response.containsKey('locations') ||
response.containsKey('Locations') ||
response.containsKey('location') ||
response.containsKey('Location'))) {
model.locations = WalaPlusRetailerModel.parseLocationsList(
response['locations'] ??
response['Locations'] ??
response['location'] ??
response['Location'],
);
}
if ((model.categories == null || model.categories!.isEmpty) &&
(response.containsKey('categories') ||
response.containsKey('Categories') ||
response.containsKey('category') ||
response.containsKey('Category'))) {
dynamic catData =
response['categories'] ??
response['Categories'] ??
response['category'] ??
response['Category'];
if (catData is List) {
model.categories =
catData
.map(
(c) =>
c is Map
? WalaPlusCategoryModel.fromJson(
Map<String, dynamic>.from(c),
)
: WalaPlusCategoryModel(name: c.toString()),
)
.toList();
} else if (catData is Map) {
model.categories = [
WalaPlusCategoryModel.fromJson(
Map<String, dynamic>.from(catData),
),
];
} else if (catData is String && catData.trim().isNotEmpty) {
model.categories = [
WalaPlusCategoryModel(name: catData.trim()),
];
}
}
}
debugPrint(
"getWalaPlusRetailerById: parsed retailer '${model.name}', isFavorite: ${model.isFavorite}, offers: ${model.offers?.length ?? 0}, locations: ${model.locations?.length ?? 0}",
);
return model;
}
}
return null;
},
url,
postParams,
);
}
String resolveUserReference({String? overrideUserReference}) {
return _resolveUserReference(overrideUserReference: overrideUserReference);
}
String _resolveUserReference({String? overrideUserReference}) {
if (overrideUserReference != null &&
overrideUserReference.trim().isNotEmpty) {
return overrideUserReference.trim();
}
// AppState().memberInformationList is never cleared on logout, so after logging out of an
// employee session and logging back in as a dependent (without a full app restart) it can
// still be sitting there from the previous session. Checking the CURRENT session type first
// - rather than just whichever field happens to be non-empty - prevents a dependent session
// from ever resolving to the previous employee's number.
if (ApiConsts.isDependetLogin) {
String? depRef = AppState().getUserName;
if (depRef != null && depRef.trim().isNotEmpty) {
return depRef.trim();
}
return "";
}
String? ref = AppState().memberInformationList?.eMPLOYEENUMBER?.toString();
if (ref != null && ref.trim().isNotEmpty) {
return ref.trim();
}
String? pUserName = AppState().getUserName;
if (pUserName != null && pUserName.trim().isNotEmpty) {
return pUserName.trim();
}
return "";
}
Future<Map<String, dynamic>?> redeemWalaPlusOffer({
required String offerId,
String? offerType,
String? userReference,
BuildContext? context,
}) async {
String url = ApiConsts.walaPlusOfferRedeem;
String pUserName = _resolveUserName();
String pUserReference = _resolveUserReference(
overrideUserReference: userReference,
);
Map<String, dynamic> postParams = _getBaseWalaPlusParams();
postParams["WP_User_Reference"] = pUserReference;
postParams["WP_Offer_Id"] = offerId;
postParams["WP_Offer_Type"] =
(offerType != null && offerType.trim().isNotEmpty)
? offerType.trim()
: "promo";
postParams["UserName"] = pUserName;
postParams["P_USER_NAME"] = pUserName;
if (ApiConsts.isDependetLogin) {
String pDepNationalId = _resolveDepNationalId();
if (pDepNationalId.isNotEmpty) {
postParams["P_DEP_NATIONAL_ID"] = pDepNationalId;
}
postParams["MemberType"] = 2;
} else {
postParams["P_DEP_NATIONAL_ID"] = "";
postParams["MemberType"] = 1;
}
_cleanWalaPlusParams(postParams);
debugPrint("redeemWalaPlusOffer calling $url with params: $postParams");
return await ApiClient().postJsonForObject(
(response) {
if (response != null) {
debugPrint("redeemWalaPlusOffer response: $response");
dynamic val =
response['WalaPlus_OfferRedeem'] ??
response['walaPlus_OfferRedeem'] ??
response['WalaPlusOfferRedeem'] ??
response['result'];
if (val is String) {
try {
val = json.decode(val);
} catch (e) {
debugPrint("Error decoding WalaPlus_OfferRedeem: $e");
}
}
if (val is Map<String, dynamic>) {
return val;
} else if (val is Map) {
return Map<String, dynamic>.from(val);
}
}
return null;
},
url,
postParams,
);
}
Future<GenericResponseModel?> addWalaPlusFavorite({
Object? retailerId,
List<String>? retailerIds,
BuildContext? context,
String? mobileNumber,
String? userReference,
}) async {
String url = ApiConsts.walaPlusFavoritesAdd;
String pUserName = _resolveUserName();
String pMobileNumber = _resolveMobileNumber(overrideMobile: mobileNumber);
String pUserReference = _resolveUserReference(
overrideUserReference: userReference,
);
List<String> ids = [];
if (retailerIds != null && retailerIds.isNotEmpty) {
ids.addAll(retailerIds);
} else if (retailerId != null) {
if (retailerId is List) {
ids.addAll(retailerId.map((e) => e.toString()));
} else {
ids.add(retailerId.toString());
}
}
Map<String, dynamic> postParams = _getBaseWalaPlusParams();
postParams["P_USER_NAME"] = pUserName;
postParams["UserName"] = pUserName;
if (pMobileNumber.isNotEmpty) {
postParams["MobileNumber"] = pMobileNumber;
}
if (pUserReference.isNotEmpty) {
postParams["WP_User_Reference"] = pUserReference;
}
postParams["WP_Retailer_Ids"] = ids;
if (context != null) {
postParams["LanguageID"] = AppState().getLanguageID(context);
}
if (ApiConsts.isDependetLogin) {
String pDepNationalId = _resolveDepNationalId();
if (pDepNationalId.isNotEmpty) {
postParams["P_DEP_NATIONAL_ID"] = pDepNationalId;
}
postParams["MemberType"] = 2;
} else {
postParams["MemberType"] = 1;
}
_cleanWalaPlusParams(postParams);
debugPrint("addWalaPlusFavorite calling $url with params: $postParams");
return await ApiClient().postJsonForObject(
(response) {
if (response != null) {
debugPrint("addWalaPlusFavorite response: $response");
return GenericResponseModel.fromJson(response);
}
return null;
},
url,
postParams,
);
}
Future<GenericResponseModel?> removeWalaPlusFavorite({
Object? retailerId,
List<String>? retailerIds,
BuildContext? context,
String? mobileNumber,
String? userReference,
}) async {
String url = ApiConsts.walaPlusFavoritesRemove;
String pUserName = _resolveUserName();
String pMobileNumber = _resolveMobileNumber(overrideMobile: mobileNumber);
String pUserReference = _resolveUserReference(
overrideUserReference: userReference,
);
List<String> ids = [];
if (retailerIds != null && retailerIds.isNotEmpty) {
ids.addAll(retailerIds);
} else if (retailerId != null) {
if (retailerId is List) {
ids.addAll(retailerId.map((e) => e.toString()));
} else {
ids.add(retailerId.toString());
}
}
Map<String, dynamic> postParams = _getBaseWalaPlusParams();
postParams["P_USER_NAME"] = pUserName;
postParams["UserName"] = pUserName;
if (pMobileNumber.isNotEmpty) {
postParams["MobileNumber"] = pMobileNumber;
}
if (pUserReference.isNotEmpty) {
postParams["WP_User_Reference"] = pUserReference;
}
postParams["WP_Retailer_Ids"] = ids;
if (context != null) {
postParams["LanguageID"] = AppState().getLanguageID(context);
}
if (ApiConsts.isDependetLogin) {
String pDepNationalId = _resolveDepNationalId();
if (pDepNationalId.isNotEmpty) {
postParams["P_DEP_NATIONAL_ID"] = pDepNationalId;
}
postParams["MemberType"] = 2;
} else {
postParams["MemberType"] = 1;
}
_cleanWalaPlusParams(postParams);
debugPrint("removeWalaPlusFavorite calling $url with params: $postParams");
return await ApiClient().postJsonForObject(
(response) {
if (response != null) {
debugPrint("removeWalaPlusFavorite response: $response");
return GenericResponseModel.fromJson(response);
}
return null;
},
url,
postParams,
);
}
}

@ -1,9 +1,11 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
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/classes/date_uitl.dart';
import 'package:mohem_flutter_app/models/generic_response_model.dart';
import 'package:mohem_flutter_app/models/get_approves_list_model.dart';
import 'package:mohem_flutter_app/models/get_contact_dff_structure_list.dart';
@ -24,8 +26,29 @@ import 'package:mohem_flutter_app/models/profile/submit_address_transaction.dart
import 'package:mohem_flutter_app/models/profile/submit_basic_details_transaction_model.dart';
import 'package:mohem_flutter_app/models/profile/submit_contact_transaction_list_model.dart';
import 'package:mohem_flutter_app/models/profile/submit_phone_transactions.dart';
import 'package:mohem_flutter_app/models/profile/patient_info_model.dart';
import 'package:mohem_flutter_app/classes/utils.dart';
import 'package:mohem_flutter_app/models/start_phone_approval_process_model.dart';
// The ERP WCF service's DataContractJsonSerializer rejects any DateTime-typed field unless
// it's wrapped as "/Date(ms)/" - confirmed by its own error on this exact call:
// "DateTime content '6/23/1989 12:00:00 AM' does not start with '/Date(' and end with ')/'
// as required for JSON." GET_EMPLOYEE_CONTACTS hands DATE_OF_BIRTH back as a plain
// "M/d/yyyy h:mm:ss a" string, so it has to be converted before going back out as P_BIRTH_DATE.
String? _toWcfJsonDate(rawDate) {
if (rawDate == null) return null;
String raw = rawDate.toString().trim();
if (raw.isEmpty) return null;
if (raw.contains('/Date(')) return raw;
try {
DateTime parsed = DateUtil.convertSimpleStringDateToDate(raw);
int utcMillis = DateTime.utc(parsed.year, parsed.month, parsed.day).millisecondsSinceEpoch;
return "/Date($utcMillis)/";
} catch (_) {
return null;
}
}
class ProfileApiClient {
static final ProfileApiClient _instance = ProfileApiClient._internal();
@ -33,99 +56,140 @@ class ProfileApiClient {
factory ProfileApiClient() => _instance;
Future<List<GetEmployeeContactsList>> getEmployeeContacts() async {
String url = "${ApiConsts.erpRest}GET_EMPLOYEE_CONTACTS";
// GET_EMP_BUSINESS_CARD_INFO requires the literal TokenID below to validate the call -
// it is not the usual session TokenID, so we don't merge AppState().postParamsJson here.
Future<GenericResponseModel?> getEmployeeBusinessCardInfo() async {
String url = "${ApiConsts.erpRest}GET_EMP_BUSINESS_CARD_INFO";
String deviceToken = AppState().getIsHuawei ? AppState().getHuaweiPushToken : (AppState().getDeviceToken ?? "");
Map<String, dynamic> postParams = {
"P_MENU_TYPE": "E",
"P_SELECTED_RESP_ID": -999,
"VersionID": AppState().postParamsObject?.versionID,
"Channel": AppState().postParamsObject?.channel,
"LanguageID": AppState().postParamsObject?.languageID,
"TokenID": "GET_EMP_BUSINESS_CARD_INFO",
"P_USER_NAME": AppState().getUserName ?? AppState().postParamsObject?.userName ?? AppState().postParamsObject?.pUserName,
"DeviceToken": deviceToken,
"DeviceType": Platform.isAndroid ? "android" : "ios",
};
print(jsonEncode(postParams));
return await ApiClient().postJsonForObject(
(json) {
return GenericResponseModel.fromJson(json);
},
url,
postParams,
);
}
Future<List<GetEmployeeContactsList>> getEmployeeContacts() async {
String url = "${ApiConsts.erpRest}GET_EMPLOYEE_CONTACTS";
Map<String, dynamic> postParams = {"P_MENU_TYPE": "E", "P_SELECTED_RESP_ID": -999};
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject((json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getEmployeeContactsList ?? [];
}, url, postParams);
return await ApiClient().postJsonForObject(
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getEmployeeContactsList ?? [];
},
url,
postParams,
);
}
Future<List<GetEmployeeBasicDetailsList>> getEmployeeBasicDetails() async {
String url = "${ApiConsts.erpRest}GET_EMPLOYEE_BASIC_DETAILS";
Map<String, dynamic> postParams = {
"P_MENU_TYPE": "E",
"P_SELECTED_RESP_ID": -999,
};
Map<String, dynamic> postParams = {"P_MENU_TYPE": "E", "P_SELECTED_RESP_ID": -999};
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject((json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getEmployeeBasicDetailsList ?? [];
}, url, postParams);
return await ApiClient().postJsonForObject(
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getEmployeeBasicDetailsList ?? [];
},
url,
postParams,
);
}
Future<List<GetEmployeePhonesList>> getEmployeePhones() async {
String url = "${ApiConsts.erpRest}GET_EMPLOYEE_PHONES";
Map<String, dynamic> postParams = {
"P_MENU_TYPE": "E",
"P_SELECTED_RESP_ID": -999,
};
Map<String, dynamic> postParams = {"P_MENU_TYPE": "E", "P_SELECTED_RESP_ID": -999};
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject((json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getEmployeePhonesList ?? [];
}, url, postParams);
return await ApiClient().postJsonForObject(
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getEmployeePhonesList ?? [];
},
url,
postParams,
);
}
Future<List<GetEmployeeAddressList>> getEmployeeAddress() async {
String url = "${ApiConsts.erpRest}GET_EMPLOYEE_ADDRESS";
Map<String, dynamic> postParams = {
"P_MENU_TYPE": "E",
"P_SELECTED_RESP_ID": -999,
};
Map<String, dynamic> postParams = {"P_MENU_TYPE": "E", "P_SELECTED_RESP_ID": -999};
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject((json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getEmployeeAddressList ?? [];
}, url, postParams);
return await ApiClient().postJsonForObject(
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getEmployeeAddressList ?? [];
},
url,
postParams,
);
}
Future<List<EmployeeDocumentsList>> getEmployeeDocuments() async {
String url = "${ApiConsts.erpRest}GET_EMPLOYEE_DOCUMENTS";
Map<String, dynamic> postParams = {
"P_MENU_TYPE": "E",
"P_SELECTED_RESP_ID": -999,
};
Map<String, dynamic> postParams = {"P_MENU_TYPE": "E", "P_SELECTED_RESP_ID": -999};
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject((json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.employeeDocumentsList ?? [];
}, url, postParams);
return await ApiClient().postJsonForObject(
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.employeeDocumentsList ?? [];
},
url,
postParams,
);
}
Future updateEmpImage(img) async {
String url = "${ApiConsts.erpRest}UPDATE_EMPLOYEE_IMAGE";
Map<String, dynamic> postParams = {"P_MENU_TYPE": "E", "P_SELECTED_RESP_ID": -999, "P_IMAGE": img};
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject((json) {
// GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return json['UpdateEmployeeImageList'];
}, url, postParams);
return await ApiClient().postJsonForObject(
(json) {
// GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return json['UpdateEmployeeImageList'];
},
url,
postParams,
);
}
Future<GenericResponseModel> getDffStructure(String pFunctionName, String uRL, String requestType) async {
String url = ApiConsts.erpRest + uRL;
Map<String, dynamic> postParams = {"P_SELECTED_RESP_ID": -999, "P_MENU_TYPE": "E", "P_REQUEST_TYPE": pFunctionName, "P_FUNCTION_NAME": pFunctionName, "P_COUNTRY_CODE": "SA"};
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject((json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData;
}, url, postParams);
return await ApiClient().postJsonForObject(
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData;
},
url,
postParams,
);
}
Future<GenericResponseModel> getColStructure(String pFunctionName, String uRL, String requestType) async {
String url = ApiConsts.erpRest + uRL;
Map<String, dynamic> postParams = {"P_SELECTED_RESP_ID": -999, "P_MENU_TYPE": "E", "P_FUNCTION_NAME": pFunctionName, "P_REQUEST_TYPE": requestType};
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject((json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData;
}, url, postParams);
return await ApiClient().postJsonForObject(
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData;
},
url,
postParams,
);
}
Future<ESERVICESVS> getValueSetValues(String pSegmentName, String pDescFlexContextCode, String pDescFlexName, List<Map<String, dynamic>> list) async {
@ -142,41 +206,38 @@ class ProfileApiClient {
"GetValueSetValuesTBL": list,
};
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject((json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getValueSetValuesList!.first;
}, url, postParams);
return await ApiClient().postJsonForObject(
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getValueSetValuesList!.first;
},
url,
postParams,
);
}
Future<List<GetPhoneNumberTypesModel>> getPhoneNumberTypes() async {
String url = "${ApiConsts.erpRest}GET_OBJECT_VALUES";
Map<String, dynamic> postParams = {"P_MENU_TYPE": "E", "P_SELECTED_RESP_ID": -999, "P_OBJECT_NAME": "PHONE_TYPE", "P_OBJECT_TYPE": "LOOKUP"};
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject((json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getObjectValuesList ?? [];
}, url, postParams);
return await ApiClient().postJsonForObject(
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getObjectValuesList ?? [];
},
url,
postParams,
);
}
Future<SubmitContactTransactionList?> submetConatctTransaction(
String pFunctionName,
String actionType,
int contactRelationId,
String date,
) async {
Future<SubmitContactTransactionList?> submetConatctTransaction(String pFunctionName, String actionType, int contactRelationId, String date) async {
String url = "${ApiConsts.erpRest}SUBMIT_CONTACT_TRANSACTION";
Map<String, dynamic> postParams = {
"P_MENU_TYPE": "E",
"P_SELECTED_RESP_ID": -999,
"P_FUNCTION_NAME": pFunctionName,
"EITTransactionTBL": [
{
"TRANSACTION_NUMBER": 1,
"NAME": "DATE_END",
"VARCHAR2_VALUE": null,
"NUMBER_VALUE": null,
"DATE_VALUE": date,
}
{"TRANSACTION_NUMBER": 1, "NAME": "DATE_END", "VARCHAR2_VALUE": null, "NUMBER_VALUE": null, "DATE_VALUE": date},
],
"P_CONTACT_RELATIONSHIP_ID": contactRelationId,
"P_ACTION": actionType,
@ -184,28 +245,29 @@ class ProfileApiClient {
"LegislationCodeStr": "SA",
};
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject((json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.submitContactTransactionList;
}, url, postParams);
return await ApiClient().postJsonForObject(
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.submitContactTransactionList;
},
url,
postParams,
);
}
Future<GetApprovesList?> getApproves(String action, String comments, int transactionId, String itemKey) async {
String url = "${ApiConsts.erpRest}GET_APPROVERS_LIST";
Map<String, dynamic> postParams = {
"P_AME_TRANSACTION_TYPE": "SSHRMS",
"P_PAGE_LIMIT": 1000,
"P_PAGE_NUM": 1,
"P_TRANSACTION_ID": transactionId,
"P_ACTION_MODE": action,
"P_COMMENTS": comments,
};
Map<String, dynamic> postParams = {"P_AME_TRANSACTION_TYPE": "SSHRMS", "P_PAGE_LIMIT": 1000, "P_PAGE_NUM": 1, "P_TRANSACTION_ID": transactionId, "P_ACTION_MODE": action, "P_COMMENTS": comments};
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject((json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
// return responseData.getApprovesList;
return (responseData.getApprovesList?.length ?? 0) > 0 ? responseData.getApprovesList!.first : null;
}, url, postParams);
return await ApiClient().postJsonForObject(
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
// return responseData.getApprovesList;
return (responseData.getApprovesList?.length ?? 0) > 0 ? responseData.getApprovesList!.first : null;
},
url,
postParams,
);
}
Future<SubmitPhonesTransactionList> submitPhoneNumbers(List<GetEmployeePhonesList> empList) async {
@ -214,50 +276,49 @@ class ProfileApiClient {
"P_MENU_TYPE": "E",
"P_SELECTED_RESP_ID": -999,
"P_FUNCTION_NAME": "HR_PERINFO_SS",
"P_MBL_PHONES_TBL": empList.map((element) {
return element.toJson();
}).toList()
"P_MBL_PHONES_TBL":
empList.map((element) {
return element.toJson();
}).toList(),
};
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject((json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return SubmitPhonesTransactionList.fromJson(responseData.submitPhonesTransactionList ?? {});
}, url, postParams);
return await ApiClient().postJsonForObject(
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return SubmitPhonesTransactionList.fromJson(responseData.submitPhonesTransactionList ?? {});
},
url,
postParams,
);
}
Future<StartPhoneApprovalProcess?> startPhoneApprovalProcess(String action, String comments, String itemKey, int transactionId) async {
String url = "${ApiConsts.erpRest}START_PHONES_APPROVAL_PROCESS";
Map<String, dynamic> postParams = {
"P_SELECTED_RESP_ID": -999,
"P_MENU_TYPE": "E",
"P_ACTION_MODE": action,
"P_COMMENTS": comments,
"P_ITEM_KEY": itemKey,
"P_TRANSACTION_ID": transactionId,
};
Map<String, dynamic> postParams = {"P_SELECTED_RESP_ID": -999, "P_MENU_TYPE": "E", "P_ACTION_MODE": action, "P_COMMENTS": comments, "P_ITEM_KEY": itemKey, "P_TRANSACTION_ID": transactionId};
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject((json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.startPhonesApprovalProcessList;
}, url, postParams);
return await ApiClient().postJsonForObject(
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.startPhonesApprovalProcessList;
},
url,
postParams,
);
}
Future<StartAddressApprovalProcess?> startAddressApprovalProcess(String action, String comments, String itemKey, int transactionId) async {
String url = "${ApiConsts.erpRest}START_ADDRESS_APPROVAL_PROCESS";
Map<String, dynamic> postParams = {
"P_SELECTED_RESP_ID": -999,
"P_MENU_TYPE": "E",
"P_ACTION_MODE": action,
"P_COMMENTS": comments,
"P_ITEM_KEY": itemKey,
"P_TRANSACTION_ID": transactionId,
};
Map<String, dynamic> postParams = {"P_SELECTED_RESP_ID": -999, "P_MENU_TYPE": "E", "P_ACTION_MODE": action, "P_COMMENTS": comments, "P_ITEM_KEY": itemKey, "P_TRANSACTION_ID": transactionId};
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject((json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.startAddressApprovalProcessList;
}, url, postParams);
return await ApiClient().postJsonForObject(
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.startAddressApprovalProcessList;
},
url,
postParams,
);
}
Future<SubmitBasicDetailsTransactionList> submitBasicDetailsTransaction(String pDescFlexContextCode, String pFunctionName, List<Map<String, dynamic>> list, int correctOrNew) async {
@ -273,31 +334,38 @@ class ProfileApiClient {
postParams.addAll(AppState().postParamsJson);
postParams["EITTransactionTBL"] = list;
postParams["EITTransactionTBLModel"] = list;
return await ApiClient().postJsonForObject((json) {
GenericResponseModel responseData = GenericResponseModel.fromJson(json);
return responseData.submitBasicDetTransactionList!; //ESERVICESDV.fromJson(responseData.getDefaultValueList!.toJson());
}, url, postParams);
return await ApiClient().postJsonForObject(
(json) {
GenericResponseModel responseData = GenericResponseModel.fromJson(json);
return responseData.submitBasicDetTransactionList!; //ESERVICESDV.fromJson(responseData.getDefaultValueList!.toJson());
},
url,
postParams,
);
}
Future<StartAddressApprovalProcess?> startBasicDetailsApprovalProcess(String action, String comments, String itemKey, int transactionId) async {
String url = "${ApiConsts.erpRest}START_BASIC_DET_APPR_PROCESS";
Map<String, dynamic> postParams = {
"P_SELECTED_RESP_ID": -999,
"P_MENU_TYPE": "E",
"P_ACTION_MODE": action,
"P_COMMENTS": comments,
"P_ITEM_KEY": itemKey,
"P_TRANSACTION_ID": transactionId,
};
Map<String, dynamic> postParams = {"P_SELECTED_RESP_ID": -999, "P_MENU_TYPE": "E", "P_ACTION_MODE": action, "P_COMMENTS": comments, "P_ITEM_KEY": itemKey, "P_TRANSACTION_ID": transactionId};
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject((json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.startAddressApprovalProcessList;
}, url, postParams);
return await ApiClient().postJsonForObject(
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.startAddressApprovalProcessList;
},
url,
postParams,
);
}
Future<SubmitAddressTransaction> submitAddressTransaction(
String pDescFlexContextCode, String pFunctionName, List<Map<String, dynamic>> list, int correctOrNew, String countryCode, String effectiveDate) async {
String pDescFlexContextCode,
String pFunctionName,
List<Map<String, dynamic>> list,
int correctOrNew,
String countryCode,
String effectiveDate,
) async {
String url = "${ApiConsts.erpRest}SUBMIT_ADDRESS_TRANSACTION";
Map<String, dynamic> postParams = {
"P_SELECTED_RESP_ID": -999,
@ -314,23 +382,28 @@ class ProfileApiClient {
postParams.addAll(AppState().postParamsJson);
postParams["EITTransactionTBL"] = list;
// postParams["EITTransactionTBLModel"] = list;
return await ApiClient().postJsonForObject((json) {
GenericResponseModel responseData = GenericResponseModel.fromJson(json);
return responseData.submitAddressTransactionList!; //ESERVICESDV.fromJson(responseData.getDefaultValueList!.toJson());
}, url, postParams);
return await ApiClient().postJsonForObject(
(json) {
GenericResponseModel responseData = GenericResponseModel.fromJson(json);
return responseData.submitAddressTransactionList!; //ESERVICESDV.fromJson(responseData.getDefaultValueList!.toJson());
},
url,
postParams,
);
}
Future<List<GetCountriesListModel>> getCountriesList() async {
String url = "${ApiConsts.erpRest}GET_COUNTRIES";
Map<String, dynamic> postParams = {
"P_MENU_TYPE": "E",
"P_SELECTED_RESP_ID": -999,
};
Map<String, dynamic> postParams = {"P_MENU_TYPE": "E", "P_SELECTED_RESP_ID": -999};
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject((json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getCountriesList ?? [];
}, url, postParams);
return await ApiClient().postJsonForObject(
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getCountriesList ?? [];
},
url,
postParams,
);
}
// add & update family member
@ -338,73 +411,75 @@ class ProfileApiClient {
String url = "${ApiConsts.erpRest}GET_BASIC_DET_DFF_STRUCTURE";
Map<String, dynamic> postParams = {"P_FUNCTION_NAME": "HR_PERINFO_SS", "P_MENU_TYPE": "E", "P_REQUEST_TYPE": "CONTACT", "P_SELECTED_RESP_ID": -999};
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject((json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getBasicDetDffStructureList ?? [];
}, url, postParams);
return await ApiClient().postJsonForObject(
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getBasicDetDffStructureList ?? [];
},
url,
postParams,
);
}
// add & update family member
Future<List<GetContactColsStructureList>> getContactColsStructureList(String actionType) async {
String url = "${ApiConsts.erpRest}GET_CONTACT_COLS_STRUCTURE";
Map<String, dynamic> postParams = {
"P_ACTION": actionType,
"P_FUNCTION_NAME": "HR_PERINFO_SS",
};
Map<String, dynamic> postParams = {"P_ACTION": actionType, "P_FUNCTION_NAME": "HR_PERINFO_SS"};
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject((json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getContactColsStructureList ?? [];
}, url, postParams);
return await ApiClient().postJsonForObject(
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getContactColsStructureList ?? [];
},
url,
postParams,
);
}
// update family member
Future<List<GetContactDetailsList>> getContactDetailsList(int contactRelationId) async {
String url = "${ApiConsts.erpRest}GET_CONTACT_DETAILS";
Map<String, dynamic> postParams = {
"P_MENU_TYPE": "E",
"P_SELECTED_RESP_ID": -999,
"P_CONTACT_RELATIONSHIP_ID": contactRelationId,
};
Map<String, dynamic> postParams = {"P_MENU_TYPE": "E", "P_SELECTED_RESP_ID": -999, "P_CONTACT_RELATIONSHIP_ID": contactRelationId};
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject((json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getContactDetailsList ?? [];
}, url, postParams);
return await ApiClient().postJsonForObject(
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getContactDetailsList ?? [];
},
url,
postParams,
);
}
// add & update family member
Future<List<GetContactDffStructureList>> getContactDffStructureList() async {
String url = "${ApiConsts.erpRest}GET_CONTACT_DFF_STRUCTURE";
Map<String, dynamic> postParams = {
"P_DESC_FLEX_CONTEXT_CODE": "HMG_ADD_CONTACT_DETAILS",
"P_FUNCTION_NAME": "HR_PERINFO_SS",
"P_MENU_TYPE": "E",
"P_SELECTED_RESP_ID": -999,
};
Map<String, dynamic> postParams = {"P_DESC_FLEX_CONTEXT_CODE": "HMG_ADD_CONTACT_DETAILS", "P_FUNCTION_NAME": "HR_PERINFO_SS", "P_MENU_TYPE": "E", "P_SELECTED_RESP_ID": -999};
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject((json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getContactDffStructureList ?? [];
}, url, postParams);
return await ApiClient().postJsonForObject(
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getContactDffStructureList ?? [];
},
url,
postParams,
);
}
Future<SubmitContactTransactionList?> submitContactTransactionAddAndUpdate(String actionType, relationId, List<Map<String, dynamic>> list) async {
String url = "${ApiConsts.erpRest}SUBMIT_CONTACT_TRANSACTION";
Map<String, dynamic> postParams = {
"P_MENU_TYPE": "E",
"P_SELECTED_RESP_ID": -999,
"P_FUNCTION_NAME": "HR_PERINFO_SS",
"P_ACTION": actionType,
"P_CONTACT_RELATIONSHIP_ID": relationId,
};
Map<String, dynamic> postParams = {"P_MENU_TYPE": "E", "P_SELECTED_RESP_ID": -999, "P_FUNCTION_NAME": "HR_PERINFO_SS", "P_ACTION": actionType, "P_CONTACT_RELATIONSHIP_ID": relationId};
postParams.addAll(AppState().postParamsJson);
postParams["EITTransactionTBL"] = list;
return await ApiClient().postJsonForObject((json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.submitContactTransactionList;
}, url, postParams);
return await ApiClient().postJsonForObject(
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.submitContactTransactionList;
},
url,
postParams,
);
}
Future<List<GetPerformanceAppraisalList>> getPerformanceAppraisal() async {
@ -412,9 +487,91 @@ class ProfileApiClient {
Map<String, dynamic> postParams = {};
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject((json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getPerformanceAppraisalList ?? [];
}, url, postParams);
return await ApiClient().postJsonForObject(
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getPerformanceAppraisalList ?? [];
},
url,
postParams,
);
}
Future<PatientInfoModel?> getPatientInfo({required String nationalId, required String dob, String? logInTokenID}) async {
// NHIC Service GetPatientInfo only works on Production (hmgwebservices.com)
// String url = "https://hmgwebservices.com/Services/NHIC.svc/REST/GetPatientInfo";
String url = "${ApiConsts.erpRest}GetMemberInfo";
String token = logInTokenID ?? AppState().postParamsObject?.logInTokenID ?? "";
Map<String, dynamic> postParams = {
"PatientIdentificationID": int.tryParse(nationalId.trim()) ?? nationalId.trim(),
"PatientOutSA": 0,
"DOB": dob.trim(),
"LogInTokenID": token,
"PatientMobileNumber": AppState().postParamsObject?.mobileNumber, // added this as per e-services changed
"TokenID": AppState().postParamsObject?.tokenID, // added this as per e-services changed
"LanguageID": AppState().postParamsObject?.languageID, // added this as per e-services changed
"Channel": AppState().postParamsObject?.channel,
"VersionID": AppState().postParamsObject?.versionID,
"UserName": AppState().postParamsObject?.userName,
};
print(jsonEncode(postParams));
return await ApiClient().postJsonForObject(
(json) {
return PatientInfoModel.fromJson(json);
},
url,
postParams,
);
}
Future<GenericResponseModel?> processContactMazayaAccess({
required String accessAction,
int? contactRelationshipId,
int? contactPersonId,
int? personId,
// Same shape as the NHIC-style patient-info call: PatientIdentificationID is the
// dependent's national ID (GetEmployeeContactsList.nATIONALIDENTIFIER), PatientOutSA is
// always 0, and the other two are passed straight through from that same contact's
// P_BIRTH_DATE / P_NHIC_VERIFIED_FLAG response fields.
String? patientIdentificationId,
patientBirthDate,
String? nhicVerifiedFlag,
}) async {
String url = "${ApiConsts.erpRest}PROCESS_CONTACT_MAZAYA_ACCESS";
String action = accessAction.trim().toLowerCase() == "revoke" ? "Revoke" : "Grant";
bool isGrant = action == "Grant";
Map<String, dynamic> postParams = {"P_MENU_TYPE": "E", "P_SELECTED_RESP_ID": -999, "P_ACCESS_ACTION": action};
if (contactRelationshipId != null) {
postParams["P_CONTACT_RELATIONSHIP_ID"] = contactRelationshipId;
}
if (contactPersonId != null) {
postParams["P_CONTACT_PERSON_ID"] = contactPersonId;
}
if (personId != null) {
postParams["P_PERSON_ID"] = personId;
}
postParams["PatientIdentificationID"] = patientIdentificationId?.replaceAll('-', '').trim();
// Only the Grant call carries these - Revoke's payload stays exactly as it was before
// this feature. P_BIRTH_DATE/P_NHIC_VERIFIED_FLAG are always sent (even as null, if the
// source contact from GET_EMPLOYEE_CONTACTS didn't have a value) once we're granting.
if (isGrant) {
postParams["PatientOutSA"] = 0;
postParams["P_BIRTH_DATE"] = _toWcfJsonDate(patientBirthDate);
postParams["P_NHIC_VERIFIED_FLAG"] = nhicVerifiedFlag?.trim();
if (patientIdentificationId != null && patientIdentificationId.trim().isNotEmpty) {
// National ID comes formatted with dashes (e.g. "2-5616-1545-7") - the backend wants
// the plain digit string (e.g. "2561615457").
}
}
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject(
(json) {
return GenericResponseModel.fromJson(json);
},
url,
postParams,
);
}
}

@ -3,9 +3,11 @@ import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:mohem_flutter_app/models/chat/get_user_login_token_model.dart';
import 'package:mohem_flutter_app/models/itg_forms_models/request_detail_model.dart';
import 'package:mohem_flutter_app/models/generic_response_model.dart';
import 'package:mohem_flutter_app/models/member_information_list_model.dart';
import 'package:mohem_flutter_app/models/member_login_list_model.dart';
import 'package:mohem_flutter_app/models/my_team/get_employee_subordinates_list.dart';
import 'package:mohem_flutter_app/models/offers_and_discounts/wala_plus_city_model.dart';
import 'package:mohem_flutter_app/models/post_params_model.dart';
import 'package:mohem_flutter_app/models/privilege_list_model.dart';
import 'package:mohem_flutter_app/models/worklist_response_model.dart';
@ -90,13 +92,74 @@ class AppState {
String get getHuaweiPushToken => _huaweiPushToken;
final PostParamsModel _postParamsInitConfig = PostParamsModel(channel: 31, versionID: 10.1, mobileType: Platform.isAndroid ? "android" : "ios");
final PostParamsModel _postParamsInitConfig = PostParamsModel(channel: 31, versionID: 10.2, mobileType: Platform.isAndroid ? "android" : "ios");
PostParamsModel get postParamsInitConfig {
if (_postParamsInitConfig.pLegislationCode == null) {
if (_postParams?.pLegislationCode != null) {
_postParamsInitConfig.pLegislationCode = _postParams?.pLegislationCode;
} else if (_memberLoginList?.pLEGISLATIONCODE != null) {
_postParamsInitConfig.pLegislationCode = _memberLoginList?.pLEGISLATIONCODE;
}
}
return _postParamsInitConfig;
}
GetWalaplusDepInfoModel? _walaplusDepInfoModel;
GetWalaplusDepInfoModel? get walaplusDepInfoModel => _walaplusDepInfoModel;
set setWalaplusDepInfoModel(GetWalaplusDepInfoModel? model) => _walaplusDepInfoModel = model;
String? _dependentName;
String? get dependentName => _dependentName;
set setDependentName(String? name) => _dependentName = name;
int? _dependentLoginType;
int? get dependentLoginType => _dependentLoginType;
set setDependentLoginType(int? type) => _dependentLoginType = type;
String? _dependentDob;
String? get dependentDob => _dependentDob;
set setDependentDob(String? dob) => _dependentDob = dob;
String? _authenticationTokenID;
String? get authenticationTokenID => _authenticationTokenID;
set setAuthenticationTokenID(String? token) => _authenticationTokenID = token;
bool? _isEmpLastLoginToDevice;
bool? get isEmpLastLoginToDevice => _isEmpLastLoginToDevice;
set setIsEmpLastLoginToDevice(bool? val) => _isEmpLastLoginToDevice = val;
WalaPlusCityModel? _selectedMazayaCity;
WalaPlusCityModel? get selectedMazayaCity => _selectedMazayaCity;
set setSelectedMazayaCity(WalaPlusCityModel? city) => _selectedMazayaCity = city;
void setPostParamsInitConfig() {
isAuthenticated = false;
isLogged = false;
_memberLoginList = null;
_memberInformationList = null;
_walaplusDepInfoModel = null;
_dependentName = null;
_dependentLoginType = null;
_dependentDob = null;
_authenticationTokenID = null;
_isEmpLastLoginToDevice = null;
_selectedMazayaCity = null;
_postParamsInitConfig.pLegislationCode = null;
_postParams = _postParamsInitConfig;
}
@ -110,9 +173,23 @@ class AppState {
this._postParams = _postParams;
}
bool isArabic(context) => EasyLocalization.of(context)?.locale.languageCode == "ar";
bool isArabic(context) {
try {
if (context != null) {
return EasyLocalization.of(context)?.locale.languageCode == "ar";
}
} catch (_) {}
return false;
}
int getLanguageID(context) => EasyLocalization.of(context)?.locale.languageCode == "ar" ? 1 : 2;
int getLanguageID(context) {
try {
if (context != null) {
return EasyLocalization.of(context)?.locale.languageCode == "ar" ? 1 : 2;
}
} catch (_) {}
return 2;
}
String? _username;
@ -129,7 +206,13 @@ class AppState {
MemberLoginListModel? get memberLoginList => _memberLoginList;
set setMemberLoginListModel(MemberLoginListModel? _memberLoginList) => this._memberLoginList = _memberLoginList;
set setMemberLoginListModel(MemberLoginListModel? _memberLoginList) {
this._memberLoginList = _memberLoginList;
if (_memberLoginList?.pLEGISLATIONCODE != null) {
_postParamsInitConfig.pLegislationCode = _memberLoginList?.pLEGISLATIONCODE?.trim().toUpperCase();
_postParams?.pLegislationCode = _memberLoginList?.pLEGISLATIONCODE?.trim().toUpperCase();
}
}
MemberInformationListModel? _memberInformationList;

@ -1,5 +1,5 @@
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/bottom_sheets/confirm_bottom_sheet.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
@ -18,20 +18,18 @@ class AppPermissions {
}
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);
},
),
ConfirmBottomSheet.show(
context,
message: LocaleKeys.pleaseProvideLocationPermission.tr(),
confirmText: LocaleKeys.openSettings.tr(),
showCancelButton: false,
onConfirm: () {
if (isPermissionError) {
Geolocator.openAppSettings();
} else {
Geolocator.openLocationSettings();
}
},
);
}

@ -1,40 +1,59 @@
import 'package:mohem_flutter_app/ui/marathon/widgets/question_card.dart';
class ApiConsts {
// static String baseUrl = "http://10.200.204.11"; // Local server
// static String baseUrl = "http://10.201.214.101:2027"; // Local server
// static String baseUrl = "https://erptstapp.srca.org.sa"; // SRCA server
// static String baseUrl = "https://uat.hmgwebservices.com"; // UAT ser343622ver
// static String baseUrl = "http://10.201.204.101:2024";
// static String baseUrl = "https://webservices.hmg.com"; // PreProd
// static String baseUrl = "https://hmgwebservices.com"; // Live server
// static String baseUrl = "https://uat.hmgwebservices.com"; // UAT server
// static String baseUrl = "http://10.201.204.101:2024";
// static String baseUrl = "https://webservices.hmg.com"; // PreProd
// static String baseUrl = "https://hmgwebservices.com"; // Live server
// static String baseUrl = "https://mohemm.hmg.com"; // New Live server
static String ssoBaseUrl = "https://sso.hmg.com"; // New Live server
//
static String baseUrl = "https://uat.hmgwebservices.com"; // UAT ser343622ver
// static String baseUrl = "https://uat.hmgwebservices.com"; // UAT ser343622ver
// static String baseUrl = "http://10.201.214.101:2027/";
// static String baseUrl = "http://10.20.200.111:1010/";
// static String baseUrl = "https://mohemm.hmg.com"; // New Live server
static String baseUrl = "https://mohemm.hmg.com"; // New Live server
//static String eLearningBaseUrl = "https://elearning.hmg.com/moodle_dev/api/"; // Elearning server UAT
static String eLearningBaseUrl = "https://elearning.hmg.com/moodle/api/"; // Elearning server live
static String enSATLiveURL = "https://ensat.hmg.com/auth/autologin"; // ENSAT server live
static String eLearningBaseUrl =
"https://elearning.hmg.com/moodle/api/"; // Elearning server live
static String enSATLiveURL =
"https://ensat.hmg.com/auth/autologin"; // ENSAT server live
// static String baseUrl = "http://10.20.200.111:1010/";
// static String baseUrl = "https://webservices.hmg.com"; // PreProd
// static String baseUrl = "https://mohemm.hmg.com";
// static String baseUrl = "https://webservices.hmg.com"; // PreProd
// static String baseUrl = "https://mohemm.hmg.com";
// 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 authenticationRest = baseUrlServices + "Authentication.svc/REST/";
static String authenticationRest =
baseUrlServices + "Authentication.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/";
static String ssoAuthRedirection = "https://sso-uat.hmg.com/api/auth/connect";
// Dependent Login Endpoints
static String memberLoginDependent = erpRest + "MemberLogin_Dependent";
static String mohemmSendOTPDependent = erpRest + "Mohemm_SendOTP_Dependent";
static String mohemmCheckOTPDependent = erpRest + "Mohemm_CheckOTP_Dependent";
// WalaPlus Endpoints
static String walaPlusCategories = erpRest + "WalaPlus_Categories";
static String walaPlusRetailers = erpRest + "WalaPlus_Retailers";
static String walaPlusRetailersByID = erpRest + "WalaPlus_RetailersByID";
static String walaPlusOfferRedeem = erpRest + "WalaPlus_OfferRedeem";
static String walaPlusFavoritesAdd = erpRest + "WalaPlus_FavoritesAdd";
static String walaPlusFavoritesRemove = erpRest + "WalaPlus_FavoritesRemove";
static String walaPlusCities = erpRest + "WalaPlus_Cities";
static String processContactMazayaAccess =
erpRest + "PROCESS_CONTACT_MAZAYA_ACCESS";
static String ssoAuthRedirection = "$ssoBaseUrl/api/auth/connect";
//Chat
static String chatServerBaseUrl = "https://apiderichat.hmg.com/";
@ -43,16 +62,24 @@ class ApiConsts {
static String chatHubConnectionUrl = chatServerBaseUrl + "ConnectionChatHub";
//Groups
static String getGroupByUserId = chatServerBaseApiUrl + "group/getgroupsbyuserid/";
static String deleteGroup = chatServerBaseApiUrl + "group/updateGroupIsDeleted/";
static String updateGroupAdmin = chatServerBaseApiUrl + "group/updateGroupAdmin/";
static String getGroupChatHistoryAsync = chatServerBaseApiUrl + "GroupChat/GetGroupChatHistoryAsync/";
static String addGroupsAndUsers = chatServerBaseApiUrl + "group/addgroupandusers/";
static String updateGroupsAndUsers = chatServerBaseApiUrl + "group/updategroupandusers/";
static String getGroupByUserId =
chatServerBaseApiUrl + "group/getgroupsbyuserid/";
static String deleteGroup =
chatServerBaseApiUrl + "group/updateGroupIsDeleted/";
static String updateGroupAdmin =
chatServerBaseApiUrl + "group/updateGroupAdmin/";
static String getGroupChatHistoryAsync =
chatServerBaseApiUrl + "GroupChat/GetGroupChatHistoryAsync/";
static String addGroupsAndUsers =
chatServerBaseApiUrl + "group/addgroupandusers/";
static String updateGroupsAndUsers =
chatServerBaseApiUrl + "group/updategroupandusers/";
// static String chatSearchMember = chatLoginTokenUrl + "user/";
static String chatRecentUrl = chatServerBaseApiUrl + "UserChatHistory/"; //For a Mem
static String chatSingleUserHistoryUrl = chatServerBaseApiUrl + "UserChatHistory/";
static String chatRecentUrl =
chatServerBaseApiUrl + "UserChatHistory/"; //For a Mem
static String chatSingleUserHistoryUrl =
chatServerBaseApiUrl + "UserChatHistory/";
static String chatMediaImageUploadUrl = chatServerBaseApiUrl + "shared/";
static String chatFavUser = chatServerBaseApiUrl + "FavUser/";
static String chatUserImages = chatServerBaseUrl + "empservice/api/employee/";
@ -64,25 +91,63 @@ class ApiConsts {
static String marathonBaseUrl = marathonBaseUrlLive;
// static String marathonBaseUrl = marathonBaseUrlUAT;
static String marathonBaseUrlServices = "https://marathoon.com/service/";
static String marathonParticipantLoginUrl = marathonBaseUrl + "auth/participantlogin";
static String marathonParticipantLoginUrl =
marathonBaseUrl + "auth/participantlogin";
static String marathonProjectGetUrl = marathonBaseUrl + "Project/Project_Get";
static String marathonUpcomingUrl = marathonBaseUrl + "marathon/upcoming/";
static String marathonJoinParticipantUrl = marathonBaseUrl + "participant/join";
static String marathonJoinParticipantUrl =
marathonBaseUrl + "participant/join";
static String marathonNextQuestionUrl = marathonBaseUrl + "question/next";
static String marathonSubmitAnswerUrl = marathonBaseUrl + "question/submit";
static String marathonQualifiersUrl = marathonBaseUrl + "winner/getWinner/";
static String marathonSelectedWinner = marathonBaseUrl + "winner/getSelectedWinner/";
static String marathonGetMarathonersCount = marathonBaseUrl + "Participant/GetRemainingParticipants";
static String marathonGetTutorial = marathonBaseUrl + "tutorial/GetTutorialNotification";
static String marathonSelectedWinner =
marathonBaseUrl + "winner/getSelectedWinner/";
static String marathonGetMarathonersCount =
marathonBaseUrl + "Participant/GetRemainingParticipants";
static String marathonGetTutorial =
marathonBaseUrl + "tutorial/GetTutorialNotification";
//DummyCards for the UI
static int tabletMinLength = 500;
// Notification Center GUIDs for OTP
static String notificationCenterAppGUID = "VDZreDY5TTNmTDVudzc4aDkwU3ZqcW9OcmI0";
static String notificationCenterLoginServiceGUID = "D8BD5C76-4B50-4122-81D0-C3AA25DB08A2";
static String notificationCenterForgotPasswordServiceGUID = "5CCCC2C9-EEA0-4207-9BE9-EE1FBBFFE8D0";
static String notificationCenterAppGUID =
"VDZreDY5TTNmTDVudzc4aDkwU3ZqcW9OcmI0";
static String notificationCenterLoginServiceGUID =
"D8BD5C76-4B50-4122-81D0-C3AA25DB08A2";
static String notificationCenterForgotPasswordServiceGUID =
"15AA0C70-8DA3-430B-8881-EAFBB508F296";
static MemberType selectedMemberType = MemberType.employee;
static bool get ISDependetLogin => selectedMemberType == MemberType.dependent;
static set ISDependetLogin(bool value) {
selectedMemberType = value ? MemberType.dependent : MemberType.employee;
}
static bool get isDependetLogin => ISDependetLogin;
static set isDependetLogin(bool value) => ISDependetLogin = value;
}
enum MemberType {
employee(1),
dependent(2);
final int value;
const MemberType(this.value);
bool get isEmployee => this == MemberType.employee;
bool get isDependent => this == MemberType.dependent;
static MemberType fromValue(int? value) {
if (value == 2) return MemberType.dependent;
return MemberType.employee;
}
}
class SharedPrefsConsts {

@ -1,6 +1,7 @@
import 'dart:developer';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:permission_handler/permission_handler.dart';
class MyInAppBrowser extends InAppBrowser {
final Function onExitCallback;
@ -30,4 +31,21 @@ class MyInAppBrowser extends InAppBrowser {
void onLoadError(Uri? url, int code, String message) {
log("Can't load $url.. Error: $message");
}
@override
Future<PermissionResponse?> onPermissionRequest(PermissionRequest permissionRequest) async {
log("onPermissionRequest: ${permissionRequest.resources}");
try {
var status = await Permission.camera.status;
if (!status.isGranted) {
await Permission.camera.request();
}
} catch (e) {
log("Error checking/requesting camera permission: $e");
}
return PermissionResponse(
resources: permissionRequest.resources,
action: PermissionResponseAction.GRANT,
);
}
}

@ -2,7 +2,6 @@ import 'dart:io';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
@ -13,7 +12,6 @@ import 'package:mohem_flutter_app/classes/date_uitl.dart';
import 'package:mohem_flutter_app/classes/utils.dart';
import 'package:mohem_flutter_app/models/get_notifications_response_model.dart';
import 'package:mohem_flutter_app/ui/notifications/notification_details_page.dart';
import 'package:permission_handler/permission_handler.dart';
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
@ -29,20 +27,14 @@ class AppNotifications {
String _huaweiToken = '';
late BuildContext context;
bool _isInitialized = false;
Future<void> requestPermissions() async {
if (Platform.isIOS) {
await flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>()?.requestPermissions(alert: true, badge: true, sound: true);
} else if (Platform.isAndroid) {
AndroidFlutterLocalNotificationsPlugin? androidImplementation = flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>();
bool? granted = await androidImplementation?.requestNotificationsPermission();
if (granted == false) {
if (kDebugMode) {
print("-------------------- Permission Granted ------------------------");
print(granted);
}
await Permission.notification.request();
}
AndroidFlutterLocalNotificationsPlugin? androidImplementation = flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>();
await androidImplementation?.requestNotificationsPermission();
}
}
@ -53,13 +45,11 @@ class AppNotifications {
this.context = context;
await requestPermissions();
if (!_isInitialized) {
_isInitialized = true;
await requestPermissions();
}
AppState().setDeviceToken = firebaseToken;
await Permission.notification.isDenied.then((bool value) {
if (value) {
Permission.notification.request();
}
});
RemoteMessage? initialMessage = await FirebaseMessaging.instance.getInitialMessage();
if (initialMessage != null) _handleMessage(initialMessage);
@ -80,7 +70,7 @@ class AppNotifications {
// await hmsApiAvailability.isHMSAvailable().then((value) async {
if (!(await Utils.isGoogleServicesAvailable())) {
huawei_push.Push.enableLogger();
var result = await huawei_push.Push.setAutoInitEnabled(true);
await huawei_push.Push.setAutoInitEnabled(true);
huawei_push.Push.onNotificationOpenedApp.listen((message) {
// newMessage(toFirebaseRemoteMessage(message));

@ -6,7 +6,6 @@ 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:google_api_availability/google_api_availability.dart';
import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:mohem_flutter_app/classes/colors.dart';
@ -19,11 +18,13 @@ import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
import 'package:mohem_flutter_app/provider/chat_provider_model.dart';
import 'package:mohem_flutter_app/widgets/dialogs/confirm_dialog.dart';
import 'package:mohem_flutter_app/widgets/loading_dialog.dart';
import 'package:mohem_flutter_app/widgets/toast/app_toast.dart';
import 'package:nfc_manager/nfc_manager.dart';
import 'package:nfc_manager/nfc_manager_android.dart';
import 'package:nfc_manager/nfc_manager_ios.dart';
// import 'package:nfc_manager/platform_tags.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:mohem_flutter_app/ui/profile/mazaya_access_result_screen.dart';
// ignore_for_file: avoid_annotating_with_dynamic
@ -32,15 +33,25 @@ class Utils {
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: 13.0,
// longDuration is accepted but no longer varies the duration - every caller already used the
// default (true), so AppToast.plainSeconds covers both cases with one fixed value.
// isSuccess: true -> soft green/green (same combo as dependent_details_screen.dart's "Active"
// badge); false (default) -> soft red/red (same combo as that screen's "Access Off" badge).
// Callers showing a value straight from an API response should pass isSuccess: response's
// MessageStatus == 1; everywhere else (validation messages, caught errors) just defaults to
// red, and the handful of hardcoded success confirmations pass isSuccess: true explicitly.
// onComplete makes this an action-toast (longer actionSeconds duration + icon shown) instead
// of a plain info toast - this is the single calling point, every caller should go through
// here instead of calling AppToast.show directly, so the color/duration/icon rules in one place.
static void showToast(String message, {bool longDuration = true, bool isSuccess = false, VoidCallback? onComplete}) {
AppToast.show(
message,
seconds: onComplete != null ? AppToast.actionSeconds : AppToast.plainSeconds,
showIcon: onComplete != null,
showCountdownText: true,
bgColor: isSuccess ? AppToast.successBgColor : AppToast.errorBgColor,
textColor: isSuccess ? AppToast.successTextColor : AppToast.errorTextColor,
onComplete: onComplete,
);
}
@ -96,9 +107,57 @@ class Utils {
return await prefs.setString(key, value);
}
static bool isMazayaAccessRevoked(dynamic error) {
if (error == null) return false;
String str = "";
if (error is APIException && error.error != null) {
String errCode = error.error?.errorCode?.toString() ?? "";
String errMsg = error.error?.errorMessage?.toString() ?? "";
str = "$errCode $errMsg ${error.message}";
if (errCode.trim().toUpperCase() == "E4") {
return true;
}
} else {
str = error.toString();
}
String lower = str.toLowerCase();
if (lower.contains("no active grant") ||
lower.contains("grant request") ||
lower.contains("grant was found") ||
lower.contains("grant_request") ||
lower.contains("authorized to access") ||
lower.contains("not authorized") ||
lower.contains("e4:") ||
lower.contains("e4 -") ||
lower.contains("e4 :")) {
return true;
}
if (str.contains("طلب منح") ||
str.contains("منح نشط") ||
str.contains("لا يوجد طلب منح") ||
str.contains("تم إلغاء وصول مزايا") ||
str.contains("إلغاء مزايا") ||
str.contains("غير مصرح لك بالوصول") ||
str.contains("غير مصرح")) {
return true;
}
return false;
}
static void handleException(dynamic exception, cxt, Function(String)? onErrorMessage) {
if (isMazayaAccessRevoked(exception)) {
String depName = AppState().dependentName ?? "";
if (depName.isEmpty) {
depName = AppState().walaplusDepInfoModel?.pDependentName ?? "";
}
if (cxt != null) {
Navigator.push(cxt, MaterialPageRoute(builder: (context) => MazayaAccessResultScreen(isGranted: false, dependentName: depName)));
return;
}
}
String errorMessage;
if (exception.error.errorType != null && exception.error.errorType == 4) {
if (exception is APIException && exception.error?.errorType == 4) {
Navigator.pushNamedAndRemoveUntil(cxt, AppRoutes.appUpdateScreen, (_) => false, arguments: exception.error?.errorMessage);
} else {
if (exception is APIException) {
@ -114,17 +173,11 @@ class Utils {
onErrorMessage(errorMessage);
} else {
if (!AppState().isAuthenticated) {
showDialog(
barrierDismissible: false,
context: cxt,
builder:
(cxt) => ConfirmDialog(
message: errorMessage,
onTap: () {
Navigator.pushNamedAndRemoveUntil(cxt, AppRoutes.login, (Route<dynamic> route) => false);
},
onCloseTap: () {},
),
showToast(
errorMessage,
onComplete: () {
Navigator.pushNamedAndRemoveUntil(cxt, AppRoutes.login, (Route<dynamic> route) => false);
},
);
} else {
if (cxt != null) {
@ -142,7 +195,15 @@ class Utils {
}
static void confirmDialog(cxt, String message, {VoidCallback? onTap}) {
showDialog(context: cxt, builder: (BuildContext cxt) => ConfirmDialog(message: message, onTap: onTap));
// No onTap means there's nothing to confirm - it's a plain notice, so a self-dismissing
// toast replaces the old "OK just closes" dialog instead of requiring a tap. Every current
// caller of this path passes an error/warning message (catch blocks, location/fake-location
// warnings, session errors) - never success - so red is correct here.
if (onTap == null) {
showToast(message);
} else {
showDialog(context: cxt, builder: (BuildContext cxt) => ConfirmDialog(message: message, onTap: onTap));
}
}
static Widget getNoDataWidget(BuildContext context) {
@ -414,7 +475,9 @@ class Utils {
AppState().isLogged = false;
AppState().setPostParamsInitConfig();
if (chatData != null) {
chatData.disposeData();
try {
chatData.disposeData();
} catch (e) {}
}
// SharedPreferences prefs = await SharedPreferences.getInstance();
// await prefs.clear();

@ -32,10 +32,15 @@ import 'package:mohem_flutter_app/ui/marathon/marathon_screen.dart';
import 'package:mohem_flutter_app/ui/marathon/marathon_sponsor_video_screen.dart';
import 'package:mohem_flutter_app/ui/marathon/marathon_tutorial_viewer_screen.dart';
import 'package:mohem_flutter_app/ui/marathon/marathon_waiting_screen.dart';
import 'package:mohem_flutter_app/ui/mazaya/retailers_list_screen.dart';
import 'package:mohem_flutter_app/ui/mazaya/mazaya_discover_screen.dart';
import 'package:mohem_flutter_app/ui/mazaya/saved_retailers_screen.dart';
import 'package:mohem_flutter_app/ui/mazaya/retailer_details_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';
@ -48,6 +53,9 @@ import 'package:mohem_flutter_app/ui/parking_qr/parking_qr.dart';
import 'package:mohem_flutter_app/ui/parking_qr/parking_qr.dart';
import 'package:mohem_flutter_app/ui/payslip/monthly_pay_slip_screen.dart';
import 'package:mohem_flutter_app/ui/performance/performance_appraisal_screen.dart';
import 'package:mohem_flutter_app/ui/profile/add_dependent_verify_screen.dart';
import 'package:mohem_flutter_app/ui/profile/dependent_details_screen.dart';
import 'package:mohem_flutter_app/ui/profile/mazaya_access_result_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';
@ -86,6 +94,7 @@ 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';
import 'package:mohem_flutter_app/widgets/app_in_app_webview_screen.dart';
class AppRoutes {
static GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
@ -100,6 +109,10 @@ class AppRoutes {
static const String newPassword = "/newPassword";
static const String loginVerification = "/loginVerification";
static const String dashboard = "/dashboard";
static const String retailersListScreen = "/dashboardScreenForDependet";
static const String retailerDetailsScreen = "/walaPlusRetailerDetails";
static const String mazayaDiscover = "/mazayaDiscover";
static const String savedReyailersScreen = "/mazayaSaved";
static const String todayAttendance = "/todayAttendance";
static const String subMenuScreen = "/submenuScreen";
static const String addEitScreen = "/addeitScreen";
@ -145,6 +158,9 @@ class AppRoutes {
static const String familyMembers = "/familyMembers";
static const String deleteFamilyMember = "/deleteFamilyMember";
static const String addUpdateFamilyMember = "/addUpdateFamilyMember";
static const String addDependentVerify = "/addDependentVerify";
static const String dependentDetails = "/dependentDetails";
static const String mazayaAccessResult = "/mazayaAccessResult";
// Mowadhafhi
static const String mowadhafhi = "/mowadhafhi";
@ -229,12 +245,24 @@ class AppRoutes {
//Parking QR
static const String parkingQr = "/parkingQrScreen";
static const String appInAppWebView = "/appInAppWebView";
static final Map<String, WidgetBuilder> routes = {
login: (BuildContext context) => LoginScreen(),
verifyLogin: (BuildContext context) => VerifyLoginScreen(),
verifyLastLogin: (BuildContext context) => VerifyLastLoginScreen(),
dashboard: (BuildContext context) => DashboardScreen(),
retailersListScreen: (BuildContext context) => const RetailersListScreen(),
retailerDetailsScreen: (BuildContext context) => const RetailerDetailsScreen(),
mazayaDiscover: (BuildContext context) => const MazayaDiscoverScreen(),
savedReyailersScreen: (BuildContext context) => const SavedRetailersScreen(),
appInAppWebView: (BuildContext context) {
Object? args = ModalRoute.of(context)?.settings.arguments;
if (args is Map<String, dynamic>) {
return AppInAppWebViewScreen(url: args['url'] ?? "", title: args['title']);
}
return const Scaffold();
},
survey: (BuildContext context) => SurveyScreen(),
advertisement: (BuildContext context) => ITGAdsScreen(),
@ -284,6 +312,9 @@ class AppRoutes {
deleteFamilyMember: (BuildContext context) => DeleteFamilyMember(ModalRoute.of(context)!.settings.arguments as int),
requestSubmitScreen: (BuildContext context) => RequestSubmitScreen(),
addUpdateFamilyMember: (BuildContext context) => AddUpdateFamilyMember(),
addDependentVerify: (BuildContext context) => const AddDependentVerifyScreen(),
dependentDetails: (BuildContext context) => const DependentDetailsScreen(),
mazayaAccessResult: (BuildContext context) => const MazayaAccessResultScreen(),
//mowadhafhi
mowadhafhi: (BuildContext context) => MowadhafhiHome(),
@ -350,13 +381,12 @@ class AppRoutes {
unsafeDeviceScreen: (BuildContext context) => const UnsafeDeviceScreen(),
appUpdateScreen: (BuildContext context) => const AppUpdateScreen(),
childEducation: (BuildContext context) => ChildEducationAssistance(),
activityScreen:(BuildContext context) => const EventActivityScreen(),
activityScreen: (BuildContext context) => const EventActivityScreen(),
//ETQAN OVR
etqanOvr: (BuildContext context) => EtqanOvrHome(),
etqanCreateRequest: (BuildContext context) => EtqanOvrCreateRequest(),
etqanGetIncidientRequest: (BuildContext context) => EtqanOvrRequestDetailed(),
activityScreen:(BuildContext context) => const EventActivityScreen(),
//Parking QR
parkingQr: (BuildContext context) => const ParkingQrScreen(),

@ -1,14 +1,17 @@
import 'dart:async';
import 'dart:io';
import 'package:easy_localization/src/public_ext.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:mohem_flutter_app/api/login_api_client.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:sms_otp_auto_verify/sms_otp_auto_verify.dart';
final ValueNotifier<String> otpFieldClear = ValueNotifier<String>("");
@ -24,10 +27,6 @@ class OtpDialog {
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>();
@ -39,12 +38,7 @@ class OtpDialog {
TextEditingController digit3 = TextEditingController(text: "");
TextEditingController digit4 = TextEditingController(text: "");
Map verifyAccountFormValue = {
'digit1': '',
'digit2': '',
'digit3': '',
'digit4': '',
};
Map verifyAccountFormValue = {'digit1': '', 'digit2': '', 'digit3': '', 'digit4': ''};
final focusD1 = FocusNode();
final focusD2 = FocusNode();
final focusD3 = FocusNode();
@ -53,24 +47,27 @@ class OtpDialog {
// ProjectViewModel projectProvider;
String displayTime = '';
String? _code;
dynamic setState;
bool stopTimer = false;
bool isCodeExpired = 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,
surfaceTintColor: Colors.transparent,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
insetPadding: const EdgeInsets.only(left: 21, right: 21),
child: StatefulBuilder(builder: (context, setState) {
otpFieldClear.value = "";
_startListeningForSms();
showDialog(
context: context,
barrierColor: Colors.black.withOpacity(0.63),
builder: (context) {
// projectProvider = Provider.of(context);
return Dialog(
backgroundColor: Colors.white,
surfaceTintColor: Colors.transparent,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
insetPadding: const EdgeInsets.only(left: 21, right: 21),
child: StatefulBuilder(
builder: (context, setState) {
if (displayTime == '') {
startTimer(setState);
}
@ -85,10 +82,10 @@ class OtpDialog {
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',
? "assets/images/login/verify_whatsapp.svg"
: type == 3
? "assets/images/login/verify_face.svg"
: 'assets/images/login/verify_thumb.svg',
height: 50,
width: 50,
),
@ -96,7 +93,20 @@ class OtpDialog {
LocaleKeys.otp.tr().toText14(),
LocaleKeys.verification.tr().toText24(isBold: true),
6.height,
(LocaleKeys.pleaseEnterTheVerificationCodeSentTo.tr() + ' xxxxxxxx' + mobileNo.toString().substring(mobileNo.toString().length - 3)).toText16(),
Builder(
builder: (context) {
String maskedNumber = "";
if (mobileNo != null) {
String numStr = mobileNo.toString();
if (numStr.length >= 3) {
maskedNumber = ' xxxxxxxx' + numStr.substring(numStr.length - 3);
} else {
maskedNumber = ' ' + numStr;
}
}
return (LocaleKeys.pleaseEnterTheVerificationCodeSentTo.tr() + maskedNumber).toText16();
},
),
18.height,
ValueListenableBuilder<String>(
builder: (BuildContext context, String value, Widget? child) {
@ -128,92 +138,63 @@ class OtpDialog {
),
),
10.height,
stopTimer
isCodeExpired
? 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,
),
],
)
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),
),
],
),
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,
],
),
DefaultButton(isCodeExpired ? LocaleKeys.resend.tr() : LocaleKeys.cancel.tr(), () async {
if (isCodeExpired) {
hideSMSBox(context);
onResendCode();
} else {
stopTimer = true;
hideSMSBox(context);
}
}, colors: isCodeExpired ? null : [MyColors.pinkDarkColor, MyColors.pinkDarkColor]),
],
),
);
}),
);
});
},
),
);
},
).then((_) {
stopTimer = true;
_stopListeningForSms();
});
}
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: Colors.red),
),
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: Colors.red)),
// focusedErrorBorder: OutlineInputBorder(
// borderRadius: const BorderRadius.all(Radius.circular(10.0)),
// borderSide: BorderSide(color: Colors.red),
// // borderSide: BorderSide(color: Theme.of(context).colorScheme.error),
// ),
focusedErrorBorder: OutlineInputBorder(
borderRadius: const BorderRadius.all(Radius.circular(10.0)),
borderSide: BorderSide(color: Theme.of(context).colorScheme.error),
),
focusedErrorBorder: OutlineInputBorder(borderRadius: const BorderRadius.all(Radius.circular(10.0)), borderSide: BorderSide(color: Theme.of(context).colorScheme.error)),
);
}
@ -240,40 +221,120 @@ class OtpDialog {
}
void startTimer(setState) {
if (stopTimer) return;
remainingTime--;
if (stopTimer) return;
setState(() {
displayTime = getSecondsAsDigitalClock(remainingTime);
});
try {
setState(() {
displayTime = getSecondsAsDigitalClock(remainingTime);
});
} catch (_) {}
timer = Future.delayed(const Duration(seconds: 1), () {
if (stopTimer) return;
if (remainingTime > 0) {
startTimer(setState);
} else {
setState(() {
stopTimer = true;
});
if (stopTimer) return;
try {
setState(() {
isCodeExpired = true;
stopTimer = true;
});
} catch (_) {}
_stopListeningForSms();
}
});
}
void hideSMSBox(context) {
stopTimer = true;
_stopListeningForSms();
onFailure();
}
void _onOtpCallBack(String otpCode, bool? isAutofill) {
if (isCodeExpired) return;
if (otpCode.length == 4) {
// stopTimer = true;
_stopListeningForSms();
otpFieldClear.value = otpCode;
onSuccess(otpCode, _pinPutController);
}
}
// static getSignature() async {
// // if (Platform.isAndroid) {
// // return await SmsRetriever.getAppSignature();
// // } else {
// // return null;
// // }
// }
void _startListeningForSms() {
if (!Platform.isAndroid) return;
try {
SmsVerification.startListeningSms()
.then((String? message) {
if (stopTimer || isCodeExpired) return;
if (message != null && message.isNotEmpty) {
debugPrint("SMS OTP Auto-Verify received message: $message");
String? otpCode = extractOtpCode(message);
if (otpCode != null && otpCode.length == 4) {
debugPrint("Auto-filling OTP: $otpCode");
_pinPutController.text = otpCode;
_pinPutController.selection = TextSelection.collapsed(offset: otpCode.length);
_onOtpCallBack(otpCode, true);
}
}
})
.catchError((e) {
debugPrint("Error in startListeningSms: $e");
});
} catch (e) {
debugPrint("Error starting SMS listening: $e");
}
}
void _stopListeningForSms() {
if (!Platform.isAndroid) return;
try {
SmsVerification.stopListening();
} catch (e) {
debugPrint("Error stopping SMS listening: $e");
}
}
static String? extractOtpCode(String? message) {
if (message == null || message.trim().isEmpty) return null;
String normalized = _normalizeDigits(message);
// 1. Try matching with OTP context keyword if available
RegExp contextRegex = RegExp(r'(?:code|otp|رمز|كود|تفعيل|تحقق)[^\d]{0,15}(\d{4})\b', caseSensitive: false);
Match? contextMatch = contextRegex.firstMatch(normalized);
if (contextMatch != null && contextMatch.group(1) != null) {
return contextMatch.group(1);
}
// 2. Try exact 4-digit word boundary match
Iterable<Match> matches = RegExp(r'\b\d{4}\b').allMatches(normalized);
if (matches.isNotEmpty) {
return matches.first.group(0);
}
// 3. Fallback
try {
Iterable<Match> allDigits = RegExp(r'\d{4}').allMatches(normalized);
if (allDigits.isNotEmpty) {
return allDigits.first.group(0);
}
} catch (_) {}
return null;
}
static String _normalizeDigits(String input) {
const String arabic = '٠١٢٣٤٥٦٧٨٩';
const String english = '0123456789';
String res = input;
for (int i = 0; i < arabic.length; i++) {
res = res.replaceAll(arabic[i], english[i]);
}
return res;
}
static Future<String?> getSignature() async {
return await LoginApiClient.getSignature();
}
}

@ -63,63 +63,68 @@ 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, bool isCenter = false, int maxLine = 0}) => Text(
this,
textAlign: isCenter ? TextAlign.center : null,
maxLines: (maxLine > 0) ? maxLine : null,
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, TextOverflow? overflow}) => Text(
this,
textAlign: isCenter ? TextAlign.center : null,
maxLines: maxLine == 0 ? null : maxLine,
style: TextStyle(
fontSize: 12,
fontWeight: isBold ? FontWeight.bold : FontWeight.w600,
color: color ?? MyColors.darkTextColor,
letterSpacing: -0.72,
decoration: isUnderLine ? TextDecoration.underline : null,
overflow: TextOverflow.visible
),
);
Widget toText12Auto({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int? maxLine,TextOverflow? overflow}) => AutoSizeText(
this,
textAlign: isCenter ? TextAlign.center : null,
maxLines: maxLine == 0 ? null : maxLine,
minFontSize: 8,
style: TextStyle(
fontSize: 12,
fontWeight: isBold ? FontWeight.bold : FontWeight.w600,
color: color ?? MyColors.darkTextColor,
overflow: overflow,
letterSpacing: -0.72,
decoration: isUnderLine ? TextDecoration.underline : null,
),
);
Widget toTextAuto({
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, bool isCenter = false, int maxLine = 0, TextOverflow? overflow}) => Text(
this,
textAlign: isCenter ? TextAlign.center : null,
maxLines: (maxLine > 0) ? maxLine : null,
overflow: overflow,
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,
FontWeight? weight,
bool isUnderLine = false,
Color? underlineColor,
double underlineGap = 2,
bool isBold = false,
bool isCenter = false,
int maxLine = 0,
double fontSize = 12,
double letterSpacing = -0.72,
double height = 1,
}) =>
int? maxLine,
TextOverflow? overflow,
}) {
Widget text = Text(
this,
textAlign: isCenter ? TextAlign.center : null,
maxLines: maxLine == 0 ? null : maxLine,
style: TextStyle(fontSize: 12, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600), color: color ?? MyColors.darkTextColor, letterSpacing: -0.72, overflow: TextOverflow.visible),
);
if (!isUnderLine) return text;
return Container(
padding: EdgeInsets.only(bottom: underlineGap),
decoration: BoxDecoration(border: Border(bottom: BorderSide(color: underlineColor ?? color ?? MyColors.darkTextColor, width: 1))),
child: text,
);
}
Widget toText12Auto({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int? maxLine, TextOverflow? overflow}) => AutoSizeText(
this,
textAlign: isCenter ? TextAlign.center : null,
maxLines: maxLine == 0 ? null : maxLine,
minFontSize: 8,
style: TextStyle(
fontSize: 12,
fontWeight: isBold ? FontWeight.bold : FontWeight.w600,
color: color ?? MyColors.darkTextColor,
overflow: overflow,
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,
@ -134,100 +139,103 @@ extension EmailValidator on String {
),
);
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, TextAlign? textAlign, bool isCenter = false}) => Text(
this,
textAlign: isCenter ? TextAlign.center : (textAlign ?? TextAlign.left),
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, double? height, bool isCentered = false}) => Text(
this,
maxLines: maxlines,
textAlign: isCentered ? TextAlign.center : null,
style: TextStyle(
color: color ?? MyColors.darkTextColor,
fontSize: 16,
letterSpacing: -0.64,
height: height,
fontWeight: isBold ? FontWeight.bold : FontWeight.w600,
decoration: isUnderLine ? TextDecoration.underline : null,
),
);
Widget toText17({Color? color, bool isBold = false, bool isCentered = false}) => Text(
this,
textAlign: isCentered ? TextAlign.center : null,
style: TextStyle(color: color ?? MyColors.darkTextColor, fontSize: 17, letterSpacing: -0.68, fontWeight: isBold ? FontWeight.bold : FontWeight.w600),
);
Widget toText18({Color? color, bool isBold = false, bool isCentered = false}) => Text(
this,
textAlign: isCentered ? TextAlign.center : null,
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, bool isCentered = false}) => Text(
this,
textAlign: isCentered ? TextAlign.center : null,
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.84, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600)),
);
Widget toText13({Color? color, FontWeight? weight, bool isUnderLine = false, bool isBold = false, int? maxlines, TextOverflow? overflow}) => Text(
this,
maxLines: maxlines,
overflow: overflow,
style: TextStyle(
fontSize: 13,
fontWeight: weight ?? (isBold ? FontWeight.bold : 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, TextAlign? textAlign, bool isCenter = false, TextOverflow? overflow}) => Text(
this,
textAlign: isCenter ? TextAlign.center : textAlign,
maxLines: maxlines,
style: TextStyle(
overflow: overflow,
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, FontWeight? weight, bool isUnderLine = false, bool isBold = false, int? maxlines, double? height, bool isCentered = false}) => Text(
this,
maxLines: maxlines,
textAlign: isCentered ? TextAlign.center : null,
style: TextStyle(
color: color ?? MyColors.darkTextColor,
fontSize: 16,
letterSpacing: -0.64,
height: height,
fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600),
decoration: isUnderLine ? TextDecoration.underline : null,
),
);
Widget toText17({Color? color, FontWeight? weight, bool isBold = false, bool isCentered = false}) => Text(
this,
textAlign: isCentered ? TextAlign.center : null,
style: TextStyle(color: color ?? MyColors.darkTextColor, fontSize: 17, letterSpacing: -0.68, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600)),
);
Widget toText18({Color? color, FontWeight? weight, bool isBold = false, bool isCentered = false}) => Text(
this,
textAlign: isCentered ? TextAlign.center : null,
style: TextStyle(fontSize: 18, fontWeight: weight ?? (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, FontWeight? weight, bool isBold = false, bool isCentered = false}) => Text(
this,
textAlign: isCentered ? TextAlign.center : null,
style: TextStyle(fontSize: 20, fontWeight: weight ?? (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.84, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600)));
Widget toText22({Color? color, bool isBold = false, bool isCentered = false}) => Text(
this,
textAlign: isCentered ? TextAlign.center : null,
style: TextStyle(height: 1, color: color ?? MyColors.darkTextColor, fontSize: 22, letterSpacing: -1.44, fontWeight: isBold ? FontWeight.bold : FontWeight.w600),
);
this,
textAlign: isCentered ? TextAlign.center : null,
style: TextStyle(height: 1, color: color ?? MyColors.darkTextColor, fontSize: 22, letterSpacing: -1.44, fontWeight: isBold ? FontWeight.bold : FontWeight.w600),
);
Widget toText23({Color? color, bool isBold = false, bool isCentered = false}) => Text(
this,
textAlign: isCentered ? TextAlign.center : null,
style: TextStyle(height: 23 / 24, color: color ?? MyColors.darkTextColor, fontSize: 23, letterSpacing: -0.92, fontWeight: isBold ? FontWeight.bold : FontWeight.w600),
);
this,
textAlign: isCentered ? TextAlign.center : null,
style: TextStyle(height: 23 / 24, color: color ?? MyColors.darkTextColor, fontSize: 23, letterSpacing: -0.92, fontWeight: isBold ? FontWeight.bold : FontWeight.w600),
);
Widget toText24({Color? color, bool isBold = false, bool isCentered = false}) => Text(
this,
textAlign: isCentered ? TextAlign.center : null,
style: TextStyle(height: 23 / 24, color: color ?? MyColors.darkTextColor, fontSize: 24, letterSpacing: -1.44, fontWeight: isBold ? FontWeight.bold : FontWeight.w600),
);
this,
textAlign: isCentered ? TextAlign.center : null,
style: TextStyle(height: 23 / 24, color: color ?? MyColors.darkTextColor, fontSize: 24, letterSpacing: -1.44, fontWeight: isBold ? FontWeight.bold : FontWeight.w600),
);
Widget toText30({Color? color, bool isBold = false, bool isCentered = false}) => Text(
this,
textAlign: isCentered ? TextAlign.center : null,
style: TextStyle(height: 20 / 32, color: color ?? MyColors.darkTextColor, fontSize: 32, letterSpacing: -1.2, fontWeight: isBold ? FontWeight.bold : FontWeight.w600),
);
this,
textAlign: isCentered ? TextAlign.center : null,
style: TextStyle(height: 20 / 32, color: color ?? MyColors.darkTextColor, fontSize: 32, letterSpacing: -1.2, fontWeight: isBold ? FontWeight.bold : FontWeight.w600),
);
Widget toText32({Color? color, bool isBold = false, bool isCentered = false}) => Text(
this,
textAlign: isCentered ? TextAlign.center : null,
style: TextStyle(height: 32 / 32, color: color ?? MyColors.darkTextColor, fontSize: 32, letterSpacing: -1.92, fontWeight: isBold ? FontWeight.bold : FontWeight.w600),
);
this,
textAlign: isCentered ? TextAlign.center : null,
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 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 = "";
@ -251,14 +259,7 @@ extension EmailValidator on String {
lower = lowerHeading;
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
if (upper.isNotEmpty) upper.toText12(),
lower.toText24(isBold: true),
],
);
return Column(crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [if (upper.isNotEmpty) upper.toText12(), lower.toText24(isBold: true)]);
}
bool isValidEmail() {

File diff suppressed because it is too large Load Diff

@ -590,6 +590,15 @@ abstract class LocaleKeys {
static const addUsers = 'addUsers';
static const admin = 'admin';
static const areYouSureWantTodelete = 'areYouSureWantTodelete';
static const confirmGrantMazayaAccess = 'confirmGrantMazayaAccess';
static const confirmRevokeMazayaAccess = 'confirmRevokeMazayaAccess';
static const yes = 'yes';
static const no = 'no';
static const mazayaInviteInstructions = 'mazayaInviteInstructions';
static const cameraPermissionRequiredToScan = 'cameraPermissionRequiredToScan';
static const cameraPermissionPermanentlyDenied = 'cameraPermissionPermanentlyDenied';
static const openSettings = 'openSettings';
static const nationalIdOrIqamaFallback = 'nationalIdOrIqamaFallback';
static const Attendance = 'Attendance';
static const audioCall = 'audioCall';
static const editGroups = 'editGroups';
@ -665,6 +674,193 @@ abstract class LocaleKeys {
static const priceSAR = 'priceSAR';
static const amountSAR = 'amountSAR';
static const amuLastThreeMonths = 'amuLastThreeMonths';
static const mazayaTitle = 'mazayaTitle';
static const mazayaAccessGranted = 'mazayaAccessGranted';
static const mazayaAccessGrantedDesc = 'mazayaAccessGrantedDesc';
static const accessIsNowActive = 'accessIsNowActive';
static const accessIsNowActiveDesc = 'accessIsNowActiveDesc';
static const dependentAccessActive = 'dependentAccessActive';
static const shareableAppDownloadLink = 'shareableAppDownloadLink';
static const shareableAppDownloadLinkDesc = 'shareableAppDownloadLinkDesc';
static const downloadLink = 'downloadLink';
static const shareDownloadLink = 'shareDownloadLink';
static const copyLink = 'copyLink';
static const done = 'done';
static const linkCopied = 'linkCopied';
static const mazayaAccessRevoked = 'mazayaAccessRevoked';
static const mazayaAccessRevokedDesc = 'mazayaAccessRevokedDesc';
static const contactYourSponsor = 'contactYourSponsor';
static const contactYourSponsorDesc = 'contactYourSponsorDesc';
static const dependentAccessBlocked = 'dependentAccessBlocked';
static const returnToSignIn = 'returnToSignIn';
static const allowMazayaAccess = 'allowMazayaAccess';
static const revokeMazayaAccess = 'revokeMazayaAccess';
static const active = 'active';
static const noAccess = 'noAccess';
static const offerDetails = 'offerDetails';
static const offerIdNotAvailable = 'offerIdNotAvailable';
static const unableToRedeemOffer = 'unableToRedeemOffer';
static const errorRedeemingOffer = 'errorRedeemingOffer';
static const useOffer = 'useOffer';
static const online = 'online';
static const inStore = 'inStore';
static const ksa = 'ksa';
static const riyadh = 'riyadh';
static const noInstructionsAvailable = 'noInstructionsAvailable';
static const contactInfo = 'contactInfo';
static const allCategories = 'allCategories';
static const noOffersAvailable = 'noOffersAvailable';
static const addDependents = 'addDependents';
static const myDependents = 'myDependents';
static const offDiscount = 'offDiscount';
static const usageLabel = 'usageLabel';
static const areYouSureWantToLogOut = 'areYouSureWantToLogOut';
static const searchDependents = 'searchDependents';
static const searchFamilyMembers = 'searchFamilyMembers';
static const addAnotherUser = 'addAnotherUser';
static const joinMazaya = 'joinMazaya';
static const joinMazayaSubtitle = 'joinMazayaSubtitle';
static const continueAsEmployee = 'continueAsEmployee';
static const continueAsEmployeeSubtitle = 'continueAsEmployeeSubtitle';
static const pleaseEnterNationalId = 'pleaseEnterNationalId';
static const pleaseSelectDob = 'pleaseSelectDob';
static const unableToRetrieveDependentInfo = 'unableToRetrieveDependentInfo';
static const failedToSendOtp = 'failedToSendOtp';
static const verificationCodeResent = 'verificationCodeResent';
static const nationalId = 'nationalId';
static const addDependent = 'addDependent';
static const stepVerify = 'stepVerify';
static const stepBasic = 'stepBasic';
static const stepAdditional = 'stepAdditional';
static const findYourDependent = 'findYourDependent';
static const findYourDependentDesc = 'findYourDependentDesc';
static const enter10DigitNationalId = 'enter10DigitNationalId';
static const nationalIdMustBe10Digits = 'nationalIdMustBe10Digits';
static const dateFormatPlaceholder = 'dateFormatPlaceholder';
static const searchUser = 'searchUser';
static const dependentNotFoundNhic = 'dependentNotFoundNhic';
static const basicInfo = 'basicInfo';
static const basicInfoDesc = 'basicInfoDesc';
static const additionalInfo = 'additionalInfo';
static const additionalInfoDesc = 'additionalInfoDesc';
static const select = 'select';
static const enterFieldPlaceholder = 'enterFieldPlaceholder';
static const requiredField = 'requiredField';
static const pleaseFillField = 'pleaseFillField';
static const reviewAndSubmit = 'reviewAndSubmit';
static const reviewAndSubmitDesc = 'reviewAndSubmitDesc';
static const arabicName = 'arabicName';
static const relationship = 'relationship';
static const sponsor = 'sponsor';
static const dependentSponsorDetail = 'dependentSponsorDetail';
static const insuranceEligibility = 'insuranceEligibility';
static const relationshipStartDate = 'relationshipStartDate';
static const dependentAddedSuccessfully = 'dependentAddedSuccessfully';
static const genderMale = 'genderMale';
static const genderFemale = 'genderFemale';
static const religionIslam = 'religionIslam';
static const back = 'back';
static const submitRequest = 'submitRequest';
static const enterDependentDetailsToContinue = 'enterDependentDetailsToContinue';
static const enterBasicDetailsDesc = 'enterBasicDetailsDesc';
static const locations = 'locations';
static const availableOffers = 'availableOffers';
static const selectOfferToRedeem = 'selectOfferToRedeem';
static const viewOnMap = 'viewOnMap';
static const showAllLocations = 'showAllLocations';
static const searchBranches = 'searchBranches';
static const allCities = 'allCities';
static const allCitiesOption = 'allCitiesOption';
static const noBranchesFound = 'noBranchesFound';
static const browseAllBranches = 'browseAllBranches';
static const branchesCount = 'branchesCount';
static const kmAway = 'kmAway';
static const dependentDetails = 'dependentDetails';
static const keyDetails = 'keyDetails';
static const verified = 'verified';
static const idEnding = 'idEnding';
static const servicesAndActions = 'servicesAndActions';
static const startDependentFlowsDesc = 'startDependentFlowsDesc';
static const mazayaAccess = 'mazayaAccess';
static const accessOff = 'accessOff';
static const allowDependentMazayaOffersDesc = 'allowDependentMazayaOffersDesc';
static const loginMethod = 'loginMethod';
static const nationalIdAndDob = 'nationalIdAndDob';
static const sponsorOtp = 'sponsorOtp';
static const revokingPreventsAccessDesc = 'revokingPreventsAccessDesc';
static const locationPermissionRequiredForAttendance = 'locationPermissionRequiredForAttendance';
static const locationServicesRequiredForAttendance = 'locationServicesRequiredForAttendance';
static const pleaseProvideLocationPermission = 'pleaseProvideLocationPermission';
static const microphoneAccessRequired = 'microphoneAccessRequired';
static const shareAccessGuide = 'shareAccessGuide';
static const nationalIdOrIqamaNumber = 'nationalIdOrIqamaNumber';
static const multipleCities = 'multipleCities';
static const filters = 'filters';
static const availability = 'availability';
static const chooseOneOrBoth = 'chooseOneOrBoth';
static const sortBy = 'sortBy';
static const recommended = 'recommended';
static const nearest = 'nearest';
static const highestDiscount = 'highestDiscount';
static const newest = 'newest';
static const distance = 'distance';
static const fromYourCurrentLocation = 'fromYourCurrentLocation';
static const any = 'any';
static const minimumDiscount = 'minimumDiscount';
static const reset = 'reset';
static const applyFilters = 'applyFilters';
static const distanceKm = 'distanceKm';
static const discountPercentPlus = 'discountPercentPlus';
static const visitMerchantWebsite = 'visitMerchantWebsite';
static const favoriteAddedSuccess = 'favoriteAddedSuccess';
static const favoriteRemovedSuccess = 'favoriteRemovedSuccess';
static const favoriteError = 'favoriteError';
static const merchantWebsiteNotAvailable = 'merchantWebsiteNotAvailable';
static const locationServicesRequiredForNearestOffers = 'locationServicesRequiredForNearestOffers';
static const locationPermissionRequiredForNearestOffers = 'locationPermissionRequiredForNearestOffers';
static const mazayaDeals = 'mazayaDeals';
static const helloName = 'helloName';
static const findABrandOrSomething = 'findABrandOrSomething';
static const exploreYourBenefits = 'exploreYourBenefits';
static const theMazayaEdit = 'theMazayaEdit';
static const exclusivelyYours = 'exclusivelyYours';
static const aLittleExtraJustForMazayaMembers = 'aLittleExtraJustForMazayaMembers';
static const seeAll = 'seeAll';
static const latestOnMazaya = 'latestOnMazaya';
static const goodFoodGreatCompanyEvenBetterPerks = 'goodFoodGreatCompanyEvenBetterPerks';
static const travelEssentials = 'travelEssentials';
static const fromEverydayCareToYourNextAdventure = 'fromEverydayCareToYourNextAdventure';
static const homeAndKitchen = 'homeAndKitchen';
static const curatedPicksForYourHomeAndKitchen = 'curatedPicksForYourHomeAndKitchen';
static const discover = 'discover';
static const saved = 'saved';
static const myFamily = 'myFamily';
static const educationAndTraining = 'educationAndTraining';
static const foodsAndDrinks = 'foodsAndDrinks';
static const travelAndEntertainment = 'travelAndEntertainment';
static const beautyAndFitness = 'beautyAndFitness';
static const healthcare = 'healthcare';
static const favoriteMerchant = 'favoriteMerchant';
static const favoriteOffers = 'favoriteOffers';
static const goodToKnow = 'goodToKnow';
static const goodToKnowDesc = 'goodToKnowDesc';
static const termsAndConditions = 'termsAndConditions';
static const haveAQuestionAboutOffer = 'haveAQuestionAboutOffer';
static const getHelp = 'getHelp';
static const redeemOffer = 'redeemOffer';
static const merchantContact = 'merchantContact';
static const website = 'website';
static const phone = 'phone';
static const viewAll = 'viewAll';
static const moreCitiesCount = 'moreCitiesCount';
static const findAnEscapeOrAnExperience = 'findAnEscapeOrAnExperience';
static const allOffers = 'allOffers';
static const sampleAvailability = 'sampleAvailability';
static const offersCount = 'offersCount';
static const mazayaExclusive = 'mazayaExclusive';
static const youAreAllCaughtUp = 'youAreAllCaughtUp';
static const selectCity = 'selectCity';
static const searchCities = 'searchCities';
static const chatBot = 'chatBot';
static const helloAskMe = 'helloAskMe';
static const anything = 'anything';
@ -674,5 +870,6 @@ abstract class LocaleKeys {
static const initializingChatPleaseWait = 'initializingChatPleaseWait';
static const am = 'am';
static const pm = 'pm';
static const carServices = 'carServices';
}

@ -0,0 +1,92 @@
class GetMobileLoginInfoDependentModel {
int? iD;
String? dependentID;
String? depedentName;
String? dob;
int? channelID;
int? companyID;
String? deviceType;
String? deviceToken;
int? language;
int? gender;
int? loginType;
String? createdOn;
String? editedOn;
bool? businessCardPrivilege;
GetMobileLoginInfoDependentModel({
this.iD,
this.dependentID,
this.depedentName,
this.dob,
this.channelID,
this.companyID,
this.deviceType,
this.deviceToken,
this.language,
this.gender,
this.loginType,
this.createdOn,
this.editedOn,
this.businessCardPrivilege,
});
// Convenience aliases for existing logic
String? get nationalId => dependentID;
String? get birthDate => dob;
String? get employeeName => depedentName;
String? get dependentName => depedentName;
GetMobileLoginInfoDependentModel.fromJson(Map<String, dynamic> json) {
iD = json['ID'];
dependentID = (json['DependentID'] ??
json['DEPENDENT_ID'] ??
json['P_DEP_NATIONAL_ID'] ??
json['NationalID'] ??
json['NationalId'] ??
json['EmployeeID'] ??
json['UserName'])
?.toString();
depedentName = (json['DepedentName'] ??
json['DependentName'] ??
json['P_DEPENDENT_NAME'] ??
json['EmployeeName'])
?.toString();
dob = (json['DOB'] ??
json['BirthDate'] ??
json['DateOfBirth'] ??
json['P_DEP_BIRTH_DATE'] ??
json['P_BIRTH_DATE'] ??
json['Birth_Date'])
?.toString();
channelID = json['ChannelID'] != null ? int.tryParse(json['ChannelID'].toString()) : null;
companyID = json['CompanyID'] != null ? int.tryParse(json['CompanyID'].toString()) : null;
deviceType = json['DeviceType']?.toString();
deviceToken = json['DeviceToken']?.toString();
language = json['Language'] != null ? int.tryParse(json['Language'].toString()) : null;
gender = json['Gender'] != null ? int.tryParse(json['Gender'].toString()) : null;
loginType = json['LoginType'] != null ? int.tryParse(json['LoginType'].toString()) : null;
createdOn = json['CreatedOn']?.toString();
editedOn = json['EditedOn']?.toString();
businessCardPrivilege = json['BusinessCardPrivilege'];
}
Map<String, dynamic> toJson() {
Map<String, dynamic> data = <String, dynamic>{};
data['ID'] = iD;
data['DependentID'] = dependentID;
data['DepedentName'] = depedentName;
data['DOB'] = dob;
data['ChannelID'] = channelID;
data['CompanyID'] = companyID;
data['DeviceType'] = deviceType;
data['DeviceToken'] = deviceToken;
data['Language'] = language;
data['Gender'] = gender;
data['LoginType'] = loginType;
data['CreatedOn'] = createdOn;
data['EditedOn'] = editedOn;
data['BusinessCardPrivilege'] = businessCardPrivilege;
return data;
}
}

@ -107,6 +107,7 @@ import 'package:mohem_flutter_app/models/vacation_rule/respond_attributes_list_m
import 'package:mohem_flutter_app/models/vacation_rule/vr_item_types_list_model.dart';
import 'package:mohem_flutter_app/models/vacation_rule/wf_look_up_list_model.dart';
import 'package:mohem_flutter_app/models/validate_eit_transaction_list_model.dart';
import 'package:mohem_flutter_app/models/offers_and_discounts/wala_plus_category_model.dart';
import 'package:mohem_flutter_app/models/worklist/GetRFCEmployeeList.dart';
import 'package:mohem_flutter_app/models/worklist/get_fad_notifications.dart';
import 'package:mohem_flutter_app/models/worklist/get_favorite_replacements_model.dart';
@ -403,6 +404,14 @@ class GenericResponseModel {
AtlasContinueChatResponseDetails? atlasContinueChatResponseDetails;
GetWalaplusDepInfoModel? getWalaplusDepInfoModel;
dynamic getWalaplusDepInfoModelInternal;
List<WalaPlusCategoryModel>? walaPlusCategories;
String? wpFavoritesAddRemoveMessage;
String? businessCardQROutside;
EmpBusinessCardInfoModel? empBusinessCardInfoModel;
bool? isEmpLastLoginToDevice;
GenericResponseModel({
this.date,
this.languageID,
@ -675,6 +684,13 @@ class GenericResponseModel {
this.isSMSSent,
this.getFADisposalNtfDetails,
this.Mohemm_ENSAT_GetItgToken_Response,
this.getWalaplusDepInfoModel,
this.getWalaplusDepInfoModelInternal,
this.walaPlusCategories,
this.wpFavoritesAddRemoveMessage,
this.businessCardQROutside,
this.empBusinessCardInfoModel,
this.isEmpLastLoginToDevice,
this.atlasInitiateChatResponse,
this.atlasContinueChatResponseDetails,
@ -686,7 +702,7 @@ class GenericResponseModel {
serviceName = json['ServiceName'];
time = json['Time'];
androidLink = json['AndroidLink'];
authenticationTokenID = json['AuthenticationTokenID'];
authenticationTokenID = json['AuthenticationTokenID']?.toString();
data = json['Data'];
dataw = json['Dataw'];
dietType = json['DietType'];
@ -1476,6 +1492,21 @@ class GenericResponseModel {
Mohemm_ENSAT_GetItgToken_Response = json['Mohemm_ENSAT_GetItgToken_Response'];
getWalaplusDepInfoModel = json['GET_WALAPLUS_DEP_INFO_Model'] != null ? GetWalaplusDepInfoModel.fromJson(json['GET_WALAPLUS_DEP_INFO_Model']) : null;
getWalaplusDepInfoModelInternal = json['GET_WALAPLUS_DEP_INFO_Model_Internal'];
businessCardQROutside = json['BusinessCardQR_Outside'];
empBusinessCardInfoModel = json['GET_EMP_BUSINESS_CARD_INFO_Model'] != null ? EmpBusinessCardInfoModel.fromJson(json['GET_EMP_BUSINESS_CARD_INFO_Model']) : null;
if (json['WalaPlus_Categories'] != null) {
walaPlusCategories = <WalaPlusCategoryModel>[];
json['WalaPlus_Categories'].forEach((v) {
walaPlusCategories!.add(WalaPlusCategoryModel.fromJson(v));
});
}
wpFavoritesAddRemoveMessage = json['WP_Favorites_AddRemove_Message'] ?? json['wp_Favorites_AddRemove_Message'];
isEmpLastLoginToDevice = json["IsEmpLastLoginToDevice"];
}
Map<String, dynamic> toJson() {
@ -1965,6 +1996,24 @@ class GenericResponseModel {
data["Atlas_ContinueChat_ResponseDetails"] = atlasContinueChatResponseDetails?.toJson();
data['Mohemm_ENSAT_GetItgToken_Response'] = this.Mohemm_ENSAT_GetItgToken_Response;
data['GET_WALAPLUS_DEP_INFO_Model_Internal'] = this.getWalaplusDepInfoModelInternal;
if (this.getWalaplusDepInfoModel != null) {
data['GET_WALAPLUS_DEP_INFO_Model'] = this.getWalaplusDepInfoModel!.toJson();
}
data['BusinessCardQR_Outside'] = this.businessCardQROutside;
if (this.empBusinessCardInfoModel != null) {
data['GET_EMP_BUSINESS_CARD_INFO_Model'] = this.empBusinessCardInfoModel!.toJson();
}
if (walaPlusCategories != null) {
data['WalaPlus_Categories'] = walaPlusCategories!.map((WalaPlusCategoryModel v) => v.toJson()).toList();
}
if (this.wpFavoritesAddRemoveMessage != null) {
data['WP_Favorites_AddRemove_Message'] = this.wpFavoritesAddRemoveMessage;
}
data["IsEmpLastLoginToDevice"] = this.isEmpLastLoginToDevice;
return data;
}
}
@ -1991,6 +2040,50 @@ class TicketBookingResult {
TicketBookingResult(this.success, this.clientId);
}
class GetWalaplusDepInfoModel {
String? pEmpEmailAddress;
String? pEmpMobileNumber;
String? pDependentName;
GetWalaplusDepInfoModel({this.pEmpEmailAddress, this.pEmpMobileNumber, this.pDependentName});
factory GetWalaplusDepInfoModel.fromJson(Map<String, dynamic> json) =>
GetWalaplusDepInfoModel(pEmpEmailAddress: json["P_EMP_EMAIL_ADDRESS"], pEmpMobileNumber: json["P_EMP_MOBILE_NUMBER"], pDependentName: json["P_DEPENDENT_NAME"]);
Map<String, dynamic> toJson() => {"P_EMP_EMAIL_ADDRESS": pEmpEmailAddress, "P_EMP_MOBILE_NUMBER": pEmpMobileNumber, "P_DEPENDENT_NAME": pDependentName};
}
class EmpBusinessCardInfoModel {
String? pEmailAddress;
String? pFirstName;
String? pLastName;
String? pMobilePhoneNumber;
String? pPositionTitle;
String? pWorkExtensionNumber;
EmpBusinessCardInfoModel({this.pEmailAddress, this.pFirstName, this.pLastName, this.pMobilePhoneNumber, this.pPositionTitle, this.pWorkExtensionNumber});
String get fullName => [pFirstName, pLastName].where((e) => e != null && e.trim().isNotEmpty).join(" ");
factory EmpBusinessCardInfoModel.fromJson(Map<String, dynamic> json) => EmpBusinessCardInfoModel(
pEmailAddress: json["P_EMAIL_ADDRESS"],
pFirstName: json["P_FIRST_NAME"],
pLastName: json["P_LAST_NAME"],
pMobilePhoneNumber: json["P_MOBILE_PHONE_NUMBER"],
pPositionTitle: json["P_POSITION_TITLE"],
pWorkExtensionNumber: json["P_WORK_EXTENSION_NUMBER"],
);
Map<String, dynamic> toJson() => {
"P_EMAIL_ADDRESS": pEmailAddress,
"P_FIRST_NAME": pFirstName,
"P_LAST_NAME": pLastName,
"P_MOBILE_PHONE_NUMBER": pMobilePhoneNumber,
"P_POSITION_TITLE": pPositionTitle,
"P_WORK_EXTENSION_NUMBER": pWorkExtensionNumber,
};
}
class AtlasInitiateChatResponse {
String? atlasAccessToken;
String? atlasChatSessionId;

@ -1,3 +1,4 @@
import 'package:flutter/foundation.dart';
class GetEmployeeContactsList {
String? cONTACTNAME;
@ -8,16 +9,34 @@ class GetEmployeeContactsList {
int? pERSONID;
String? pRIMARYCONTACTFLAG;
String? rELATIONSHIP;
String? mAZAYAACCESSFLG;
dynamic mazayaAccess;
String? nATIONALIDENTIFIER;
GetEmployeeContactsList(
{this.cONTACTNAME,
this.cONTACTPERSONID,
this.cONTACTRELATIONSHIPID,
this.cONTACTTYPE,
this.dATEOFBIRTH,
this.pERSONID,
this.pRIMARYCONTACTFLAG,
this.rELATIONSHIP});
// Real ERP response fields - needed to call the grant-access API later (as P_BIRTH_DATE /
// P_NHIC_VERIFIED_FLAG request params). PatientIdentificationID/PatientOutSA are NOT
// response fields - they're constructed at call time (from nATIONALIDENTIFIER, and a
// hardcoded 0, respectively), so they don't belong on this model.
// Not a plain string per ERP - keep untyped so whatever shape comes back (epoch, nested
// date object, etc.) survives round-trip, instead of assuming a format.
dynamic pBIRTHDATE;
String? pNHICVERIFIEDFLAG;
GetEmployeeContactsList({
this.cONTACTNAME,
this.cONTACTPERSONID,
this.cONTACTRELATIONSHIPID,
this.cONTACTTYPE,
this.dATEOFBIRTH,
this.pERSONID,
this.pRIMARYCONTACTFLAG,
this.rELATIONSHIP,
this.mAZAYAACCESSFLG,
this.mazayaAccess,
this.nATIONALIDENTIFIER,
this.pBIRTHDATE,
this.pNHICVERIFIEDFLAG,
});
GetEmployeeContactsList.fromJson(Map<String, dynamic> json) {
cONTACTNAME = json['CONTACT_NAME'];
@ -28,18 +47,41 @@ class GetEmployeeContactsList {
pERSONID = json['PERSON_ID'];
pRIMARYCONTACTFLAG = json['PRIMARY_CONTACT_FLAG'];
rELATIONSHIP = json['RELATIONSHIP'];
mAZAYAACCESSFLG = json['MAZAYA_ACCESS_FLG']?.toString() ?? json['MAZAYA_ACCESS']?.toString();
mazayaAccess = mAZAYAACCESSFLG;
nATIONALIDENTIFIER =
json['NATIONAL_IDENTIFIER']?.toString() ??
json['NATIONAL_ID']?.toString() ??
json['CIVIL_ID']?.toString() ??
json['IQAMA_NUMBER']?.toString() ??
json['CONTACT_NATIONAL_IDENTIFIER']?.toString() ??
json['CONTACT_NATIONAL_ID']?.toString() ??
json['CONTACT_IDENTIFICATION_ID']?.toString() ??
json['IDENTIFICATION_ID']?.toString() ??
json['NATIONALID']?.toString();
if (nATIONALIDENTIFIER == null || nATIONALIDENTIFIER!.trim().isEmpty) {
debugPrint("GetEmployeeContactsList: no national id match found, raw contact json: $json");
}
pBIRTHDATE = json['P_BIRTH_DATE'];
pNHICVERIFIEDFLAG = json['P_NHIC_VERIFIED_FLAG']?.toString();
}
Map<String, dynamic> toJson() {
Map<String, dynamic> data = new Map<String, dynamic>();
data['CONTACT_NAME'] = this.cONTACTNAME;
data['CONTACT_PERSON_ID'] = this.cONTACTPERSONID;
data['CONTACT_RELATIONSHIP_ID'] = this.cONTACTRELATIONSHIPID;
data['CONTACT_TYPE'] = this.cONTACTTYPE;
data['DATE_OF_BIRTH'] = this.dATEOFBIRTH;
data['PERSON_ID'] = this.pERSONID;
data['PRIMARY_CONTACT_FLAG'] = this.pRIMARYCONTACTFLAG;
data['RELATIONSHIP'] = this.rELATIONSHIP;
Map<String, dynamic> data = <String, dynamic>{};
data['CONTACT_NAME'] = cONTACTNAME;
data['CONTACT_PERSON_ID'] = cONTACTPERSONID;
data['CONTACT_RELATIONSHIP_ID'] = cONTACTRELATIONSHIPID;
data['CONTACT_TYPE'] = cONTACTTYPE;
data['DATE_OF_BIRTH'] = dATEOFBIRTH;
data['PERSON_ID'] = pERSONID;
data['PRIMARY_CONTACT_FLAG'] = pRIMARYCONTACTFLAG;
data['RELATIONSHIP'] = rELATIONSHIP;
data['MAZAYA_ACCESS_FLG'] = mAZAYAACCESSFLG;
data['MAZAYA_ACCESS'] = mazayaAccess;
data['NATIONAL_IDENTIFIER'] = nATIONALIDENTIFIER;
data['P_BIRTH_DATE'] = pBIRTHDATE;
data['P_NHIC_VERIFIED_FLAG'] = pNHICVERIFIEDFLAG;
return data;
}
}
}

@ -1,6 +1,6 @@
class GetMobileLoginInfoListModel {
int? iD;
String? employeeID;
dynamic? employeeID; //do not change this
int? channelID;
int? companyID;
String? deviceType;

@ -0,0 +1,75 @@
class WalaPlusCategoryModel {
String? id;
String? image;
String? imageThumb;
String? name;
String? parentId;
String? countryNameAr;
String? countryNameEn;
WalaPlusCategoryModel({
this.id,
this.image,
this.imageThumb,
this.name,
this.parentId,
this.countryNameAr,
this.countryNameEn,
}) {
if ((image == null || image!.isEmpty) && id != null && id!.trim().isNotEmpty && id != "all" && id != "0") {
String cleanId = id!.trim();
if (cleanId.toLowerCase().endsWith('.png')) {
cleanId = cleanId.substring(0, cleanId.length - 4);
}
image = Uri.encodeFull("https://epharmacy.hmg.com/middleware/images/category/$cleanId.png");
}
}
WalaPlusCategoryModel.fromJson(Map<String, dynamic> json) {
id = (json['id'] ?? json['Id'] ?? json['ID'])?.toString();
name = (json['name'] ?? json['Name'] ?? json['category_name'] ?? json['categoryName'] ?? json['cat_name'] ?? json['CategoryName'])?.toString();
parentId = (json['parent_id'] ?? json['Parent_Id'] ?? json['parentId'])?.toString();
imageThumb = (json['image_thumb'] ?? json['Image_Thumb'] ?? json['imageThumb'])?.toString();
countryNameAr = (json['country_name_ar'] ?? json['countryNameAr'] ?? json['category_name_ar'] ?? json['categoryNameAr'] ?? json['cat_name_ar'] ?? json['name_ar'])?.toString();
countryNameEn = (json['country_name_en'] ?? json['countryNameEn'] ?? json['category_name_en'] ?? json['categoryNameEn'] ?? json['cat_name_en'] ?? json['name_en'])?.toString();
// Use category image URL from HMG middleware using ID from WalaPlus_Categories API:
// https://epharmacy.hmg.com/middleware/images/category/{id}.png
if (id != null && id!.trim().isNotEmpty && id != "all" && id != "0") {
String cleanId = id!.trim();
if (cleanId.toLowerCase().endsWith('.png')) {
cleanId = cleanId.substring(0, cleanId.length - 4);
}
image = Uri.encodeFull("https://epharmacy.hmg.com/middleware/images/category/$cleanId.png");
} else {
image = (json['image'] ?? json['Image'])?.toString();
}
}
Map<String, dynamic> toJson() {
Map<String, dynamic> data = <String, dynamic>{};
data['id'] = id;
data['image'] = image;
data['image_thumb'] = imageThumb;
data['name'] = name;
data['parent_id'] = parentId;
data['country_name_ar'] = countryNameAr;
data['country_name_en'] = countryNameEn;
return data;
}
bool get isMainCategory => parentId == null || parentId!.isEmpty;
bool get isSvg => (image ?? '').toLowerCase().contains('.svg');
String get categoryImageUrl {
if (id != null && id!.trim().isNotEmpty && id != "all" && id != "0") {
String cleanId = id!.trim();
if (cleanId.toLowerCase().endsWith('.png')) {
cleanId = cleanId.substring(0, cleanId.length - 4);
}
return Uri.encodeFull("https://epharmacy.hmg.com/middleware/images/category/$cleanId.png");
}
return image ?? '';
}
}

@ -0,0 +1,51 @@
class WalaPlusCityModel {
int? cityId;
String? cityNameAr;
String? cityNameEn;
String? countryNameAr;
String? countryNameEn;
WalaPlusCityModel({
this.cityId,
this.cityNameAr,
this.cityNameEn,
this.countryNameAr,
this.countryNameEn,
});
factory WalaPlusCityModel.fromJson(Map<String, dynamic> json) {
return WalaPlusCityModel(
cityId:
json['city_id'] is int
? json['city_id']
: int.tryParse(json['city_id']?.toString() ?? ''),
cityNameAr: json['city_name_ar']?.toString(),
cityNameEn: json['city_name_en']?.toString(),
countryNameAr: json['country_name_ar']?.toString(),
countryNameEn: json['country_name_en']?.toString(),
);
}
Map<String, dynamic> toJson() {
return {
'city_id': cityId,
'city_name_ar': cityNameAr,
'city_name_en': cityNameEn,
'country_name_ar': countryNameAr,
'country_name_en': countryNameEn,
};
}
String getDisplayName(bool isAr) {
String? primary = isAr ? cityNameAr : cityNameEn;
if (primary != null && primary.trim().isNotEmpty) return primary.trim();
String? fallback = isAr ? cityNameEn : cityNameAr;
return fallback?.trim() ?? '';
}
bool get isAllCities {
String en = (cityNameEn ?? '').trim().toLowerCase();
String ar = (cityNameAr ?? '').trim();
return en == 'all cities' || ar == 'جميع المدن';
}
}

@ -1,5 +1,3 @@
import 'package:mohem_flutter_app/app_state/app_state.dart';
class PostParamsModel {
double? versionID;
int? channel;
@ -15,6 +13,7 @@ class PostParamsModel {
String? payrollCodeStr;
int? pSessionId;
String? userName;
String? sMSSignature;
PostParamsModel({
this.versionID,
@ -31,6 +30,7 @@ class PostParamsModel {
this.pSelectedEmployeeNumber,
this.payrollCodeStr,
this.pLegislationCode,
this.sMSSignature,
});
PostParamsModel.fromJson(Map<String, dynamic> json) {
@ -40,7 +40,7 @@ class PostParamsModel {
mobileType = json['MobileType'];
logInTokenID = json['LogInTokenID'];
payrollCodeStr = json['PayrollCodeStr'];
pLegislationCode = json['LegislationCodeStr'];
pLegislationCode = json['LegislationCodeStr']?.toString().trim().toUpperCase();
tokenID = json['TokenID'];
}
@ -92,7 +92,9 @@ class PostParamsModel {
set setPSelectedEmployeeNumber(String? v) => pSelectedEmployeeNumber = v;
set setPLegislationCode(String? v) => pLegislationCode = v;
set setPLegislationCode(String? v) => pLegislationCode = v?.trim().toUpperCase();
set setPayrollCodeStr(String? v) => payrollCodeStr = v;
set setSMSSignature(String? v) => sMSSignature = v;
set setSmsSignature(String? v) => sMSSignature = v;
}

@ -7,20 +7,23 @@ class PrivilegeListModel {
int? iD;
String? serviceName;
bool? previlege;
bool? previlegeHMG;
PrivilegeListModel({this.iD, this.serviceName, this.previlege});
PrivilegeListModel({this.iD, this.serviceName, this.previlege, this.previlegeHMG});
PrivilegeListModel.fromJson(Map<String, dynamic> json) {
iD = json['ID'];
serviceName = json['ServiceName'];
previlege = json['Previlege'];
previlege = json['Previlege'] ?? json['previlege'] ?? json['Privilege'] ?? json['privilege'];
previlegeHMG = json['Previlege_HMG'] ?? json['PrevilegeHMG'] ?? json['previlege_HMG'] ?? json['previlegeHMG'] ?? json['Privilege_HMG'] ?? json['PrivilegeHMG'];
}
Map<String, dynamic> toJson() {
Map<String, dynamic> data = new Map<String, dynamic>();
data['ID'] = this.iD;
data['ServiceName'] = this.serviceName;
data['Previlege'] = this.previlege;
Map<String, dynamic> data = <String, dynamic>{};
data['ID'] = iD;
data['ServiceName'] = serviceName;
data['Previlege'] = previlege;
data['Previlege_HMG'] = previlegeHMG;
return data;
}

@ -0,0 +1,139 @@
class PatientInfoModel {
int? age;
String? dateOfBirth;
String? firstNameAr;
String? firstNameEn;
String? secondNameAr;
String? secondNameEn;
String? thirdNameAr;
String? thirdNameEn;
String? lastNameAr;
String? lastNameEn;
String? gender;
String? healthId;
String? idNumber;
String? idType;
bool? isHijri;
String? maritalStatus;
String? maritalStatusCode;
String? nationalDateOfBirth;
String? nationality;
String? nationalityCode;
String? occupation;
String? patientStatus;
String? placeOfBirth;
int? messageStatus;
String? errorEndUserMessage;
String? errorMessage;
PatientInfoModel({
this.age,
this.dateOfBirth,
this.firstNameAr,
this.firstNameEn,
this.secondNameAr,
this.secondNameEn,
this.thirdNameAr,
this.thirdNameEn,
this.lastNameAr,
this.lastNameEn,
this.gender,
this.healthId,
this.idNumber,
this.idType,
this.isHijri,
this.maritalStatus,
this.maritalStatusCode,
this.nationalDateOfBirth,
this.nationality,
this.nationalityCode,
this.occupation,
this.patientStatus,
this.placeOfBirth,
this.messageStatus,
this.errorEndUserMessage,
this.errorMessage,
});
factory PatientInfoModel.fromJson(Map<String, dynamic> json) {
return PatientInfoModel(
age: json['Age'],
dateOfBirth: json['DateOfBirth'],
firstNameAr: json['FirstNameAr'],
firstNameEn: json['FirstNameEn'],
secondNameAr: json['SecondNameAr'],
secondNameEn: json['SecondNameEn'],
thirdNameAr: json['ThirdNameAr'],
thirdNameEn: json['ThirdNameEn'],
lastNameAr: json['LastNameAr'],
lastNameEn: json['LastNameEn'],
gender: json['Gender'],
healthId: json['HealthId'],
idNumber: json['IdNumber'],
idType: json['IdType'],
isHijri: json['IsHijri'],
maritalStatus: json['MaritalStatus'],
maritalStatusCode: json['MaritalStatusCode'],
nationalDateOfBirth: json['NationalDateOfBirth'],
nationality: json['Nationality'],
nationalityCode: json['NationalityCode'],
occupation: json['Occupation'],
patientStatus: json['PatientStatus']?.toString(),
placeOfBirth: json['PlaceofBirth'],
messageStatus: json['MessageStatus'],
errorEndUserMessage: json['ErrorEndUserMessage'],
errorMessage: json['ErrorMessage'],
);
}
Map<String, dynamic> toJson() {
return {
'Age': age,
'DateOfBirth': dateOfBirth,
'FirstNameAr': firstNameAr,
'FirstNameEn': firstNameEn,
'SecondNameAr': secondNameAr,
'SecondNameEn': secondNameEn,
'ThirdNameAr': thirdNameAr,
'ThirdNameEn': thirdNameEn,
'LastNameAr': lastNameAr,
'LastNameEn': lastNameEn,
'Gender': gender,
'HealthId': healthId,
'IdNumber': idNumber,
'IdType': idType,
'IsHijri': isHijri,
'MaritalStatus': maritalStatus,
'MaritalStatusCode': maritalStatusCode,
'NationalDateOfBirth': nationalDateOfBirth,
'Nationality': nationality,
'NationalityCode': nationalityCode,
'Occupation': occupation,
'PatientStatus': patientStatus,
'PlaceofBirth': placeOfBirth,
'MessageStatus': messageStatus,
'ErrorEndUserMessage': errorEndUserMessage,
'ErrorMessage': errorMessage,
};
}
String get fullNameAr {
List<String> parts = [
firstNameAr ?? '',
secondNameAr ?? '',
(thirdNameAr != null && thirdNameAr != '-') ? thirdNameAr! : '',
lastNameAr ?? '',
].where((s) => s.trim().isNotEmpty && s != '-').toList();
return parts.join(' ').trim();
}
String get fullNameEn {
List<String> parts = [
firstNameEn ?? '',
secondNameEn ?? '',
(thirdNameEn != null && thirdNameEn != '-') ? thirdNameEn! : '',
lastNameEn ?? '',
].where((s) => s.trim().isNotEmpty && s != '-').toList();
return parts.join(' ').trim();
}
}

@ -17,12 +17,12 @@ class ChatCallProvider with ChangeNotifier, DiagnosticableTreeMixin {
MediaStream? _remoteStream;
void initCallListeners() {
chatHubConnection.on("OnCallAcceptedAsync", onCallAcceptedAsync);
chatHubConnection.on("OnIceCandidateAsync", onIceCandidateAsync);
chatHubConnection.on("OnOfferAsync", onOfferAsync);
chatHubConnection.on("OnAnswerOffer", onAnswerOffer);
chatHubConnection.on("OnHangUpAsync", onHangUpAsync);
chatHubConnection.on("OnCallDeclinedAsync", onCallDeclinedAsync);
chatHubConnection?.on("OnCallAcceptedAsync", onCallAcceptedAsync);
chatHubConnection?.on("OnIceCandidateAsync", onIceCandidateAsync);
chatHubConnection?.on("OnOfferAsync", onOfferAsync);
chatHubConnection?.on("OnAnswerOffer", onAnswerOffer);
chatHubConnection?.on("OnHangUpAsync", onHangUpAsync);
chatHubConnection?.on("OnCallDeclinedAsync", onCallDeclinedAsync);
}
//Video Constraints
@ -175,13 +175,13 @@ class ChatCallProvider with ChangeNotifier, DiagnosticableTreeMixin {
args = [targetUserID, data];
//json In Data
}
await chatHubConnection.invoke(invokeMethod, args: args);
await chatHubConnection?.invoke(invokeMethod, args: args);
}
void stopListeners() async {
chatHubConnection.off('OnCallDeclinedAsync');
chatHubConnection.off('OnCallAcceptedAsync');
chatHubConnection.off('OnIceCandidateAsync');
chatHubConnection.off('OnAnswerOffer');
chatHubConnection?.off('OnCallDeclinedAsync');
chatHubConnection?.off('OnCallAcceptedAsync');
chatHubConnection?.off('OnIceCandidateAsync');
chatHubConnection?.off('OnAnswerOffer');
}
}

@ -16,6 +16,8 @@ import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:mohem_flutter_app/classes/consts.dart';
import 'package:mohem_flutter_app/classes/encryption.dart';
import 'package:mohem_flutter_app/classes/utils.dart';
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
import 'package:mohem_flutter_app/widgets/bottom_sheets/confirm_bottom_sheet.dart';
import 'package:mohem_flutter_app/config/routes.dart';
import 'package:mohem_flutter_app/main.dart';
import 'package:mohem_flutter_app/models/chat/chat_user_image_model.dart';
@ -113,16 +115,16 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
Future<void> buildHubConnection() async {
chatHubConnection = await getHubConnection();
await chatHubConnection.start();
await chatHubConnection?.start();
if (kDebugMode) {
logger.i("Hub Conn: Startedddddddd");
}
chatHubConnection.on("OnDeliveredChatUserAsync", onMsgReceived);
chatHubConnection.on("OnGetChatConversationCount", onNewChatConversion);
chatHubConnection?.on("OnDeliveredChatUserAsync", onMsgReceived);
chatHubConnection?.on("OnGetChatConversationCount", onNewChatConversion);
//group On message
chatHubConnection.on("OnDeliveredGroupChatHistoryAsync", onGroupMsgReceived);
chatHubConnection?.on("OnDeliveredGroupChatHistoryAsync", onGroupMsgReceived);
}
Future<HubConnection> getHubConnection() async {
@ -135,16 +137,16 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
}
void registerEvents() {
chatHubConnection.on("OnUpdateUserStatusAsync", changeStatus);
// chatHubConnection.on("OnDeliveredChatUserAsync", onMsgReceived);
chatHubConnection?.on("OnUpdateUserStatusAsync", changeStatus);
// chatHubConnection?.on("OnDeliveredChatUserAsync", onMsgReceived);
chatHubConnection.on("OnSubmitChatAsync", OnSubmitChatAsync);
chatHubConnection.on("OnUserTypingAsync", onUserTyping);
chatHubConnection.on("OnUserCountAsync", userCountAsync);
// chatHubConnection.on("OnUpdateUserChatHistoryWindowsAsync", updateChatHistoryWindow);
chatHubConnection.on("OnGetUserChatHistoryNotDeliveredAsync", chatNotDelivered);
chatHubConnection.on("OnUpdateUserChatHistoryStatusAsync", updateUserChatStatus);
chatHubConnection.on("OnGetGroupUserStatusAsync", getGroupUserStatus);
chatHubConnection?.on("OnSubmitChatAsync", OnSubmitChatAsync);
chatHubConnection?.on("OnUserTypingAsync", onUserTyping);
chatHubConnection?.on("OnUserCountAsync", userCountAsync);
// chatHubConnection?.on("OnUpdateUserChatHistoryWindowsAsync", updateChatHistoryWindow);
chatHubConnection?.on("OnGetUserChatHistoryNotDeliveredAsync", chatNotDelivered);
chatHubConnection?.on("OnUpdateUserChatHistoryStatusAsync", updateUserChatStatus);
chatHubConnection?.on("OnGetGroupUserStatusAsync", getGroupUserStatus);
//
// {"type":1,"target":"","arguments":[[{"id":217869,"userName":"Sultan.Khan","email":"Sultan.Khan@cloudsolutions.com.sa","phone":null,"title":"Sultan.Khan","userStatus":1,"image":null,"unreadMessageCount":0,"userAction":3,"isPin":false,"isFav":false,"isAdmin":false,"rKey":null,"totalCount":0,"isHuaweiDevice":false,"deviceToken":null},{"id":15153,"userName":"Tamer.Fanasheh","email":"Tamer.F@cloudsolutions.com.sa","phone":null,"title":"Tamer Fanasheh","userStatus":2,"image":null,"unreadMessageCount":0,"userAction":3,"isPin":false,"isFav":false,"isAdmin":true,"rKey":null,"totalCount":0,"isHuaweiDevice":false,"deviceToken":null}]]}
@ -183,7 +185,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
}
Future invokeUserChatHistoryNotDeliveredAsync({required int userId}) async {
await chatHubConnection.invoke("GetUserChatHistoryNotDeliveredAsync", args: [userId]);
await chatHubConnection?.invoke("GetUserChatHistoryNotDeliveredAsync", args: [userId]);
return "";
}
@ -252,7 +254,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
void updateUserChatHistoryStatusAsync(List data) {
try {
chatHubConnection.invoke("UpdateUserChatHistoryStatusAsync", args: [data]);
chatHubConnection?.invoke("UpdateUserChatHistoryStatusAsync", args: [data]);
} catch (e) {
throw e;
}
@ -260,7 +262,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
void updateUserChatHistoryOnMsg(List data) {
try {
chatHubConnection.invoke("UpdateUserChatHistoryStatusAsync", args: [data]);
chatHubConnection?.invoke("UpdateUserChatHistoryStatusAsync", args: [data]);
} catch (e) {
throw e;
}
@ -753,7 +755,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
String chatData =
'{"contant":"$msg","contantNo":"$contentNo","chatEventId":$chatEventId,"fileTypeId": $fileTypeId,"currentUserId":${AppState().chatDetails!.response!.id},"chatSource":1,"userChatHistoryLineRequestList":[{"isSeen":false,"isDelivered":false,"targetUserId":$targetUserId,"targetUserStatus":1}],"chatReplyId":$chatReplyId,"conversationId":"$chatCID"}';
await chatHubConnection.invoke("AddChatUserAsync", args: <Object>[json.decode(chatData)]);
await chatHubConnection?.invoke("AddChatUserAsync", args: <Object>[json.decode(chatData)]);
}
//groupChatMessage
@ -829,7 +831,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
String chatData =
'{"contant":"$msg","contantNo":"$contentNo","chatEventId":$chatEventId,"fileTypeId":$fileTypeId,"currentUserId":${AppState().chatDetails!.response!.id},"chatSource":1,"groupId":$targetGroupId,"groupChatHistoryLineRequestList":${json.encode(targetUsers)},"chatReplyId": $chatReplyId,"conversationId":"${uuid.v4()}"}';
await chatHubConnection.invoke("AddGroupChatHistoryAsync", args: <Object>[json.decode(chatData)]);
await chatHubConnection?.invoke("AddGroupChatHistoryAsync", args: <Object>[json.decode(chatData)]);
}
void sendGroupChatMessage(
@ -1450,7 +1452,14 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
pChatHistory?.clear();
uGroups?.clear();
searchGroup?.clear();
chatHubConnection.stop();
try {
chatHubConnection?.stop();
} catch (e) {
if (kDebugMode) {
print("Error stopping chatHubConnection: $e");
}
}
chatHubConnection = null;
AppState().chatDetails = null;
}
}
@ -1582,17 +1591,17 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
}
Future invokeChatCounter({required int userId}) async {
await chatHubConnection.invoke("GetChatCounversationCount", args: [userId]);
await chatHubConnection?.invoke("GetChatCounversationCount", args: [userId]);
return "";
}
void userTypingInvoke({required int currentUser, required int reciptUser}) async {
await chatHubConnection.invoke("UserTypingAsync", args: [reciptUser, currentUser]);
await chatHubConnection?.invoke("UserTypingAsync", args: [reciptUser, currentUser]);
}
void groupTypingInvoke({required GroupResponse groupDetails, required int groupId}) async {
var data = json.decode(json.encode(groupDetails.groupUserList));
await chatHubConnection.invoke("GroupTypingAsync", args: ["${groupDetails.adminUser!.userName}", data, groupId]);
await chatHubConnection?.invoke("GroupTypingAsync", args: ["${groupDetails.adminUser!.userName}", data, groupId]);
}
//////// Audio Recoding Work ////////////////////
@ -1631,20 +1640,22 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
void startRecoding(BuildContext context) async {
await Permission.microphone.request().then((PermissionStatus status) {
if (status.isPermanentlyDenied) {
Utils.confirmDialog(
ConfirmBottomSheet.show(
context,
"The app needs microphone access to be able to record audio.",
onTap: () {
Navigator.of(context).pop();
message: LocaleKeys.microphoneAccessRequired.tr(),
confirmText: LocaleKeys.openSettings.tr(),
showCancelButton: false,
onConfirm: () {
openAppSettings();
},
);
} else if (status.isDenied) {
Utils.confirmDialog(
ConfirmBottomSheet.show(
context,
"The app needs microphone access to be able to record audio.",
onTap: () {
Navigator.of(context).pop();
message: LocaleKeys.microphoneAccessRequired.tr(),
confirmText: LocaleKeys.openSettings.tr(),
showCancelButton: false,
onConfirm: () {
openAppSettings();
},
);

@ -27,7 +27,6 @@ import 'package:mohem_flutter_app/models/greetings/greeting_card_model.dart';
import 'package:mohem_flutter_app/models/itg/itg_response_model.dart';
import 'package:mohem_flutter_app/models/offers_and_discounts/get_offers_list.dart';
import 'package:mohem_flutter_app/models/sso_auth_model.dart';
import 'package:mohem_flutter_app/widgets/dialogs/confirm_dialog.dart';
/// Mix-in [DiagnosticableTreeMixin] to have access to [debugFillProperties] for the devtool
// ignore: prefer_mixin
@ -190,15 +189,11 @@ class DashboardProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
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);
},
),
Utils.showToast(
err,
onComplete: () {
Navigator.pushNamedAndRemoveUntil(context, AppRoutes.login, (Route<dynamic> route) => false);
},
);
});
}
@ -230,9 +225,9 @@ class DashboardProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
notifyListeners();
} catch (ex) {
isLeaveTicketBalanceLoading = false;
logger.wtf(ex);
notifyListeners();
Utils.handleException(ex, context, null);
// logger.wtf(ex);
// notifyListeners();
// Utils.handleException(ex, context, null);
}
}

@ -24,7 +24,7 @@ import 'package:mohem_flutter_app/widgets/app_bar_widget.dart';
import 'package:mohem_flutter_app/widgets/bottom_sheet.dart';
import 'package:mohem_flutter_app/widgets/bottom_sheets/search_employee_bottom_sheet.dart';
import 'package:mohem_flutter_app/widgets/button/default_button.dart';
import 'package:mohem_flutter_app/widgets/dialogs/confirm_dialog.dart';
import 'package:mohem_flutter_app/widgets/bottom_sheets/confirm_bottom_sheet.dart';
import 'package:mohem_flutter_app/widgets/dynamic_forms/dynamic_textfield_widget.dart';
import 'package:mohem_flutter_app/widgets/item_detail_view_widget.dart';
@ -216,7 +216,7 @@ class _AddVacationRuleScreenState extends State<AddVacationRuleScreen> {
CreateVacationRuleList? createVacationRuleList = await VacationRuleApiClient().createVacationRule(DateUtil.convertDateToStringLocation(startTime), DateUtil.convertDateToStringLocation(endTime!),
selectedItemType!.iTEMTYPE!, selectedItemTypeNotification!.nOTIFICATIONNAME!, message, getPAction(), selectedReplacementEmployee!.userName!, respondAttributeList);
Utils.hideLoading(context);
Utils.showToast("Vacation rule added");
Utils.showToast(LocaleKeys.vacationRuleAdded.tr(), isSuccess: true);
Navigator.popUntil(context, ModalRoute.withName(AppRoutes.dashboard));
} catch (ex) {
Utils.hideLoading(context);
@ -490,15 +490,14 @@ class _AddVacationRuleScreenState extends State<AddVacationRuleScreen> {
}
}
showDialog(
context: context,
builder: (cxt) => ConfirmDialog(
message: LocaleKeys.areYouSureYouWantToSubmit.tr(),
onTap: () {
Navigator.pop(context);
createVacationRule(list);
},
),
ConfirmBottomSheet.show(
context,
message: LocaleKeys.areYouSureYouWantToSubmit.tr(),
confirmText: LocaleKeys.yes.tr(),
cancelText: LocaleKeys.no.tr(),
onConfirm: () {
createVacationRule(list);
},
);
},
).insideContainer,

@ -27,14 +27,14 @@ class ChatHome extends StatefulWidget {
class _ChatHomeState extends State<ChatHome> {
int tabIndex = 0;
PageController controller = PageController();
late ChatProviderModel data;
late ChatProviderModel data;
@override
void initState() {
super.initState();
if (chatHubConnection.state == HubConnectionState.Connected) {
data = Provider.of<ChatProviderModel>(context, listen: false);
data = Provider.of<ChatProviderModel>(context, listen: false);
if (chatHubConnection?.state == HubConnectionState.Connected) {
data.registerEvents();
}
}
@ -46,7 +46,7 @@ class _ChatHomeState extends State<ChatHome> {
}
void fetchAgain() {
if (chatHubConnection.state != HubConnectionState.Connected) {
if (chatHubConnection?.state != HubConnectionState.Connected) {
data.getUserAutoLoginToken().whenComplete(() async {
await data.buildHubConnection();
data.getUserRecentChats();

@ -19,6 +19,7 @@ import 'package:mohem_flutter_app/ui/chat/create_group.dart';
import 'package:mohem_flutter_app/ui/chat/group_chat_detaied_screen.dart';
import 'package:mohem_flutter_app/ui/chat/manage_group.dart';
import 'package:mohem_flutter_app/widgets/bottom_sheet.dart';
import 'package:mohem_flutter_app/widgets/bottom_sheets/confirm_bottom_sheet.dart';
import 'package:mohem_flutter_app/widgets/bottom_sheets/search_employee_bottom_sheet.dart';
import 'package:mohem_flutter_app/widgets/shimmer/dashboard_shimmer_widget.dart';
import 'package:provider/provider.dart';
@ -269,11 +270,13 @@ void goToSelected(GroupResponse? groupDetails, ChatProviderModel m, String value
void deleteGroup(
GroupResponse? groupDetails, ChatProviderModel m, BuildContext context) {
groupDetails!.groupUserList;
Utils.confirmDialog(
ConfirmBottomSheet.show(
context,
LocaleKeys.areYouSureWantTodelete.tr(),
onTap: () {
Navigator.pop(context);
message: LocaleKeys.areYouSureWantTodelete.tr(),
confirmText: LocaleKeys.yes.tr(),
cancelText: LocaleKeys.no.tr(),
isDestructive: true,
onConfirm: () {
m.deleteGroup(groupDetails);
},
);

@ -1,15 +1,19 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:mohem_flutter_app/classes/colors.dart';
import 'package:mohem_flutter_app/classes/utils.dart';
import 'package:mohem_flutter_app/extensions/int_extensions.dart';
import 'package:mohem_flutter_app/extensions/string_extensions.dart';
import 'package:mohem_flutter_app/extensions/widget_extensions.dart';
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
import 'package:mohem_flutter_app/models/generic_response_model.dart';
class BusinessCardDialog extends StatelessWidget {
const BusinessCardDialog({Key? key}) : super(key: key);
final String? qrBase64;
final EmpBusinessCardInfoModel? employeeInfo;
const BusinessCardDialog({Key? key, this.qrBase64, this.employeeInfo})
: super(key: key);
@override
Widget build(BuildContext context) {
@ -17,65 +21,78 @@ class BusinessCardDialog extends StatelessWidget {
quarterTurns: 1,
child: AspectRatio(
aspectRatio: 3.5 / 2,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Image.asset(
"assets/images/logos/bn_cloud_soloution.jpg",
width: 94,
height: 91.15,
).paddingOnly(top: 4),
],
),
),
Image.memory(
Utils.getPostBytes(qrBase64 ?? ""),
width: 129,
height: 129,
),
],
).paddingOnly(top: 26, left: 8.5),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
(employeeInfo?.fullName ?? "")
.toText23(isBold: true)
.paddingOnly(top: 15),
(employeeInfo?.pPositionTitle ?? "").toText17(
color: MyColors.grey57Color,
),
30.height,
Row(
children: [
const Icon(
Icons.email,
size: 17.5,
color: MyColors.grey3AColor,
).paddingOnly(right: 11.5),
("${LocaleKeys.email.tr()}: ").toText18(
color: MyColors.grey57Color,
),
(employeeInfo?.pEmailAddress ?? "").toText18(),
],
),
Row(
children: [
Image.asset(
"assets/images/logos/bn_cloud_soloution.jpg",
width: 94,
height: 91.15,
).paddingOnly(top: 4),
const Icon(
Icons.call,
size: 17.5,
color: MyColors.grey3AColor,
).paddingOnly(right: 11.5),
("${LocaleKeys.mobile.tr()}: ").toText18(
color: MyColors.grey57Color,
),
(employeeInfo?.pMobilePhoneNumber ?? "").toText18(),
],
),
),
Image.memory(
Utils.getPostBytes(AppState().memberInformationList!.businessCardQR ?? ""),
width: 129,
height: 129,
),
],
).paddingOnly(top: 26, left: 8.5),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
(AppState().memberInformationList!.eMPLOYEENAME ?? "").toText23(isBold: true).paddingOnly(top: 15),
(AppState().memberInformationList!.getPositionName() ?? "").toText17(
color: MyColors.grey57Color,
),
30.height,
// (AppState().memberInformationList!.eMPLOYEENUMBER ?? "").toText13(),
Row(
children: [
const Icon(Icons.email, size: 17.5, color: MyColors.grey3AColor).paddingOnly(right: 11.5),
("${LocaleKeys.email.tr()}: ").toText18(color: MyColors.grey57Color),
(AppState().memberInformationList!.eMPLOYEEEMAILADDRESS ?? "").toText18(),
],
),
Row(
children: [
const Icon(Icons.call, size: 17.5, color: MyColors.grey3AColor).paddingOnly(right: 11.5),
("${LocaleKeys.mobile.tr()}: ").toText18(color: MyColors.grey57Color),
(AppState().memberInformationList!.mobileNumberWithZipCode ?? "").toText18(),
],
),
// 1.height,
// (AppState().memberInformationList!.jOBNAME ?? "").toText13(),
],
),
],
).paddingOnly(right: 13, left: 32, bottom: 28),
],
),
],
).paddingOnly(right: 13, left: 32, bottom: 28),
),
),
);
}

@ -87,23 +87,11 @@ class _EtqanOvrCreateRequestState extends State<EtqanOvrCreateRequest> {
DefaultButton(LocaleKeys.submit.tr(), () async {
provider.setDescription(_descriptionController.text);
if (provider.selectedProject == null) {
Utils.showErrorDialog(
context: context,
message: LocaleKeys.pleaseSelectProject.tr(),
onOkTapped: () {
Navigator.of(context).pop();
},
);
Utils.showToast(LocaleKeys.pleaseSelectProject.tr());
return;
}
if (!provider.isDescriptionValid()) {
Utils.showErrorDialog(
context: context,
message: LocaleKeys.pleaseEnterDescription.tr(),
onOkTapped: () {
Navigator.of(context).pop();
},
);
Utils.showToast(LocaleKeys.pleaseEnterDescription.tr());
return;
}
Map<String, dynamic>? ticketInfo = await provider.submitRequest(context);
@ -112,15 +100,10 @@ class _EtqanOvrCreateRequestState extends State<EtqanOvrCreateRequest> {
String ticketId = ticketInfo['id'] ?? '';
// String successMessage = '${LocaleKeys.requestCreatedSuccessfully.tr()}\n\n${LocaleKeys.ticketNumber.tr()}: $ticketNumber\n${LocaleKeys.ticketId.tr()}: $ticketId';
String successMessage = '${LocaleKeys.requestCreatedSuccessfully.tr()}\n\n${LocaleKeys.ticketNumber.tr()}: $ticketNumber';
Utils.showErrorDialog(
context: context,
message: successMessage,
onOkTapped: () {
Navigator.pop(context);
Navigator.pop(context);
// Navigator.popAndPushNamed(context, AppRoutes.etqanOvr);
},
onCloseTap: () {
Utils.showToast(
successMessage,
isSuccess: true,
onComplete: () {
Navigator.pop(context);
Navigator.pop(context);
// Navigator.popAndPushNamed(context, AppRoutes.etqanOvr);

@ -30,12 +30,13 @@ import 'package:mohem_flutter_app/widgets/bottom_sheet.dart';
import 'package:mohem_flutter_app/widgets/mark_attendance_widget.dart';
import 'package:mohem_flutter_app/widgets/shimmer/dashboard_shimmer_widget.dart';
import 'package:mohem_flutter_app/widgets/shimmer/offers_shimmer_widget.dart';
import 'package:provider/provider.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import 'package:signalr_netcore/signalr_client.dart';
import 'package:url_launcher/url_launcher.dart';
late HubConnection chatHubConnection;
HubConnection? chatHubConnection;
class DashboardScreen extends StatefulWidget {
DashboardScreen({Key? key}) : super(key: key);
@ -56,12 +57,13 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
int currentIndex = 0;
bool isDisplayMazaya = false;
bool isDisplayMazaya = true;
@override
void initState() {
WidgetsBinding.instance.addObserver(this);
super.initState();
isDisplayMazaya = checkIfPrivilegedForMazaya();
cProvider = Provider.of<ChatProviderModel>(context, listen: false);
scheduleMicrotask(() {
data = Provider.of<DashboardProviderModel>(context, listen: false);
@ -93,7 +95,7 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
WidgetsBinding.instance.removeObserver(this);
super.dispose();
if (!cProvider.disbaleChatForThisUser) {
chatHubConnection.stop();
chatHubConnection?.stop();
}
}
@ -111,7 +113,7 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
} else {
cProvider.buildHubConnection();
Future.delayed(const Duration(seconds: 2), () {
cProvider.invokeChatCounter(userId: AppState().chatDetails!.response!.id!);
if (AppState().chatDetails != null) cProvider.invokeChatCounter(userId: AppState().chatDetails!.response!.id!);
});
}
}
@ -120,16 +122,16 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
Future<void> checkHubCon() async {
// chatHubConnection = await context.read<ChatProviderModel>().getHubConnection();
if (chatHubConnection.state == HubConnectionState.Connected) {
await chatHubConnection.stop();
await chatHubConnection.start();
} else if (chatHubConnection.state != HubConnectionState.Connected) {
await chatHubConnection.start();
if (chatHubConnection?.state == HubConnectionState.Connected) {
await chatHubConnection?.stop();
await chatHubConnection?.start();
} else if (chatHubConnection != null && chatHubConnection!.state != HubConnectionState.Connected) {
await chatHubConnection?.start();
}
}
void gotoChat(BuildContext context) async {
if (chatHubConnection.state == HubConnectionState.Connected) {
if (chatHubConnection?.state == HubConnectionState.Connected) {
Utils.hideLoading(context);
Navigator.pushNamed(context, AppRoutes.chat);
String isAppOpendByChat = await Utils.getStringFromPrefs("isAppOpendByChat");
@ -254,6 +256,7 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
@override
Widget build(BuildContext context) {
isDisplayMazaya = checkIfPrivilegedForMazaya();
return SafeArea(
bottom: Platform.isAndroid ? true : false,
top: false,
@ -290,9 +293,7 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
});
},
),
Image
.asset("assets/images/logos/main_mohemm_logo.png", width: 134, height: 28)
.expanded,
Image.asset("assets/images/logos/main_mohemm_logo.png", width: 134, height: 28).expanded,
SvgPicture.asset("assets/images/announcements.svg", matchTextDirection: true).onPress(() async {
await Navigator.pushNamed(context, AppRoutes.announcements);
}),
@ -453,232 +454,120 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
return const SizedBox();
}
},
).paddingOnly(bottom: 24, left: 24, right: 24),
).paddingOnly(bottom: 18, left: 24, right: 24),
// eventActivityWidget(context),
if (isDisplayMazaya) ...[
if (isDisplayMazaya) ...<Widget>[
InkWell(
borderRadius: BorderRadius.circular(20),
onTap: () {
Navigator.pushNamed(context, AppRoutes.mazayaDiscover);
},
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: AspectRatio(
aspectRatio: 386 / 121.37,
child: SvgPicture.asset(
AppState().isArabic(context) ? "assets/images/mazaya_banner_ar.svg" : "assets/images/mazaya_banner_en.svg",
width: double.infinity,
fit: BoxFit.fill,
),
),
),
).paddingOnly(left: 21, right: 21, top: 0, bottom: 21),
] else ...<Widget>[
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Directionality(
textDirection: AppState().isArabic(context) ? ui.TextDirection.rtl : ui.TextDirection.ltr,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
gradient: const LinearGradient(colors: [Color(0xFF91C481), Color(0xFF7CCED7)], begin: Alignment.centerLeft, end: Alignment.centerRight),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
LocaleKeys.offers.tr().toText12(),
Row(
children: [
LocaleKeys.discounts.tr().toText24(isBold: true),
6.width,
Container(
padding: const EdgeInsets.only(left: 8, right: 8),
decoration: BoxDecoration(color: MyColors.yellowColor, borderRadius: BorderRadius.circular(10)),
child: LocaleKeys.newString.tr().toText10(isBold: true),
),
],
),
],
),
),
child: Padding(
padding: const EdgeInsets.all(3.0), // This creates the border width
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(17), // Slightly less than outer radius
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: 4,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
LocaleKeys.viewAllOffers.tr().toText12(isUnderLine: true).onPress(() {
Navigator.pushNamed(context, AppRoutes.offersAndDiscounts);
}),
],
).paddingOnly(left: 21, right: 21),
Consumer<DashboardProviderModel>(
builder: (BuildContext context, DashboardProviderModel model, Widget? child) {
return SizedBox(
height: 103 + 33,
child: ListView.separated(
shrinkWrap: true,
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.only(left: 21, right: 21, top: 13),
scrollDirection: Axis.horizontal,
itemBuilder: (BuildContext cxt, int index) {
return model.isOffersLoading
? const OffersShimmerWidget()
: data.getOffersList.isNotEmpty
? InkWell(
onTap: () {
navigateToDetails(data.getOffersList[index]);
},
child: SizedBox(
width: 73,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
flex: 2,
child: RichText(
text:
AppState().isArabic(context)
? TextSpan(
children: [
TextSpan(
text: 'اطلع على مميزات',
style: TextStyle(
fontSize: 16,
letterSpacing: -0.2,
fontFamily: AppState().isArabic(context) ? 'Cairo' : 'Poppins',
fontWeight: FontWeight.w700,
height: 24 / 16,
color: const Color(0xFF5D5E5E),
),
),
TextSpan(
text: ' مزايا',
style: TextStyle(
fontSize: 16,
fontFamily: AppState().isArabic(context) ? 'Cairo' : 'Poppins',
fontWeight: FontWeight.w700,
letterSpacing: -0.2,
height: 24 / 16,
color: MyColors.mazayaRedColor, // Use your MAZAYA red color here if defined, e.g. MyColors.mazayaRed
),
),
],
)
: TextSpan(
children: [
TextSpan(
text: LocaleKeys.explore.tr() + ' ',
style: const TextStyle(
fontSize: 16,
letterSpacing: -0.2,
fontFamily: 'Poppins',
fontWeight: FontWeight.w700,
height: 24 / 16,
color: Color(0xFF5D5E5E),
),
),
TextSpan(
text: LocaleKeys.mazaya.tr(),
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
fontFamily: 'Poppins',
letterSpacing: -0.2,
height: 24 / 16,
color: MyColors.mazayaRedColor, // Use your MAZAYA red color here if defined, e.g. MyColors.mazayaRed
),
),
TextSpan(
text: ' ' + LocaleKeys.benefits.tr(),
style: const TextStyle(
fontSize: 16,
letterSpacing: -0.2,
fontFamily: 'Poppins',
fontWeight: FontWeight.w700,
height: 24 / 16,
color: Color(0xFF5D5E5E),
),
),
],
),
Container(
width: 73,
height: 73,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(100)),
border: Border.all(color: MyColors.lightGreyE3Color, width: 1),
),
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(50)),
child: Hero(
tag: "ItemImage" + data.getOffersList[index].offersDiscountId.toString(),
transitionOnUserGestures: true,
child: Image.network(data.getOffersList[index].logo ?? "", fit: BoxFit.contain),
),
),
),
const Expanded(flex: 1, child: SizedBox()),
4.height,
Expanded(
child:
AppState().isArabic(context)
? data.getOffersList[index].titleAr!.toText12(isCenter: true, maxLine: 1)
: data.getOffersList[index].titleEn!.toText12(isCenter: true, maxLine: 1),
),
],
),
const SizedBox(height: 8),
LocaleKeys.mazayaDesc.tr().toText11(color: const Color(0xFF5D5E5E)),
],
),
),
Expanded(
flex: 2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: [
SvgPicture.asset("assets/icons/mazaya_brand.svg", width: 90, height: 47),
const SizedBox(height: 28),
LocaleKeys.viewallofferMazaya.tr().toText12(isUnderLine: true, color: const Color(0xFF3B3D4A)).onPress(() {
Navigator.pushNamed(context, AppRoutes.offersAndDiscounts);
}),
],
),
),
],
).paddingOnly(left: 21, right: 21, top: 14, bottom: 14),
),
)
: Container();
},
separatorBuilder: (BuildContext cxt, int index) => 8.width,
itemCount: model.isOffersLoading ? 9 : (data.getOffersList.length > 9 ? 9 : data.getOffersList.length),
),
),
).paddingOnly(left: 21, right: 21, top: 0, bottom: 21),
);
},
),
],
),
],
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
LocaleKeys.offers.tr().toText12(),
Row(
children: [
LocaleKeys.discounts.tr().toText24(isBold: true),
6.width,
Container(
padding: const EdgeInsets.only(left: 8, right: 8),
decoration: BoxDecoration(color: MyColors.yellowColor, borderRadius: BorderRadius.circular(10)),
child: LocaleKeys.newString.tr().toText10(isBold: true),
),
],
),
],
),
),
LocaleKeys.viewAllOffers.tr().toText12(isUnderLine: true).onPress(() {
Navigator.pushNamed(context, AppRoutes.offersAndDiscounts);
}),
],
).paddingOnly(left: 21, right: 21),
Consumer<DashboardProviderModel>(
builder: (BuildContext context, DashboardProviderModel model, Widget? child) {
return SizedBox(
height: 103 + 33,
child: ListView.separated(
shrinkWrap: true,
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.only(left: 21, right: 21, top: 13),
scrollDirection: Axis.horizontal,
itemBuilder: (BuildContext cxt, int index) {
return model.isOffersLoading
? const OffersShimmerWidget()
: InkWell(
onTap: () {
navigateToDetails(data.getOffersList[index]);
},
child: SizedBox(
width: 73,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: 73,
height: 73,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(100)),
border: Border.all(color: MyColors.lightGreyE3Color, width: 1),
),
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(50)),
child: Hero(
tag: "ItemImage" + data.getOffersList[index].offersDiscountId.toString()!,
transitionOnUserGestures: true,
child: Image.network(data.getOffersList[index].logo ?? "", fit: BoxFit.contain),
),
),
),
4.height,
Expanded(
child:
AppState().isArabic(context)
? data.getOffersList[index].titleAr!.toText12(isCenter: true, maxLine: 1)
: data.getOffersList[index].titleEn!.toText12(isCenter: true, maxLine: 1),
),
],
),
),
);
},
separatorBuilder: (BuildContext cxt, int index) => 8.width,
itemCount: 9,
),
);
},
),
],
),
Container(
width: double.infinity,
padding: const EdgeInsets.only(top: 31),
@ -691,9 +580,7 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
mainAxisSize: MainAxisSize.min,
children: [
ServicesWidget(),
context
.watch<MarathonProvider>()
.isLoading ? const MarathonBannerShimmer().paddingAll(20) : const MarathonBanner().paddingOnly(left: 21, right: 21, bottom: 8, top: 8),
context.watch<MarathonProvider>().isLoading ? const MarathonBannerShimmer().paddingAll(20) : const MarathonBanner().paddingOnly(left: 21, right: 21, bottom: 8, top: 8),
// context.watch<MarathonProvider>().isTutorialLoading
// ? const MarathonBannerShimmer().paddingAll(20)
// : Container(
@ -867,16 +754,9 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
}
Widget eventActivityWidget(BuildContext context) {
return (context
.watch<DashboardProviderModel>()
.isEventLoadingLoading)
return (context.watch<DashboardProviderModel>().isEventLoadingLoading)
? const MarathonBannerShimmer().paddingOnly(left: 21, right: 21, bottom: 21, top: 0)
: (context
.watch<DashboardProviderModel>()
.eventActivity != null && context
.watch<DashboardProviderModel>()
.eventActivity!
.isActive == true)
: (context.watch<DashboardProviderModel>().eventActivity != null && context.watch<DashboardProviderModel>().eventActivity!.isActive == true)
? const EventActivityBanner().paddingOnly(left: 21, right: 21, bottom: 21, top: 0)
: const SizedBox();
}
@ -909,4 +789,14 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
}
return false;
}
bool checkIfPrivilegedForMazaya() {
if (AppState().privilegeListModel == null) return false;
for (PrivilegeListModel element in AppState().privilegeListModel!) {
if (element.serviceName?.toLowerCase() == "mazayaeligibility") {
return (element.previlege ?? false) || (element.previlegeHMG ?? false);
}
}
return false;
}
}

@ -41,7 +41,7 @@ class _ChangeItgAdPasswordScreenState extends State<ChangeItgAdPasswordScreen> {
GenericResponseModel response = await LoginApiClient().changePasswordFromActiveDirectorySession(password.text, AppState().memberInformationList!.eMPLOYEEEMAILADDRESS!);
Utils.hideLoading(context);
if ((response.messageStatus ?? 0) == 1) {
Utils.showToast(LocaleKeys.passwordChangedSuccessfully.tr());
Utils.showToast(LocaleKeys.passwordChangedSuccessfully.tr(), isSuccess: true);
Navigator.pop(context);
}
} catch (ex) {

@ -222,7 +222,7 @@ class _SurveyScreenState extends State<SurveyScreen> {
Utils.hideLoading(context);
if (res!.mohemmItgResponseItem!.statusCode == 200) {
Utils.showToast("Survey has been submitted successfully");
Utils.showToast("Survey has been submitted successfully", isSuccess: true);
Navigator.pop(context);
} else {
Utils.showToast(res.mohemmItgResponseItem!.message.toString());

@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:mohem_flutter_app/api/profile_api_client.dart';
import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:mohem_flutter_app/classes/colors.dart';
import 'package:mohem_flutter_app/classes/utils.dart';
@ -11,6 +12,7 @@ import 'package:mohem_flutter_app/extensions/string_extensions.dart';
import 'package:mohem_flutter_app/extensions/widget_extensions.dart';
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
import 'package:mohem_flutter_app/models/dashboard/drawer_menu_item_model.dart';
import 'package:mohem_flutter_app/models/generic_response_model.dart';
import 'package:mohem_flutter_app/provider/chat_provider_model.dart';
import 'package:mohem_flutter_app/provider/dashboard_provider_model.dart';
import 'package:mohem_flutter_app/ui/dialogs/id/business_card_dialog.dart';
@ -37,6 +39,22 @@ class _AppDrawerState extends State<AppDrawer> {
chatData = Provider.of<ChatProviderModel>(context, listen: false);
}
Future<void> _showBusinessCard(BuildContext context) async {
Utils.showLoading(context);
try {
GenericResponseModel? response = await ProfileApiClient().getEmployeeBusinessCardInfo();
Utils.hideLoading(context);
showMDialog(
context,
child: BusinessCardDialog(qrBase64: response?.businessCardQROutside, employeeInfo: response?.empBusinessCardInfoModel),
isBusniessCard: true,
);
} catch (ex) {
Utils.hideLoading(context);
Utils.handleException(ex, context, null);
}
}
@override
Widget build(BuildContext context) {
if (drawerMenuItemList.isEmpty) {
@ -119,7 +137,7 @@ class _AppDrawerState extends State<AppDrawer> {
}),
menuItem("assets/images/drawer/employee_id.svg", LocaleKeys.employeeDigitalID.tr(), "", closeDrawer: false, onPress: () => showMDialog(context, child: EmployeeDigitialIdDialog())),
if (AppState().businessCardPrivilege)
menuItem("assets/images/drawer/view_business_card.svg", LocaleKeys.viewBusinessCard.tr(), "", closeDrawer: false, onPress: () => showMDialog(context, child: BusinessCardDialog(), isBusniessCard: true)),
menuItem("assets/images/drawer/view_business_card.svg", LocaleKeys.viewBusinessCard.tr(), "", closeDrawer: false, onPress: () => _showBusinessCard(context)),
menuItem("assets/images/drawer/logout.svg", LocaleKeys.logout.tr(), "", color: MyColors.redA3Color, closeDrawer: false, onPress: (){Utils.performLogout(context, chatData);}),
// menuItem("assets/images/drawer/logout.svg", LocaleKeys.logout.tr(), "", color: MyColors.redA3Color, closeDrawer: false, onPress: () {Navigator.pushNamed(context, AppRoutes.survey,);
],

@ -111,7 +111,7 @@ class ServicesWidget extends StatelessWidget {
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(child: data.homeMenus![parentIndex].menuEntiesList[index].prompt!.toText11(isBold: true)),
Expanded(child: data.homeMenus![parentIndex].menuEntiesList[index].prompt!.toText11(isBold: true, maxLine: 3)),
RotatedBox(quarterTurns: AppState().isArabic(context) ? 2 : 4, child: SvgPicture.asset("assets/images/arrow_next.svg").paddingOnly(bottom: 4)),
],
),

@ -39,7 +39,7 @@ class _ChangePasswordScreenState extends State<ChangePasswordScreen> {
try {
var genericResponseModel = await LoginApiClient().changePasswordFromActiveSession(oldPassword.text, password.text, confirmPassword.text);
Utils.hideLoading(context);
Utils.showToast(LocaleKeys.passwordChangedSuccessfully.tr());
Utils.showToast(LocaleKeys.passwordChangedSuccessfully.tr(), isSuccess: true);
Navigator.pop(context);
} catch (ex) {
Utils.hideLoading(context);

@ -50,29 +50,27 @@ class _ForgotPasswordScreenState extends State<ForgotPasswordScreen> {
context,
1,
int.tryParse(_basicMemberInformation?.pMOBILENUMBER ?? ""),
(value,TextEditingController _pinPutController) async {
(value, TextEditingController _pinPutController) async {
Utils.showLoading(context);
try{
GenericResponseModel? genericResponseModel = await LoginApiClient().checkPublicActivationCode(value, employeeId.text);
if (genericResponseModel?.errorMessage != null) {
Utils.showToast(genericResponseModel?.errorMessage ?? "");
return;
}
Utils.hideLoading(context);
await Navigator.pushNamed(context, AppRoutes.newPassword, arguments: employeeId.text);
Navigator.pop(context);
Navigator.pop(context);
}catch(ex){
print(ex);
_pinPutController.clear();
otpFieldClear.value = "";
Utils.hideLoading(context);
Utils.handleException(ex, context, null);
}
},
() => {
Navigator.pop(context),
try {
GenericResponseModel? genericResponseModel = await LoginApiClient().checkPublicActivationCode(value, employeeId.text);
if (genericResponseModel?.errorMessage != null) {
Utils.showToast(genericResponseModel?.errorMessage ?? "");
return;
}
Utils.hideLoading(context);
await Navigator.pushNamed(context, AppRoutes.newPassword, arguments: employeeId.text);
Navigator.pop(context);
Navigator.pop(context);
} catch (ex) {
print(ex);
_pinPutController.clear();
otpFieldClear.value = "";
Utils.hideLoading(context);
Utils.handleException(ex, context, null);
}
},
() => {Navigator.pop(context)},
onResendCode: () {
performForgotPassword();
},
@ -87,13 +85,7 @@ class _ForgotPasswordScreenState extends State<ForgotPasswordScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.transparent,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios, color: MyColors.darkIconColor),
onPressed: () => Navigator.pop(context),
),
),
appBar: AppBar(backgroundColor: Colors.transparent, leading: IconButton(icon: const Icon(Icons.arrow_back_ios, color: MyColors.darkIconColor), onPressed: () => Navigator.pop(context))),
body: Column(
children: [
//const SizedBox(height: 23),
@ -122,19 +114,19 @@ class _ForgotPasswordScreenState extends State<ForgotPasswordScreen> {
),
],
),
)
),
],
),
),
),
DefaultButton(
LocaleKeys.changePassword.tr(),
employeeId.text.isEmpty
? null
: () async {
performForgotPassword();
})
.insideContainer
LocaleKeys.changePassword.tr(),
employeeId.text.isEmpty
? null
: () async {
performForgotPassword();
},
).insideContainer,
],
),
);

File diff suppressed because it is too large Load Diff

@ -41,7 +41,7 @@ class _NewPasswordScreenState extends State<NewPasswordScreen> {
try {
var genericResponseModel = await LoginApiClient().changePasswordForget(AppState().getForgetPasswordTokenID ?? "", password.text, confirmPassword.text, userName);
Utils.hideLoading(context);
Utils.showToast(LocaleKeys.passwordChangedSuccessfully.tr());
Utils.showToast(LocaleKeys.passwordChangedSuccessfully.tr(), isSuccess: true);
Navigator.pop(context);
} catch (ex) {
print(ex);

@ -8,8 +8,10 @@ import 'package:local_auth/local_auth.dart';
import 'package:local_auth_darwin/local_auth_darwin.dart';
import 'package:local_auth_android/local_auth_android.dart';
import 'package:mohem_flutter_app/api/login_api_client.dart';
import 'package:mohem_flutter_app/api/profile_api_client.dart';
import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:mohem_flutter_app/classes/colors.dart';
import 'package:mohem_flutter_app/classes/consts.dart';
import 'package:mohem_flutter_app/classes/date_uitl.dart';
import 'package:mohem_flutter_app/classes/utils.dart';
import 'package:mohem_flutter_app/config/routes.dart';
@ -58,10 +60,9 @@ class _VerifyLastLoginScreenState extends State<VerifyLastLoginScreen> {
@override
Widget build(BuildContext context) {
if (ModalRoute.of(context)!.settings.arguments != null) {
mobileLoginInfoListModel ??= ModalRoute.of(context)!.settings.arguments as GetMobileLoginInfoListModel;
// String empName = AppState().isArabic(context) ? AppState().memberInformationList!.eMPLOYEEDISPLAYNAMEAr! : AppState().memberInformationList!.eMPLOYEEDISPLAYNAMEEn!;
String empName = mobileLoginInfoListModel!.employeeName!;
Object? args = ModalRoute.of(context)?.settings.arguments;
if (args is GetMobileLoginInfoListModel) {
mobileLoginInfoListModel ??= args;
}
return Scaffold(
@ -69,14 +70,22 @@ class _VerifyLastLoginScreenState extends State<VerifyLastLoginScreen> {
surfaceTintColor: Colors.transparent,
backgroundColor: Colors.transparent,
automaticallyImplyLeading: false,
title: (mobileLoginInfoListModel?.businessCardPrivilege ?? false)
? LocaleKeys.viewBusinessCard.tr().toText12(color: MyColors.textMixColor, isUnderLine: true).onPress(() {
showMDialog(context, child: const BusinessCardDialog());
})
: Container(),
title:
(mobileLoginInfoListModel?.businessCardPrivilege ?? false)
? LocaleKeys.viewBusinessCard.tr().toText12(color: MyColors.textMixColor, isUnderLine: true).onPress(() async {
Utils.showLoading(context);
try {
GenericResponseModel? response = await ProfileApiClient().getEmployeeBusinessCardInfo();
Utils.hideLoading(context);
showMDialog(context, child: BusinessCardDialog(qrBase64: response?.businessCardQROutside, employeeInfo: response?.empBusinessCardInfoModel));
} catch (ex) {
Utils.hideLoading(context);
Utils.handleException(ex, context, null);
}
})
: Container(),
actions: [
Center(
child: LocaleKeys.employeeDigitalID.tr().toText12(color: MyColors.textMixColor, isUnderLine: true).onPress(() {
showMDialog(context, child: EmployeeDigitialIdDialog());
}),
@ -112,16 +121,19 @@ class _VerifyLastLoginScreenState extends State<VerifyLastLoginScreen> {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [LocaleKeys.lastLoginDetails.tr().toText16(), DateUtil.formatDateToDate(DateUtil.convertStringToDate(mobileLoginInfoListModel?.editedOn ?? null), false).toText12()],
children: [
LocaleKeys.lastLoginDetails.tr().toText16(),
mobileLoginInfoListModel != null ? DateUtil.formatDateToDate(DateUtil.convertStringToDate(mobileLoginInfoListModel?.editedOn ?? null), false).toText12() : const SizedBox(),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
LocaleKeys.verificationType.tr().toText10(color: MyColors.grey57Color),
getVerificationType(mobileLoginInfoListModel!.loginType!).toText12(),
getVerificationType(mobileLoginInfoListModel?.loginType).toText12(),
const Expanded(child: SizedBox()),
DateUtil.formatDateToTime(DateUtil.convertStringToDate(mobileLoginInfoListModel?.editedOn)).toText12(),
DateUtil.formatDateToTime(DateUtil.convertStringToDate(mobileLoginInfoListModel?.editedOn ?? DateTime.now().toString())).toText12(),
],
),
],
@ -193,7 +205,7 @@ class _VerifyLastLoginScreenState extends State<VerifyLastLoginScreen> {
if (mounted) setState(() {});
}
String getVerificationType(int type) {
String getVerificationType(int? type) {
if (type == 1) {
return LocaleKeys.sms.tr();
} else if (type == 2) {
@ -207,8 +219,12 @@ class _VerifyLastLoginScreenState extends State<VerifyLastLoginScreen> {
}
Future<bool> loginWithFaceIDAndBiometrics() async {
IOSAuthMessages iosStrings = const IOSAuthMessages(cancelButton: 'cancel');
IOSAuthMessages iosStrings = const IOSAuthMessages(
cancelButton: 'cancel',
// goToSettingsButton: 'settings',
// goToSettingsDescription: 'Please set up your Touch ID.',
// lockOut: 'Please reenable your Touch ID',
);
// IOSAuthMessages iosStrings = const IOSAuthMessages(
// cancelButton: 'cancel',
@ -219,11 +235,12 @@ class _VerifyLastLoginScreenState extends State<VerifyLastLoginScreen> {
bool authenticated = false;
try {
authenticated = await auth.authenticate(localizedReason: 'Scan your fingerprint to authenticate',
authMessages: [
iosStrings,
const AndroidAuthMessages(),
],
authenticated = await auth.authenticate(
localizedReason: 'Scan your fingerprint to authenticate',
// options: const AuthenticationOptions(useErrorDialogs: true, stickyAuth: true, biometricOnly: true),
persistAcrossBackgrounding: false, // Replaces stickyAuth
biometricOnly: true,
authMessages: [iosStrings, const AndroidAuthMessages()],
);
} on PlatformException catch (e) {
print(e);
@ -234,60 +251,58 @@ class _VerifyLastLoginScreenState extends State<VerifyLastLoginScreen> {
}
Widget _loginOptionButton(String _title, String _icon, int _flag, int? _loginIndex) {
// <<<<<<< HEAD
// <<<<<<< HEAD
bool isDisable = false;
if(_flag >= 3 && _flag <= 4) {
bool isFaceEnabled = (_flag == 3 && (checkBiometricIsAvailable(BiometricType.face) ||
checkBiometricIsAvailable(BiometricType.weak)));
if (_flag >= 3 && _flag <= 4) {
bool isFaceEnabled = (_flag == 3 && (checkBiometricIsAvailable(BiometricType.face) || checkBiometricIsAvailable(BiometricType.weak)));
bool isThumbEnabled =
(_flag == 4 && (checkBiometricIsAvailable(BiometricType.fingerprint) ||
checkBiometricIsAvailable(BiometricType.strong)));
bool isThumbEnabled = (_flag == 4 && (checkBiometricIsAvailable(BiometricType.fingerprint) || checkBiometricIsAvailable(BiometricType.strong)));
isDisable = !(isFaceEnabled || isThumbEnabled);
}
// =======
// bool isDisable = (_flag == 3 && !checkBiometricIsAvailable(BiometricType.face) ||
// _flag == 4 && !checkBiometricIsAvailable(BiometricType.fingerprint) && _flag == 4 && !checkBiometricIsAvailable(BiometricType.face));
// // >>>>>>> master
// =======
// bool isDisable = (_flag == 3 && !checkBiometricIsAvailable(BiometricType.face) ||
// _flag == 4 && !checkBiometricIsAvailable(BiometricType.fingerprint) && _flag == 4 && !checkBiometricIsAvailable(BiometricType.face));
// // >>>>>>> master
return InkWell(
onTap: isDisable
? null
: () async {
if (_flag == 0) {
setState(() {
// isMoreOption = true;
});
} else {
if (_flag == 3 || _flag == 4) {
bool authenticateWithFaceAndTouchID = await loginWithFaceIDAndBiometrics();
if (!authenticateWithFaceAndTouchID) {
return;
} else {
if (mobileLoginInfoListModel!.loginType == 3 || mobileLoginInfoListModel!.loginType == 4) {
// bool authenticateWithFaceAndTouchID = await loginWithFaceIDAndBiometrics();
// if (!authenticateWithFaceAndTouchID) {
// return;
// } else {
// performApiCall(_title, _icon, _flag, isDirectLogin: true);
// }
performApiCall(_title, _icon, _flag, _flag, isDirectLogin: true);
onTap:
isDisable
? null
: () async {
if (_flag == 0) {
setState(() {
// isMoreOption = true;
});
} else {
if (_flag == 3 || _flag == 4) {
bool authenticateWithFaceAndTouchID = await loginWithFaceIDAndBiometrics();
if (!authenticateWithFaceAndTouchID) {
return;
} else {
isNeedVerifyWithFaceIDAndBiometrics = true;
selectedFlag = _flag;
setState(() {
return;
});
if (mobileLoginInfoListModel?.loginType == 3 || mobileLoginInfoListModel?.loginType == 4) {
// bool authenticateWithFaceAndTouchID = await loginWithFaceIDAndBiometrics();
// if (!authenticateWithFaceAndTouchID) {
// return;
// } else {
// performApiCall(_title, _icon, _flag, isDirectLogin: true);
// }
performApiCall(_title, _icon, _flag, _flag, isDirectLogin: true);
} else {
isNeedVerifyWithFaceIDAndBiometrics = true;
selectedFlag = _flag;
setState(() {
return;
});
}
}
} else {
if (isNeedVerifyWithFaceIDAndBiometrics)
performApiCall(_title, _icon, selectedFlag, _flag);
else
performApiCall(_title, _icon, _flag, _flag);
}
} else {
if (isNeedVerifyWithFaceIDAndBiometrics)
performApiCall(_title, _icon, selectedFlag, _flag);
else
performApiCall(_title, _icon, _flag, _flag);
}
}
},
},
child: Container(
padding: const EdgeInsets.only(left: 20, right: 20, bottom: 15, top: 28),
decoration: BoxDecoration(
@ -374,7 +389,12 @@ class _VerifyLastLoginScreenState extends State<VerifyLastLoginScreen> {
Future<void> performDirectApiCall(String _title, String _icon, int _flag, String value, TextEditingController? _pinPutController, {bool isDirectLogin = false}) async {
try {
GenericResponseModel? genericResponseModel = await LoginApiClient().checkActivationCode(true, AppState().memberLoginList?.pMOBILENUMBER, value, AppState().getUserName);
GenericResponseModel? genericResponseModel = await LoginApiClient().checkActivationCode(true, AppState().memberLoginList?.pMOBILENUMBER, value, AppState().getUserName,_flag.toString());
String devToken = AppState().getIsHuawei
? AppState().getHuaweiPushToken
: ((mobileLoginInfoListModel?.deviceToken != null && mobileLoginInfoListModel!.deviceToken!.isNotEmpty && mobileLoginInfoListModel!.deviceToken != "null")
? mobileLoginInfoListModel!.deviceToken!
: (AppState().getDeviceToken ?? ""));
GenericResponseModel? genericResponseModel1 = await LoginApiClient().insertMobileLoginInfoNEW(
AppState().memberLoginList?.pEMAILADDRESS ?? "",
genericResponseModel?.pSESSIONID ?? 0,
@ -382,8 +402,9 @@ class _VerifyLastLoginScreenState extends State<VerifyLastLoginScreen> {
_flag,
AppState().memberLoginList?.pMOBILENUMBER ?? "",
AppState().getUserName!,
AppState().getIsHuawei ? AppState().getHuaweiPushToken : mobileLoginInfoListModel!.deviceToken!,
devToken,
Platform.isAndroid ? "android" : "ios",
memberType: MemberType.employee.value,
);
AppState().setMemberInformationListModel = genericResponseModel!.memberInformationList?.first;
AppState().setPrivilegeListModel = genericResponseModel!.privilegeList ?? [];
@ -411,15 +432,15 @@ class _VerifyLastLoginScreenState extends State<VerifyLastLoginScreen> {
}
}
//
// formatDate(date) {
// return date;
// return DateFormat('MMM dd, yyy, kk:mm').format(date);
// }
//
// showLoader(bool isTrue) {
// setState(() {
// // isLoading = isTrue;
// });
// }
//
// formatDate(date) {
// return date;
// return DateFormat('MMM dd, yyy, kk:mm').format(date);
// }
//
// showLoader(bool isTrue) {
// setState(() {
// // isLoading = isTrue;
// });
// }
}

@ -55,7 +55,18 @@ class _VerifyLoginScreenState extends State<VerifyLoginScreen> {
@override
Widget build(BuildContext context) {
firebaseToken ??= ModalRoute.of(context)!.settings.arguments as String;
Object? rawArg = ModalRoute.of(context)?.settings.arguments;
if (rawArg != null) {
String argStr = rawArg.toString().trim();
if (argStr.isNotEmpty && argStr != "null") {
firebaseToken ??= argStr;
}
}
if (firebaseToken == null || firebaseToken!.isEmpty || firebaseToken == "null") {
if (AppState().getDeviceToken != null && AppState().getDeviceToken!.isNotEmpty && AppState().getDeviceToken != "null") {
firebaseToken = AppState().getDeviceToken;
}
}
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.transparent,
@ -641,7 +652,12 @@ class _VerifyLoginScreenState extends State<VerifyLoginScreen> {
(value, TextEditingController _pinPutController) async {
Utils.showLoading(context);
try {
GenericResponseModel? genericResponseModel = await LoginApiClient().checkActivationCode(true, AppState().memberLoginList?.pMOBILENUMBER, value, AppState().getUserName);
GenericResponseModel? genericResponseModel = await LoginApiClient().checkActivationCode(true, AppState().memberLoginList?.pMOBILENUMBER, value, AppState().getUserName, _flag.toString());
String devToken = AppState().getIsHuawei
? AppState().getHuaweiPushToken
: ((firebaseToken != null && firebaseToken!.isNotEmpty && firebaseToken != "null")
? firebaseToken!
: (AppState().getDeviceToken ?? ""));
GenericResponseModel? genericResponseModel1 = await LoginApiClient().insertMobileLoginInfoNEW(
AppState().memberLoginList?.pEMAILADDRESS ?? "",
genericResponseModel?.pSESSIONID ?? 0,
@ -649,8 +665,9 @@ class _VerifyLoginScreenState extends State<VerifyLoginScreen> {
_flag,
AppState().memberLoginList?.pMOBILENUMBER ?? "",
AppState().getUserName!,
AppState().getIsHuawei ? AppState().getHuaweiPushToken : firebaseToken!,
Platform.isAndroid ? "android" : "ios");
devToken,
Platform.isAndroid ? "android" : "ios",
memberType: MemberType.employee.value);
if (genericResponseModel?.errorMessage != null) {
Utils.showToast(genericResponseModel?.errorMessage ?? "");
} else {

@ -23,7 +23,7 @@ import 'package:mohem_flutter_app/ui/marathon/widgets/question_card.dart';
import 'package:mohem_flutter_app/ui/marathon/widgets/question_card_builder.dart';
import 'package:mohem_flutter_app/widgets/app_bar_widget.dart';
import 'package:mohem_flutter_app/widgets/button/default_button.dart';
import 'package:mohem_flutter_app/widgets/dialogs/confirm_dialog.dart';
import 'package:mohem_flutter_app/widgets/bottom_sheets/confirm_bottom_sheet.dart';
import 'package:provider/provider.dart';
class MarathonScreen extends StatelessWidget {
@ -299,16 +299,16 @@ class MarathonScreen extends StatelessWidget {
provider.getMarathonDetailsFromApi();
Navigator.of(context).popUntil(ModalRoute.withName(AppRoutes.dashboard));
} else {
showDialog(
context: context,
builder: (BuildContext context) => ConfirmDialog(
message: LocaleKeys.youWantToLeaveMarathon.tr(),
onTap: () {
provider.resetValues();
provider.getMarathonDetailsFromApi();
Navigator.of(context).popUntil(ModalRoute.withName(AppRoutes.dashboard));
},
),
ConfirmBottomSheet.show(
context,
message: LocaleKeys.youWantToLeaveMarathon.tr(),
confirmText: LocaleKeys.yes.tr(),
cancelText: LocaleKeys.no.tr(),
onConfirm: () {
provider.resetValues();
provider.getMarathonDetailsFromApi();
Navigator.of(context).popUntil(ModalRoute.withName(AppRoutes.dashboard));
},
);
}
},
@ -318,15 +318,15 @@ class MarathonScreen extends StatelessWidget {
provider.getMarathonDetailsFromApi();
Navigator.of(context).popUntil(ModalRoute.withName(AppRoutes.dashboard));
} else {
showDialog(
context: context,
builder: (BuildContext context) => ConfirmDialog(
message: LocaleKeys.youWantToLeaveMarathon.tr(),
onTap: () {
provider.resetValues();
Navigator.of(context).popUntil(ModalRoute.withName(AppRoutes.dashboard));
},
),
ConfirmBottomSheet.show(
context,
message: LocaleKeys.youWantToLeaveMarathon.tr(),
confirmText: LocaleKeys.yes.tr(),
cancelText: LocaleKeys.no.tr(),
onConfirm: () {
provider.resetValues();
Navigator.of(context).popUntil(ModalRoute.withName(AppRoutes.dashboard));
},
);
}
},
@ -390,15 +390,15 @@ class MarathonScreen extends StatelessWidget {
),
),
onWillPop: () {
showDialog(
context: context,
builder: (BuildContext context) => ConfirmDialog(
message: LocaleKeys.youWantToLeaveMarathon.tr(),
onTap: () {
provider.resetValues();
Navigator.of(context).popUntil(ModalRoute.withName(AppRoutes.dashboard));
},
),
ConfirmBottomSheet.show(
context,
message: LocaleKeys.youWantToLeaveMarathon.tr(),
confirmText: LocaleKeys.yes.tr(),
cancelText: LocaleKeys.no.tr(),
onConfirm: () {
provider.resetValues();
Navigator.of(context).popUntil(ModalRoute.withName(AppRoutes.dashboard));
},
);
return Future<bool>.value(false);
},

@ -10,7 +10,7 @@ import 'package:mohem_flutter_app/extensions/widget_extensions.dart';
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
import 'package:mohem_flutter_app/ui/marathon/marathon_provider.dart';
import 'package:mohem_flutter_app/widgets/app_bar_widget.dart';
import 'package:mohem_flutter_app/widgets/dialogs/confirm_dialog.dart';
import 'package:mohem_flutter_app/widgets/bottom_sheets/confirm_bottom_sheet.dart';
import 'package:provider/provider.dart';
class MarathonWaitingScreen extends StatelessWidget {
@ -21,15 +21,15 @@ class MarathonWaitingScreen extends StatelessWidget {
MarathonProvider provider = context.watch<MarathonProvider>();
return WillPopScope(
onWillPop: () {
showDialog(
context: context,
builder: (BuildContext context) => ConfirmDialog(
message: LocaleKeys.youWantToLeaveMarathon.tr(),
onTap: () {
provider.resetValues();
Navigator.of(context).popUntil(ModalRoute.withName(AppRoutes.dashboard));
},
),
ConfirmBottomSheet.show(
context,
message: LocaleKeys.youWantToLeaveMarathon.tr(),
confirmText: LocaleKeys.yes.tr(),
cancelText: LocaleKeys.no.tr(),
onConfirm: () {
provider.resetValues();
Navigator.of(context).popUntil(ModalRoute.withName(AppRoutes.dashboard));
},
);
return Future<bool>.value(false);
},
@ -38,27 +38,27 @@ class MarathonWaitingScreen extends StatelessWidget {
context,
title: LocaleKeys.brainMarathon.tr(),
onHomeTapped: () {
showDialog(
context: context,
builder: (BuildContext context) => ConfirmDialog(
message: LocaleKeys.youWantToLeaveMarathon.tr(),
onTap: () {
provider.resetValues();
Navigator.of(context).popUntil(ModalRoute.withName(AppRoutes.dashboard));
},
),
ConfirmBottomSheet.show(
context,
message: LocaleKeys.youWantToLeaveMarathon.tr(),
confirmText: LocaleKeys.yes.tr(),
cancelText: LocaleKeys.no.tr(),
onConfirm: () {
provider.resetValues();
Navigator.of(context).popUntil(ModalRoute.withName(AppRoutes.dashboard));
},
);
},
onBackTapped: () {
showDialog(
context: context,
builder: (BuildContext context) => ConfirmDialog(
message: LocaleKeys.youWantToLeaveMarathon.tr(),
onTap: () {
provider.resetValues();
Navigator.of(context).popUntil(ModalRoute.withName(AppRoutes.dashboard));
},
),
ConfirmBottomSheet.show(
context,
message: LocaleKeys.youWantToLeaveMarathon.tr(),
confirmText: LocaleKeys.yes.tr(),
cancelText: LocaleKeys.no.tr(),
onConfirm: () {
provider.resetValues();
Navigator.of(context).popUntil(ModalRoute.withName(AppRoutes.dashboard));
},
);
},
),

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,390 @@
import 'dart:async';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:mohem_flutter_app/api/offers_and_discounts_api_client.dart';
import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:mohem_flutter_app/classes/colors.dart';
import 'package:mohem_flutter_app/classes/consts.dart';
import 'package:mohem_flutter_app/classes/utils.dart';
import 'package:mohem_flutter_app/config/routes.dart';
import 'package:mohem_flutter_app/extensions/string_extensions.dart';
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
import 'package:mohem_flutter_app/models/generic_response_model.dart';
import 'package:mohem_flutter_app/models/offers_and_discounts/wala_plus_retailer_model.dart';
import 'package:mohem_flutter_app/ui/mazaya/widgets/mazaya_bottom_nav.dart';
import 'package:mohem_flutter_app/ui/mazaya/widgets/wala_plus_retailer_grid.dart';
import 'package:mohem_flutter_app/widgets/app_bar_widget.dart';
// Mazaya Saved tab - full-screen version of what was FavoriteMerchantsBottomSheet (a modal
// bottom sheet). Same fetch/search/pagination/toggle-favorite logic, now its own pushed route
// with the shared MazayaBottomNav instead of a dismissible sheet - matches Discover's "own
// route, fetches fresh on mount" pattern.
class SavedRetailersScreen extends StatefulWidget {
const SavedRetailersScreen({Key? key}) : super(key: key);
@override
State<SavedRetailersScreen> createState() => _SavedRetailersScreenState();
}
class _SavedRetailersScreenState extends State<SavedRetailersScreen> {
List<WalaPlusRetailerModel> favoriteRetailers = <WalaPlusRetailerModel>[];
bool isLoading = true;
bool isLoadingMore = false;
bool hasMore = true;
bool _isFetchingPage = false;
int currentPage = 1;
int totalPages = 1;
final int pageSize = 10;
String searchQuery = "";
Timer? _debounceTimer;
final TextEditingController _searchController = TextEditingController();
final ScrollController _scrollController = ScrollController();
@override
void initState() {
super.initState();
_scrollController.addListener(_scrollListener);
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) fetchFavorites();
});
}
@override
void dispose() {
_debounceTimer?.cancel();
_scrollController.removeListener(_scrollListener);
_scrollController.dispose();
_searchController.dispose();
super.dispose();
}
void _scrollListener() {
if (!_scrollController.hasClients) return;
double maxScroll = _scrollController.position.maxScrollExtent;
double currentScroll = _scrollController.position.pixels;
if (currentScroll >= maxScroll - 35) {
if (!isLoading && !isLoadingMore && !_isFetchingPage && hasMore) {
fetchNextPage();
}
}
}
String? _getUserReferenceForFavorites() {
String ref = OffersAndDiscountsApiClient().resolveUserReference();
if (ref.trim().isNotEmpty) return ref.trim();
String? userName = AppState().getUserName;
if (userName != null && userName.trim().isNotEmpty) return userName.trim();
return null;
}
Future<void> fetchFavorites({String? query}) async {
if (query != null) searchQuery = query;
setState(() {
isLoading = true;
currentPage = 1;
hasMore = true;
});
try {
String? queryParam =
searchQuery.trim().isNotEmpty ? searchQuery.trim() : null;
String? userRef = _getUserReferenceForFavorites();
WalaPlusRetailersResponse? response = await OffersAndDiscountsApiClient()
.getWalaPlusRetailers(
categoryId: "",
page: 1,
perPage: pageSize,
offerType: null,
query: queryParam,
sortBy: null,
radius: null,
minDiscount: null,
context: null,
userReference: userRef,
);
favoriteRetailers = response?.retailers ?? <WalaPlusRetailerModel>[];
for (WalaPlusRetailerModel r in favoriteRetailers) {
r.isFavorite = true;
}
currentPage = 1;
totalPages = response?.pagesCount ?? 1;
hasMore =
currentPage < totalPages &&
(response?.retailers?.isNotEmpty ?? false);
} catch (e) {
debugPrint("MazayaSavedScreen: error fetching favorites: $e");
favoriteRetailers = <WalaPlusRetailerModel>[];
} finally {
if (mounted) setState(() => isLoading = false);
}
}
Future<void> fetchNextPage() async {
if (isLoading || isLoadingMore || _isFetchingPage || !hasMore) return;
_isFetchingPage = true;
setState(() => isLoadingMore = true);
try {
int nextPage = currentPage + 1;
String? queryParam =
searchQuery.trim().isNotEmpty ? searchQuery.trim() : null;
String? userRef = _getUserReferenceForFavorites();
WalaPlusRetailersResponse? response = await OffersAndDiscountsApiClient()
.getWalaPlusRetailers(
categoryId: "",
page: nextPage,
perPage: pageSize,
offerType: null,
query: queryParam,
sortBy: null,
radius: null,
minDiscount: null,
context: null,
userReference: userRef,
);
List<WalaPlusRetailerModel> newRetailers =
response?.retailers ?? <WalaPlusRetailerModel>[];
if (newRetailers.isNotEmpty) {
for (WalaPlusRetailerModel r in newRetailers) {
r.isFavorite = true;
}
currentPage = nextPage;
totalPages = response?.pagesCount ?? totalPages;
hasMore = currentPage < totalPages;
favoriteRetailers.addAll(newRetailers);
} else {
hasMore = false;
}
} catch (e) {
debugPrint("MazayaSavedScreen: error fetching next page: $e");
} finally {
_isFetchingPage = false;
if (mounted) setState(() => isLoadingMore = false);
}
}
void _onSearchChanged(String query) {
_debounceTimer?.cancel();
searchQuery = query;
if (query.trim().isEmpty) {
fetchFavorites(query: "");
} else {
_debounceTimer = Timer(const Duration(milliseconds: 600), () {
fetchFavorites(query: query.trim());
});
}
}
Future<void> _toggleFavorite(WalaPlusRetailerModel item) async {
String retailerId = item.id ?? "";
if (retailerId.isEmpty) return;
bool currentFav = item.isFavorite ?? true;
bool targetFav = !currentFav;
setState(() {
item.isFavorite = targetFav;
if (!targetFav) {
favoriteRetailers.removeWhere(
(WalaPlusRetailerModel r) => r.id == item.id,
);
}
});
Utils.showLoading(context);
try {
GenericResponseModel? response;
String? userRef = _getUserReferenceForFavorites();
if (targetFav) {
response = await OffersAndDiscountsApiClient().addWalaPlusFavorite(
retailerId: retailerId,
context: context,
userReference: userRef,
);
} else {
response = await OffersAndDiscountsApiClient().removeWalaPlusFavorite(
retailerId: retailerId,
context: context,
userReference: userRef,
);
}
Utils.hideLoading(context);
if (response != null && response.errorMessage == null) {
bool isAr = AppState().isArabic(context);
String msg =
isAr
? (targetFav
? LocaleKeys.favoriteAddedSuccess.tr()
: LocaleKeys.favoriteRemovedSuccess.tr())
: (response.wpFavoritesAddRemoveMessage ??
(targetFav
? LocaleKeys.favoriteAddedSuccess.tr()
: LocaleKeys.favoriteRemovedSuccess.tr()));
Utils.showToast(msg, isSuccess: true);
} else {
Utils.showToast(
response?.errorEndUserMessage ??
response?.errorMessage ??
LocaleKeys.favoriteError.tr(),
);
await fetchFavorites(query: _searchController.text);
}
} catch (e) {
Utils.hideLoading(context);
debugPrint("MazayaSavedScreen: error toggling favorite: $e");
Utils.showToast(LocaleKeys.favoriteError.tr());
await fetchFavorites(query: _searchController.text);
}
}
void _openRetailer(WalaPlusRetailerModel item) async {
await Navigator.pushNamed(
context,
AppRoutes.retailerDetailsScreen,
arguments: item,
);
if (mounted) {
await fetchFavorites(query: _searchController.text);
}
}
@override
Widget build(BuildContext context) {
bool isAr = AppState().isArabic(context);
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBarWidget(
context,
title: LocaleKeys.favoriteOffers.tr(),
showHomeButton: false,
// Reached from the Mazaya bottom nav - same as Discover, no back button for a
// dependent-login session since this is one of their root tabs, not a pushed screen.
showBackButton: false,
),
bottomNavigationBar: const MazayaBottomNav(currentTab: MazayaTab.saved),
body: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Container(
width: double.infinity,
height: 70,
padding: const EdgeInsets.only(
top: 14,
right: 16,
bottom: 14,
left: 16,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: const Color(0xFFE8E8E8), width: 1.0),
boxShadow: <BoxShadow>[
BoxShadow(
color: const Color(0xFF000000).withOpacity(0.0314),
offset: const Offset(0, 2),
blurRadius: 5,
spreadRadius: 0,
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
LocaleKeys.search.tr().toText12(
isBold: true,
color: const Color(0xFF2A333C),
),
const SizedBox(height: 2),
TextField(
controller: _searchController,
onChanged: _onSearchChanged,
onSubmitted: (String val) {
_debounceTimer?.cancel();
fetchFavorites(query: val.trim());
},
textInputAction: TextInputAction.search,
decoration: InputDecoration(
isDense: true,
contentPadding: EdgeInsets.zero,
hintText: LocaleKeys.searchItems.tr(),
hintStyle: const TextStyle(
color: Color(0xFFACAFB1),
fontSize: 14,
fontWeight: FontWeight.w400,
height: 1.2,
),
border: InputBorder.none,
suffixIcon: ValueListenableBuilder<TextEditingValue>(
valueListenable: _searchController,
builder: (
BuildContext context,
TextEditingValue value,
Widget? child,
) {
if (value.text.isEmpty)
return const SizedBox.shrink();
return InkWell(
child: const Icon(
Icons.clear,
size: 18,
color: Color(0xFF848486),
),
onTap: () {
_searchController.clear();
_onSearchChanged("");
},
);
},
),
suffixIconConstraints: const BoxConstraints(
maxWidth: 24,
maxHeight: 28,
),
),
style: const TextStyle(
color: Color(0xFF2A333C),
fontSize: 14,
fontWeight: FontWeight.w400,
),
),
],
),
),
),
Expanded(
child: RefreshIndicator(
onRefresh: () => fetchFavorites(query: _searchController.text),
color: MyColors.textMixColor,
child: SingleChildScrollView(
controller: _scrollController,
physics: const AlwaysScrollableScrollPhysics(
parent: BouncingScrollPhysics(),
),
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
child: Column(
children: <Widget>[
WalaPlusRetailerGrid(
isLoading: isLoading,
retailers: favoriteRetailers,
isAr: isAr,
isLoadingMore: isLoadingMore,
onTapRetailer: _openRetailer,
onFavoriteTap: _toggleFavorite,
),
const SizedBox(height: 20),
],
),
),
),
),
],
),
);
}
}

@ -0,0 +1,130 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:mohem_flutter_app/classes/consts.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/ui/mazaya/mazaya_discover_screen.dart';
import 'package:mohem_flutter_app/ui/mazaya/saved_retailers_screen.dart';
import 'package:mohem_flutter_app/ui/profile/family_members.dart';
enum MazayaTab { discover, saved, myFamily }
// Shared bottom nav for the Mazaya module (Discover / Saved / My Family). Each tab is its own
// screen, swapped in with zero-duration transition (see _onTabTapped) so switching tabs feels
// instant rather than like a new screen opening - not an IndexedStack shell, since each tab still
// needs to reload its own data fresh on every switch, not preserve state across tabs.
class MazayaBottomNav extends StatelessWidget {
final MazayaTab currentTab;
const MazayaBottomNav({Key? key, required this.currentTab}) : super(key: key);
static const Color _activeColor = Color(0xFF147D6B);
static const Color _inactiveColor = Color(0xFF78817F);
void _onTabTapped(BuildContext context, MazayaTab tab) {
if (tab == currentTab) return;
Widget targetScreen;
RouteSettings? settings;
switch (tab) {
case MazayaTab.discover:
targetScreen = const MazayaDiscoverScreen();
break;
case MazayaTab.saved:
targetScreen = const SavedRetailersScreen();
break;
case MazayaTab.myFamily:
targetScreen = const FamilyMembers();
settings = const RouteSettings(
arguments: <String, dynamic>{'fromMazaya': true},
);
break;
}
Navigator.pushReplacement(
context,
PageRouteBuilder<void>(
settings: settings,
pageBuilder:
(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) => targetScreen,
transitionDuration: Duration.zero,
reverseTransitionDuration: Duration.zero,
),
);
}
Widget _buildItem(
BuildContext context, {
required MazayaTab tab,
required String svgAsset,
required String label,
}) {
bool isActive = tab == currentTab;
return Expanded(
child: InkWell(
onTap: () => _onTabTapped(context, tab),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SvgPicture.asset(
svgAsset,
width: 22,
height: 22,
colorFilter: ColorFilter.mode(
isActive ? _activeColor : _inactiveColor,
BlendMode.srcIn,
),
),
const SizedBox(height: 5),
label.toText11(
weight: isActive ? FontWeight.w600 : FontWeight.w400,
color: isActive ? _activeColor : _inactiveColor,
),
],
),
),
);
}
@override
Widget build(BuildContext context) {
return SafeArea(
top: false,
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
border: Border(top: BorderSide(color: Color(0xFFE8EAE4))),
),
padding: const EdgeInsets.only(top: 13, bottom: 9),
child: Row(
children: <Widget>[
_buildItem(
context,
tab: MazayaTab.discover,
svgAsset: "assets/icons/mazaya_compass.svg",
label: LocaleKeys.discover.tr(),
),
_buildItem(
context,
tab: MazayaTab.saved,
svgAsset: "assets/icons/mazaya_heart_outline.svg",
label: LocaleKeys.saved.tr(),
),
// My Family lets the account holder manage their dependents' Mazaya access - not
// applicable (and not shown) when a dependent is the one logged in.
if (!ApiConsts.isDependetLogin)
_buildItem(
context,
tab: MazayaTab.myFamily,
svgAsset: "assets/icons/mazaya_user_round.svg",
label: LocaleKeys.myFamily.tr(),
),
],
),
),
);
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save