Merge remote-tracking branch 'origin/master'

# Conflicts:
#	assets/langs/ar-SA.json
#	assets/langs/en-US.json
#	ios/Runner.xcodeproj/project.pbxproj
#	lib/generated/locale_keys.g.dart
#	lib/theme/colors.dart
calender_package
tahaalam 1 month ago
commit 02398d3bb4

@ -0,0 +1,6 @@
-----BEGIN PRIVATE KEY-----
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgaPetsOq3tqUlswBN
BFUWIIdEg4dcTCGRUKbAEx8ViFygCgYIKoZIzj0DAQehRANCAAS6IKkZ4tCeBlN6
DtXiBekCiIbgHxWk8AWppP0H3mJ+I2s8cIEaquL4/yeLDcHzAHf8tHcYhF/TvggT
nc30yDLv
-----END PRIVATE KEY-----

Binary file not shown.

@ -320,6 +320,7 @@ internal class PenguinView(
if(clinicId.isEmpty()) return
navigateTo(clinicId)
// navigateTo("3-1")
}
/**

@ -0,0 +1,19 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.4375 0.9375C14.5976 0.9375 14.7363 1.04846 14.7715 1.20465L14.9472 1.98451C15.0671 2.51701 15.483 2.93285 16.0155 3.05281L16.7954 3.22848C16.9515 3.26366 17.0625 3.40239 17.0625 3.5625C17.0625 3.72261 16.9515 3.86134 16.7954 3.89652L16.0155 4.07219C15.483 4.19215 15.0671 4.60799 14.9472 5.14049L14.7715 5.92035C14.7363 6.07654 14.5976 6.1875 14.4375 6.1875C14.2774 6.1875 14.1387 6.07654 14.1035 5.92035L13.9278 5.14049C13.8079 4.60799 13.392 4.19215 12.8595 4.07219L12.0796 3.89652C11.9235 3.86134 11.8125 3.72261 11.8125 3.5625C11.8125 3.40239 11.9235 3.26366 12.0796 3.22848L12.8595 3.05281C13.392 2.93285 13.8079 2.51701 13.9278 1.98451L14.1035 1.20465C14.1387 1.04846 14.2774 0.9375 14.4375 0.9375Z" fill="url(#paint0_linear_9523_4592)"/>
<path d="M9 1.6875C9.31066 1.6875 9.5625 1.93934 9.5625 2.25C9.5625 3.75456 10.3204 5.3062 11.5071 6.49288C12.6938 7.67956 14.2454 8.4375 15.75 8.4375C16.0607 8.4375 16.3125 8.68934 16.3125 9C16.3125 9.31066 16.0607 9.5625 15.75 9.5625C14.2454 9.5625 12.6938 10.3204 11.5071 11.5071C10.3204 12.6938 9.5625 14.2454 9.5625 15.75C9.5625 16.0607 9.31066 16.3125 9 16.3125C8.68934 16.3125 8.4375 16.0607 8.4375 15.75C8.4375 14.2454 7.67956 12.6938 6.49288 11.5071C5.3062 10.3204 3.75456 9.5625 2.25 9.5625C1.93934 9.5625 1.6875 9.31066 1.6875 9C1.6875 8.68934 1.93934 8.4375 2.25 8.4375C3.75456 8.4375 5.3062 7.67956 6.49288 6.49288C7.67956 5.3062 8.4375 3.75456 8.4375 2.25C8.4375 1.93934 8.68934 1.6875 9 1.6875Z" fill="url(#paint1_linear_9523_4592)"/>
<path d="M3.89652 12.0796C3.86134 11.9235 3.72261 11.8125 3.5625 11.8125C3.40239 11.8125 3.26366 11.9235 3.22848 12.0796L3.05281 12.8595C2.93285 13.392 2.51701 13.8079 1.98451 13.9278L1.20465 14.1035C1.04846 14.1387 0.9375 14.2774 0.9375 14.4375C0.9375 14.5976 1.04846 14.7363 1.20465 14.7715L1.98451 14.9472C2.51701 15.0671 2.93285 15.483 3.05281 16.0155L3.22848 16.7954C3.26366 16.9515 3.40239 17.0625 3.5625 17.0625C3.72261 17.0625 3.86134 16.9515 3.89652 16.7954L4.07219 16.0155C4.19215 15.483 4.60799 15.0671 5.14049 14.9472L5.92035 14.7715C6.07654 14.7363 6.1875 14.5976 6.1875 14.4375C6.1875 14.2774 6.07654 14.1387 5.92035 14.1035L5.14049 13.9278C4.60799 13.8079 4.19215 13.392 4.07219 12.8595L3.89652 12.0796Z" fill="url(#paint2_linear_9523_4592)"/>
<defs>
<linearGradient id="paint0_linear_9523_4592" x1="9" y1="0.9375" x2="9" y2="17.0625" gradientUnits="userSpaceOnUse">
<stop stop-color="#8A38F5"/>
<stop offset="1" stop-color="#E20BBB"/>
</linearGradient>
<linearGradient id="paint1_linear_9523_4592" x1="9" y1="0.9375" x2="9" y2="17.0625" gradientUnits="userSpaceOnUse">
<stop stop-color="#8A38F5"/>
<stop offset="1" stop-color="#E20BBB"/>
</linearGradient>
<linearGradient id="paint2_linear_9523_4592" x1="9" y1="0.9375" x2="9" y2="17.0625" gradientUnits="userSpaceOnUse">
<stop stop-color="#8A38F5"/>
<stop offset="1" stop-color="#E20BBB"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

@ -0,0 +1,3 @@
<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.03901 1.3125L8.961 1.3125C7.31466 1.31249 6.02729 1.31249 5.0234 1.43396C4.00096 1.55768 3.18715 1.81548 2.54214 2.39599C1.88865 2.98413 1.59058 3.73922 1.4491 4.68627C1.31247 5.60088 1.31249 6.769 1.3125 8.24081L1.3125 9.75917C1.31249 11.231 1.31247 12.3991 1.4491 13.3137C1.59058 14.2608 1.88865 15.0159 2.54214 15.604C3.18715 16.1845 4.00096 16.4423 5.0234 16.566C6.02729 16.6875 7.31465 16.6875 8.96099 16.6875H9.03901C10.6854 16.6875 11.9727 16.6875 12.9766 16.566C13.999 16.4423 14.8129 16.1845 15.4579 15.604C16.1114 15.0159 16.4094 14.2608 16.5509 13.3137C16.6875 12.3991 16.6875 11.231 16.6875 9.75919V8.24081C16.6875 6.76899 16.6875 5.60087 16.5509 4.68627C16.4094 3.73922 16.1114 2.98413 15.4579 2.39599C14.8129 1.81548 13.999 1.55768 12.9766 1.43396C11.9727 1.31249 10.6853 1.31249 9.03901 1.3125ZM12.5625 4.5C12.5625 4.18934 12.3107 3.9375 12 3.9375C11.6893 3.9375 11.4375 4.18934 11.4375 4.5L11.4375 5.4375H10.5C10.1893 5.4375 9.9375 5.68934 9.9375 6C9.9375 6.31066 10.1893 6.5625 10.5 6.5625H11.4375V7.5C11.4375 7.81066 11.6893 8.0625 12 8.0625C12.3107 8.0625 12.5625 7.81066 12.5625 7.5V6.5625H13.5C13.8107 6.5625 14.0625 6.31066 14.0625 6C14.0625 5.68934 13.8107 5.4375 13.5 5.4375H12.5625L12.5625 4.5ZM9.9375 13.125C9.9375 12.8143 10.1893 12.5625 10.5 12.5625L13.5 12.5625C13.8107 12.5625 14.0625 12.8143 14.0625 13.125C14.0625 13.4357 13.8107 13.6875 13.5 13.6875L10.5 13.6875C10.1893 13.6875 9.9375 13.4357 9.9375 13.125ZM10.5 10.3125C10.1893 10.3125 9.9375 10.5643 9.9375 10.875C9.9375 11.1857 10.1893 11.4375 10.5 11.4375L13.5 11.4375C13.8107 11.4375 14.0625 11.1857 14.0625 10.875C14.0625 10.5643 13.8107 10.3125 13.5 10.3125L10.5 10.3125ZM7.89775 10.1023C8.11742 10.3219 8.11742 10.6781 7.89775 10.8977L6.983 11.8125L7.89775 12.7273C8.11742 12.9469 8.11742 13.3031 7.89775 13.5227C7.67808 13.7424 7.32192 13.7424 7.10225 13.5227L6.1875 12.608L5.27275 13.5227C5.05308 13.7424 4.69692 13.7424 4.47725 13.5227C4.25758 13.3031 4.25758 12.9469 4.47725 12.7273L5.392 11.8125L4.47725 10.8977C4.25758 10.6781 4.25758 10.3219 4.47725 10.1023C4.69692 9.88258 5.05308 9.88258 5.27275 10.1023L6.1875 11.017L7.10225 10.1023C7.32192 9.88258 7.67808 9.88258 7.89775 10.1023ZM4.5 5.4375C4.18934 5.4375 3.9375 5.68934 3.9375 6C3.9375 6.31066 4.18934 6.5625 4.5 6.5625L7.5 6.5625C7.81066 6.5625 8.0625 6.31066 8.0625 6C8.0625 5.68934 7.81066 5.4375 7.5 5.4375L4.5 5.4375Z" fill="#8F9AA3"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 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 d="M9.56265 5.25C9.56265 4.93934 9.31081 4.6875 9.00015 4.6875C8.68949 4.6875 8.43765 4.93934 8.43765 5.25L8.43765 6.75C8.43765 7.06066 8.68949 7.3125 9.00015 7.3125C9.31081 7.3125 9.56265 7.06066 9.56265 6.75V5.25Z" fill="#2E3039"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.9988 0.9375C7.73784 0.9375 6.71271 1.36345 5.81862 1.82666C5.54843 1.96664 5.29743 2.1051 5.05759 2.23741C4.47075 2.56113 3.95062 2.84805 3.37945 3.03029L3.36037 3.03638C3.02491 3.14341 2.74681 3.23213 2.53612 3.3147C2.33781 3.39241 2.10212 3.4989 1.9349 3.68475C1.78469 3.85169 1.70752 4.0391 1.65635 4.21837C1.6099 4.38111 1.57434 4.57846 1.53625 4.78983L1.53211 4.81279C0.603657 9.96205 2.6339 14.8403 7.59174 16.7384L7.61642 16.7479C8.10374 16.9345 8.43795 17.0625 9.00126 17.0625C9.56458 17.0625 9.89877 16.9345 10.3861 16.7479L10.4107 16.7384C15.3684 14.8402 17.3966 9.96183 16.4679 4.81275L16.4638 4.78975C16.4257 4.57837 16.3901 4.38099 16.3437 4.21825C16.2925 4.03897 16.2153 3.85153 16.065 3.68458C15.8978 3.49874 15.6621 3.39228 15.4638 3.31459C15.2531 3.23206 14.975 3.14338 14.6395 3.0364L14.6205 3.03033C14.049 2.84808 13.5285 2.56109 12.9412 2.23732C12.7012 2.10503 12.4501 1.96659 12.1799 1.82665C11.2853 1.36346 10.2598 0.9375 8.9988 0.9375ZM5.69431 3.17268C5.91405 3.05144 6.12837 2.9332 6.33612 2.82557C7.16979 2.39367 8.00392 2.0625 8.9988 2.0625C9.99375 2.0625 10.8283 2.39372 11.6626 2.82567C11.8704 2.9333 12.0848 3.05153 12.3047 3.17275C12.9278 3.51634 13.5945 3.88397 14.2787 4.10215C14.638 4.21674 14.8809 4.29449 15.0535 4.3621C15.1671 4.4066 15.2164 4.43472 15.2324 4.44405C15.2383 4.45602 15.2486 4.48074 15.2619 4.52707C15.291 4.62916 15.3171 4.77013 15.3608 5.01244C16.2166 9.75713 14.3493 14.0257 10.0085 15.6878C9.5102 15.8786 9.34656 15.9375 9.00125 15.9375C8.65594 15.9375 8.49229 15.8786 7.99399 15.6878C3.65273 14.0257 1.78378 9.75694 2.63926 5.01241C2.68294 4.77015 2.70902 4.62921 2.73815 4.52713C2.75137 4.48082 2.7617 4.45611 2.76765 4.44414C2.78356 4.4348 2.83295 4.40667 2.94657 4.36215C3.11916 4.29452 3.36205 4.21672 3.72141 4.10206C4.40531 3.88385 5.0716 3.51625 5.69431 3.17268ZM15.2282 4.43645C15.2272 4.43489 15.2267 4.43428 15.2267 4.43436C15.2267 4.43441 15.2269 4.43474 15.2273 4.43527C15.2275 4.43559 15.2278 4.43599 15.2282 4.43645Z" fill="#2E3039"/>
<path d="M2.77331 4.43444C2.77332 4.43453 2.77283 4.43532 2.7718 4.43654M3.37945 3.03029C3.95062 2.84805 4.47075 2.56113 5.05759 2.23741C5.29743 2.1051 5.54843 1.96664 5.81862 1.82666C6.71271 1.36345 7.73784 0.9375 8.9988 0.9375C10.2598 0.9375 11.2853 1.36346 12.1799 1.82665C12.4501 1.96659 12.7012 2.10503 12.9412 2.23732C13.5285 2.56109 14.049 2.84808 14.6205 3.03033L14.6395 3.0364C14.975 3.14338 15.2531 3.23206 15.4638 3.31459C15.6621 3.39228 15.8978 3.49874 16.065 3.68458C16.2153 3.85153 16.2925 4.03897 16.3437 4.21825C16.3901 4.38099 16.4257 4.57837 16.4638 4.78975L16.4679 4.81275C17.3966 9.96183 15.3684 14.8402 10.4107 16.7384L10.3861 16.7479C9.89877 16.9345 9.56458 17.0625 9.00126 17.0625C8.43795 17.0625 8.10374 16.9345 7.61642 16.7479L7.59174 16.7384C2.6339 14.8403 0.603657 9.96205 1.53211 4.81279L1.53625 4.78983C1.57434 4.57846 1.6099 4.38111 1.65635 4.21837C1.70752 4.0391 1.78469 3.85169 1.9349 3.68475C2.10212 3.4989 2.33781 3.39241 2.53612 3.3147C2.74681 3.23213 3.02491 3.14341 3.36037 3.03638L3.37945 3.03029ZM3.37945 3.03029L3.55043 3.56618M5.69431 3.17268C5.91405 3.05144 6.12837 2.9332 6.33612 2.82557C7.16979 2.39367 8.00392 2.0625 8.9988 2.0625C9.99375 2.0625 10.8283 2.39372 11.6626 2.82567C11.8704 2.9333 12.0848 3.05153 12.3047 3.17275C12.9278 3.51634 13.5945 3.88397 14.2787 4.10215C14.638 4.21674 14.8809 4.29449 15.0535 4.3621C15.1671 4.4066 15.2164 4.43472 15.2324 4.44405C15.2383 4.45602 15.2486 4.48074 15.2619 4.52707C15.291 4.62916 15.3171 4.77013 15.3608 5.01244C16.2166 9.75713 14.3493 14.0257 10.0085 15.6878C9.5102 15.8786 9.34656 15.9375 9.00125 15.9375C8.65594 15.9375 8.49229 15.8786 7.99399 15.6878C3.65273 14.0257 1.78378 9.75694 2.63926 5.01241C2.68294 4.77015 2.70902 4.62921 2.73815 4.52713C2.75137 4.48082 2.7617 4.45611 2.76765 4.44414C2.78356 4.4348 2.83295 4.40667 2.94657 4.36215C3.11916 4.29452 3.36205 4.21672 3.72141 4.10206C4.40531 3.88385 5.0716 3.51625 5.69431 3.17268ZM15.2282 4.43645C15.2272 4.43489 15.2267 4.43428 15.2267 4.43436C15.2267 4.43441 15.2269 4.43474 15.2273 4.43527C15.2275 4.43559 15.2278 4.43599 15.2282 4.43645ZM9.56265 5.25C9.56265 4.93934 9.31081 4.6875 9.00015 4.6875C8.68949 4.6875 8.43765 4.93934 8.43765 5.25L8.43765 6.75C8.43765 7.06066 8.68949 7.3125 9.00015 7.3125C9.31081 7.3125 9.56265 7.06066 9.56265 6.75V5.25Z" stroke="#2E3039" stroke-width="0.5" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.9922 16C10.9922 15.4477 11.4399 15 11.9922 15H12.0012C12.5535 15 13.0012 15.4477 13.0012 16C13.0012 16.5523 12.5535 17 12.0012 17H11.9922C11.4399 17 10.9922 16.5523 10.9922 16Z" fill="#ED1C2B"/>
<path d="M11.25 13C11.25 13.4142 11.5858 13.75 12 13.75C12.4142 13.75 12.75 13.4142 12.75 13L12.75 9C12.75 8.58579 12.4142 8.25 12 8.25C11.5858 8.25 11.25 8.58579 11.25 9L11.25 13Z" fill="#ED1C2B"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.362 2.015C11.4264 1.66167 12.5736 1.66167 13.638 2.015C14.6981 2.36687 15.5304 3.20141 16.3665 4.37395C17.1999 5.5426 18.1208 7.17206 19.3078 9.2725L19.3544 9.35496C20.5417 11.4557 21.4625 13.0851 22.0364 14.4065C22.613 15.7343 22.9002 16.8807 22.6711 17.9821C22.4403 19.0911 21.8714 20.0995 21.0428 20.8617C20.2162 21.622 19.0907 21.9428 17.6736 22.0968C16.2645 22.25 14.4212 22.25 12.0488 22.25H11.9513C9.57882 22.25 7.73554 22.25 6.32642 22.0968C4.90927 21.9428 3.78379 21.622 2.95722 20.8617C2.12862 20.0995 1.55968 19.0911 1.32895 17.9821C1.0998 16.8807 1.387 15.7343 1.96365 14.4065C2.53752 13.0851 3.45835 11.4557 4.64558 9.35495L4.69218 9.2725C5.87921 7.17207 6.80008 5.5426 7.63347 4.37395C8.46963 3.20141 9.30194 2.36687 10.362 2.015ZM13.1655 3.43862C12.4078 3.18713 11.5922 3.18713 10.8345 3.43862C10.2386 3.63645 9.63943 4.1445 8.85474 5.24486C8.07275 6.34143 7.18998 7.90144 5.97466 10.0519C4.75941 12.2023 3.87825 13.7635 3.33951 15.004C2.79983 16.2467 2.66463 17.0379 2.7975 17.6766C2.96511 18.4821 3.37754 19.2102 3.97272 19.7577C4.44016 20.1877 5.16852 20.4621 6.48854 20.6056C7.80696 20.7489 9.56861 20.75 12 20.75C14.4314 20.75 16.193 20.7489 17.5115 20.6056C18.8315 20.4621 19.5599 20.1877 20.0273 19.7577C20.6225 19.2102 21.0349 18.4821 21.2025 17.6766C21.3354 17.0379 21.2002 16.2467 20.6605 15.004C20.1218 13.7635 19.2406 12.2023 18.0254 10.0519C16.81 7.90144 15.9273 6.34143 15.1453 5.24486C14.3606 4.1445 13.7615 3.63645 13.1655 3.43862Z" fill="#ED1C2B"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 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 fill-rule="evenodd" clip-rule="evenodd" d="M0.729492 7.0013C0.729492 10.4646 3.53704 13.2721 7.00033 13.2721C10.4636 13.2721 13.2712 10.4646 13.2712 7.0013C13.2712 3.53802 10.4636 0.730469 7.00033 0.730469C3.53704 0.730469 0.729492 3.53802 0.729492 7.0013ZM6.81476 6.43387C6.95887 6.45324 7.15822 6.50511 7.32738 6.67427C7.49654 6.84343 7.5484 7.04278 7.56778 7.18689C7.58383 7.30627 7.58374 7.44509 7.58367 7.56168L7.58366 9.91799C7.58366 10.2402 7.32249 10.5013 7.00033 10.5013C6.67816 10.5013 6.41699 10.2402 6.41699 9.91799L6.41699 7.58466C6.09483 7.58466 5.83366 7.32349 5.83366 7.00132C5.83366 6.67916 6.09483 6.41799 6.41699 6.41799L6.43997 6.41798C6.55656 6.41791 6.69537 6.41782 6.81476 6.43387ZM6.99767 4.08464C6.67694 4.08464 6.41694 4.3458 6.41694 4.66797C6.41694 4.99014 6.67694 5.2513 6.99767 5.2513H7.00288C7.32361 5.2513 7.58361 4.99014 7.58361 4.66797C7.58361 4.3458 7.32361 4.08464 7.00288 4.08464H6.99767Z" fill="#2E3039"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@ -1522,8 +1522,56 @@
"trackerAndOthers": "المتتبع وأخرى",
"bmi": "مؤشر كتلة الجسم",
"yesPleaseINeedHelp": "نعم، من فضلك أريد المساعدة",
"noThanksIKnowTheClinic": "لا شكراً، أعرف العيادة",
"unableToSendOTP": "تعذر إرسال رمز التحقق لمرة واحدة",
"aiOverView": "AI ملخص",
"generateAiAnalysis": "قم بإجراء تحليل لهذا الطلب AI",
"aiAnalysis": "AI تحليل",
"prediction": "تنبؤ",
"prevention": "وقاية",
"disclaimer": "تنصل",
"thisAboveInfo": "المعلومات المذكورة أعلاه تم إنشاؤها بواسطة AI",
"aiDisclaimer": "سيتم مشاركة بيانات نتائج تحاليلك المخبرية بشكل آمن مع خدمة الذكاء الاصطناعي لدينا لتحليلها. يساعد هذا في تقديم رؤى صحية مُخصصة. هل ترغب في المتابعة؟",
"consent": "موافقة",
"bmiNormalDescription": "مؤشر كتلة جسمك ضمن المعدل الصحي. استمر في اتباع نظام غذائي متوازن، وممارسة النشاط البدني بانتظام، وإجراء الفحوصات الدورية للحفاظ على صحة جيدة. راقب أي تغييرات ملحوظة مع مرور الوقت.",
"bmiUnderweightDescription": "يشير مؤشر كتلة الجسم إلى أنك تعاني من نقص الوزن. لذا، يُنصح بزيادة السعرات الحرارية المتناولة من خلال تناول الأطعمة الغنية بالعناصر الغذائية، وممارسة تمارين المقاومة لبناء العضلات، واستشارة أخصائي رعاية صحية للتحقق من وجود أسباب كامنة.",
"bmiOverweightDescription": "يشير مؤشر كتلة الجسم إلى زيادة الوزن. لذا، يُنصح بالسعي إلى إنقاص الوزن تدريجياً وبشكل مستدام من خلال تقليل السعرات الحرارية بشكل معتدل، وممارسة النشاط البدني بانتظام، واختيار الأطعمة الصحية. استشر طبيبك قبل إجراء أي تغييرات جذرية في نمط حياتك.",
"caloriesMaintenanceDescription": "السعرات الحرارية اليومية المُقدَّرة للحفاظ على وزنك الحالي بناءً على مستوى نشاطك ومعدل الأيض لديك. استخدم هذه الكمية كخط أساس: زدها قليلاً لزيادة الكتلة العضلية، أو قللها قليلاً لإنقاص الوزن تدريجياً. تابع تقدمك وعدّل الخطة كل أسبوعين إلى أربعة أسابيع.",
"caloriesModerateDeficitDescription": "نظام غذائي معتدل لتقليل السعرات الحرارية مصمم لإنقاص الوزن بشكل ثابت ومستدام (عادةً من 0.25 إلى 0.5 كيلوغرام أسبوعيًا). يُنصح بدمجه مع تمارين المقاومة وتناول كمية كافية من البروتين للحفاظ على الكتلة العضلية. تجنب التخفيضات الحادة في السعرات الحرارية واستشر طبيبًا مختصًا إذا كنت تعاني من أي حالة صحية.",
"caloriesAggressiveDeficitDescription": "يؤدي خفض السعرات الحرارية بشكل كبير إلى فقدان الوزن بشكل أسرع، ولكنه قد يصعب الاستمرار عليه ويزيد من خطر نقص العناصر الغذائية والإرهاق وفقدان الكتلة العضلية. لا يُنصح باتباعه على المدى الطويل؛ استشر طبيبًا أو أخصائي تغذية قبل الاستمرار.",
"bodyFatEssentialDescription": "نسبة الدهون في جسمك ضمن النطاق الأساسي/الرياضي. هذا المستوى شائع لدى الرياضيين ويشير إلى انخفاض نسبة الدهون في الجسم. مع أن هذا قد يكون مثاليًا للأداء، تأكد من الحفاظ على تغذية سليمة ووظائف هرمونية متوازنة. استشر طبيبًا إذا شعرت بالتعب، أو واجهت مشاكل هرمونية، أو أي مخاوف صحية أخرى.",
"bodyFatFitnessDescription": "نسبة الدهون في جسمك ضمن النطاق الصحي. وهذا يدل على مستوى صحي من الدهون في الجسم يرتبط بلياقة بدنية جيدة. حافظ على نمط حياتك الحالي، بما في ذلك ممارسة الرياضة بانتظام واتباع نظام غذائي متوازن، للحفاظ على هذا المستوى الصحي.",
"bodyFatAverageDescription": "نسبة الدهون في جسمك ضمن المعدل الطبيعي/المقبول، وهي نسبة تُعتبر صحية لعموم الناس. لتحسين تكوين جسمك، يُنصح بممارسة تمارين القوة، وتمارين القلب والأوعية الدموية، واتباع نظام غذائي متوازن. ركّز على التغييرات التدريجية والمستدامة للحفاظ على صحتك على المدى الطويل.",
"bodyFatObeseDescription": "نسبة الدهون في جسمك أعلى من المعدل الصحي الموصى به. قد يزيد هذا من المخاطر الصحية، بما في ذلك أمراض القلب والأوعية الدموية، والسكري، ومشاكل المفاصل. يُنصح باستشارة طبيب أو أخصائي تغذية معتمد لوضع خطة علاجية شخصية تشمل التغذية المتوازنة، والنشاط البدني المنتظم، وتعديلات نمط الحياة لتحقيق إدارة مستدامة للوزن.",
"bmrNormalDescription": "معدل الأيض الأساسي لديك ضمن النطاق المتوقع لحالتك. معدل الأيض الأساسي هو الطاقة التي يحتاجها جسمك في حالة الراحة للحفاظ على وظائفه الحيوية (التنفس، الدورة الدموية، درجة الحرارة). استخدم هذه القيمة كأساس لتقدير إجمالي احتياجاتك اليومية من الطاقة من خلال تطبيق مُعامل النشاط. حافظ على نظام غذائي متوازن وممارسة النشاط البدني بانتظام لدعم صحة التمثيل الغذائي.",
"bmrLowDescription": "معدل الأيض الأساسي لديك أقل من المعدل الطبيعي لحالتك. تشمل الأسباب المحتملة انخفاض كتلة العضلات، أو تراجع التمثيل الغذائي المرتبط بالتقدم في السن، أو التكيف الأيضي الناتج عن تقييد السعرات الحرارية لفترة طويلة. يُنصح بالتركيز على تمارين المقاومة لبناء العضلات والحفاظ عليها، والتأكد من تناول كميات كافية من البروتين والمغذيات الدقيقة، واستشارة أخصائي رعاية صحية إذا كان هذا الأمر غير متوقع.",
"bmrHighDescription": "معدل الأيض الأساسي لديك أعلى من المتوسط \u200B\u200Bبالنسبة لحالتك. قد يعكس هذا زيادة في كتلة العضلات، أو صغر السن، أو ارتفاع معدل الأيض لديك بشكل طبيعي، مما يعني أنك تحرق طاقة أكبر أثناء الراحة. استخدم هذه المعلومات لتحديد احتياجاتك من السعرات الحرارية: احتياجات أعلى للحفاظ على الوزن أو بناء العضلات، ونقص معتدل في السعرات الحرارية لإنقاص الوزن. استشر طبيبك إذا لاحظت أي تغيرات سريعة غير مبررة.",
"ibwAtTargetDescription": "Your weight is at the ideal target. Maintain your current habits — balanced diet and regular activity — to keep this.",
"ibwBelowTargetDescription": "You are below the ideal weight by {kg} kg. Aim to gradually increase weight with a modest calorie surplus, nutrient-dense foods, and resistance training.",
"ibwAboveTargetDescription": "You are above the ideal weight by {kg} kg. Aim for a gradual, sustainable weight reduction through a moderate calorie deficit, increased activity, and balanced nutrition.",
"ibwCloseToRangeDescription": "Your weight is close to the ideal range. Maintain a balanced diet and regular activity; follow personalized advice from a healthcare professional if needed.",
"ibwUnderweightDescription": "You are below the ideal weight by {kg} kg. Focus on increasing calorie intake with nutrient-dense foods, prioritize protein and resistance training to build healthy mass, and consider consulting a healthcare professional.",
"ibwUnderweightGenericDescription": "You appear underweight compared to the ideal. Consider increasing calorie intake with nutrient-dense foods and resistance training; seek professional guidance if needed.",
"ibwOverweightDescription": "You are above the ideal weight by {kg} kg. Aim for a gradual, sustainable weight loss strategy — moderate calorie deficit, regular physical activity, and balanced nutrition. Consult a professional before major changes.",
"ibwOverweightGenericDescription": "You appear above the ideal weight. Consider a gradual, sustainable calorie deficit combined with activity and balanced nutrition; consult a professional if needed.",
"dietVeryLowCarbsDescription": "Very lowcarb (ketogenic) approach: minimal carbohydrates, higher fat and moderate protein. May support rapid weight loss and improved blood sugar control for some, but can be hard to sustain and may cause nutrient gaps. Monitor hydration/electrolytes and consult a professional for longterm use.",
"dietLowCarbsDescription": "Lowcarb, higherprotein plan: reduced carbohydrates with increased protein to support satiety and muscle maintenance. Helpful for weight management and glycemic control when balanced with vegetables and healthy fats. Ensure adequate fiber and micronutrients.",
"dietModerateCarbsDescription": "Moderate carbohydrate intake with balanced protein and fats: provides steady energy for daily activity and exercise while allowing flexibility. Suitable for many people aiming for sustainable weight management and performance.",
"dietUSDAGuidelinesDescription": "USDAbased balanced pattern: emphasizes fruits, vegetables, whole grains, lean proteins, and limited added sugars and saturated fats. Evidencebased framework for general health, nutrient adequacy, and chronic disease prevention.",
"dietZoneDietDescription": "Zone Diet (~40% carbs / 30% protein / 30% fat): emphasizes hormonal balance and portion control. May improve body composition and energy for some, but requires planning; personalize with a nutrition professional.",
"contextDependent": "يعتمد السياق",
"calculatedValue": "القيمة المحسوبة",
"calculationFormula": "صيغة الحساب"
"confirmYourLocation": "قم بتأكيد موقعك",
"confirmYourLocationDesc": "يرجى تأكيد موقعك الحالي لعرض الاتجاهات المناسبة.",
"insideHospital": "أنا داخل المستشفى",
"outsideHospital": "أنا خارج المستشفى",
"servicePriceList": "قائمة أسعار الخدمات",
"servicePriceListDesc": "توضح قائمة أسعار الخدمات أدناه رسوم الخدمات الصحية المقدمة للمرضى بنظام الدفع النقدي. أما فيما يتعلق بالخدمات المشمولة بالتأمين، فسيتم تطبيق التغطية التأمينية والتحقق من الأهلية واحتساب نسب التحمل وفقًا لشروط وثيقة التأمين وجدول المنافع المعتمد لكل شركة تأمين.",
"servicePriceListRights": "يحق للمريض الحصول على متابعة مجانية في غضون 14 يومًا من الزيارة الأولى",
"serviceName": "اسم الخدمة",
"noFreeSlot": "لا توجد مواعيد متاحة في التاريخ المحدد. يرجى اختيار تاريخ آخر أو إعادة المحاولة لاحقاً.",
"applewatch": "ساعة آبل",
"applehealthapplicationshouldbeinstalledinyourphone": "يجب تثبيت تطبيق Apple Health على هاتفك",
"unabletodetectapplicationinstalledpleasecomebackonceinstalled": "لا يمكننا اكتشاف التطبيق المثبت على جهازك. يرجى العودة إلى هنا بمجرد تثبيت هذا التطبيق.",
@ -1538,5 +1586,4 @@
"whoophealthapplicationshouldbeinstalledinyourphone": "يجب تثبيت تطبيق Whoop Health على هاتفك",
"whoopwatchshouldbeconnected": "يجب توصيل ساعة Whoop",
"updatetheinformation": "سيتيح ذلك جمع أحدث المعلومات من ساعة آبل الخاصة بك"
}

@ -1475,7 +1475,6 @@
"remindersSet": "Reminders Set!",
"dailyWaterRemindersScheduledAt": "Daily water reminders scheduled at:",
"waterConsumption": "Water Consumption",
"selectActivityLevel": "Select Activity Level",
"selectNumberOfReminders": "Select Number of Reminders",
"h2oSettings": "H20 Settings",
"settingsSavedSuccessfully": "Settings saved successfully",
@ -1515,8 +1514,55 @@
"trackerAndOthers": "Tracker & Others",
"bmi": "BMI",
"yesPleaseINeedHelp": "Yes, Please I need help",
"confirmYourLocation": "Confirm Your Location",
"confirmYourLocationDesc": "Please confirm your present location to view appropriate directions.",
"insideHospital": "I am inside the hospital",
"outsideHospital": "I am outside the hospital",
"servicePriceList": "Services Price List",
"servicePriceListDesc": "Below is the services price list outline the healthcare services fees for cash payments, where the insurance coverage, eligibility, and co-payment deductions will be processed in accordance with the insurance policy terms and the table of benefits of each insurance providers:",
"servicePriceListRights": "The patient has the right to a free follow-up within 14 days of initial visit",
"serviceName": "Service Name",
"noFreeSlot": "No free slot available for the selected date. Please select another date or check back later."
"noThanksIKnowTheClinic": "No, Thanks. I Know the clinic",
"unableToSendOTP": "Unable to send Verification OTP",
"aiOverView": "AI Overview",
"generateAiAnalysis": "Generate AI analysis for this order",
"aiAnalysis": "AI Analysis",
"prediction": "Prediction",
"prevention": "Prevention",
"disclaimer": "Disclaimer",
"thisAboveInfo": "The above information is generated by",
"aiDisclaimer": "Your lab result data will be securely shared with our AI service for analysis. This helps provide personalized health insights. Do you want to proceed?",
"consent": "Consent",
"bmiNormalDescription": "Your BMI is within the healthy range. Continue a balanced diet, regular physical activity, and routine checkups to maintain good health. Monitor any significant changes over time.",
"bmiUnderweightDescription": "BMI suggests you are underweight. Consider increasing calorie intake with nutrient-dense foods, include resistance training to build muscle, and consult a healthcare professional to check for underlying causes.",
"bmiOverweightDescription": "BMI indicates overweight. Aim for gradual, sustainable weight loss through a modest calorie deficit, regular physical activity, and healthier food choices. Seek medical advice before major lifestyle changes.",
"caloriesMaintenanceDescription": "Estimated daily calories to maintain your current weight given your activity level and metabolism. Use this as a baseline: increase slightly for muscle gain, or create a small deficit for gradual weight loss. Track progress and adjust every 24 weeks.",
"caloriesModerateDeficitDescription": "A moderate calorie reduction designed for steady, sustainable weight loss (typically 0.250.5 kg per week). Combine with resistance training and adequate protein to preserve muscle. Avoid extreme cuts and consult a professional if you have medical conditions.",
"caloriesAggressiveDeficitDescription": "A large calorie reduction that may produce faster weight loss but can be hard to sustain and increase risk of nutrient deficiencies, fatigue, or muscle loss. Not recommended long-term; seek guidance from a healthcare or nutrition professional before continuing.",
"bodyFatEssentialDescription": "Your body fat percentage is in the essential/athletic range. This level is typical for athletes and indicates very low body fat. While this may be optimal for performance, ensure you maintain adequate nutrition and hormone function. Consult a healthcare professional if you experience fatigue, hormonal issues, or other health concerns.",
"bodyFatFitnessDescription": "Your body fat percentage is in the fitness range. This indicates a healthy level of body fat associated with good physical conditioning. Maintain your current lifestyle habits including regular exercise and balanced nutrition to sustain this healthy range.",
"bodyFatAverageDescription": "Your body fat percentage is in the average/acceptable range. This is considered a healthy range for general population. To improve body composition, consider incorporating strength training, cardiovascular exercise, and a balanced diet. Focus on gradual, sustainable changes for long-term health.",
"bodyFatObeseDescription": "Your body fat percentage is above the recommended healthy range. This may increase health risks including cardiovascular disease, diabetes, and joint problems. Consider consulting a healthcare provider or registered dietitian to develop a personalized plan that includes balanced nutrition, regular physical activity, and lifestyle modifications for sustainable weight management.",
"bmrNormalDescription": "Your Basal Metabolic Rate (BMR) is within the expected range for your profile. BMR is the energy your body needs at rest to maintain vital functions (breathing, circulation, temperature). Use this value as the baseline for estimating total daily energy needs by applying an activity multiplier. Maintain a balanced diet and regular physical activity to support metabolic health.",
"bmrLowDescription": "Your BMR is lower than typical for your profile. Possible causes include lower lean muscle mass, age-related metabolic decline, or metabolic adaptation from prolonged calorie restriction. Consider focusing on resistance training to build/maintain muscle, ensure adequate protein and micronutrient intake, and consult a healthcare professional if this is unexpected.",
"bmrHighDescription": "Your BMR is higher than average for your profile. This can reflect greater lean mass, younger age, or a naturally higher metabolic rate, meaning you burn more energy at rest. Use this information to tailor calorie targets: higher needs for maintenance or muscle gain, moderate deficit for weight loss. Seek medical advice if you notice rapid unexplained changes.",
"ibwAtTargetDescription": "Your weight is at the ideal target. Maintain your current habits — balanced diet and regular activity — to keep this.",
"ibwBelowTargetDescription": "You are below the ideal weight by {kg} kg. Aim to gradually increase weight with a modest calorie surplus, nutrient-dense foods, and resistance training.",
"ibwAboveTargetDescription": "You are above the ideal weight by {kg} kg. Aim for a gradual, sustainable weight reduction through a moderate calorie deficit, increased activity, and balanced nutrition.",
"ibwCloseToRangeDescription": "Your weight is close to the ideal range. Maintain a balanced diet and regular activity; follow personalized advice from a healthcare professional if needed.",
"ibwUnderweightDescription": "You are below the ideal weight by {kg} kg. Focus on increasing calorie intake with nutrient-dense foods, prioritize protein and resistance training to build healthy mass, and consider consulting a healthcare professional.",
"ibwUnderweightGenericDescription": "You appear underweight compared to the ideal. Consider increasing calorie intake with nutrient-dense foods and resistance training; seek professional guidance if needed.",
"ibwOverweightDescription": "You are above the ideal weight by {kg} kg. Aim for a gradual, sustainable weight loss strategy — moderate calorie deficit, regular physical activity, and balanced nutrition. Consult a professional before major changes.",
"ibwOverweightGenericDescription": "You appear above the ideal weight. Consider a gradual, sustainable calorie deficit combined with activity and balanced nutrition; consult a professional if needed.",
"dietVeryLowCarbsDescription": "Very lowcarb (ketogenic) approach: minimal carbohydrates, higher fat and moderate protein. May support rapid weight loss and improved blood sugar control for some, but can be hard to sustain and may cause nutrient gaps. Monitor hydration/electrolytes and consult a professional for longterm use.",
"dietLowCarbsDescription": "Lowcarb, higherprotein plan: reduced carbohydrates with increased protein to support satiety and muscle maintenance. Helpful for weight management and glycemic control when balanced with vegetables and healthy fats. Ensure adequate fiber and micronutrients.",
"dietModerateCarbsDescription": "Moderate carbohydrate intake with balanced protein and fats: provides steady energy for daily activity and exercise while allowing flexibility. Suitable for many people aiming for sustainable weight management and performance.",
"dietUSDAGuidelinesDescription": "USDAbased balanced pattern: emphasizes fruits, vegetables, whole grains, lean proteins, and limited added sugars and saturated fats. Evidencebased framework for general health, nutrient adequacy, and chronic disease prevention.",
"dietZoneDietDescription": "Zone Diet (~40% carbs / 30% protein / 30% fat): emphasizes hormonal balance and portion control. May improve body composition and energy for some, but requires planning; personalize with a nutrition professional.",
"contextDependent": "Context Dependent",
"calculatedValue": "Calculated Value",
"calculationFormula": "Calculation Formula",
"applewatch": "Apple Watch",
"applehealthapplicationshouldbeinstalledinyourphone": "Apple Health application should be installed in your phone",
"unabletodetectapplicationinstalledpleasecomebackonceinstalled": "We are unable to detect the application installed in your device. Please come back here once you have installed this application.",

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

@ -2,10 +2,6 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CLIENT_ID</key>
<string>815750722565-da8p56le8bd6apsbm9eft0jjl1rtpgkt.apps.googleusercontent.com</string>
<key>REVERSED_CLIENT_ID</key>
<string>com.googleusercontent.apps.815750722565-da8p56le8bd6apsbm9eft0jjl1rtpgkt</string>
<key>ANDROID_CLIENT_ID</key>
<string>815750722565-m14h8mkosm7cnq6uh6rhqr54dn02d705.apps.googleusercontent.com</string>
<key>API_KEY</key>
@ -15,11 +11,11 @@
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>com.HMG.HMG-Smartphone</string>
<string>com.cloudsolutions.HMGPatientApp</string>
<key>PROJECT_ID</key>
<string>api-project-815750722565</string>
<key>STORAGE_BUCKET</key>
<string>api-project-815750722565.appspot.com</string>
<string>api-project-815750722565.firebasestorage.app</string>
<key>IS_ADS_ENABLED</key>
<false></false>
<key>IS_ANALYTICS_ENABLED</key>
@ -31,7 +27,7 @@
<key>IS_SIGNIN_ENABLED</key>
<true></true>
<key>GOOGLE_APP_ID</key>
<string>1:815750722565:ios:328ec247a81a2ca23c186c</string>
<string>1:815750722565:ios:34ec03c09e02e21f3c186c</string>
<key>DATABASE_URL</key>
<string>https://api-project-815750722565.firebaseio.com</string>
</dict>

@ -109,7 +109,7 @@
"size" : "83.5x83.5"
},
{
"filename" : "icon 1.jpg",
"filename" : "icon.jpg",
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"

@ -13,7 +13,7 @@
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Dr. Alhabib</string>
<string>Dr. Alhabib Beta</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>

@ -309,22 +309,13 @@ class PenguinView: NSObject, FlutterPlatformView, PIEventsDelegate, PenNavInitia
PenNavUIManager.shared.eventsDelegate = self
print("====== after eventsDelegate onPenNavSuccess =========")
PenNavUIManager.shared.navigate(to: "108")
// print("====== after onPenNavSuccess navigate =========")
// PenNavUIManager.shared.navigate(to: "3-1")
// Present the Penguin UI on top of the Flutter view controller
print("====== after onPenNavSuccess present =========")
PenNavUIManager.shared.present(root: controller, view: _view)
print("====== after present onPenNavSuccess =========")
print(model?.clinicID)
@ -347,11 +338,21 @@ class PenguinView: NSObject, FlutterPlatformView, PIEventsDelegate, PenNavInitia
let clientID = self.model?.clientID, !clientID.isEmpty else {
print("Error: Config Client ID is nil or empty")
print("Error: Config Clinic ID is nil or empty")
return
}
print("====== after onPenNavSuccess navigate =========")
// PenNavUIManager.shared.navigate(to: "3-1") { success, error in
PenNavUIManager.shared.navigate(to: clinicID) { success, error in
if success {
print("Nav Success")
} else {
print("Nav Error")
}
}
@ -373,20 +374,20 @@ class PenguinView: NSObject, FlutterPlatformView, PIEventsDelegate, PenNavInitia
guard let token = token else {
print("Token is nil")
return
}
print("Token Generated")
print(token);
// guard let token = token else {
//
// print("Token is nil")
//
// return
//
// }
//
// print("Token Generated")
//
// print(token);
//
// self?.handleNavigation(clinicID: clinicID, token: token) { success, errorMessage in
//
// if success {
@ -425,7 +426,8 @@ class PenguinView: NSObject, FlutterPlatformView, PIEventsDelegate, PenNavInitia
PenNavUIManager.shared.setToken(token: token)
PenNavUIManager.shared.navigate(to: clinicID)
// PenNavUIManager.shared.navigate(to: clinicID)
PenNavUIManager.shared.navigate(to: "3-1")
completion(true,nil)

@ -188,7 +188,9 @@ class ApiClientImp implements ApiClient {
// body['SessionID'] = "45786230487560q";
}
// body['IsNewFlutterApp'] = true;
if(!url.contains("/paymentApi")) {
body['IsNewFlutterApp'] = true;
}
body.removeWhere((key, value) => value == null);
}

@ -380,6 +380,8 @@ var GET_PATIENT_SICK_LEAVE = 'Services/Patients.svc/REST/GetPatientSickLeave';
var GET_PATIENT_SICK_LEAVE_STATUS = 'Services/Patients.svc/REST/GetPatientSickLeave_Status';
var GET_SERVICES_PRICE_LIST = 'Services/OUTPs.svc/REST/GetServicesPriceList';
var SendSickLeaveEmail = 'Services/Notifications.svc/REST/SendSickLeaveEmail';
var GET_PATIENT_AdVANCE_BALANCE_AMOUNT = 'Services/Patients.svc/REST/GetPatientAdvanceBalanceAmount';
@ -681,7 +683,7 @@ const DASHBOARD = 'Services/Patients.svc/REST/PatientDashboard';
class ApiConsts {
static const maxSmallScreen = 660;
static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat;
static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod;
// static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT
@ -902,9 +904,11 @@ class ApiConsts {
static String getPatientBloodGroup = "services/PatientVarification.svc/REST/BloodDonation_GetBloodGroupDetails";
static String getPatientBloodAgreement = "Services/PatientVarification.svc/REST/CheckUserAgreementForBloodDonation";
static String getAiOverViewLabOrders = "Services/Patients.svc/REST/HMGAI_Lab_Analyze_Orders_API";
static String getAiOverViewLabOrder = "Services/Patients.svc/REST/HMGAI_Lab_Analyzer_API";
// ************ static values for Api ****************
static final double appVersionID = 50.3;
static final double appVersionID = 19.7;
static final int appChannelId = 3;
static final String appIpAddress = "10.20.10.20";
static final String appGeneralId = "Cs2020@2016\$2958";

@ -266,6 +266,10 @@ class AppAssets {
static const String alertSquare = '$svgBasePath/alert-square.svg';
static const String arrowRight = '$svgBasePath/arrow-right.svg';
static const String tickIcon = '$svgBasePath/tick.svg';
static const String guradIcon = '$svgBasePath/guard_icon.svg';
static const String calculatorIcon = '$svgBasePath/calculator_icon.svg';
static const String informationIcon = '$svgBasePath/information-circle.svg';
static const String highCautionIcon = '$svgBasePath/high_caution.svg';
// Symptoms Checker
static const String calendarGrey = '$svgBasePath/calendar-grey.svg';
@ -337,6 +341,7 @@ class AppAssets {
static const String doctorResponseIcon = '$svgBasePath/doctor_response_icon.svg';
static const String changeLanguageHomePageIcon = '$svgBasePath/change_language_home_page.svg';
static const String aiOverView = '$svgBasePath/ai_overview.svg';
// PNGS //
static const String hmgLogo = '$pngBasePath/hmg_logo.png';

@ -54,6 +54,8 @@ import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_v
import 'package:hmg_patient_app_new/features/qr_parking/qr_parking_repo.dart';
import 'package:hmg_patient_app_new/features/radiology/radiology_repo.dart';
import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart';
import 'package:hmg_patient_app_new/features/services_price_list/services_price_list_repo.dart';
import 'package:hmg_patient_app_new/features/services_price_list/services_price_list_view_model.dart';
import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_provider.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_repo.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart';
@ -169,6 +171,7 @@ class AppDependencies {
getIt.registerLazySingleton<QrParkingRepo>(() => QrParkingRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<NotificationsRepo>(() => NotificationsRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<AskDoctorRepo>(() => AskDoctorRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<ServicesPriceListRepo>(() => ServicesPriceListRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
// ViewModels
// Global/shared VMs LazySingleton
@ -311,5 +314,6 @@ class AppDependencies {
getIt.registerLazySingleton<AskDoctorViewModel>(() => AskDoctorViewModel(askDoctorRepo: getIt(), errorHandlerService: getIt()));
getIt.registerLazySingleton<ServicesPriceListViewModel>(() => ServicesPriceListViewModel(servicesPriceListRepo: getIt(), errorHandlerService: getIt()));
}
}

@ -110,7 +110,7 @@ class DateUtil {
}
static String formatDateToTime(DateTime date) {
return DateFormat('hh:mm a').format(date);
return DateFormat('hh:mm a', "en-US").format(date);
}
static String yearMonthDay(DateTime dateTime) {
@ -487,7 +487,9 @@ class DateUtil {
}
static String getFormattedDate(DateTime dateTime, String formattedString) {
return DateFormat(formattedString).format(dateTime);
String formattedDate = DateFormat(formattedString, "en-US").format(dateTime);
print(formattedDate);
return formattedDate;
}
static convertISODateToJsonDate(String isoDate) {

@ -372,6 +372,14 @@ class PushNotificationHandler {
}
});
if (Platform.isIOS) {
FirebaseMessaging.instance.getAPNSToken().then((String? token) {
print("Push Notification getAPNSToken: ${token!}");
}).catchError((err) {
print("Push Notification getAPNSToken ERR: ${err.toString()}");
});
}
FirebaseMessaging.instance.getToken().then((String? token) {
print("Push Notification getToken: ${token!}");
onToken(token!);

@ -210,8 +210,8 @@ class RequestUtils {
List<String> names = fullName != null ? fullName.split(" ") : [];
var dob = appState.getUserRegistrationPayload.dob;
final DateFormat dateFormat1 = DateFormat('MM/dd/yyyy');
final DateFormat dateFormat2 = DateFormat('dd/MM/yyyy');
final DateFormat dateFormat1 = DateFormat('MM/dd/yyyy', "en-US");
final DateFormat dateFormat2 = DateFormat('dd/MM/yyyy', "en-US");
DateTime gregorianDate = dateFormat2.parse(dob!);
HijriGregDate hijriDate = HijriGregConverter.gregorianToHijri(gregorianDate);
String? date = "${hijriDate.day}/${hijriDate.month}/${hijriDate.year}";

@ -128,7 +128,7 @@ class Utils {
static String convertStringToDateTime(String dateTimeString) {
String timeString = dateTimeString;
// Parse the time string using DateFormat
DateFormat format = DateFormat.Hms(); // 'Hms' = 'HH:mm:ss'
DateFormat format = DateFormat.Hms(["en-US"]); // 'Hms' = 'HH:mm:ss'
DateTime time = format.parse(timeString);
DateTime now = DateTime.now();
@ -987,4 +987,17 @@ class Utils {
return isAllowed;
}
static String toEnglishNumbers(String input) {
const english = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
const arabic = ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩'];
const persian = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
String result = input;
for (int i = 0; i < 10; i++) {
result = result.replaceAll(arabic[i], english[i]);
result = result.replaceAll(persian[i], english[i]);
}
return result;
}
}

@ -23,8 +23,7 @@ extension CapExtension on String {
extension EmailValidator on String {
Widget get toWidget => Text(this);
Widget toText8({Color? color, FontWeight? fontWeight, bool isBold = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow}) =>
Text(
Widget toText8({Color? color, FontWeight? fontWeight, bool isBold = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow}) => Text(
this,
maxLines: maxlines,
overflow: textOverflow,
@ -38,7 +37,8 @@ extension EmailValidator on String {
);
Widget toText10(
{Color? color,
{bool isEnglishOnly = false,
Color? color,
FontWeight? weight,
bool isBold = false,
bool isUnderLine = false,
@ -59,19 +59,20 @@ extension EmailValidator on String {
color: color ?? AppColors.blackColor,
letterSpacing: letterSpacing,
decoration: isUnderLine ? TextDecoration.underline : null,
fontFamily: isEnglishOnly ? "Poppins" : getIt.get<AppState>().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins',
decorationColor: color ?? AppColors.blackColor),
);
Widget toText9(
{Color? color,
FontWeight? weight,
bool isBold = false,
bool isUnderLine = false,
bool isCenter = false,
int? maxlines,
FontStyle? fontStyle,
TextOverflow? textOverflow,
double letterSpacing = 0}) =>
{Color? color,
FontWeight? weight,
bool isBold = false,
bool isUnderLine = false,
bool isCenter = false,
int? maxlines,
FontStyle? fontStyle,
TextOverflow? textOverflow,
double letterSpacing = 0}) =>
Text(
this,
textAlign: isCenter ? TextAlign.center : null,
@ -87,15 +88,7 @@ extension EmailValidator on String {
decorationColor: color ?? AppColors.blackColor),
);
Widget toText11(
{Color? color,
FontWeight? weight,
bool isUnderLine = false,
bool isCenter = false,
bool isBold = false,
int maxLine = 0,
double letterSpacing = 0}) =>
Text(
Widget toText11({Color? color, FontWeight? weight, bool isUnderLine = false, bool isCenter = false, bool isBold = false, int maxLine = 0, double letterSpacing = 0}) => Text(
this,
textAlign: isCenter ? TextAlign.center : null,
maxLines: (maxLine > 0) ? maxLine : null,
@ -110,7 +103,7 @@ extension EmailValidator on String {
);
Widget toText12(
{Color? color,
{bool isEnglishOnly = false, Color? color,
bool isUnderLine = false,
TextAlign textAlignment = TextAlign.start,
bool isBold = false,
@ -131,6 +124,7 @@ extension EmailValidator on String {
height: height,
decorationColor: isUnderLine ? AppColors.blackColor : null,
decoration: isUnderLine ? TextDecoration.underline : null,
fontFamily: isEnglishOnly ? "Poppins" : getIt.get<AppState>().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins',
),
);
@ -176,15 +170,7 @@ extension EmailValidator on String {
),
);
Widget toText13(
{Color? color,
bool isUnderLine = false,
bool isBold = false,
bool isCenter = false,
int maxLine = 0,
FontWeight? weight,
double? letterSpacing = 0}) =>
Text(
Widget toText13({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int maxLine = 0, FontWeight? weight, double? letterSpacing = 0}) => Text(
this,
textAlign: isCenter ? TextAlign.center : null,
maxLines: (maxLine > 0) ? maxLine : null,
@ -197,6 +183,7 @@ extension EmailValidator on String {
);
Widget toText14({
bool isEnglishOnly = false,
Color? color,
bool isUnderLine = false,
bool isBold = false,
@ -219,18 +206,11 @@ extension EmailValidator on String {
height: height,
fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal),
decoration: isUnderLine ? TextDecoration.underline : null,
fontFamily: isEnglishOnly ? "Poppins" : getIt.get<AppState>().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins',
decorationColor: color ?? AppColors.blackColor),
);
Widget toText15(
{Color? color,
bool isUnderLine = false,
bool isBold = false,
bool isCenter = false,
FontWeight? weight,
int? maxlines,
double? letterSpacing = -1}) =>
Text(
Widget toText15({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, FontWeight? weight, int? maxlines, double? letterSpacing = -1}) => Text(
this,
textAlign: isCenter ? TextAlign.center : null,
maxLines: maxlines,
@ -242,8 +222,7 @@ extension EmailValidator on String {
decoration: isUnderLine ? TextDecoration.underline : null),
);
Widget toText16(
{Color? color,
Widget toText16({bool isEnglishOnly = false, Color? color,
bool isUnderLine = false,
bool isBold = false,
bool isCenter = false,
@ -258,6 +237,7 @@ extension EmailValidator on String {
this,
maxLines: maxlines,
textAlign: isCenter ? TextAlign.center : null,
// locale: Locale('en', 'US'),
style: TextStyle(
color: color ?? AppColors.blackColor,
fontSize: 16.f,
@ -266,14 +246,14 @@ extension EmailValidator on String {
overflow: textOverflow,
fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal),
decoration: isUnderLine ? TextDecoration.underline : null,
fontFamily: isEnglishOnly ? "Poppins" : getIt.get<AppState>().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins',
decorationColor: decorationColor),
);
Widget toText17({Color? color, bool isBold = false, bool isCenter = false}) => Text(
Widget toText17({bool isEnglishOnly = false, Color? color, bool isBold = false, bool isCenter = false}) => Text(
this,
textAlign: isCenter ? TextAlign.center : null,
style: TextStyle(
color: color ?? AppColors.blackColor, fontSize: 17.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
style: TextStyle(color: color ?? AppColors.blackColor, fontSize: 17.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, fontFamily: isEnglishOnly ? "Poppins" : getIt.get<AppState>().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'),
);
Widget toText18({Color? color, FontWeight? weight, bool isBold = false, bool isCenter = false, int? maxlines, TextOverflow? textOverflow}) => Text(
@ -281,17 +261,12 @@ extension EmailValidator on String {
textAlign: isCenter ? TextAlign.center : null,
this,
overflow: textOverflow,
style: TextStyle(
fontSize: 18.f,
fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal),
color: color ?? AppColors.blackColor,
letterSpacing: -0.4),
style: TextStyle(fontSize: 18.f, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), color: color ?? AppColors.blackColor, letterSpacing: -0.4),
);
Widget toText19({Color? color, bool isBold = false}) => Text(
this,
style: TextStyle(
fontSize: 19.f, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: color ?? AppColors.blackColor, letterSpacing: -0.4),
style: TextStyle(fontSize: 19.f, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: color ?? AppColors.blackColor, letterSpacing: -0.4),
);
Widget toText20({
@ -301,86 +276,51 @@ extension EmailValidator on String {
}) =>
Text(
this,
style: TextStyle(
fontSize: 20.f,
fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal),
color: color ?? AppColors.blackColor,
letterSpacing: -0.4),
style: TextStyle(fontSize: 20.f, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), color: color ?? AppColors.blackColor, letterSpacing: -0.4),
);
Widget toText21({Color? color, bool isBold = false, FontWeight? weight, int? maxlines}) => Text(
this,
maxLines: maxlines,
style: TextStyle(
color: color ?? AppColors.blackColor,
fontSize: 21.f,
letterSpacing: -1,
fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)),
style: TextStyle(color: color ?? AppColors.blackColor, fontSize: 21.f, letterSpacing: -1, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)),
);
Widget toText22({Color? color, bool isBold = false, bool isCenter = false}) => Text(
this,
textAlign: isCenter ? TextAlign.center : null,
style: TextStyle(
height: 1,
color: color ?? AppColors.blackColor,
fontSize: 22.f,
letterSpacing: -1,
fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
style: TextStyle(height: 1, color: color ?? AppColors.blackColor, fontSize: 22.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
);
Widget toText24({Color? color, bool isBold = false, bool isCenter = false, FontWeight? fontWeight, double? letterSpacing}) => Text(
this,
textAlign: isCenter ? TextAlign.center : null,
style: TextStyle(
height: 23 / 24,
color: color ?? AppColors.blackColor,
fontSize: 24.f,
letterSpacing: letterSpacing ?? -1,
fontWeight: isBold ? FontWeight.bold : fontWeight ?? FontWeight.normal),
height: 23 / 24, color: color ?? AppColors.blackColor, fontSize: 24.f, letterSpacing: letterSpacing ?? -1, fontWeight: isBold ? FontWeight.bold : fontWeight ?? FontWeight.normal),
);
Widget toText26({Color? color, bool isBold = false, double? height, bool isCenter = false, FontWeight? weight, double? letterSpacing}) => Text(
this,
textAlign: isCenter ? TextAlign.center : null,
style: TextStyle(
height: height ?? 23 / 26,
color: color ?? AppColors.blackColor,
fontSize: 26.f,
letterSpacing: letterSpacing ?? -1,
fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)),
height: height ?? 23 / 26, color: color ?? AppColors.blackColor, fontSize: 26.f, letterSpacing: letterSpacing ?? -1, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)),
);
Widget toText28({Color? color, bool isBold = false, double? height, bool isCenter = false, double? letterSpacing}) => Text(
Widget toText28({bool isEnglishOnly = false, Color? color, bool isBold = false, double? height, bool isCenter = false, double? letterSpacing}) => Text(
this,
textAlign: isCenter ? TextAlign.center : null,
style: TextStyle(
height: height ?? 23 / 28,
color: color ?? AppColors.blackColor,
fontSize: 28.f,
letterSpacing: letterSpacing ?? -1,
fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
style: TextStyle(height: height ?? 23 / 28, color: color ?? AppColors.blackColor, fontSize: 28.f, letterSpacing: letterSpacing ?? -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, fontFamily: isEnglishOnly ? "Poppins" : getIt.get<AppState>().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'),
);
Widget toText32({FontWeight? weight, Color? color, bool isBold = false, bool isCenter = false}) => Text(
Widget toText32({bool isEnglishOnly = false, FontWeight? weight, Color? color, bool isBold = false, bool isCenter = false}) => Text(
this,
textAlign: isCenter ? TextAlign.center : null,
style: TextStyle(
height: 32 / 32,
color: color ?? AppColors.blackColor,
fontSize: 32.f,
letterSpacing: -1,
fontWeight: isBold ? FontWeight.bold : weight ?? FontWeight.normal),
height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 32.f, letterSpacing: -1, fontFamily: isEnglishOnly ? "Poppins" : getIt.get<AppState>().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins', fontWeight: isBold ? FontWeight.bold : weight ?? FontWeight.normal),
);
Widget toText44({Color? color, bool isBold = false}) => Text(
this,
style: TextStyle(
height: 32 / 32,
color: color ?? AppColors.blackColor,
fontSize: 44.f,
letterSpacing: -1,
fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
style: TextStyle(height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 44.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
);
Widget toSectionHeading({String upperHeading = "", String lowerHeading = ""}) {
@ -416,9 +356,7 @@ extension EmailValidator on String {
}
bool isValidEmail() {
return RegExp(
r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$')
.hasMatch(this);
return RegExp(r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$').hasMatch(this);
}
String toFormattedDate() {

@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:get_it/get_it.dart';
import 'package:hijri_gregorian_calendar/hijri_gregorian_calendar.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/cache_consts.dart';
import 'package:hmg_patient_app_new/core/common_models/nationality_country_model.dart';
@ -74,6 +75,8 @@ class AuthenticationViewModel extends ChangeNotifier {
dobController = TextEditingController(),
nameController = TextEditingController(),
emailController = TextEditingController();
CountryEnum selectedCountrySignup = CountryEnum.saudiArabia;
MaritalStatusTypeEnum? maritalStatus;
GenderTypeEnum? genderType;
@ -279,9 +282,10 @@ class AuthenticationViewModel extends ChangeNotifier {
loginTypeEnum = otpTypeEnum == OTPTypeEnum.sms ? LoginTypeEnum.sms : LoginTypeEnum.whatsapp;
if (phoneNumberController.text.isEmpty) {
phoneNumberController.text = "504278212";
}
// if (phoneNumberController.text.isEmpty) {
// phoneNumberController.text = "504278212";
// }
bool isValidated = ValidationUtils.isValidatePhoneAndId(phoneNumber: phoneNumberController.text, nationalId: nationalIdController.text);
if (!isValidated) {
@ -404,7 +408,6 @@ class AuthenticationViewModel extends ChangeNotifier {
return;
}
patientShareRequestID = apiResponse.data['PatientShareRequestID'];
print("PatientShareRequestID ==============: $patientShareRequestID");
_appState.setFamilyFileTokenID = apiResponse.data['LogInTokenID'];
}
if (apiResponse.messageStatus == 2) {
@ -1045,6 +1048,7 @@ class AuthenticationViewModel extends ChangeNotifier {
}
Future<void> getServicePrivilege() async {
ApiConsts.setBackendURLs();
final resultEither = await _authenticationRepo.getServicePrivilege();
List<ListPrivilege> privilegeModelList = [];
List<VidaPlusProjectListModel> vidaPlusProjectListModel = [];

@ -185,7 +185,7 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo {
body: mapDevice,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
onError!(error);
// onError!(error);
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {

@ -103,6 +103,7 @@ class BookAppointmentsViewModel extends ChangeNotifier {
String selectedAppointmentDate = "";
String selectedAppointmentTime = "";
String selectedAppointmentDisplayDate = "";
dynamic freeSlotsResponse;
@ -385,9 +386,10 @@ class BookAppointmentsViewModel extends ChangeNotifier {
notifyListeners();
}
setSelectedAppointmentDateTime(String date, String time) {
setSelectedAppointmentDateTime(String date, String time, String displayDate) {
selectedAppointmentDate = date;
selectedAppointmentTime = time;
selectedAppointmentDisplayDate = displayDate;
notifyListeners();
}
@ -581,7 +583,7 @@ class BookAppointmentsViewModel extends ChangeNotifier {
result.fold(
(failure) async {
onError?.call(LocaleKeys.noDoctorFound.tr());
onError?.call(selectedClinic.clinicID == 23 ? failure.message : LocaleKeys.noDoctorFound.tr());
},
(apiResponse) async {
if (apiResponse.messageStatus == 2) {
@ -721,7 +723,7 @@ class BookAppointmentsViewModel extends ChangeNotifier {
Future<void> getLiveCareDoctorFreeSlots({bool isBookingForLiveCare = false, Function(dynamic)? onSuccess, Function(String)? onError}) async {
docFreeSlots.clear();
DateTime date;
final DateFormat formatter = DateFormat('HH:mm');
final DateFormat formatter = DateFormat('HH:mm', "en-US");
final DateFormat dateFormatter = DateFormat('yyyy-MM-dd');
Map<DateTime, List> _eventsParsed;
@ -806,6 +808,7 @@ class BookAppointmentsViewModel extends ChangeNotifier {
result.fold(
(failure) async {
print(failure);
onError!(failure.message);
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {

@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_repo.dart';
import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_repo.dart';
@ -17,6 +18,7 @@ import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get
import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/search_e_referral_resp_model.dart';
import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/vital_sign_respo_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
@ -31,8 +33,7 @@ class HmgServicesViewModel extends ChangeNotifier {
final ErrorHandlerService errorHandlerService;
final NavigationService navigationService;
HmgServicesViewModel(
{required this.bookAppointmentsRepo, required this.hmgServicesRepo, required this.errorHandlerService, required this.navigationService});
HmgServicesViewModel({required this.bookAppointmentsRepo, required this.hmgServicesRepo, required this.errorHandlerService, required this.navigationService});
bool isCmcOrdersLoading = false;
bool isCmcServicesLoading = false;
@ -55,9 +56,11 @@ class HmgServicesViewModel extends ChangeNotifier {
// Vital Sign PageView Controller
PageController _vitalSignPageController = PageController();
PageController get vitalSignPageController => _vitalSignPageController;
int _vitalSignCurrentPage = 0;
int get vitalSignCurrentPage => _vitalSignCurrentPage;
void setVitalSignCurrentPage(int page) {
@ -65,7 +68,6 @@ class HmgServicesViewModel extends ChangeNotifier {
notifyListeners();
}
// HHC specific lists
List<GetCMCAllOrdersResponseModel> hhcOrdersList = [];
List<GetCMCServicesResponseModel> hhcServicesList = [];
@ -82,9 +84,8 @@ class HmgServicesViewModel extends ChangeNotifier {
List<SearchEReferralResponseModel> searchReferralList = [];
List<Covid19GetTestProceduresResp> covidTestProcedureList = [];
Covid19GetPaymentInfo? covidPaymentInfo;
Future<void> getOrdersList() async {}
Future<void> getOrdersList() async {}
// HHC multiple services selection
List<GetCMCServicesResponseModel> selectedHhcServices = [];
@ -792,22 +793,20 @@ class HmgServicesViewModel extends ChangeNotifier {
},
);
}
List<CovidQuestionnaireModel> getQuestionsFromJson() {
final String questionsJson = '''[ { "id": 1, "questionEN": "Is the test intended for travel?", "questionAR": "هل تجري التحليل بغرض السفر؟", "ans": 2 }, { "id": 2, "questionEN": "Coming from outside KSA within last 2 weeks?", "questionAR": "هل قدمت من خارج المملكة خلال الأسبوعين الماضيين؟", "ans": 2 }, { "id": 3, "questionEN": "Do you currently have fever?", "questionAR": "هل تعاني حاليا من حرارة؟", "ans": 2 }, { "id": 4, "questionEN": "Did you have fever in last 2 weeks?", "questionAR": "هل عانيت من حرارة في الأسبوعين الماضيين؟", "ans": 2 }, { "id": 5, "questionEN": "Do you have a sore throat?", "questionAR": "هل لديك التهاب في الحلق؟", "ans": 2 }, { "id": 6, "questionEN": "Do you have a runny nose?", "questionAR": "هل لديك سيلان بالأنف؟" }, { "id": 7, "questionEN": "Do you have a cough?", "questionAR": "هل لديك سعال؟", "ans": 2 }, { "id": 8, "questionEN": "Do you have shortness of breath?", "questionAR": "هل تعاني من ضيق في التنفس؟", "ans": 2 }, { "id": 9, "questionEN": "Do you have nausea?", "questionAR": "هل تعاني من غثيان؟", "ans": 2 }, { "id": 10, "questionEN": "Do you have vomiting?", "questionAR": "هل تعاني من القيء؟", "ans": 2 }, { "id": 11, "questionEN": "Do you have a headache?", "questionAR": "هل تعاني من صداع في الرأس؟", "ans": 2 }, { "id": 12, "questionEN": "Do you have muscle pain?", "questionAR": "هل تعانين من آلام عضلية؟", "ans": 2 }, { "id": 13, "questionEN": "Do you have joint pain?", "questionAR": "هل تعاني من آلام المفاصل؟", "ans": 2 }, { "id": 14, "questionEN": "Do you have diarrhea?", "questionAR": "هل لديك اسهال؟", "ans": 2 } ]''';
final String questionsJson =
'''[ { "id": 1, "questionEN": "Is the test intended for travel?", "questionAR": "هل تجري التحليل بغرض السفر؟", "ans": 2 }, { "id": 2, "questionEN": "Coming from outside KSA within last 2 weeks?", "questionAR": "هل قدمت من خارج المملكة خلال الأسبوعين الماضيين؟", "ans": 2 }, { "id": 3, "questionEN": "Do you currently have fever?", "questionAR": "هل تعاني حاليا من حرارة؟", "ans": 2 }, { "id": 4, "questionEN": "Did you have fever in last 2 weeks?", "questionAR": "هل عانيت من حرارة في الأسبوعين الماضيين؟", "ans": 2 }, { "id": 5, "questionEN": "Do you have a sore throat?", "questionAR": "هل لديك التهاب في الحلق؟", "ans": 2 }, { "id": 6, "questionEN": "Do you have a runny nose?", "questionAR": "هل لديك سيلان بالأنف؟" }, { "id": 7, "questionEN": "Do you have a cough?", "questionAR": "هل لديك سعال؟", "ans": 2 }, { "id": 8, "questionEN": "Do you have shortness of breath?", "questionAR": "هل تعاني من ضيق في التنفس؟", "ans": 2 }, { "id": 9, "questionEN": "Do you have nausea?", "questionAR": "هل تعاني من غثيان؟", "ans": 2 }, { "id": 10, "questionEN": "Do you have vomiting?", "questionAR": "هل تعاني من القيء؟", "ans": 2 }, { "id": 11, "questionEN": "Do you have a headache?", "questionAR": "هل تعاني من صداع في الرأس؟", "ans": 2 }, { "id": 12, "questionEN": "Do you have muscle pain?", "questionAR": "هل تعانين من آلام عضلية؟", "ans": 2 }, { "id": 13, "questionEN": "Do you have joint pain?", "questionAR": "هل تعاني من آلام المفاصل؟", "ans": 2 }, { "id": 14, "questionEN": "Do you have diarrhea?", "questionAR": "هل لديك اسهال؟", "ans": 2 } ]''';
try {
final parsed = json.decode(questionsJson) as List<dynamic>;
return parsed
.map((e) => CovidQuestionnaireModel.fromJson(Map<String, dynamic>.from(e)))
.toList();
return parsed.map((e) => CovidQuestionnaireModel.fromJson(Map<String, dynamic>.from(e))).toList();
} catch (_) {
return <CovidQuestionnaireModel>[];
}
}
}
Future<void> getCovidProcedureList({
Function(dynamic)? onSuccess,
Function(String)? onError,
}) async {
@ -816,14 +815,14 @@ class HmgServicesViewModel extends ChangeNotifier {
final result = await hmgServicesRepo.getCovidTestProcedures();
result.fold(
(failure) async {
(failure) async {
notifyListeners();
await errorHandlerService.handleError(failure: failure);
if (onError != null) {
onError(failure.toString());
}
},
(apiResponse) {
(apiResponse) {
if (apiResponse.messageStatus == 1) {
covidTestProcedureList = apiResponse.data ?? [];
notifyListeners();
@ -840,7 +839,6 @@ class HmgServicesViewModel extends ChangeNotifier {
);
}
Future<void> getPaymentInfo({
String? procedureID,
int? projectID,
@ -852,14 +850,14 @@ class HmgServicesViewModel extends ChangeNotifier {
final result = await hmgServicesRepo.getCovidPaymentInfo(procedureID!, projectID!);
result.fold(
(failure) async {
(failure) async {
notifyListeners();
await errorHandlerService.handleError(failure: failure);
if (onError != null) {
onError(failure.toString());
}
},
(apiResponse) {
(apiResponse) {
if (apiResponse.messageStatus == 1) {
covidPaymentInfo = apiResponse.data;
notifyListeners();
@ -900,6 +898,8 @@ class HmgServicesViewModel extends ChangeNotifier {
if (apiResponse.messageStatus == 1) {
vitalSignList = apiResponse.data ?? [];
hasVitalSignDataLoaded = true;
getIt.get<SymptomsCheckerViewModel>().setSelectedHeight(vitalSignList.first.heightCm);
getIt.get<SymptomsCheckerViewModel>().setSelectedWeight(vitalSignList.first.weightKg);
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);

@ -97,7 +97,7 @@ class HospitalSelectionBottomSheetViewModel extends ChangeNotifier {
initPenguinSDK(hospital.iD, clinicID: clinicID);
}
initPenguinSDK(int projectID, {String clinicID = "1"}) async {
initPenguinSDK(int projectID, {String clinicID = ""}) async {
NavigationClinicDetails data = NavigationClinicDetails();
data.projectId = projectID.toString();
data.clinicId = clinicID;

@ -5,6 +5,8 @@ import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
import 'package:dartz/dartz.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/features/lab/models/resp_models/ai_overview_labresults_by_ai_model.dart';
import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_order_response_by_ai_model.dart';
import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart';
import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_special_result.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
@ -13,6 +15,7 @@ import 'models/resp_models/lab_result.dart' show LabResult;
abstract class LabRepo {
Future<Either<Failure, GenericApiModel<List<PatientLabOrdersResponseModel>>>> getPatientLabOrders();
Future<Either<Failure, GenericApiModel<List<LabResult>>>> getPatientLabResults(PatientLabOrdersResponseModel laborder, bool isVidaPlus, String procedureName);
Future<Either<Failure, GenericApiModel<List<LabResult>>>> getPatientLabResultsByHospitals(PatientLabOrdersResponseModel laborder, bool isVidaPlus);
@ -22,6 +25,10 @@ abstract class LabRepo {
Future<Either<Failure, GenericApiModel<String>>> getLabResultReportPDF({required PatientLabOrdersResponseModel labOrder});
Future<Either<Failure, GenericApiModel<dynamic>>> getLabResultsByAppointmentNo({required num appointmentNo, required num projectID, required num clinicID});
Future<Either<Failure, GenericApiModel<LabOrdersResponseByAi>>> getPatientAiOverViewLabOrders(var payload);
Future<Either<Failure, GenericApiModel<LabOrderResponseByAi>>> getPatientAiOverViewLabOrder(var payload);
}
class LabRepoImp implements LabRepo {
@ -71,10 +78,7 @@ class LabRepoImp implements LabRepo {
}
@override
Future<Either<Failure, GenericApiModel<List<LabResult>>>> getPatientLabResults(
PatientLabOrdersResponseModel laborder, bool isVidaPlus, String procedureName
) async {
Future<Either<Failure, GenericApiModel<List<LabResult>>>> getPatientLabResults(PatientLabOrdersResponseModel laborder, bool isVidaPlus, String procedureName) async {
Map<String, dynamic> request = Map();
request['InvoiceNo_VP'] = isVidaPlus ? laborder!.invoiceNo : "0";
request['InvoiceNo'] = isVidaPlus ? "0" : laborder!.invoiceNo;
@ -100,10 +104,7 @@ class LabRepoImp implements LabRepo {
throw Exception("lab list is empty");
}
final labOrders = list
.map((item) => LabResult.fromJson(item as Map<String, dynamic>))
.toList()
.cast<LabResult>();
final labOrders = list.map((item) => LabResult.fromJson(item as Map<String, dynamic>)).toList().cast<LabResult>();
apiResponse = GenericApiModel<List<LabResult>>(
messageStatus: messageStatus,
@ -125,9 +126,7 @@ class LabRepoImp implements LabRepo {
}
@override
Future<Either<Failure, GenericApiModel<List<LabResult>>>>
getPatientLabResultsByHospitals(
PatientLabOrdersResponseModel laborder, bool isVidaPlus) async {
Future<Either<Failure, GenericApiModel<List<LabResult>>>> getPatientLabResultsByHospitals(PatientLabOrdersResponseModel laborder, bool isVidaPlus) async {
Map<String, dynamic> request = Map();
request['InvoiceNo_VP'] = isVidaPlus ? laborder!.invoiceNo : "0";
request['InvoiceNo'] = isVidaPlus ? "0" : laborder!.invoiceNo;
@ -175,9 +174,7 @@ class LabRepoImp implements LabRepo {
}
@override
Future<Either<Failure, GenericApiModel<List<PatientLabSpecialResult>>>>
getSpecialLabResult(
PatientLabOrdersResponseModel laborder, bool isVidaPlus) async {
Future<Either<Failure, GenericApiModel<List<PatientLabSpecialResult>>>> getSpecialLabResult(PatientLabOrdersResponseModel laborder, bool isVidaPlus) async {
Map<String, dynamic> request = Map();
request['InvoiceNo_VP'] = isVidaPlus ? laborder!.invoiceNo : "0";
request['InvoiceNo'] = isVidaPlus ? "0" : laborder!.invoiceNo;
@ -203,11 +200,7 @@ class LabRepoImp implements LabRepo {
throw Exception("lab list is empty");
}
final labOrders = list
.map((item) => PatientLabSpecialResult.fromJson(
item as Map<String, dynamic>))
.toList()
.cast<PatientLabSpecialResult>();
final labOrders = list.map((item) => PatientLabSpecialResult.fromJson(item as Map<String, dynamic>)).toList().cast<PatientLabSpecialResult>();
apiResponse = GenericApiModel<List<PatientLabSpecialResult>>(
messageStatus: messageStatus,
@ -318,4 +311,76 @@ class LabRepoImp implements LabRepo {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<LabOrderResponseByAi>>> getPatientAiOverViewLabOrder(var labOrder) async {
try {
GenericApiModel<LabOrderResponseByAi>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.getAiOverViewLabOrder,
body: labOrder,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
var res;
if (response["analysis_result"] != null) {
res = response["analysis_result"];
}
apiResponse = GenericApiModel<LabOrderResponseByAi>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: LabOrderResponseByAi.fromJson(res),
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<LabOrdersResponseByAi>>> getPatientAiOverViewLabOrders(var labOrder) async {
try {
GenericApiModel<LabOrdersResponseByAi>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.getAiOverViewLabOrders,
body: {"lab_results": labOrder},
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
var res;
if (response["lab_analysis_result"] != null) {
res = response["lab_analysis_result"];
}
apiResponse = GenericApiModel<LabOrdersResponseByAi>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: LabOrdersResponseByAi.fromJson(res),
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
}

@ -1,16 +1,24 @@
import 'dart:collection';
import 'dart:convert';
import 'dart:core';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/common_models/data_points.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/loading_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart' show Utils;
import 'package:hmg_patient_app_new/features/lab/lab_repo.dart';
import 'package:hmg_patient_app_new/features/lab/models/ai_overview_labresult_model_payload.dart';
import 'package:hmg_patient_app_new/features/lab/models/resp_models/ai_overview_labresults_by_ai_model.dart';
import 'package:hmg_patient_app_new/features/lab/models/resp_models/lab_result.dart';
import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_order_response_by_ai_model.dart';
import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_results/lab_result_details.dart';
import 'package:hmg_patient_app_new/presentation/labAiAnalysis/lab_ai_analysis_detailed_page.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
@ -53,20 +61,7 @@ class LabViewModel extends ChangeNotifier {
List<LabResult> mainLabResults = [];
List<DataPoint> mainGraphPoints = [];
List<DataPoint> filteredGraphValues = [];
List months = [
'Jan',
'Feb',
'Mar',
'April',
'May',
'Jun',
'July',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
];
List months = ['Jan', 'Feb', 'Mar', 'April', 'May', 'Jun', 'July', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
late List<String> _labSuggestionsList = [];
@ -86,22 +81,22 @@ class LabViewModel extends ChangeNotifier {
List<List<PatientLabOrdersResponseModel>> patientLabOrdersByHospital = [];
List<List<PatientLabOrdersResponseModel>> patientLabOrdersViewList = [];
LabViewModel(
{required this.labRepo,
required this.errorHandlerService,
required this.navigationService});
LabOrderResponseByAi? labOrderResponseByAi;
LabOrdersResponseByAi? labOrdersResponseByAi;
LabViewModel({required this.labRepo, required this.errorHandlerService, required this.navigationService});
initLabProvider() {
// if (isLabNeedToLoad) {
patientLabOrders.clear();
filteredLabOrders.clear();
labOrderTests.clear();
isLabOrdersLoading = true;
isLabResultsLoading = true;
patientLabOrdersByClinic.clear();
patientLabOrdersByHospital.clear();
patientLabOrdersViewList.clear();
getPatientLabOrders();
patientLabOrders.clear();
filteredLabOrders.clear();
labOrderTests.clear();
isLabOrdersLoading = true;
isLabResultsLoading = true;
patientLabOrdersByClinic.clear();
patientLabOrdersByHospital.clear();
patientLabOrdersViewList.clear();
getPatientLabOrders();
// }
notifyListeners();
}
@ -133,13 +128,13 @@ class LabViewModel extends ChangeNotifier {
final result = await labRepo.getPatientLabOrders();
result.fold(
(failure) async {
isLabOrdersLoading = false;
isLabResultsLoading = false;
notifyListeners();
},
// => await errorHandler_service.handleError(failure: failure),
(apiResponse) {
(failure) async {
isLabOrdersLoading = false;
isLabResultsLoading = false;
notifyListeners();
},
// => await errorHandler_service.handleError(failure: failure),
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
@ -203,11 +198,7 @@ class LabViewModel extends ChangeNotifier {
}
filterSuggestions() {
final List<String> labels = patientLabOrders
.expand((order) => order.testDetails ?? [])
.map((detail) => detail.description)
.whereType<String>()
.toList();
final List<String> labels = patientLabOrders.expand((order) => order.testDetails ?? []).map((detail) => detail.description).whereType<String>().toList();
_labSuggestionsList = labels.toSet().toList();
notifyListeners();
@ -322,14 +313,12 @@ class LabViewModel extends ChangeNotifier {
);
}
Future<void> getPatientLabResultByHospital(
PatientLabOrdersResponseModel laborder) async {
Future<void> getPatientLabResultByHospital(PatientLabOrdersResponseModel laborder) async {
isLabResultByHospitalLoading = true;
notifyListeners();
mainLabResultsByHospitals.clear();
final result = await labRepo.getPatientLabResultsByHospitals(laborder,
Utils.isVidaPlusProject(int.parse(laborder.projectID ?? "0")));
final result = await labRepo.getPatientLabResultsByHospitals(laborder, Utils.isVidaPlusProject(int.parse(laborder.projectID ?? "0")));
result.fold(
(failure) async {
@ -353,12 +342,9 @@ class LabViewModel extends ChangeNotifier {
mainLabResults.clear();
filteredGraphValues.clear();
maxY = double.negativeInfinity;
minY = double.infinity;
maxX = double.infinity;
final result = await labRepo.getPatientLabResults(
laborder,
Utils.isVidaPlusProject(int.parse(laborder.projectID ?? "0")),
procedureName);
minY = double.infinity;
maxX = double.infinity;
final result = await labRepo.getPatientLabResults(laborder, Utils.isVidaPlusProject(int.parse(laborder.projectID ?? "0")), procedureName);
result.fold(
(failure) async {
@ -372,7 +358,6 @@ class LabViewModel extends ChangeNotifier {
var recentThree = sort(apiResponse.data!);
mainLabResults = recentThree;
double highRefrenceValue = double.negativeInfinity;
String? flagForHighReferenceRange;
double lowRefenceValue = double.infinity;
@ -380,11 +365,10 @@ class LabViewModel extends ChangeNotifier {
recentThree.reversed.forEach((element) {
try {
var dateTime =
DateUtil.convertStringToDate(element.verifiedOnDateTime!);
var dateTime = DateUtil.convertStringToDate(element.verifiedOnDateTime!);
var resultValue = double.parse(element.resultValue!);
var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??"");
if (resultValue>maxY) {
var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag ?? "");
if (resultValue > maxY) {
maxY = resultValue;
maxX = maxY;
}
@ -401,14 +385,13 @@ class LabViewModel extends ChangeNotifier {
}
filteredGraphValues.add(DataPoint(
value: transformedValue,
actualValue:element.resultValue!,
label: formatDateAsMMYY(dateTime),
unitOfMeasurement: element.uOM,
displayTime: resultDate(dateTime),
time: DateUtil.convertStringToDate(element.verifiedOnDateTime),
referenceValue: element.calculatedResultFlag ?? "",
value: transformedValue,
actualValue: element.resultValue!,
label: formatDateAsMMYY(dateTime),
unitOfMeasurement: element.uOM,
displayTime: resultDate(dateTime),
time: DateUtil.convertStringToDate(element.verifiedOnDateTime),
referenceValue: element.calculatedResultFlag ?? "",
));
} catch (e) {}
});
@ -424,7 +407,7 @@ class LabViewModel extends ChangeNotifier {
this.highRefrenceValue = double.parse(highRefrenceValue.toStringAsFixed(1));
this.lowRefenceValue = double.parse(lowRefenceValue.toStringAsFixed(1));
if(maxY< highRefrenceValue) {
if (maxY < highRefrenceValue) {
maxY = highRefrenceValue;
}
maxY += 25;
@ -434,7 +417,9 @@ class LabViewModel extends ChangeNotifier {
checkIfGraphShouldBeDisplayed(recentResult);
recentResult.verifiedOn = resultDate(DateUtil.convertStringToDate(recentResult.verifiedOnDateTime!));
// filteredGraphValues = [filteredGraphValues.first];
navigationService.push(MaterialPageRoute(
labOrderResponseByAi = null;
navigationService.push(
MaterialPageRoute(
builder: (_) => LabResultDetails(recentLabResult: recentResult, testDescription: testDescription),
),
);
@ -444,14 +429,13 @@ class LabViewModel extends ChangeNotifier {
);
}
void checkIfGraphShouldBeDisplayed(LabResult recentResult){
void checkIfGraphShouldBeDisplayed(LabResult recentResult) {
shouldShowGraph = recentResult.checkIfGraphShouldBeDisplayed();
isGraphVisible = shouldShowGraph;
notifyListeners();
}
Future<void> getPatientSpecialResult(
PatientLabOrdersResponseModel laborder) async {
Future<void> getPatientSpecialResult(PatientLabOrdersResponseModel laborder) async {
isSpecialResultsLoading = true;
labSpecialResult = "";
notifyListeners();
@ -473,8 +457,7 @@ class LabViewModel extends ChangeNotifier {
StringBuffer htmlbuffer = StringBuffer("");
apiResponse.data?.forEach((element) {
if(element.resultDataHTML != null && element.resultDataHTML?.isNotEmpty == true)
htmlbuffer.write("${element.resultDataHTML} <br/> <br/>");
if (element.resultDataHTML != null && element.resultDataHTML?.isNotEmpty == true) htmlbuffer.write("${element.resultDataHTML} <br/> <br/>");
});
labSpecialResult = htmlbuffer.toString();
@ -486,10 +469,8 @@ class LabViewModel extends ChangeNotifier {
);
}
String resultDate(DateTime date){
return '${date.day} ${months[date.month-1]},${date.year}';
String resultDate(DateTime date) {
return '${date.day} ${months[date.month - 1]},${date.year}';
}
double transformValueInRange(double inputValue, String flag) {
@ -497,7 +478,7 @@ class LabViewModel extends ChangeNotifier {
double rangeStart, rangeEnd;
switch (flag) {
case'LCL':
case 'LCL':
case 'CL':
rangeStart = 0.0;
rangeEnd = 19.0;
@ -534,16 +515,17 @@ class LabViewModel extends ChangeNotifier {
debugPrint("the transformed value is $transformedValue");
return transformedValue;
}
void getSelectedDateRange(DateTime? start, DateTime? end) {
maxY = double.negativeInfinity;
if(start == null && end == null) {
if (start == null && end == null) {
mainLabResults.forEach((element) {
final time = DateUtil.convertStringToDate(element.verifiedOnDateTime!);
try{
try {
var resultValue = double.parse(element.resultValue!);
var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??"");
var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag ?? "");
if (resultValue > maxY) {
maxY = resultValue;
}
@ -552,54 +534,43 @@ class LabViewModel extends ChangeNotifier {
actualValue: element.resultValue!,
label: formatDateAsMMYY(time),
unitOfMeasurement: element.uOM,
displayTime: resultDate(time),
time: DateUtil.convertStringToDate(element.verifiedOnDateTime),
referenceValue: element.calculatedResultFlag ?? "",
));
}catch(e){
}
} catch (e) {}
});
}else {
} else {
filteredGraphValues.clear();
mainLabResults.forEach((element) {
try {
var dateTime =
DateUtil.convertStringToDate(element.verifiedOnDateTime!);
var dateTime = DateUtil.convertStringToDate(element.verifiedOnDateTime!);
var resultValue = double.parse(element.resultValue!);
var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??"");
var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag ?? "");
if (resultValue > maxY) {
maxY = resultValue;
}
if (start != null && end == null) {
if (dateTime.isAtSameMomentAs(start)) {
filteredGraphValues.add(DataPoint(
value: transformedValue,
actualValue: element.resultValue!,
unitOfMeasurement: element.uOM,
label: formatDateAsMMYY(dateTime),
displayTime: resultDate(dateTime),
time:
DateUtil.convertStringToDate(element.verifiedOnDateTime),
time: DateUtil.convertStringToDate(element.verifiedOnDateTime),
referenceValue: element.calculatedResultFlag ?? ""));
}
} else if (start != null && end != null) {
if ((dateTime.isAfter(start)) && (dateTime.isBefore(end))) {
filteredGraphValues.add(DataPoint(
value: transformedValue,
actualValue: element.resultValue!,
label: formatDateAsMMYY(dateTime),
unitOfMeasurement: element.uOM,
displayTime: resultDate(dateTime),
time:
DateUtil.convertStringToDate(element.verifiedOnDateTime),
time: DateUtil.convertStringToDate(element.verifiedOnDateTime),
referenceValue: element.calculatedResultFlag ?? ""));
}
}
@ -608,16 +579,13 @@ class LabViewModel extends ChangeNotifier {
}
filteredGraphValues = sortFilteredList(filteredGraphValues).reversed.toList();
notifyListeners();
}
String formatDateAsMMYY(DateTime date) {
return '${months[date.month-1]}, ${date.year}';
return '${months[date.month - 1]}, ${date.year}';
}
List<LabResult> sortByFlagAndValue(List<LabResult> original) {
const priorityOrder = ['LCL', 'CL', 'L', 'N', 'H', 'CH', 'HCH'];
@ -651,7 +619,6 @@ class LabViewModel extends ChangeNotifier {
}
List<LabResult> sort(List<LabResult> original) {
final copy = List<LabResult>.from(original);
copy.sort((a, b) {
final aDate = DateUtil.convertStringToDate(a.verifiedOnDateTime);
@ -666,8 +633,6 @@ class LabViewModel extends ChangeNotifier {
}
List<DataPoint> sortFilteredList(List<DataPoint> original) {
final copy = List<DataPoint>.from(original);
copy.sort((a, b) {
final aDate = a.time;
@ -702,7 +667,7 @@ class LabViewModel extends ChangeNotifier {
}
}
String getFormattedDate(DateTime date){
String getFormattedDate(DateTime date) {
return DateFormat('EEEE, dd MMMM. yyyy').format(date);
}
@ -716,7 +681,7 @@ class LabViewModel extends ChangeNotifier {
case 'N':
return AppAssets.ic_normal_result;
case 'H':
return AppAssets.ic_low_result;
return AppAssets.ic_low_result;
case 'CH':
case 'HCH':
return AppAssets.ic_critical_low_result;
@ -758,8 +723,80 @@ class LabViewModel extends ChangeNotifier {
}
}
alterGraphVisibility(){
alterGraphVisibility() {
isGraphVisible = !isGraphVisible;
notifyListeners();
}
Future<void> getAiOverviewLabOrders({required PatientLabOrdersResponseModel labOrder}) async {
LoadingUtils.showFullScreenLoader(loadingText: "Loading and analysing your data,\nPlease be patient and let the AI do the magic");
List<Map<String, dynamic>> results = [];
Map<String, dynamic> orderData = {"order_date": labOrder.orderDate ?? "", "clinic": labOrder.clinicDescription ?? "", "doctor": labOrder.doctorName ?? "", "results": []};
List<Map<String, dynamic>> testResults = [];
if (labOrder.testDetails != null && labOrder.testDetails!.isNotEmpty) {
for (var testDetail in labOrder.testDetails!) {
LabResult? matchingResult =
mainLabResultsByHospitals.firstWhere((labResult) => labResult.testCode == testDetail.testCode || labResult.description == testDetail.description, orElse: () => LabResult());
testResults.add({
"test_name": testDetail.description ?? "",
"result_value": matchingResult.resultValue ?? "",
"reference_range": matchingResult.referanceRange ?? "",
"unit": matchingResult.uOM ?? "",
"flag": matchingResult.calculatedResultFlag ?? "N"
});
}
}
orderData["results"] = testResults;
results.add(orderData);
final result = await labRepo.getPatientAiOverViewLabOrders(results);
result.fold(
(failure) async {
LoadingUtils.hideFullScreenLoader();
await errorHandlerService.handleError(failure: failure);
},
(apiResponse) {
LoadingUtils.hideFullScreenLoader();
if (apiResponse.messageStatus == 2) {
} else if (apiResponse.messageStatus == 1) {
labOrdersResponseByAi = apiResponse.data;
navigationService.push(
MaterialPageRoute(builder: (_) => LabAiAnalysisDetailedPage()),
);
}
},
);
LoadingUtils.hideFullScreenLoader();
}
Future<void> getAiOverviewSingleLabResult({required String langId, required LabResult recentLabResult}) async {
LoaderBottomSheet.showLoader();
List<Map<String, dynamic>> results = [];
results.add({
"Description": recentLabResult.description ?? '',
"ResultValue": recentLabResult.resultValue ?? '',
"CalculatedResultFlag": recentLabResult.calculatedResultFlag ?? '',
"UOM": recentLabResult.uOM ?? '',
"ReferanceRange": recentLabResult.referanceRange ?? '',
});
var payload = {"patient_id": currentlySelectedPatientOrder!.patientID, "language_id": langId, "lab_results": results};
final result = await labRepo.getPatientAiOverViewLabOrder(payload);
result.fold(
(failure) async {
LoaderBottomSheet.hideLoader();
await errorHandlerService.handleError(failure: failure);
},
(apiResponse) {
LoaderBottomSheet.hideLoader();
if (apiResponse.messageStatus == 2) {
} else if (apiResponse.messageStatus == 1) {
labOrderResponseByAi = apiResponse.data;
notifyListeners();
}
},
);
LoaderBottomSheet.hideLoader();
}
}

@ -0,0 +1,69 @@
import 'dart:convert';
class AiLabOrdersOverView {
String? orderDate;
String? clinic;
String? doctor;
List<Result>? results;
AiLabOrdersOverView({
this.orderDate,
this.clinic,
this.doctor,
this.results,
});
factory AiLabOrdersOverView.fromRawJson(String str) => AiLabOrdersOverView.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory AiLabOrdersOverView.fromJson(Map<String, dynamic> json) => AiLabOrdersOverView(
orderDate: json["order_date"] == null ? null : json["order_date"],
clinic: json["clinic"],
doctor: json["doctor"],
results: json["results"] == null ? [] : List<Result>.from(json["results"]!.map((x) => Result.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"order_date": orderDate,
"clinic": clinic,
"doctor": doctor,
"results": results == null ? [] : List<dynamic>.from(results!.map((x) => x.toJson())),
};
}
class Result {
String? testName;
String? resultValue;
String? referenceRange;
String? unit;
String? flag;
Result({
this.testName,
this.resultValue,
this.referenceRange,
this.unit,
this.flag,
});
factory Result.fromRawJson(String str) => Result.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory Result.fromJson(Map<String, dynamic> json) => Result(
testName: json["test_name"],
resultValue: json["result_value"],
referenceRange: json["reference_range"],
unit: json["unit"],
flag: json["flag"],
);
Map<String, dynamic> toJson() => {
"test_name": testName,
"result_value": resultValue,
"reference_range": referenceRange,
"unit": unit,
"flag": flag,
};
}

@ -0,0 +1,177 @@
import 'dart:convert';
class LabOrdersResponseByAi {
Analysis? analysis;
String? patientId;
int? totalOrders;
int? totalResults;
LabOrdersResponseByAi({
this.analysis,
this.patientId,
this.totalOrders,
this.totalResults,
});
factory LabOrdersResponseByAi.fromRawJson(String str) => LabOrdersResponseByAi.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory LabOrdersResponseByAi.fromJson(Map<String, dynamic> json) => LabOrdersResponseByAi(
analysis: json["analysis"] == null ? null : Analysis.fromJson(json["analysis"]),
patientId: json["patient_id"],
totalOrders: json["total_orders"],
totalResults: json["total_results"],
);
Map<String, dynamic> toJson() => {
"analysis": analysis?.toJson(),
"patient_id": patientId,
"total_orders": totalOrders,
"total_results": totalResults,
};
}
class Analysis {
List<AiGeneratedAttribute>? aiGeneratedAttributes;
List<String>? aiPredictions;
List<AiRecommendation>? aiRecommendations;
List<String>? prevention;
String? rawMessage;
List<RiskFactor>? riskFactors;
List<dynamic>? suggestedDoctors;
String? summary;
Analysis({
this.aiGeneratedAttributes,
this.aiPredictions,
this.aiRecommendations,
this.prevention,
this.rawMessage,
this.riskFactors,
this.suggestedDoctors,
this.summary,
});
factory Analysis.fromRawJson(String str) => Analysis.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory Analysis.fromJson(Map<String, dynamic> json) => Analysis(
aiGeneratedAttributes: json["ai_generated_attributes"] == null ? [] : List<AiGeneratedAttribute>.from(json["ai_generated_attributes"]!.map((x) => AiGeneratedAttribute.fromJson(x))),
aiPredictions: json["ai_predictions"] == null ? [] : List<String>.from(json["ai_predictions"]!.map((x) => x)),
aiRecommendations: json["ai_recommendations"] == null ? [] : List<AiRecommendation>.from(json["ai_recommendations"]!.map((x) => AiRecommendation.fromJson(x))),
prevention: json["prevention"] == null ? [] : List<String>.from(json["prevention"]!.map((x) => x)),
rawMessage: json["raw_message"],
riskFactors: json["riskFactors"] == null ? [] : List<RiskFactor>.from(json["riskFactors"]!.map((x) => RiskFactor.fromJson(x))),
suggestedDoctors: json["suggested_doctors"] == null ? [] : List<dynamic>.from(json["suggested_doctors"]!.map((x) => x)),
summary: json["summary"],
);
Map<String, dynamic> toJson() => {
"ai_generated_attributes": aiGeneratedAttributes == null ? [] : List<dynamic>.from(aiGeneratedAttributes!.map((x) => x.toJson())),
"ai_predictions": aiPredictions == null ? [] : List<dynamic>.from(aiPredictions!.map((x) => x)),
"ai_recommendations": aiRecommendations == null ? [] : List<dynamic>.from(aiRecommendations!.map((x) => x.toJson())),
"prevention": prevention == null ? [] : List<dynamic>.from(prevention!.map((x) => x)),
"raw_message": rawMessage,
"riskFactors": riskFactors == null ? [] : List<dynamic>.from(riskFactors!.map((x) => x.toJson())),
"suggested_doctors": suggestedDoctors == null ? [] : List<dynamic>.from(suggestedDoctors!.map((x) => x)),
"summary": summary,
};
}
class AiGeneratedAttribute {
String? calculatedResultFlag;
String? description;
String? referanceRange;
String? resultValue;
String? testShortDescription;
String? uom;
String? calculationFormula;
AiGeneratedAttribute({
this.calculatedResultFlag,
this.description,
this.referanceRange,
this.resultValue,
this.testShortDescription,
this.uom,
this.calculationFormula,
});
factory AiGeneratedAttribute.fromRawJson(String str) => AiGeneratedAttribute.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory AiGeneratedAttribute.fromJson(Map<String, dynamic> json) => AiGeneratedAttribute(
calculatedResultFlag: json["CalculatedResultFlag"],
description: json["Description"],
referanceRange: json["ReferanceRange"],
resultValue: json["ResultValue"],
testShortDescription: json["TestShortDescription"],
uom: json["UOM"],
calculationFormula: json["calculationFormula"],
);
Map<String, dynamic> toJson() => {
"CalculatedResultFlag": calculatedResultFlag,
"Description": description,
"ReferanceRange": referanceRange,
"ResultValue": resultValue,
"TestShortDescription": testShortDescription,
"UOM": uom,
"calculationFormula": calculationFormula,
};
}
class AiRecommendation {
String? priority;
String? recommendation;
AiRecommendation({
this.priority,
this.recommendation,
});
factory AiRecommendation.fromRawJson(String str) => AiRecommendation.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory AiRecommendation.fromJson(Map<String, dynamic> json) => AiRecommendation(
priority: json["priority"],
recommendation: json["recommendation"],
);
Map<String, dynamic> toJson() => {
"priority": priority,
"recommendation": recommendation,
};
}
class RiskFactor {
String? description;
String? factor;
String? risk;
RiskFactor({
this.description,
this.factor,
this.risk,
});
factory RiskFactor.fromRawJson(String str) => RiskFactor.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory RiskFactor.fromJson(Map<String, dynamic> json) => RiskFactor(
description: json["description"],
factor: json["factor"],
risk: json["risk"],
);
Map<String, dynamic> toJson() => {
"description": description,
"factor": factor,
"risk": risk,
};
}

@ -26,6 +26,7 @@ class LabResult {
String? referenceLow;
num? resultTypeID;
String? packageShortDescription;
int? testId;
LabResult(
{this.description,
@ -52,6 +53,7 @@ class LabResult {
this.referenceHigh,
this.criticalLow,
this.referenceLow,
this.testId,
});
LabResult.fromJson(Map<String, dynamic> json) {
@ -82,6 +84,7 @@ class LabResult {
referenceLow = json['ReferenceLow'];
packageShortDescription = json['PackageShortDescription'];
resultTypeID = json['ResultTypeID'];
testId = json['TestID'];
}
Map<String, dynamic> toJson() {
@ -110,6 +113,7 @@ class LabResult {
data['ReferenceHigh'] = this.referenceHigh;
data['CriticalLow'] = this.criticalLow;
data['ReferenceLow'] = this.referenceLow;
data['TestID'] = this.testId;
return data;
}

@ -0,0 +1,145 @@
import 'dart:convert';
class LabOrderResponseByAi {
String? summary;
List<AiGeneratedAttribute>? aiGeneratedAttributes;
List<String>? aiPredictions;
List<AiRecommendation>? aiRecommendations;
List<RiskFactor>? riskFactors;
List<String>? prevention;
List<dynamic>? suggestedDoctors;
String? rawMessage;
LabOrderResponseByAi({
this.summary,
this.aiGeneratedAttributes,
this.aiPredictions,
this.aiRecommendations,
this.riskFactors,
this.prevention,
this.suggestedDoctors,
this.rawMessage,
});
factory LabOrderResponseByAi.fromRawJson(String str) => LabOrderResponseByAi.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory LabOrderResponseByAi.fromJson(Map<String, dynamic> json) => LabOrderResponseByAi(
summary: json["summary"],
aiGeneratedAttributes: json["ai_generated_attributes"] == null ? [] : List<AiGeneratedAttribute>.from(json["ai_generated_attributes"]!.map((x) => AiGeneratedAttribute.fromJson(x))),
aiPredictions: json["ai_predictions"] == null ? [] : List<String>.from(json["ai_predictions"]!.map((x) => x)),
aiRecommendations: json["ai_recommendations"] == null ? [] : List<AiRecommendation>.from(json["ai_recommendations"]!.map((x) => AiRecommendation.fromJson(x))),
riskFactors: json["riskFactors"] == null ? [] : List<RiskFactor>.from(json["riskFactors"]!.map((x) => RiskFactor.fromJson(x))),
prevention: json["prevention"] == null ? [] : List<String>.from(json["prevention"]!.map((x) => x)),
suggestedDoctors: json["suggested_doctors"] == null ? [] : List<dynamic>.from(json["suggested_doctors"]!.map((x) => x)),
rawMessage: json["raw_message"],
);
Map<String, dynamic> toJson() => {
"summary": summary,
"ai_generated_attributes": aiGeneratedAttributes == null ? [] : List<dynamic>.from(aiGeneratedAttributes!.map((x) => x.toJson())),
"ai_predictions": aiPredictions == null ? [] : List<dynamic>.from(aiPredictions!.map((x) => x)),
"ai_recommendations": aiRecommendations == null ? [] : List<dynamic>.from(aiRecommendations!.map((x) => x.toJson())),
"riskFactors": riskFactors == null ? [] : List<dynamic>.from(riskFactors!.map((x) => x.toJson())),
"prevention": prevention == null ? [] : List<dynamic>.from(prevention!.map((x) => x)),
"suggested_doctors": suggestedDoctors == null ? [] : List<dynamic>.from(suggestedDoctors!.map((x) => x)),
"raw_message": rawMessage,
};
}
class AiGeneratedAttribute {
String? description;
String? resultValue;
String? referanceRange;
String? calculatedResultFlag;
String? uom;
String? calculationFormula;
String? testShortDescription;
AiGeneratedAttribute({
this.description,
this.resultValue,
this.referanceRange,
this.calculatedResultFlag,
this.uom,
this.calculationFormula,
this.testShortDescription,
});
factory AiGeneratedAttribute.fromRawJson(String str) => AiGeneratedAttribute.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory AiGeneratedAttribute.fromJson(Map<String, dynamic> json) => AiGeneratedAttribute(
description: json["Description"],
resultValue: json["ResultValue"],
referanceRange: json["ReferanceRange"],
calculatedResultFlag: json["CalculatedResultFlag"],
uom: json["UOM"],
calculationFormula: json["calculationFormula"],
testShortDescription: json["TestShortDescription"],
);
Map<String, dynamic> toJson() => {
"Description": description,
"ResultValue": resultValue,
"ReferanceRange": referanceRange,
"CalculatedResultFlag": calculatedResultFlag,
"UOM": uom,
"calculationFormula": calculationFormula,
"TestShortDescription": testShortDescription,
};
}
class AiRecommendation {
String? recommendation;
String? priority;
AiRecommendation({
this.recommendation,
this.priority,
});
factory AiRecommendation.fromRawJson(String str) => AiRecommendation.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory AiRecommendation.fromJson(Map<String, dynamic> json) => AiRecommendation(
recommendation: json["recommendation"],
priority: json["priority"],
);
Map<String, dynamic> toJson() => {
"recommendation": recommendation,
"priority": priority,
};
}
class RiskFactor {
String? factor;
String? risk;
String? description;
RiskFactor({
this.factor,
this.risk,
this.description,
});
factory RiskFactor.fromRawJson(String str) => RiskFactor.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory RiskFactor.fromJson(Map<String, dynamic> json) => RiskFactor(
factor: json["factor"],
risk: json["risk"],
description: json["description"],
);
Map<String, dynamic> toJson() => {
"factor": factor,
"risk": risk,
"description": description,
};
}

@ -510,7 +510,7 @@ class MyAppointmentsViewModel extends ChangeNotifier {
}
Future<void> getPatientMyDoctors({Function(dynamic)? onSuccess, Function(String)? onError}) async {
if (!isAppointmentDataToBeLoaded) return;
// if (!isAppointmentDataToBeLoaded) return;
isPatientMyDoctorsLoading = true;
notifyListeners();

@ -0,0 +1,54 @@
class ServicesPriceListResponseModel {
int? createdBy;
String? createdOn;
int? editedBy;
String? editedOn;
int? id;
bool? isEnabled;
String? nameAR;
String? nameEN;
num? price;
int? rowID;
ServicesPriceListResponseModel({
this.createdBy,
this.createdOn,
this.editedBy,
this.editedOn,
this.id,
this.isEnabled,
this.nameAR,
this.nameEN,
this.price,
this.rowID,
});
ServicesPriceListResponseModel.fromJson(Map<String, dynamic> json) {
createdBy = json['CreatedBy'];
createdOn = json['CreatedOn'];
editedBy = json['EditedBy'];
editedOn = json['EditedOn'];
id = json['ID'];
isEnabled = json['IsEnabled'];
nameAR = json['NameAR'];
nameEN = json['NameEN'];
price = json['Price'];
rowID = json['RowID'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['CreatedBy'] = createdBy;
data['CreatedOn'] = createdOn;
data['EditedBy'] = editedBy;
data['EditedOn'] = editedOn;
data['ID'] = id;
data['IsEnabled'] = isEnabled;
data['NameAR'] = nameAR;
data['NameEN'] = nameEN;
data['Price'] = price;
data['RowID'] = rowID;
return data;
}
}

@ -0,0 +1,80 @@
import 'package:dartz/dartz.dart';
import 'package:hmg_patient_app_new/core/api/api_client.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
import 'package:hmg_patient_app_new/features/services_price_list/models/resp_models/services_price_list_response_model.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
abstract class ServicesPriceListRepo {
Future<Either<Failure, GenericApiModel<List<ServicesPriceListResponseModel>>>> getServicesPriceList({
String searchKey = "",
int pageIndex = 0,
int pageSize = 0,
});
}
class ServicesPriceListRepoImp implements ServicesPriceListRepo {
final ApiClient apiClient;
final LoggerService loggerService;
ServicesPriceListRepoImp({required this.loggerService, required this.apiClient});
@override
Future<Either<Failure, GenericApiModel<List<ServicesPriceListResponseModel>>>> getServicesPriceList({
String searchKey = "",
int pageIndex = 0,
int pageSize = 0,
}) async {
Map<String, dynamic> mapDevice = {
"ID": 1,
"SearchKey": searchKey,
"PageIndex": pageIndex,
"PageSize": pageSize,
"RowCount": 0,
"TokenID": "@dm!n"
};
try {
GenericApiModel<List<ServicesPriceListResponseModel>>? apiResponse;
Failure? failure;
await apiClient.post(
GET_SERVICES_PRICE_LIST,
body: mapDevice,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['getServicesPriceList'];
if (list == null || list.isEmpty) {
throw Exception("Services price list is empty");
}
final servicesList = list
.map((item) => ServicesPriceListResponseModel.fromJson(item as Map<String, dynamic>))
.toList()
.cast<ServicesPriceListResponseModel>();
apiResponse = GenericApiModel<List<ServicesPriceListResponseModel>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: servicesList,
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
}

@ -0,0 +1,87 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/features/services_price_list/models/resp_models/services_price_list_response_model.dart';
import 'package:hmg_patient_app_new/features/services_price_list/services_price_list_repo.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
class ServicesPriceListViewModel extends ChangeNotifier {
bool isServicesPriceListLoading = false;
ServicesPriceListRepo servicesPriceListRepo;
ErrorHandlerService errorHandlerService;
List<ServicesPriceListResponseModel> servicesPriceList = [];
List<ServicesPriceListResponseModel> filteredServicesPriceList = [];
String searchKey = "";
ServicesPriceListViewModel({
required this.servicesPriceListRepo,
required this.errorHandlerService,
});
initServicesPriceListProvider() {
servicesPriceList.clear();
filteredServicesPriceList.clear();
isServicesPriceListLoading = true;
getServicesPriceList();
notifyListeners();
}
setIsServicesPriceListLoading(bool val) {
isServicesPriceListLoading = val;
notifyListeners();
}
setSearchKey(String val) {
searchKey = val;
filterServicesList(val);
}
void filterServicesList(String query) {
if (query.isEmpty) {
filteredServicesPriceList = List.from(servicesPriceList);
} else {
filteredServicesPriceList = servicesPriceList.where((service) {
final nameEN = service.nameEN?.toLowerCase() ?? '';
final nameAR = service.nameAR?.toLowerCase() ?? '';
final searchLower = query.toLowerCase();
return nameEN.contains(searchLower) || nameAR.contains(searchLower);
}).toList();
}
notifyListeners();
}
Future<void> getServicesPriceList({Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await servicesPriceListRepo.getServicesPriceList(
searchKey: searchKey,
);
result.fold(
(failure) async {
isServicesPriceListLoading = false;
notifyListeners();
if (onError != null) {
onError(failure.toString());
}
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
isServicesPriceListLoading = false;
notifyListeners();
if (onError != null) {
onError(apiResponse.errorMessage ?? "Error loading services price list");
}
} else if (apiResponse.messageStatus == 1) {
servicesPriceList = apiResponse.data!;
servicesPriceList.removeWhere((element) => element.isEnabled == false);
filteredServicesPriceList = List.from(servicesPriceList);
isServicesPriceListLoading = false;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
}

@ -84,9 +84,9 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
String? _selectedGender;
DateTime? _dateOfBirth;
int? _selectedAge;
double _selectedHeight = 170;
num _selectedHeight = 170;
bool _isHeightCm = true;
double _selectedWeight = 60;
num _selectedWeight = 60;
bool _isWeightKg = true;
// Getters
@ -104,11 +104,25 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
int? get selectedAge => _selectedAge;
double? get selectedHeight => _selectedHeight;
setSelectedAge(int age) {
_selectedAge = age;
}
setSelectedHeight(num height) {
_selectedHeight = height;
notifyListeners();
}
setSelectedWeight(num weight) {
_selectedWeight = weight;
notifyListeners();
}
num? get selectedHeight => _selectedHeight;
bool get isHeightCm => _isHeightCm;
double? get selectedWeight => _selectedWeight;
num? get selectedWeight => _selectedWeight;
bool get isWeightKg => _isWeightKg;

@ -1515,6 +1515,53 @@ abstract class LocaleKeys {
static const yesPleaseINeedHelp = 'yesPleaseINeedHelp';
static const noThanksIKnowTheClinic = 'noThanksIKnowTheClinic';
static const unableToSendOTP = 'unableToSendOTP';
static const aiOverView = 'aiOverView';
static const generateAiAnalysis = 'generateAiAnalysis';
static const aiAnalysis = 'aiAnalysis';
static const prediction = 'prediction';
static const prevention = 'prevention';
static const disclaimer = 'disclaimer';
static const thisAboveInfo = 'thisAboveInfo';
static const aiDisclaimer = 'aiDisclaimer';
static const consent = 'consent';
static const bmiNormalDescription = 'bmiNormalDescription';
static const bmiUnderweightDescription = 'bmiUnderweightDescription';
static const bmiOverweightDescription = 'bmiOverweightDescription';
static const caloriesMaintenanceDescription = 'caloriesMaintenanceDescription';
static const caloriesModerateDeficitDescription = 'caloriesModerateDeficitDescription';
static const caloriesAggressiveDeficitDescription = 'caloriesAggressiveDeficitDescription';
static const bodyFatEssentialDescription = 'bodyFatEssentialDescription';
static const bodyFatFitnessDescription = 'bodyFatFitnessDescription';
static const bodyFatAverageDescription = 'bodyFatAverageDescription';
static const bodyFatObeseDescription = 'bodyFatObeseDescription';
static const bmrNormalDescription = 'bmrNormalDescription';
static const bmrLowDescription = 'bmrLowDescription';
static const bmrHighDescription = 'bmrHighDescription';
static const ibwAtTargetDescription = 'ibwAtTargetDescription';
static const ibwBelowTargetDescription = 'ibwBelowTargetDescription';
static const ibwAboveTargetDescription = 'ibwAboveTargetDescription';
static const ibwCloseToRangeDescription = 'ibwCloseToRangeDescription';
static const ibwUnderweightDescription = 'ibwUnderweightDescription';
static const ibwUnderweightGenericDescription = 'ibwUnderweightGenericDescription';
static const ibwOverweightDescription = 'ibwOverweightDescription';
static const ibwOverweightGenericDescription = 'ibwOverweightGenericDescription';
static const dietVeryLowCarbsDescription = 'dietVeryLowCarbsDescription';
static const dietLowCarbsDescription = 'dietLowCarbsDescription';
static const dietModerateCarbsDescription = 'dietModerateCarbsDescription';
static const dietUSDAGuidelinesDescription = 'dietUSDAGuidelinesDescription';
static const dietZoneDietDescription = 'dietZoneDietDescription';
static const contextDependent = 'contextDependent';
static const calculatedValue = 'calculatedValue';
static const calculationFormula = 'calculationFormula';
static const confirmYourLocation = 'confirmYourLocation';
static const confirmYourLocationDesc = 'confirmYourLocationDesc';
static const insideHospital = 'insideHospital';
static const outsideHospital = 'outsideHospital';
static const servicePriceList = 'servicePriceList';
static const servicePriceListDesc = 'servicePriceListDesc';
static const servicePriceListRights = 'servicePriceListRights';
static const serviceName = 'serviceName';
static const noFreeSlot = 'noFreeSlot';
static const applewatch = 'applewatch';
static const applehealthapplicationshouldbeinstalledinyourphone = 'applehealthapplicationshouldbeinstalledinyourphone';
static const unabletodetectapplicationinstalledpleasecomebackonceinstalled = 'unabletodetectapplicationinstalledpleasecomebackonceinstalled';

@ -36,6 +36,7 @@ import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_mo
import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart';
import 'package:hmg_patient_app_new/features/qr_parking/qr_parking_view_model.dart';
import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart';
import 'package:hmg_patient_app_new/features/services_price_list/services_price_list_view_model.dart';
import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_provider.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart';
@ -202,6 +203,9 @@ void main() async {
),
ChangeNotifierProvider<AskDoctorViewModel>(
create: (_) => getIt.get<AskDoctorViewModel>(),
),
ChangeNotifierProvider<ServicesPriceListViewModel>(
create: (_) => getIt.get<ServicesPriceListViewModel>(),
)
], child: MyApp()),
), // Wrap your app
@ -224,7 +228,7 @@ class MyApp extends StatelessWidget {
return MaterialApp(
title: 'Dr. AlHabib',
builder: (context, mchild) {
return MediaQuery(data: MediaQuery.of(context).copyWith(textScaler: TextScaler.noScaling), child: mchild!);
return MediaQuery(data: MediaQuery.of(context).copyWith(textScaler: TextScaler.noScaling, alwaysUse24HourFormat: true,), child: mchild!, );
},
showSemanticsDebugger: false,
debugShowCheckedModeBanner: false,

@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
@ -8,6 +9,7 @@ import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/calender_utils_new.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/penguin_method_channel.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
@ -16,6 +18,7 @@ import 'package:hmg_patient_app_new/features/book_appointments/book_appointments
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart';
import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart';
import 'package:hmg_patient_app_new/features/contact_us/models/feedback_type.dart';
import 'package:hmg_patient_app_new/features/hospital/AppPermission.dart';
import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
@ -38,7 +41,8 @@ import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:maps_launcher/maps_launcher.dart';
import 'package:map_launcher/map_launcher.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart';
import '../medical_file/widgets/medical_file_card.dart';
@ -187,15 +191,57 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
LocaleKeys.appointmentStatus.tr(context: context).toText16(isBold: true),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.appointmentStatus.tr(context: context).toText16(isBold: true),
SizedBox(height: 4.h),
(!AppointmentType.isConfirmed(widget.patientAppointmentHistoryResponseModel)
? LocaleKeys.notConfirmed.tr(context: context).toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500)
: LocaleKeys.confirmed.tr(context: context).toText12(color: AppColors.successColor, fontWeight: FontWeight.w500)),
SizedBox(height: 16.h),
],
),
((!AppointmentType.isConfirmed(widget.patientAppointmentHistoryResponseModel) && widget.patientAppointmentHistoryResponseModel.nextAction != 10)
? CustomButton(
text: LocaleKeys.confirm.tr(),
onPressed: () async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.confirmingAppointmentPleaseWait.tr(context: context));
await myAppointmentsViewModel.confirmAppointment(
patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel,
onSuccess: (apiResponse) {
LoaderBottomSheet.hideLoader();
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
showCommonBottomSheetWithoutHeight(
title: "",
context,
child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentConfirmedSuccessfully.tr(context: context)),
callBackFunc: () {
Navigator.of(context).pop();
},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
},
backgroundColor: AppColors.successColor,
borderColor: AppColors.successColor,
textColor: AppColors.whiteColor,
fontSize: 14.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
height: 40.h,
icon: AppAssets.confirm_appointment_icon,
iconColor: AppColors.whiteColor,
iconSize: 16.h,
)
: SizedBox.shrink())
],
),
SizedBox(height: 4.h),
(!AppointmentType.isConfirmed(widget.patientAppointmentHistoryResponseModel)
? LocaleKeys.notConfirmed.tr(context: context).toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500)
: LocaleKeys.confirmed.tr(context: context).toText12(color: AppColors.successColor, fontWeight: FontWeight.w500)),
SizedBox(height: 16.h),
//TODO Add countdown timer in case of LiveCare Appointment
widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false
? Row(
@ -228,9 +274,24 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.785,
child: CustomButton(
onPressed: () {
MapsLauncher.launchCoordinates(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!),
double.parse(widget.patientAppointmentHistoryResponseModel.longitude!), widget.patientAppointmentHistoryResponseModel.projectName);
onPressed: () async {
if (widget.patientAppointmentHistoryResponseModel.projectID == 130 || widget.patientAppointmentHistoryResponseModel.projectID == 120) {
showDirectionsBottomSheet();
} else {
await MapLauncher.showMarker(
mapType: MapType.google,
coords: Coords(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!),
double.parse(widget.patientAppointmentHistoryResponseModel.longitude!)),
title: widget.patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital",
).catchError((err) {
MapLauncher.showMarker(
mapType: Platform.isIOS ? MapType.apple : MapType.google,
coords: Coords(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!),
double.parse(widget.patientAppointmentHistoryResponseModel.longitude!)),
title: widget.patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital",
);
});
}
},
text: LocaleKeys.getDirections.tr(context: context),
backgroundColor: AppColors.textColor.withValues(alpha: 0.8),
@ -807,6 +868,89 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
);
}
void showDirectionsBottomSheet() {
showCommonBottomSheetWithoutHeight(
context,
title: "",
isDismissible: true,
isCloseButtonVisible: false,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.confirmYourLocation.tr(context: context).toText28(color: AppColors.textColor, isBold: true, height: 1.5),
SizedBox(height: 4.h),
LocaleKeys.confirmYourLocationDesc.tr(context: context).toText12(
color: AppColors.greyTextColor,
fontWeight: FontWeight.w500,
),
SizedBox(height: 24.h),
CustomButton(
text: LocaleKeys.insideHospital.tr(context: context),
onPressed: () {
Navigator.pop(context);
initPenguinSDK(widget.patientAppointmentHistoryResponseModel.projectID == 130 ? 1 : (widget.patientAppointmentHistoryResponseModel.projectID == 120 ? 3 : -1),
clinicID: widget.patientAppointmentHistoryResponseModel.clinicID.toString());
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
fontSize: 16.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
// padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 56.h,
),
SizedBox(height: 8.h),
CustomButton(
text: LocaleKeys.outsideHospital.tr(context: context),
onPressed: () async {
Navigator.pop(context);
await MapLauncher.showMarker(
mapType: MapType.google,
coords: Coords(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), double.parse(widget.patientAppointmentHistoryResponseModel.longitude!)),
title: widget.patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital",
).catchError((err) {
MapLauncher.showMarker(
mapType: Platform.isIOS ? MapType.apple : MapType.google,
coords: Coords(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), double.parse(widget.patientAppointmentHistoryResponseModel.longitude!)),
title: widget.patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital",
);
});
},
backgroundColor: AppColors.chipSecondaryLightRedColor,
borderColor: AppColors.chipSecondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 16.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
// padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 56.h,
),
],
).paddingSymmetrical(24.w, 20.h),
callBackFunc: () {},
);
}
initPenguinSDK(int projectID, {String clinicID = ""}) async {
NavigationClinicDetails data = NavigationClinicDetails();
data.projectId = projectID.toString();
data.clinicId = clinicID;
// data.clinicId = "1";
final bool permited = await AppPermission.askPenguinPermissions();
if (!permited) {
Map<Permission, PermissionStatus> statuses = await [
Permission.location,
Permission.bluetooth,
Permission.bluetoothConnect,
Permission.bluetoothScan,
Permission.activityRecognition,
].request().whenComplete(() {
PenguinMethodChannel().launch("penguin", getIt.get<AppState>().isArabic() ? "ar" : "en", getIt.get<AppState>().getAuthenticatedUser()?.patientId?.toString() ?? "", true, details: data);
});
}
}
openDoctorScheduleCalendar() async {
DoctorsListResponseModel doctor = DoctorsListResponseModel(
clinicID: widget.patientAppointmentHistoryResponseModel.clinicID,

@ -26,6 +26,7 @@ import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'dart:ui' as ui;
class AppointmentCard extends StatelessWidget {
final PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel;
@ -39,20 +40,20 @@ class AppointmentCard extends StatelessWidget {
final ContactUsViewModel? contactUsViewModel;
final BookAppointmentsViewModel bookAppointmentsViewModel;
final bool isForRate;
const AppointmentCard({
super.key,
required this.patientAppointmentHistoryResponseModel,
required this.myAppointmentsViewModel,
required this.bookAppointmentsViewModel,
this.isLoading = false,
this.isFromHomePage = false,
this.isFromMedicalReport = false,
this.isForEyeMeasurements = false,
this.isForFeedback = false,
this.medicalFileViewModel,
this.contactUsViewModel,
this.isForRate =false
});
const AppointmentCard(
{super.key,
required this.patientAppointmentHistoryResponseModel,
required this.myAppointmentsViewModel,
required this.bookAppointmentsViewModel,
this.isLoading = false,
this.isFromHomePage = false,
this.isFromMedicalReport = false,
this.isForEyeMeasurements = false,
this.isForFeedback = false,
this.medicalFileViewModel,
this.contactUsViewModel,
this.isForRate = false});
@override
Widget build(BuildContext context) {
@ -64,11 +65,11 @@ class AppointmentCard extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
isForRate ? SizedBox(): _buildHeader(context, appState),
isForRate ? SizedBox() : _buildHeader(context, appState),
SizedBox(height: 16.h),
_buildDoctorRow(context),
SizedBox(height: 16.h),
isForRate ? SizedBox(): _buildActionArea(context, appState),
isForRate ? SizedBox() : _buildActionArea(context, appState),
],
),
),
@ -101,18 +102,13 @@ class AppointmentCard extends StatelessWidget {
textColor: isLoading ? AppColors.textColor : (isLiveCare ? AppColors.whiteColor : AppColors.textColor),
).toShimmer2(isShow: isLoading),
AppCustomChipWidget(
labelText: isLoading
? 'OutPatient'
: (appState.isArabic()
? patientAppointmentHistoryResponseModel.isInOutPatientDescriptionN!
: patientAppointmentHistoryResponseModel.isInOutPatientDescription!),
labelText:
isLoading ? 'OutPatient' : (appState.isArabic() ? patientAppointmentHistoryResponseModel.isInOutPatientDescriptionN! : patientAppointmentHistoryResponseModel.isInOutPatientDescription!),
backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.1),
textColor: AppColors.primaryRedColor,
).toShimmer2(isShow: isLoading),
AppCustomChipWidget(
labelText: isLoading
? 'Booked'
: AppointmentType.getAppointmentStatusType(patientAppointmentHistoryResponseModel.patientStatusType!),
labelText: isLoading ? 'Booked' : AppointmentType.getAppointmentStatusType(patientAppointmentHistoryResponseModel.patientStatusType!),
backgroundColor: AppColors.successColor.withValues(alpha: 0.1),
textColor: AppColors.successColor,
).toShimmer2(isShow: isLoading),
@ -188,13 +184,17 @@ class AppointmentCard extends StatelessWidget {
? '${(patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital").substring(0, 15)}...'
: patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital")
.toShimmer2(isShow: isLoading),
AppCustomChipWidget(
labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w),
icon: AppAssets.appointment_calendar_icon,
labelText: isLoading
? 'Cardiology'
: "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)}",
).toShimmer2(isShow: isLoading),
Directionality(
textDirection: ui.TextDirection.ltr,
child: AppCustomChipWidget(
labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w),
icon: AppAssets.appointment_calendar_icon,
richText: isLoading
? 'Cardiology'.toText10().toShimmer2(isShow: isLoading)
: "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)}"
.toText10(isEnglishOnly: true),
),
),
// AppCustomChipWidget(
// labelPadding: EdgeInsetsDirectional.only(start: -2.w, end: 6.w),

@ -58,17 +58,17 @@ class AppointmentCheckinBottomSheet extends StatelessWidget {
projectDetailListModel = Utils.getProjectDetailObj(appState, patientAppointmentHistoryResponseModel.projectID);
double dist = Utils.distance(value.latitude, value.longitude, double.parse(projectDetailListModel.latitude!), double.parse(projectDetailListModel.longitude!)).ceilToDouble() * 1000;
print(dist);
// if (dist <= projectDetailListModel.geofenceRadius!) {
if (dist <= projectDetailListModel.geofenceRadius!) {
sendCheckInRequest(projectDetailListModel.checkInQrCode!, 3, context);
// } else {
// showCommonBottomSheetWithoutHeight(context,
// title: LocaleKeys.error.tr(context: context),
// child: Utils.getErrorWidget(
// loadingText: LocaleKeys.ensureWithinHospitalLocation.tr(context: context),
// ), callBackFunc: () {
// Navigator.of(context).pop();
// }, isFullScreen: false);
// }
} else {
showCommonBottomSheetWithoutHeight(context,
title: LocaleKeys.error.tr(context: context),
child: Utils.getErrorWidget(
loadingText: LocaleKeys.ensureWithinHospitalLocation.tr(context: context),
), callBackFunc: () {
Navigator.of(context).pop();
}, isFullScreen: false);
}
});
}),
SizedBox(height: 16.h),

@ -12,6 +12,7 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'dart:ui' as ui;
class AppointmentDoctorCard extends StatelessWidget {
const AppointmentDoctorCard(
@ -98,13 +99,16 @@ class AppointmentDoctorCard extends StatelessWidget {
labelText: patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital",
labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w),
),
AppCustomChipWidget(
labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w),
icon: AppAssets.doctor_calendar_icon,
labelText: "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang(
DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate),
false,
)}",
Directionality(
textDirection: ui.TextDirection.ltr,
child: AppCustomChipWidget(
labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w),
icon: AppAssets.doctor_calendar_icon,
richText: "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang(
DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate),
false,
)}".toText10(isEnglishOnly: true),
),
),
AppCustomChipWidget(
labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w),

@ -73,6 +73,7 @@ class LoginScreenState extends State<LoginScreen> {
SizedBox(height: 130.h), // Adjusted to sizer unit
LocaleKeys.welcomeToDrSulaiman.tr(context: context).toText32(isBold: true, color: AppColors.textColor),
SizedBox(height: 32.h),
Localizations.override(context: context, locale: Locale('en', 'US'), child: Container()), // Force English locale for this widget
TextInputWidget(
labelText: "${LocaleKeys.nationalId.tr(context: context)} / ${LocaleKeys.fileNo.tr(context: context)}",
hintText: "xxxxxxxxx",
@ -89,6 +90,7 @@ class LoginScreenState extends State<LoginScreen> {
leadingIcon: AppAssets.student_card,
errorMessage: LocaleKeys.enterValidIDorIqama.tr(context: context),
hasError: false,
fontFamily: "Poppins",
),
SizedBox(height: 16.h),
CustomButton(

@ -100,6 +100,7 @@ class _RegisterNew extends State<RegisterNew> {
hintText: "xxxxxxxxx",
controller: authVm.nationalIdController,
focusNode: _nationalIdFocusNode,
keyboardType: TextInputType.number,
isEnable: true,
prefix: null,
isAllowRadius: true,
@ -108,6 +109,7 @@ class _RegisterNew extends State<RegisterNew> {
autoFocus: true,
padding: EdgeInsets.symmetric(vertical: 8.h),
leadingIcon: AppAssets.student_card,
fontFamily: "Poppins",
).withVerticalPadding(8),
Divider(height: 1),
TextInputWidget(
@ -125,6 +127,7 @@ class _RegisterNew extends State<RegisterNew> {
selectionType: SelectionTypeEnum.calendar,
onCalendarTypeChanged: authVm.onCalenderTypeChange,
onChange: authVm.onDobChange,
fontFamily: "Poppins",
).withVerticalPadding(8),
],
),

@ -18,6 +18,7 @@ import 'package:hmg_patient_app_new/widgets/bottomsheet/generic_bottom_sheet.dar
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:provider/provider.dart';
import 'dart:ui' as ui;
class SavedLogin extends StatefulWidget {
const SavedLogin({super.key});
@ -93,14 +94,15 @@ class _SavedLogin extends State<SavedLogin> {
("${LocaleKeys.lastLoginBy.tr()} ${loginType.displayName}")
.toText14(isBold: true, color: AppColors.greyTextColor, letterSpacing: -1),
appState.getSelectDeviceByImeiRespModelElement != null
? (appState.getSelectDeviceByImeiRespModelElement!.createdOn != null
? DateUtil.getFormattedDate(
DateUtil.convertStringToDate(appState.getSelectDeviceByImeiRespModelElement!.createdOn!),
"d MMMM, y 'at' HH:mm")
: '--')
.toText16(isBold: true, color: AppColors.textColor)
: SizedBox(),
Directionality(
textDirection: ui.TextDirection.ltr,
child: appState.getSelectDeviceByImeiRespModelElement != null
? (appState.getSelectDeviceByImeiRespModelElement!.createdOn != null
? DateUtil.getFormattedDate(DateUtil.convertStringToDate(appState.getSelectDeviceByImeiRespModelElement!.createdOn!), "d MMMM, y 'at' HH:mm")
: '--')
.toText16(isBold: true, color: AppColors.textColor, isEnglishOnly: true)
: SizedBox(),
),
appState.getSelectDeviceByImeiRespModelElement != null
? Container(
@ -115,6 +117,7 @@ class _SavedLogin extends State<SavedLogin> {
CustomButton(
text: "${LocaleKeys.loginBy.tr()} ${loginType.displayName}",
onPressed: () {
if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) {
authVm.loginWithFingerPrintFace(() {});
} else {

@ -1,4 +1,3 @@
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
@ -185,7 +184,8 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
children: [
LocaleKeys.amountBeforeTax.tr(context: context).toText14(isBold: true),
Utils.getPaymentAmountWithSymbol(immediateLiveCareVM.liveCareImmediateAppointmentFeesList.amount!.toText16(isBold: true), AppColors.blackColor, 13,
isSaudiCurrency: immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar"),
isSaudiCurrency: immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar" ||
immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "ريال"),
],
).paddingSymmetrical(24.h, 0.h),
Row(
@ -194,7 +194,8 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
LocaleKeys.vat15.tr(context: context).toText14(isBold: true, color: AppColors.greyTextColor),
Utils.getPaymentAmountWithSymbol(
immediateLiveCareVM.liveCareImmediateAppointmentFeesList.tax!.toText14(isBold: true, color: AppColors.greyTextColor), AppColors.greyTextColor, 13,
isSaudiCurrency: immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar"),
isSaudiCurrency: (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar" ||
immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "ريال")),
],
).paddingSymmetrical(24.h, 0.h),
SizedBox(height: 17.h),
@ -203,7 +204,8 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
children: [
SizedBox(width: 150.h, child: Utils.getPaymentMethods()),
Utils.getPaymentAmountWithSymbol(immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total!.toText24(isBold: true), AppColors.blackColor, 17,
isSaudiCurrency: immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar"),
isSaudiCurrency: (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar" ||
immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "ريال")),
],
).paddingSymmetrical(24.h, 0.h),
(immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0" || immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0.0")
@ -214,18 +216,16 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
if (val) {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.confirmingLiveCareRequest.tr(context: context));
await immediateLiveCareVM.addNewCallRequestForImmediateLiveCare("${appState.getAuthenticatedUser()!.patientId}${DateTime
.now()
.millisecondsSinceEpoch}");
await immediateLiveCareVM.getPatientLiveCareHistory();
LoaderBottomSheet.hideLoader();
if (immediateLiveCareVM.patientHasPendingLiveCareRequest) {
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
await immediateLiveCareVM.addNewCallRequestForImmediateLiveCare("${appState.getAuthenticatedUser()!.patientId}${DateTime.now().millisecondsSinceEpoch}");
await immediateLiveCareVM.getPatientLiveCareHistory();
LoaderBottomSheet.hideLoader();
if (immediateLiveCareVM.patientHasPendingLiveCareRequest) {
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
Navigator.of(context).push(
CustomPageRoute(
page: ImmediateLiveCarePendingRequestPage(),

@ -120,7 +120,7 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
),
AppCustomChipWidget(
labelText:
"${LocaleKeys.date.tr(context: context)}: ${bookAppointmentsViewModel.isWaitingAppointmentSelected ? DateUtil.formatDateToDate(DateTime.now(), false) : bookAppointmentsViewModel.selectedAppointmentDate}",
"${LocaleKeys.date.tr(context: context)}: ${bookAppointmentsViewModel.isWaitingAppointmentSelected ? DateUtil.formatDateToDate(DateTime.now(), false) : bookAppointmentsViewModel.selectedAppointmentDisplayDate}",
),
AppCustomChipWidget(
labelText:
@ -286,8 +286,20 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
} else {
//TODO: Add patient Derma package check API Here
await bookAppointmentsViewModel.insertSpecificAppointment(onError: (err) {
print(err.data["ErrorEndUserMessage"]);
// print(err.data["ErrorEndUserMessage"]);
LoadingUtils.hideFullScreenLoader();
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.notice.tr(context: context),
context,
child: Utils.getErrorWidget(loadingText: err.toString()),
callBackFunc: () {
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
// Navigator.pop(context);
},
isFullScreen: false,
isCloseButtonVisible: true,
);
}, onSuccess: (apiResp) async {
LoadingUtils.hideFullScreenLoader();
await Future.delayed(Duration(milliseconds: 50)).then((value) async {

@ -261,8 +261,7 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
calorieWidget(
title: calculatedResult["bmiCategory"],
calories: calculatedResult["bmiResult"],
description:
'Your BMI is within the healthy range. Continue a balanced diet, regular physical activity, and routine checkups to maintain good health. Monitor any significant changes over time.',
description: LocaleKeys.bmiNormalDescription.tr(),
color: Colors.green,
icon: Icons.scale,
hide: true,
@ -272,8 +271,7 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
calorieWidget(
title: calculatedResult["bmiCategory"],
calories: calculatedResult["bmiResult"],
description:
'BMI suggests you are underweight. Consider increasing calorie intake with nutrient-dense foods, include resistance training to build muscle, and consult a healthcare professional to check for underlying causes.',
description: LocaleKeys.bmiUnderweightDescription.tr(),
color: Colors.orange,
hide: true,
icon: Icons.trending_down,
@ -283,8 +281,7 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
calorieWidget(
title: calculatedResult["bmiCategory"],
calories: calculatedResult["bmiResult"],
description:
'BMI indicates overweight. Aim for gradual, sustainable weight loss through a modest calorie deficit, regular physical activity, and healthier food choices. Seek medical advice before major lifestyle changes.',
description: LocaleKeys.bmiOverweightDescription.tr(),
color: Colors.red,
hide: true,
icon: Icons.warning_rounded,
@ -295,8 +292,7 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
calorieWidget(
title: calculatedResult["calorieRange"],
calories: calculatedResult["calories"],
description:
'Estimated daily calories to maintain your current weight given your activity level and metabolism. Use this as a baseline: increase slightly for muscle gain, or create a small deficit for gradual weight loss. Track progress and adjust every 24 weeks.',
description: LocaleKeys.caloriesMaintenanceDescription.tr(),
color: Colors.green,
icon: Icons.scale,
),
@ -305,8 +301,7 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
calorieWidget(
title: calculatedResult["calorieRange"],
calories: calculatedResult["calories"],
description:
'A moderate calorie reduction designed for steady, sustainable weight loss (typically 0.250.5 kg per week). Combine with resistance training and adequate protein to preserve muscle. Avoid extreme cuts and consult a professional if you have medical conditions.',
description: LocaleKeys.caloriesModerateDeficitDescription.tr(),
color: Colors.orange,
icon: Icons.trending_down,
),
@ -315,19 +310,59 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
calorieWidget(
title: calculatedResult["calorieRange"],
calories: calculatedResult["calories"],
description:
'A large calorie reduction that may produce faster weight loss but can be hard to sustain and increase risk of nutrient deficiencies, fatigue, or muscle loss. Not recommended long-term; seek guidance from a healthcare or nutrition professional before continuing.',
description: LocaleKeys.caloriesAggressiveDeficitDescription.tr(),
color: Colors.red,
icon: Icons.warning_rounded,
),
]
] else if (type == HealthCalculatorsTypeEnum.bodyFat) ...[
if (calculatedResult["fatCategory"] == "Essential Fat" || calculatedResult["fatCategory"] == "Athletes") ...[
calorieWidget(
title: calculatedResult["fatCategory"],
calories: calculatedResult["fatPercentage"],
description: LocaleKeys.bodyFatEssentialDescription.tr(),
color: Colors.green,
hide: true,
icon: Icons.fitness_center,
),
SizedBox(height: 16.h),
] else if (calculatedResult["fatCategory"] == "Fitness") ...[
calorieWidget(
title: calculatedResult["fatCategory"],
calories: calculatedResult["fatPercentage"],
description: LocaleKeys.bodyFatFitnessDescription.tr(),
color: Colors.green,
hide: true,
icon: Icons.fitness_center,
),
SizedBox(height: 16.h),
] else if (calculatedResult["fatCategory"] == "Average" || calculatedResult["fatCategory"] == "Acceptable") ...[
calorieWidget(
title: calculatedResult["fatCategory"],
calories: calculatedResult["fatPercentage"],
description: LocaleKeys.bodyFatAverageDescription.tr(),
color: Colors.orange,
hide: true,
icon: Icons.accessibility_new,
),
SizedBox(height: 16.h),
] else if (calculatedResult["fatCategory"] == "Obese" || calculatedResult["fatCategory"] == "Overweight") ...[
calorieWidget(
title: calculatedResult["fatCategory"],
calories: calculatedResult["fatPercentage"],
description: LocaleKeys.bodyFatObeseDescription.tr(),
color: Colors.red,
hide: true,
icon: Icons.warning_rounded,
),
SizedBox(height: 16.h),
]
] else if (type == HealthCalculatorsTypeEnum.bmr) ...[
if (calculatedResult["bmrRange"] == "Normal") ...[
calorieWidget(
title: calculatedResult["bmrRange"],
calories: calculatedResult["bmr"],
description:
'Your Basal Metabolic Rate (BMR) is within the expected range for your profile. BMR is the energy your body needs at rest to maintain vital functions (breathing, circulation, temperature). Use this value as the baseline for estimating total daily energy needs by applying an activity multiplier. Maintain a balanced diet and regular physical activity to support metabolic health.',
description: LocaleKeys.bmrNormalDescription.tr(),
color: Colors.green,
hide: true,
icon: Icons.scale,
@ -337,8 +372,7 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
calorieWidget(
title: calculatedResult["bmrRange"],
calories: calculatedResult["bmr"],
description:
'Your BMR is lower than typical for your profile. Possible causes include lower lean muscle mass, age-related metabolic decline, or metabolic adaptation from prolonged calorie restriction. Consider focusing on resistance training to build/maintain muscle, ensure adequate protein and micronutrient intake, and consult a healthcare professional if this is unexpected.',
description: LocaleKeys.low.tr(),
color: Colors.orange,
hide: true,
icon: Icons.trending_down,
@ -348,8 +382,7 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
calorieWidget(
title: calculatedResult["bmrRange"],
calories: calculatedResult["bmr"],
description:
'Your BMR is higher than average for your profile. This can reflect greater lean mass, younger age, or a naturally higher metabolic rate, meaning you burn more energy at rest. Use this information to tailor calorie targets: higher needs for maintenance or muscle gain, moderate deficit for weight loss. Seek medical advice if you notice rapid unexplained changes.',
description: LocaleKeys.bmrHighDescription.tr(),
color: Colors.red,
hide: true,
icon: Icons.warning_rounded,
@ -915,9 +948,16 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
Widget _buildStatusIcon(String title) {
final key = title.trim();
if (key == 'Maintenance Calories' || key == 'Normal' || key == 'Ideal Weight' || key == 'Moderate Crabs' || key == 'USDA Guidelines') {
if (key == 'Maintenance Calories' ||
key == 'Normal' ||
key == 'Ideal Weight' ||
key == 'Moderate Crabs' ||
key == 'USDA Guidelines' ||
key == 'Essential Fat' ||
key == 'Athletes' ||
key == 'Fitness') {
return Utils.buildSvgWithAssets(icon: AppAssets.checkmark_icon, width: 24.w, height: 24.h);
} else if (key == 'Moderate Deficit' || key == 'Low' || key == 'Underweight' || key == 'Very Low Crabs' || key == 'Low Crabs' || key == 'Zone Diet') {
} else if (key == 'Moderate Deficit' || key == 'Low' || key == 'Underweight' || key == 'Very Low Crabs' || key == 'Low Crabs' || key == 'Zone Diet' || key == 'Average' || key == 'Acceptable') {
return Utils.buildSvgWithAssets(icon: AppAssets.trade_down_yellow, width: 24.w, height: 24.h);
} else if (key == 'Aggressive Deficit' || key == 'High' || key == 'Overweight' || key == "Obese") {
return Utils.buildSvgWithAssets(icon: AppAssets.trade_down_red, width: 24.w, height: 24.h);
@ -928,9 +968,16 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
Color getTextColor(String title) {
final key = title.trim();
if (key == 'Maintenance Calories' || key == 'Normal' || key == 'Ideal Weight' || key == 'Moderate Crabs' || key == 'USDA Guidelines') {
if (key == 'Maintenance Calories' ||
key == 'Normal' ||
key == 'Ideal Weight' ||
key == 'Moderate Crabs' ||
key == 'USDA Guidelines' ||
key == 'Essential Fat' ||
key == 'Athletes' ||
key == 'Fitness') {
return AppColors.successColor;
} else if (key == 'Moderate Deficit' || key == 'Low' || key == 'Underweight' || key == 'Very Low Crabs' || key == 'Low Crabs' || key == 'Zone Diet') {
} else if (key == 'Moderate Deficit' || key == 'Low' || key == 'Underweight' || key == 'Very Low Crabs' || key == 'Low Crabs' || key == 'Zone Diet' || key == 'Average' || key == 'Acceptable') {
return AppColors.warningColor;
} else if (key == 'Aggressive Deficit' || key == 'High' || key == 'Overweight' || key == "Obese") {
return AppColors.errorColor;
@ -1156,7 +1203,8 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
bookAppointmentsViewModel.patientDentalPlanEstimationList[index].procedureName!.toText12(isBold: true),
AppCustomChipWidget(icon: AppAssets.appointment_time_icon, labelText: "${bookAppointmentsViewModel.totalTimeNeededForDentalProcedure} ${LocaleKeys.mins.tr(context: context)}"),
AppCustomChipWidget(
icon: AppAssets.appointment_time_icon, labelText: "${bookAppointmentsViewModel.totalTimeNeededForDentalProcedure} ${LocaleKeys.mins.tr(context: context)}"),
],
);
},

@ -75,7 +75,7 @@ class _SelectDoctorPageState extends State<SelectDoctorPage> {
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView(
title: LocaleKeys.chooseDoctor.tr(),
title: LocaleKeys.chooseDoctor.tr(context: context),
// bottomChild: Container(
// decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.only(topLeft: Radius.circular(24.r), topRight: Radius.circular(24.r))),
// padding: EdgeInsets.symmetric(vertical: 20.h, horizontal: 20.h),

@ -7,6 +7,7 @@ import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart';
@ -144,13 +145,14 @@ class _AppointmentCalendarState extends State<AppointmentCalendar> {
child: selectedDateDisplay.toText16(weight: FontWeight.w500),
),
//TODO: Add Next Day Span here
SizedBox(
height: 100.h,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Wrap(
direction: Axis.horizontal,
alignment: WrapAlignment.start,
dayEvents.isNotEmpty
? SizedBox(
height: 100.h,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Wrap(
direction: Axis.horizontal,
alignment: WrapAlignment.start,
spacing: 6.h,
runSpacing: 6.h,
children: List.generate(
@ -168,10 +170,13 @@ class _AppointmentCalendarState extends State<AppointmentCalendar> {
),
),
),
),
)
: Utils.getNoDataWidget(context, noDataText: LocaleKeys.noFreeSlot.tr(context: context)),
SizedBox(height: 16.h),
CustomButton(
text: LocaleKeys.select.tr(context: context),
isDisabled: dayEvents.isEmpty,
onPressed: () async {
if (appState.isAuthenticated) {
if(selectedTime == LocaleKeys.waitingAppointment.tr(context: context)){
@ -186,7 +191,7 @@ class _AppointmentCalendarState extends State<AppointmentCalendar> {
);
} else {
bookAppointmentsViewModel.getAppointmentNearestGate(projectID: bookAppointmentsViewModel.selectedDoctor.projectID!, clinicID: bookAppointmentsViewModel.selectedDoctor.clinicID!);
bookAppointmentsViewModel.setSelectedAppointmentDateTime(selectedDate, selectedTime);
bookAppointmentsViewModel.setSelectedAppointmentDateTime(selectedDate, selectedTime, selectedDateDisplay);
Navigator.of(context).pop();
Navigator.of(context).push(
CustomPageRoute(
@ -304,11 +309,14 @@ class _AppointmentCalendarState extends State<AppointmentCalendar> {
Map<String, dynamic> timeSlot = {"isoTime": dayEvents[i].isoTime, "start": dayEvents[i].start.toString(), "end": dayEvents[i].end.toString(), "vidaDate": dayEvents[i].vidaDate};
timeList.add(timeSlot);
}
selectedTime = dayEvents[selectedButtonIndex].isoTime!;
if (dayEvents.isNotEmpty) {
selectedTime = dayEvents[selectedButtonIndex].isoTime!;
}
setState(() {});
}
void _onDaySelected(DateTime day) {
final DateFormat formatter = DateFormat('yyyy-MM-dd');
final DateFormat formatter = DateFormat('yyyy-MM-dd', "en-US");
setState(() {
selectedDateDisplay = DateUtil.getMonthDayYearDateFormatted(day);
selectedNextDate = DateUtil.getWeekDayMonthDayYearDateFormatted(day.add(Duration(days: 1)), "en");

@ -38,7 +38,7 @@ class DoctorCard extends StatelessWidget {
hasShadow: false,
),
child: Padding(
padding: EdgeInsets.only(top: 14.h,bottom: 20.h),
padding: EdgeInsets.only(top: 14.h, bottom: 20.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -91,9 +91,7 @@ class DoctorCard extends StatelessWidget {
children: [
SizedBox(
width: MediaQuery.of(context).size.width * 0.55,
child: (isLoading ? "Dr John Smith" : "${doctorsListResponseModel.doctorTitle} ${doctorsListResponseModel.name}")
.toString()
.toText16(isBold: true, maxlines: 1),
child: (isLoading ? "Dr John Smith" : "${doctorsListResponseModel.doctorTitle} ${doctorsListResponseModel.name}").toString().toText16(isBold: true, maxlines: 1),
).toShimmer2(isShow: isLoading),
],
),
@ -135,12 +133,14 @@ class DoctorCard extends StatelessWidget {
).toShimmer2(isShow: isLoading),
bookAppointmentsViewModel.isNearestAppointmentSelected
? doctorsListResponseModel.nearestFreeSlot != null
? AppCustomChipWidget(
labelText: (isLoading ? "Cardiologist" : DateUtil.getDateStringForNearestSlot(doctorsListResponseModel.nearestFreeSlot)),
backgroundColor: AppColors.successColor,
textColor: AppColors.whiteColor,
).toShimmer2(isShow: isLoading)
: SizedBox.shrink()
? AppCustomChipWidget(
// labelText: (isLoading ? "Cardiologist" : DateUtil.getDateStringForNearestSlot(doctorsListResponseModel.nearestFreeSlot)),
richText: (isLoading ? "Cardiologist" : DateUtil.getDateStringForNearestSlot(doctorsListResponseModel.nearestFreeSlot))
.toText10(isEnglishOnly: true, color: AppColors.whiteColor),
backgroundColor: AppColors.successColor,
textColor: AppColors.whiteColor,
).toShimmer2(isShow: isLoading)
: SizedBox.shrink()
: SizedBox.shrink(),
],
),
@ -149,8 +149,7 @@ class DoctorCard extends StatelessWidget {
),
Expanded(
flex: 1,
child: Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown)
.toShimmer2(isShow: isLoading),
child: Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown).toShimmer2(isShow: isLoading),
),
],
),

@ -1,4 +1,5 @@
import 'dart:developer';
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
@ -22,7 +23,7 @@ import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/map/location_map_widget.dart';
import 'package:maps_launcher/maps_launcher.dart';
import 'package:map_launcher/map_launcher.dart';
import 'package:provider/provider.dart';
class CmcSelectionReviewPage extends StatefulWidget {
@ -319,16 +320,27 @@ class _CmcSelectionReviewPageState extends State<CmcSelectionReviewPage> {
CmcUiSelectionHelper.showHospitalSelectionBottomSheet(context: context, onHospitalSelected: (hospital) => context.pop());
}
void _launchDirections(HospitalsModel selectedHospital) {
void _launchDirections(HospitalsModel selectedHospital) async {
final double lat = double.parse(selectedHospital.latitude ?? "0.0");
final double lng = double.parse(selectedHospital.longitude ?? "0.0");
if (lat != 0.0 && lng != 0.0) {
MapsLauncher.launchCoordinates(
lat,
lng,
selectedHospital.name ?? "Hospital",
);
await MapLauncher.showMarker(
mapType: MapType.google,
coords: Coords(lat, lng),
title: selectedHospital.name ?? "Hospital",
).catchError((err) {
MapLauncher.showMarker(
mapType: Platform.isIOS ? MapType.apple : MapType.google,
coords: Coords(lat, lng),
title: selectedHospital.name ?? "Hospital",
);
});
// MapsLauncher.launchCoordinates(
// lat,
// lng,
// selectedHospital.name ?? "Hospital",
// );
}
}

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
@ -10,7 +12,7 @@ import 'package:hmg_patient_app_new/features/contact_us/models/resp_models/get_h
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'package:maps_launcher/maps_launcher.dart';
import 'package:map_launcher/map_launcher.dart';
import 'package:url_launcher/url_launcher.dart';
class FindUsItemCard extends StatelessWidget {
@ -82,8 +84,18 @@ class FindUsItemCard extends StatelessWidget {
iconColor: AppColors.whiteColor,
backgroundColor: AppColors.textColor.withValues(alpha: 0.8),
textColor: AppColors.whiteColor,
onChipTap: () {
MapsLauncher.launchCoordinates(double.parse(getHMGLocationsModel.latitude ?? "0.0"), double.parse(getHMGLocationsModel.longitude ?? "0.0"), getHMGLocationsModel.locationName!);
onChipTap: () async {
await MapLauncher.showMarker(
mapType: MapType.google,
coords: Coords(double.parse(getHMGLocationsModel.latitude ?? "0.0"), double.parse(getHMGLocationsModel.longitude ?? "0.0")),
title: getHMGLocationsModel.locationName ?? "Hospital",
).catchError((err) {
MapLauncher.showMarker(
mapType: Platform.isIOS ? MapType.apple : MapType.google,
coords: Coords(double.parse(getHMGLocationsModel.latitude ?? "0.0"), double.parse(getHMGLocationsModel.longitude ?? "0.0")),
title: getHMGLocationsModel.locationName ?? "Hospital",
);
});
},
),
SizedBox(width: 4.w),

@ -140,6 +140,7 @@ class PatientInformationStepState extends State<PatientInformationStep> {
controller: _phoneController,
padding: const EdgeInsets.all(8),
keyboardType: TextInputType.number,
fontFamily: "Poppins",
onChange: (value) {
formManager.updatePatientPhone(value ?? '');
},

@ -73,6 +73,7 @@ class SearchEReferralFormFormState extends State<SearchEReferralFormForm> {
hintText: formManager.searchCriteria == 0 ? "Enter Identification Number" : "Enter Referral Number",
labelText: formManager.searchCriteria == 0 ? "Identification Number" : "Referral Number",
keyboardType: TextInputType.number,
fontFamily: "Poppins",
errorMessage: formManager.errors.searchValue,
hasError: !ValidationUtils.isNullOrEmpty(formManager.errors.searchValue),
onChange: (value) {
@ -94,6 +95,7 @@ class SearchEReferralFormFormState extends State<SearchEReferralFormForm> {
controller: _phoneController,
padding: const EdgeInsets.all(8),
keyboardType: TextInputType.number,
fontFamily: "Poppins",
onChange: (value) {
formManager.updateSearchPhone(value ?? '');
// _validateForm(formManager);

@ -98,11 +98,12 @@ class _RechargeWalletPageState extends State<RechargeWalletPage> {
isBorderAllowed: false,
isAllowLeadingIcon: true,
autoFocus: true,
fontSize: 40,
fontSize: 40.f,
padding: EdgeInsets.symmetric(horizontal: 8.h, vertical: 0.h),
focusNode: textFocusNode,
isWalletAmountInput: true,
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
fontFamily: "Poppins",
// leadingIcon: AppAssets.student_card,
),
),
@ -217,12 +218,11 @@ class _RechargeWalletPageState extends State<RechargeWalletPage> {
keyboardType: TextInputType.text,
isEnable: true,
prefix: null,
autoFocus: true,
autoFocus: false,
isAllowRadius: true,
isBorderAllowed: false,
isAllowLeadingIcon: true,
leadingIcon: AppAssets.notes_icon,
errorMessage: LocaleKeys.enterValidIDorIqama.tr(context: context),
hasError: false,
),
SizedBox(height: 8.h),

@ -52,8 +52,8 @@ class _HealthCalculatorDetailedPageState extends State<HealthCalculatorDetailedP
widget.calculatorType == HealthCalculatorsTypeEnum.triglycerides
? SizedBox()
: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor, customBorder: BorderRadius.only(topLeft: Radius.circular(24.r), topRight: Radius.circular(24.r))),
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.only(topLeft: Radius.circular(24.r), topRight: Radius.circular(24.r))),
padding: EdgeInsets.symmetric(vertical: 20.h, horizontal: 20.h),
child: CustomButton(
text: widget.calculatorType == HealthCalculatorsTypeEnum.bloodSugar ||
@ -63,8 +63,6 @@ class _HealthCalculatorDetailedPageState extends State<HealthCalculatorDetailedP
: LocaleKeys.calculate.tr(context: context),
onPressed: () {
if (calculatedResult == null) return;
DialogService dialogService = getIt.get<DialogService>();
Navigator.of(context).push(CustomPageRoute(
page: SelectClinicPage(calculatorType: widget.calculatorType, calculatedResult: calculatedResult),
));
@ -174,69 +172,58 @@ class _HealthCalculatorDetailedPageState extends State<HealthCalculatorDetailedP
switch (widget.calculatorType) {
case HealthCalculatorsTypeEnum.bmi:
return Container(
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: BMIWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.calories:
return Container(
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: CaloriesWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.bmr:
return Container(
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: BMRWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.idealBodyWeight:
return Container(
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: IdealBodyWeightWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.bodyFat:
return Container(
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: BodyFatWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.crabsProteinFat:
return Container(
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: CrabsWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.ovulation:
return Container(
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: OvulationWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.deliveryDueDate:
return Container(
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: DeliveryDueDWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.bloodSugar:
return Container(
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: BloodSugarWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.bloodCholesterol:
return Container(
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: BloodCholesterolWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.triglycerides:
return Container(
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: TriglyceridesWidget(onChange: onCalculate),
);
}

@ -412,6 +412,7 @@ class HealthCalcualtorViewModel extends ChangeNotifier {
bodyFatResult = bodyFat;
bodyFatCategory = category;
notifyListeners();
} catch (e) {
} catch (e) {
bodyFatResult = null;
bodyFatCategory = null;

@ -589,7 +589,7 @@ class _BodyFatWidgetState extends State<BodyFatWidget> {
children: [
Radio<String>(
value: unit,
groupValue: selectedHeightUnit,
groupValue: selectedHipUnit,
activeColor: AppColors.errorColor,
onChanged: (String? value) {
if (value == null) return;

@ -29,6 +29,7 @@ import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dar
import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart';
import 'package:hmg_patient_app_new/presentation/parking/paking_page.dart';
import 'package:hmg_patient_app_new/presentation/servicesPriceList/services_price_list_page.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
@ -150,13 +151,27 @@ class ServicesPage extends StatelessWidget {
}),
HmgServicesComponentModel(
3,
"Blood Donation".needTranslation,
LocaleKeys.bloodDonation.tr(context: getIt.get<NavigationService>().navigatorKey.currentContext!),
"".needTranslation,
AppAssets.blood_donation_icon,
bgColor: AppColors.bloodDonationCardColor,
true,
route: AppRoutes.bloodDonationPage,
),
HmgServicesComponentModel(
113,
LocaleKeys.servicePriceList.tr(context: getIt.get<NavigationService>().navigatorKey.currentContext!),
"".needTranslation,
AppAssets.saudi_riyal_icon,
bgColor: AppColors.textColor,
false,
route: null,
onTap: () {
Navigator.of(getIt.get<NavigationService>().navigatorKey.currentContext!).push(
CustomPageRoute(page: ServicesPriceListPage()),
);
},
)
// HmgServicesComponentModel(
// 3,
// "My Child Vaccine".needTranslation,
@ -302,6 +317,9 @@ class ServicesPage extends StatelessWidget {
Widget build(BuildContext context) {
bloodDonationViewModel = Provider.of<BloodDonationViewModel>(context);
medicalFileViewModel = Provider.of<MedicalFileViewModel>(context);
hmgServices.removeWhere((element) => Utils.havePrivilege(element.action) == false);
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView(
@ -382,7 +400,7 @@ class ServicesPage extends StatelessWidget {
children: [
Utils.buildSvgWithAssets(icon: AppAssets.wallet, width: 40.w, height: 40.h),
LocaleKeys.habibWallet.tr().toText14(weight: FontWeight.w600, maxlines: 2).expanded,
Utils.buildSvgWithAssets(icon: AppAssets.arrow_forward),
Utils.buildSvgWithAssets(icon: getIt.get<AppState>().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward),
],
),
Spacer(),
@ -442,7 +460,7 @@ class ServicesPage extends StatelessWidget {
children: [
Utils.buildSvgWithAssets(icon: AppAssets.services_medical_file_icon, width: 40.w, height: 40.h),
LocaleKeys.medicalFile.tr().toText16(weight: FontWeight.w600, maxlines: 2).expanded,
Utils.buildSvgWithAssets(icon: AppAssets.arrow_forward),
Utils.buildSvgWithAssets(icon: getIt.get<AppState>().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward),
],
),
Spacer(),
@ -667,7 +685,7 @@ class ServicesPage extends StatelessWidget {
fit: BoxFit.contain,
),
SizedBox(width: 8.w),
LocaleKeys.hmgContact.tr().toText14(weight: FontWeight.w500)
Expanded(child: LocaleKeys.hmgContact.tr().toText14(weight: FontWeight.w500))
],
),
),

@ -87,7 +87,7 @@ class HabibWalletCard extends StatelessWidget {
fit: BoxFit.contain,
),
SizedBox(width: 8.h),
habibWalletVM.habibWalletAmount.toString().toText32(isBold: true).toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.h, width: 80.h, height: 40.h),
habibWalletVM.habibWalletAmount.toString().toText32(isBold: true, isEnglishOnly: true).toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.h, width: 80.h, height: 40.h),
],
);
}),

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
@ -19,7 +21,7 @@ import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/map/location_map_widget.dart';
import 'package:maps_launcher/maps_launcher.dart';
import 'package:map_launcher/map_launcher.dart';
import 'package:provider/provider.dart';
class HhcSelectionReviewPage extends StatefulWidget {
@ -142,9 +144,19 @@ class _HhcSelectionReviewPageState extends State<HhcSelectionReviewPage> {
);
}
void _launchDirectionsToLocation(double lat, double lng, String locationName) {
void _launchDirectionsToLocation(double lat, double lng, String locationName) async {
if (lat != 0.0 && lng != 0.0) {
MapsLauncher.launchCoordinates(lat, lng, locationName);
await MapLauncher.showMarker(
mapType: MapType.google,
coords: Coords(lat, lng),
title: locationName,
).catchError((err) {
MapLauncher.showMarker(
mapType: Platform.isIOS ? MapType.apple : MapType.google,
coords: Coords(lat, lng),
title: locationName,
);
});
}
}

@ -75,10 +75,11 @@ class PatientInsuranceCard extends StatelessWidget {
children: [
AppCustomChipWidget(
icon: AppAssets.doctor_calendar_icon,
labelText: "${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}",
// labelText: "${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}",
richText: "${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}".toText10(isEnglishOnly: true),
labelPadding: EdgeInsetsDirectional.only(start: -4.h, end: 8.h),
),
AppCustomChipWidget(labelText: LocaleKeys.patientCardID.tr(namedArgs: {'id': insuranceCardDetailsModel.patientCardID ?? ''}, context: context)),
AppCustomChipWidget(richText: LocaleKeys.patientCardID.tr(namedArgs: {'id': insuranceCardDetailsModel.patientCardID ?? ''}, context: context).toText10(isEnglishOnly: true)),
],
),
SizedBox(height: 10.h),

File diff suppressed because one or more lines are too long

@ -1,17 +1,22 @@
import 'package:easy_localization/easy_localization.dart'
show tr, StringTranslateExtension;
import 'package:easy_localization/easy_localization.dart' show tr, StringTranslateExtension;
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/route_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart';
import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_order_response_by_ai_model.dart';
import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/LabResultList.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/ai_analysis_widget.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/lab_order_specialResult.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
@ -25,6 +30,7 @@ class LabResultByClinic extends StatelessWidget {
late LabViewModel labViewModel;
PatientLabOrdersResponseModel labOrder;
@override
Widget build(BuildContext context) {
labViewModel = Provider.of<LabViewModel>(context, listen: false);
@ -35,6 +41,21 @@ class LabResultByClinic extends StatelessWidget {
Expanded(
child: CollapsingListView(
title: LocaleKeys.labResults.tr(),
aiOverview: () async {
final _dialogService = getIt.get<DialogService>();
await _dialogService.showCommonBottomSheetWithoutH(
message: LocaleKeys.aiDisclaimer.tr(),
label: LocaleKeys.consent.tr(),
okLabel: LocaleKeys.acceptLbl.tr(),
cancelLabel: LocaleKeys.rejectView.tr(),
onOkPressed: () {
context.pop();
labViewModel.getAiOverviewLabOrders(labOrder: labOrder);
},
onCancelPressed: () {
context.pop();
});
},
child: SingleChildScrollView(
child: Column(
spacing: 8.h,
@ -77,7 +98,7 @@ class LabResultByClinic extends StatelessWidget {
}
},
),
LabOrderSpecialResult()
LabOrderSpecialResult(),
],
).paddingAll(24.h),
)),

@ -0,0 +1,100 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_order_response_by_ai_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
class AiAnalysisWidget extends StatelessWidget {
final LabOrderResponseByAi data;
const AiAnalysisWidget({super.key, required this.data});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(borderRadius: BorderRadius.circular(24.r), gradient: AppColors.aiLinearGradient),
padding: EdgeInsets.all(1.5.r), // Border thickness
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(22.r),
),
padding: EdgeInsets.all(20.r),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
LocaleKeys.aiAnalysis.tr().toText18(weight: FontWeight.w700, color: AppColors.blackColor),
Container(
padding: EdgeInsets.all(8.r),
decoration: BoxDecoration(gradient: AppColors.aiLinearGradient, borderRadius: BorderRadius.circular(8.r)),
child: SvgPicture.asset(AppAssets.aiOverView, colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn), height: 18.r, width: 18.r),
),
],
),
SizedBox(height: 16.h),
if (data.summary != null && data.summary!.isNotEmpty) ...[
LocaleKeys.suggestion.tr().toText16(weight: FontWeight.w700, color: AppColors.blackColor),
SizedBox(height: 8.h),
data.summary!.toText14(color: AppColors.textColorLight, height: 1.5, weight: FontWeight.w400),
SizedBox(height: 16.h),
const Divider(color: AppColors.dividerColor, thickness: 1),
SizedBox(height: 16.h),
],
if (data.aiPredictions != null && data.aiPredictions!.isNotEmpty) ...[
LocaleKeys.prediction.tr().toText16(weight: FontWeight.w700, color: AppColors.blackColor),
SizedBox(height: 12.h),
...data.aiPredictions!.map((item) => _buildBulletItem(item)),
SizedBox(height: 16.h),
const Divider(color: AppColors.dividerColor, thickness: 1),
SizedBox(height: 16.h),
],
if (data.prevention != null && data.prevention!.isNotEmpty) ...[
LocaleKeys.prevention.tr().toText16(weight: FontWeight.w700, color: AppColors.blackColor),
SizedBox(height: 12.h),
...data.prevention!.map((item) => _buildBulletItem(item)),
SizedBox(height: 20.h),
],
Row(
children: [
Icon(Icons.info_outline, color: AppColors.greyTextColor, size: 20.r),
SizedBox(width: 8.w),
Expanded(
child: ("${LocaleKeys.disclaimer.tr()}:${LocaleKeys.thisAboveInfo.tr()}").toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w400),
),
],
),
],
),
),
);
}
Widget _buildBulletItem(String text) {
return Padding(
padding: EdgeInsets.only(bottom: 12.h),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(top: 8.h, right: 12.w, left: 4.w),
child: Container(
width: 5.r,
height: 5.r,
decoration: const BoxDecoration(color: AppColors.blackColor, shape: BoxShape.circle),
),
),
Expanded(
child: text.toText14(color: AppColors.textColorLight, height: 1.5, weight: FontWeight.w400),
),
],
),
);
}
}

@ -1,45 +1,72 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/route_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart';
import 'package:hmg_patient_app_new/features/lab/models/resp_models/lab_result.dart';
import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_order_response_by_ai_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/ai_analysis_widget.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/date_range_calender.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_results/lab_result_list_item.dart';
import 'package:hmg_patient_app_new/theme/colors.dart' show AppColors;
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/graph/custom_graph.dart';
import 'package:provider/provider.dart' show Consumer, Provider, ReadContext;
import 'package:provider/provider.dart' show Consumer, Provider, ReadContext, Selector;
import '../../../widgets/common_bottom_sheet.dart'
show showCommonBottomSheetWithoutHeight;
import '../../../widgets/common_bottom_sheet.dart' show showCommonBottomSheetWithoutHeight;
class LabResultDetails extends StatelessWidget {
final LabResult recentLabResult;
final String? testDescription;
const LabResultDetails(
{super.key,
required this.recentLabResult,
required this.testDescription});
const LabResultDetails({super.key, required this.recentLabResult, required this.testDescription});
@override
Widget build(BuildContext context) {
LabViewModel labViewModel = Provider.of<LabViewModel>(context, listen: false);
final appState = getIt.get<AppState>();
return CollapsingListView(
title: LocaleKeys.labResultDetails.tr(context: context),
aiOverview: () async {
final _dialogService = getIt.get<DialogService>();
await _dialogService.showCommonBottomSheetWithoutH(
message: LocaleKeys.aiDisclaimer.tr(),
label: LocaleKeys.consent.tr(),
okLabel: LocaleKeys.acceptLbl.tr(),
cancelLabel: LocaleKeys.rejectView.tr(),
onOkPressed: () {
context.pop();
labViewModel.getAiOverviewSingleLabResult(langId: appState.getLanguageID().toString(), recentLabResult: recentLabResult);
},
onCancelPressed: () {
context.pop();
});
},
child: SingleChildScrollView(
child: Column(
spacing: 16.h,
children: [
LabNameAndStatus(context),
getLabDescription(context),
LabGraph(context)
LabGraph(context),
Selector<LabViewModel, LabOrderResponseByAi?>(
selector: (_, model) => model.labOrderResponseByAi,
builder: (_, aiData, __) {
if (aiData != null) {
return AiAnalysisWidget(data: aiData).paddingOnly(bottom: 16.h);
}
return const SizedBox.shrink();
},
),
],
).paddingAll(24.h),
),
@ -47,11 +74,7 @@ class LabResultDetails extends StatelessWidget {
}
Widget LabNameAndStatus(BuildContext context) => Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: true,
),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -107,7 +130,9 @@ class LabResultDetails extends StatelessWidget {
),
letterSpacing: -2),
),
SizedBox(width: 4.h,),
SizedBox(
width: 4.h,
),
],
),
),
@ -150,9 +175,7 @@ class LabResultDetails extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
labmodel.isGraphVisible
? LocaleKeys.historyFlowchart.tr(context: context)
: LocaleKeys.history.tr(context: context),
labmodel.isGraphVisible ? LocaleKeys.historyFlowchart.tr(context: context) : LocaleKeys.history.tr(context: context),
style: TextStyle(
fontSize: 16,
fontFamily: 'Poppins',
@ -164,22 +187,12 @@ class LabResultDetails extends StatelessWidget {
spacing: 16.h,
children: [
//todo handle when the graph icon is being displayed
Utils.buildSvgWithAssets(
icon: labmodel.isGraphVisible
? AppAssets.ic_list
: AppAssets.ic_graph,
width: 24.h,
height: 24.h)
.onPress(() {
Utils.buildSvgWithAssets(icon: labmodel.isGraphVisible ? AppAssets.ic_list : AppAssets.ic_graph, width: 24.h, height: 24.h).onPress(() {
if (labmodel.shouldShowGraph) {
labmodel.alterGraphVisibility();
}
}),
Utils.buildSvgWithAssets(
icon: AppAssets.ic_date_filter,
width: 24,
height: 24)
.onPress(() {
Utils.buildSvgWithAssets(icon: AppAssets.ic_date_filter, width: 24, height: 24).onPress(() {
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.setTheDateRange.tr(context: context),
context,
@ -218,21 +231,17 @@ class LabResultDetails extends StatelessWidget {
Widget buildBottomLabel(String label) {
return Padding(
padding: const EdgeInsets.only(top:8.0),
padding: const EdgeInsets.only(top: 8.0),
child: Text(
label,
style: TextStyle(
fontSize: 8.f,
fontFamily: 'Poppins',
fontWeight: FontWeight.w600,
color: AppColors.labelTextColor),
style: TextStyle(fontSize: 8.f, fontFamily: 'Poppins', fontWeight: FontWeight.w600, color: AppColors.labelTextColor),
),
);
}
Widget historyBody(LabViewModel labmodel) {
if (labmodel.isGraphVisible && labmodel.shouldShowGraph) {
var graphColor = labmodel.getColor(recentLabResult.calculatedResultFlag??"N");
var graphColor = labmodel.getColor(recentLabResult.calculatedResultFlag ?? "N");
return CustomGraph(
dataPoints: labmodel.filteredGraphValues,
makeGraphBasedOnActualValue: true,
@ -242,11 +251,11 @@ class LabResultDetails extends StatelessWidget {
// maxY: (labmodel.maxY)+(getInterval(labmodel)??0)/2,
maxY: (labmodel.maxY),
minY: labmodel.minY,
maxX: labmodel.filteredGraphValues.length.toDouble()-.75,
maxX: labmodel.filteredGraphValues.length.toDouble() - .75,
horizontalInterval: .1,
getDrawingHorizontalLine: (value){
getDrawingHorizontalLine: (value) {
value = double.parse(value.toStringAsFixed(1));
if(value == labmodel.highRefrenceValue ||value== labmodel.lowRefenceValue) {
if (value == labmodel.highRefrenceValue || value == labmodel.lowRefenceValue) {
return FlLine(
color: AppColors.bgGreenColor.withOpacity(0.6),
strokeWidth: 1,
@ -256,7 +265,8 @@ class LabResultDetails extends StatelessWidget {
return FlLine(
color: Colors.transparent,
strokeWidth: 1,
);;
);
;
},
leftLabelFormatter: (value) {
value = double.parse(value.toStringAsFixed(1));
@ -272,11 +282,11 @@ class LabResultDetails extends StatelessWidget {
return SizedBox.shrink();
// }
},
graphColor:AppColors.blackColor,
graphColor: AppColors.blackColor,
graphShadowColor: Colors.transparent,
graphGridColor: graphColor.withOpacity(.4),
bottomLabelFormatter: (value, data) {
if(data.isEmpty) return SizedBox.shrink();
if (data.isEmpty) return SizedBox.shrink();
if (value == 0) {
return buildBottomLabel(data[value.toInt()].label);
}
@ -288,34 +298,34 @@ class LabResultDetails extends StatelessWidget {
}
return SizedBox.shrink();
},
rangeAnnotations: RangeAnnotations(
horizontalRangeAnnotations: _buildRangeShades(labmodel)
),
minX:(labmodel.filteredGraphValues.length == 1)?null : -.2,
rangeAnnotations: RangeAnnotations(horizontalRangeAnnotations: _buildRangeShades(labmodel)),
minX: (labmodel.filteredGraphValues.length == 1) ? null : -.2,
scrollDirection: Axis.horizontal,
height: 180.h);
}else {
} else {
return labHistoryList(labmodel);
}
}
List<HorizontalRangeAnnotation> _buildRangeShades( LabViewModel model,) {
List<HorizontalRangeAnnotation> _buildRangeShades(
LabViewModel model,
) {
List<HorizontalRangeAnnotation> ranges = [];
ranges.add(HorizontalRangeAnnotation(
y1:model.minY,
y2: model.lowRefenceValue,
color: AppColors.highAndLow.withOpacity(0.05),
));
ranges.add(HorizontalRangeAnnotation(
y1: model.minY,
y2: model.lowRefenceValue,
color: AppColors.highAndLow.withOpacity(0.05),
));
ranges.add(HorizontalRangeAnnotation(
y1:model.lowRefenceValue,
y1: model.lowRefenceValue,
y2: model.highRefrenceValue,
color: AppColors.bgGreenColor.withOpacity(0.05),
));
ranges.add(HorizontalRangeAnnotation(
y1:model.highRefrenceValue,
y1: model.highRefrenceValue,
y2: model.maxY,
color: AppColors.criticalLowAndHigh.withOpacity(0.05),
));
@ -324,11 +334,12 @@ class LabResultDetails extends StatelessWidget {
Widget labHistoryList(LabViewModel labmodel) {
return SizedBox(
height: labmodel.filteredGraphValues.length<3?labmodel.filteredGraphValues.length*64:180.h,
height: labmodel.filteredGraphValues.length < 3 ? labmodel.filteredGraphValues.length * 64 : 180.h,
child: ListView.separated(
padding: EdgeInsets.zero,
itemCount: labmodel.filteredGraphValues.length,itemBuilder: (context, index){
var data = labmodel.filteredGraphValues.reversed.toList()[index];
itemCount: labmodel.filteredGraphValues.length,
itemBuilder: (context, index) {
var data = labmodel.filteredGraphValues.reversed.toList()[index];
return LabHistoryItem(
dayNameAndDate: labmodel.getFormattedDate(data.time),
result: data.actualValue,
@ -368,11 +379,8 @@ class LabResultDetails extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8.h,
children: [
LocaleKeys.whatIsThisResult.tr(context: context)
.toText16(weight: FontWeight.w600, color: AppColors.textColor),
testDescription?.toText12(
fontWeight: FontWeight.w500, color: AppColors.textColorLight) ??
SizedBox.shrink()
LocaleKeys.whatIsThisResult.tr(context: context).toText16(weight: FontWeight.w600, color: AppColors.textColor),
testDescription?.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight) ?? SizedBox.shrink()
],
));
}

@ -0,0 +1,538 @@
import 'dart:convert';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/expandable_list_widget.dart';
import 'package:provider/provider.dart';
import '../../features/lab/models/resp_models/ai_overview_labresults_by_ai_model.dart';
class LabAiAnalysisDetailedPage extends StatefulWidget {
const LabAiAnalysisDetailedPage({super.key});
@override
State<LabAiAnalysisDetailedPage> createState() => _LabAiAnalysisDetailedPageState();
}
class _LabAiAnalysisDetailedPageState extends State<LabAiAnalysisDetailedPage> {
late LabViewModel labProvider;
@override
Widget build(BuildContext context) {
labProvider = Provider.of<LabViewModel>(context, listen: false);
return CollapsingListView(
title: LocaleKeys.aiAnalysis.tr(context: context),
child: Consumer<LabViewModel>(
builder: (context, model, child) {
if (model.labOrdersResponseByAi == null || model.labOrdersResponseByAi?.analysis == null) {
return Utils.getNoDataWidget(context);
}
final analysis = model.labOrdersResponseByAi!.analysis!;
List<ExpandableListItem> expandableItems = [];
// Summary Section
if (analysis.summary != null && analysis.summary!.isNotEmpty) {
expandableItems.add(
ExpandableListItem(
title: LocaleKeys.summary.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor),
initiallyExpanded: true,
children: [
SizedBox(height: 10.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, side: BorderSide(width: 1, color: Color(0xFF0B85F7))),
padding: EdgeInsets.all(16.r),
child: (analysis.summary ?? "").toText14(color: AppColors.textColorLight, height: 1.5, weight: FontWeight.w400),
),
SizedBox(height: 8.h),
],
),
);
}
// AI Generated Attributes Section (Lab Test Results)
if (analysis.aiGeneratedAttributes != null && analysis.aiGeneratedAttributes!.isNotEmpty) {
expandableItems.add(
ExpandableListItem(
title: LocaleKeys.labResults.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor),
children: [
SizedBox(height: 10.h),
...analysis.aiGeneratedAttributes!.map((attr) {
return Column(
children: [
_buildCombinedLabCard(attr),
SizedBox(height: 16.h),
],
);
}).toList(),
],
),
);
}
// AI Predictions Section
if (analysis.aiPredictions != null && analysis.aiPredictions!.isNotEmpty) {
expandableItems.add(
ExpandableListItem(
title: LocaleKeys.prediction.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor),
children: [
SizedBox(height: 10.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, side: BorderSide(width: 1, color: Color(0xFFFFAF15))),
padding: EdgeInsets.all(16.r),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: analysis.aiPredictions!.map((prediction) => _buildBulletItem(prediction)).toList(),
),
),
SizedBox(height: 8.h),
],
),
);
}
// AI Recommendations Section
if (analysis.aiRecommendations != null && analysis.aiRecommendations!.isNotEmpty) {
expandableItems.add(
ExpandableListItem(
title: LocaleKeys.suggestions.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor),
children: [
SizedBox(height: 10.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r),
padding: EdgeInsets.all(16.r),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: analysis.aiRecommendations!.asMap().entries.map((entry) {
final rec = entry.value;
final isLast = entry.key == analysis.aiRecommendations!.length - 1;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (rec.priority != null) ...[
Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(color: _getPriorityColor(rec.priority), borderRadius: BorderRadius.circular(4.r)),
child: rec.priority!.toText12(fontWeight: FontWeight.w600, color: _getPriorityTextColor(rec.priority)),
),
SizedBox(height: 8.h),
],
(rec.recommendation ?? "").toText14(
color: AppColors.textColorLight,
height: 1.5,
weight: FontWeight.w400,
),
if (!isLast) ...[
SizedBox(height: 12.h),
const Divider(color: AppColors.dividerColor, thickness: 1),
SizedBox(height: 12.h),
],
],
);
}).toList(),
),
),
SizedBox(height: 8.h),
],
),
);
}
// Risk Factors Section
if (analysis.riskFactors != null && analysis.riskFactors!.isNotEmpty) {
expandableItems.add(
ExpandableListItem(
title: LocaleKeys.riskFactors.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor),
children: [
SizedBox(height: 10.h),
...analysis.riskFactors!.asMap().entries.map((entry) {
final risk = entry.value;
return Column(
children: [
Container(
decoration: RoundedRectangleBorder()
.toSmoothCornerDecoration(color: Color(0xFFFBCB6E).withOpacity(0.10), borderRadius: 12.r, side: BorderSide(width: 1, color: Color(0xFFFBCB6E).withOpacity(0.10))),
padding: EdgeInsets.all(16.r),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(color: Color(0xFFFBCB6E).withAlpha(38), borderRadius: BorderRadius.circular(8)),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: (risk.risk ?? "").toText10(weight: FontWeight.w500, color: AppColors.blackColor),
),
),
),
],
),
if (risk.risk != null && risk.risk!.isNotEmpty) ...[
SizedBox(height: 4.h),
Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.guradIcon),
Padding(
padding: const EdgeInsets.all(8.0),
child: (risk.factor ?? "").toText16(weight: FontWeight.w700, color: AppColors.blackColor),
),
],
),
SizedBox(height: 4.h),
(risk.description ?? "").toText14(
color: AppColors.textColorLight,
height: 1.5,
weight: FontWeight.w400,
),
],
if (risk.description != null && risk.description!.isNotEmpty) ...[
SizedBox(height: 8.h),
(risk.description ?? "").toText14(
color: AppColors.textColorLight,
height: 1.5,
weight: FontWeight.w400,
),
],
],
),
),
SizedBox(height: 16.h),
],
);
}).toList(),
],
),
);
}
// Prevention Section
if (analysis.prevention != null && analysis.prevention!.isNotEmpty) {
expandableItems.add(
ExpandableListItem(
title: LocaleKeys.prevention.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor),
children: [
SizedBox(height: 10.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, side: BorderSide(width: 1, color: Color(0xFF18C273))),
padding: EdgeInsets.all(16.r),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: analysis.prevention!.map((prevention) => _buildBulletItem(prevention)).toList(),
),
),
SizedBox(height: 8.h),
],
),
);
}
// // Suggested Doctors Section
// if (analysis.suggestedDoctors != null && analysis.suggestedDoctors!.isNotEmpty) {
// expandableItems.add(
// ExpandableListItem(
// title: "Suggested Doctors".toText18(weight: FontWeight.w700, color: AppColors.blackColor),
// children: [
// SizedBox(height: 10.h),
// Container(
// decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
// color: AppColors.whiteColor,
// borderRadius: 12.r,
// side: BorderSide(width: 1, color: Color(0xFF3498DB)),
// ),
// padding: EdgeInsets.all(16.r),
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: analysis.suggestedDoctors!.asMap().entries.map((entry) {
// final doctor = entry.value;
// final isLast = entry.key == analysis.suggestedDoctors!.length - 1;
// return Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Row(
// children: [
// Container(
// width: 40.r,
// height: 40.r,
// decoration: BoxDecoration(
// gradient: AppColors.aiLinearGradient,
// shape: BoxShape.circle,
// ),
// child: Icon(
// Icons.person,
// color: Colors.white,
// size: 24.r,
// ),
// ),
// SizedBox(width: 12.w),
// Expanded(
// child: doctor.toString().toText14(
// color: AppColors.textColorLight,
// height: 1.5,
// weight: FontWeight.w600,
// ),
// ),
// ],
// ),
// if (!isLast) ...[
// SizedBox(height: 12.h),
// const Divider(color: AppColors.dividerColor, thickness: 1),
// SizedBox(height: 12.h),
// ],
// ],
// );
// }).toList(),
// ),
// ),
// SizedBox(height: 8.h),
// ],
// ),
// );
// }
return Column(
children: [
if (expandableItems.isEmpty)
Utils.getNoDataWidget(context)
else
CustomExpandableList(
expansionMode: ExpansionMode.exactlyOne,
dividerColor: Color(0xff2b353e1a),
itemPadding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 14.h),
items: expandableItems,
theme: ExpandableListTheme.custom(
defaultTrailingIcon: Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, height: 22.h, width: 22.w, iconColor: AppColors.textColor),
),
).paddingSymmetrical(16.w, 0.0),
],
);
},
),
);
}
Color _getPriorityColor(String? priority) {
if (priority == null) return AppColors.primaryRedColor;
switch (priority.toLowerCase()) {
case 'high':
return Color(0xFFED1C2B).withOpacity(.10);
case 'medium' || 'moderate':
return AppColors.lightGreenButtonColor;
case 'low':
return AppColors.warningColorYellow.withOpacity(.3);
default:
return AppColors.primaryRedColor;
}
}
Color _getPriorityTextColor(String? priority) {
if (priority == null) return AppColors.whiteColor;
switch (priority.toLowerCase()) {
case 'high':
return AppColors.primaryRedColor;
case 'medium' || 'moderate':
return AppColors.lightGreenColor;
case 'low':
return AppColors.alertColor;
default:
return AppColors.primaryRedColor;
}
}
Color _getResultFlagColor(String? flag) {
if (flag == null || flag.isEmpty) return Colors.grey;
switch (flag.toUpperCase()) {
case 'H': // High
case 'HH': // Very High
case 'CRITICAL HIGH':
return AppColors.lightRedButtonColor;
case 'L': // Low
case 'LL': // Very Low
case 'CRITICAL LOW':
return AppColors.chipColorSeekMedicalAdvice;
case 'N': // Normal
return AppColors.lightGreenButtonColor;
case 'A': // Abnormal
return Colors.deepOrange;
default:
return AppColors.greyLightColor;
}
}
Color _getResultTextColor(String? priority) {
if (priority == null) return AppColors.whiteColor;
switch (priority.toUpperCase()) {
case 'H': // High
case 'HH': // Very High
case 'CRITICAL HIGH':
return AppColors.primaryRedColor;
case 'N':
return AppColors.lightGreenColor;
case 'L': // Low
case 'LL': // Very Low
case 'CRITICAL LOW':
return AppColors.chipTextColorSeekMedicalAdvice;
default:
return AppColors.textColor;
}
}
Widget _buildBulletItem(String text) {
return Padding(
padding: EdgeInsets.only(bottom: 12.h),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: text.toText14(color: AppColors.textColorLight, height: 1.5, weight: FontWeight.w400),
),
],
),
);
}
Widget _buildCombinedLabCard(AiGeneratedAttribute attr) {
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 2.h,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (attr.calculatedResultFlag != null && attr.calculatedResultFlag!.isNotEmpty && attr.calculatedResultFlag == "-") ...[
Row(
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(color: AppColors.greyLightColor, borderRadius: BorderRadius.circular(4.r)),
child: Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.informationIcon, width: 14.w, height: 14.h),
SizedBox(width: 4.w),
LocaleKeys.contextDependent.tr().toText12(fontWeight: FontWeight.w600, color: AppColors.textColor),
],
),
),
],
),
] else ...[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(color: _getResultFlagColor(attr.calculatedResultFlag), borderRadius: BorderRadius.circular(4.r)),
child: Row(
children: [
(attr.calculatedResultFlag ?? "").toText12(fontWeight: FontWeight.w600, color: _getResultTextColor(attr.calculatedResultFlag)),
],
),
),
if (attr.calculatedResultFlag != null && attr.calculatedResultFlag!.isNotEmpty && attr.calculatedResultFlag == "H")
Utils.buildSvgWithAssets(icon: AppAssets.highCautionIcon, iconColor: AppColors.primaryRedColor, width: 24.w, height: 24.h),
],
),
],
SizedBox(height: 4.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(child: (attr.description ?? "").toText16(isBold: true, color: _getResultTextColor(attr.calculatedResultFlag))),
// if (attr.calculatedResultFlag != null && attr.calculatedResultFlag!.isNotEmpty)
// Column(
// spacing: 2.h,
// children: [
// Consumer<LabViewModel>(
// builder: (context, labViewModel, child) {
// return labViewModel.getSeverityText(attr.calculatedResultFlag ?? "").tr(context: context).toText10(weight: FontWeight.w500, color: AppColors.greyTextColor);
// },
// ),
// Consumer<LabViewModel>(
// builder: (context, labViewModel, child) {
// return Utils.buildSvgWithAssets(
// icon: AppAssets.lab_result_indicator,
// width: 21,
// height: 23,
// iconColor: labViewModel.getColor(attr.calculatedResultFlag ?? ""),
// );
// },
// ),
// ],
// ),
],
),
SizedBox(height: 4.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(child: (attr.testShortDescription ?? "").toText12(color: AppColors.textColor)),
],
),
SizedBox(height: 4.h)
],
),
Row(
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(color: _getResultFlagColor(attr.calculatedResultFlag), borderRadius: BorderRadius.circular(4.r)),
child: ("${LocaleKeys.calculatedValue.tr()}: ${attr.resultValue!}").toText12(fontWeight: FontWeight.w600, color: _getResultTextColor(attr.calculatedResultFlag)),
),
SizedBox(width: 8.h),
Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(color: AppColors.greyLightColor, borderRadius: BorderRadius.circular(4.r)),
child: ("${LocaleKeys.unit.tr()}: ${attr.uom!}").toText12(fontWeight: FontWeight.w600, color: AppColors.textColor),
),
],
),
SizedBox(
height: 4.h,
),
if (attr.referanceRange != null && attr.referanceRange!.isNotEmpty)
Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(color: AppColors.greyLightColor, borderRadius: BorderRadius.circular(4.r)),
child: ("${LocaleKeys.referenceRange.tr(context: context)}: ${attr.referanceRange!.trim()}").toText12(fontWeight: FontWeight.w600, color: AppColors.textColor),
),
if (attr.calculationFormula != null && attr.calculationFormula!.isNotEmpty) ...[
SizedBox(height: 8.h),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Utils.buildSvgWithAssets(icon: AppAssets.calendar, iconColor: Colors.grey),
SizedBox(width: 8.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
LocaleKeys.calculationFormula.tr().toText16(weight: FontWeight.w600),
(attr.calculationFormula ?? "").toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight),
],
),
),
],
),
],
],
),
);
}
}

@ -250,7 +250,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
children: [
AppCustomChipWidget(
icon: AppAssets.file_icon,
labelText: "${LocaleKeys.fileno.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}",
richText: "${LocaleKeys.fileno.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}".toText10(isEnglishOnly: true),
labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w),
),
AppCustomChipWidget(
@ -1579,6 +1579,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
weight: FontWeight.w600,
),
),
Utils.buildSvgWithAssets(icon: getIt.get<AppState>().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward, width: 18.w, height: 18.h),
],
),
SizedBox(height: 14.h),
@ -1601,6 +1602,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
child: value.toText17(
isBold: true,
color: AppColors.textColor,
isEnglishOnly: true
),
),
if (unit.isNotEmpty) ...[

@ -18,6 +18,8 @@ import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'dart:ui' as ui;
class MedicalFileAppointmentCard extends StatelessWidget {
final PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel;
final MyAppointmentsViewModel myAppointmentsViewModel;
@ -39,11 +41,12 @@ class MedicalFileAppointmentCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AppCustomChipWidget(
richText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)
.toText12(
color: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor,
fontWeight: FontWeight.w500)
.paddingOnly(left: 8.w),
richText: Directionality(
textDirection: ui.TextDirection.ltr,
child: DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)
.toText12(color: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor, fontWeight: FontWeight.w500, isEnglishOnly: true)
.paddingSymmetrical(8.w, 0),
),
icon: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppAssets.appointment_calendar_icon : AppAssets.alarm_clock_icon,
iconColor: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor,
iconSize: 16.w,
@ -71,9 +74,7 @@ class MedicalFileAppointmentCard extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
(patientAppointmentHistoryResponseModel.doctorNameObj ?? "")
.toText14(isBold: true, maxlines: 1)
.toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading),
(patientAppointmentHistoryResponseModel.doctorNameObj ?? "").toText14(isBold: true, maxlines: 1).toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading),
(patientAppointmentHistoryResponseModel.clinicName ?? "")
.toText12(maxLine: 1, fontWeight: FontWeight.w500, color: AppColors.greyTextColor)
.toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading),
@ -103,10 +104,8 @@ class MedicalFileAppointmentCard extends StatelessWidget {
// widget.myAppointmentsViewModel.getPatientAppointments(true, false);
});
},
backgroundColor:
AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.15),
borderColor:
AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01),
backgroundColor: AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.15),
borderColor: AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01),
textColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction),
fontSize: 14.f,
fontWeight: FontWeight.w500,

@ -1,4 +1,4 @@
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
@ -7,12 +7,12 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/qr_parking/models/qr_parking_response_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:map_launcher/map_launcher.dart';
import '../../features/qr_parking/qr_parking_view_model.dart';
import '../../theme/colors.dart';
import '../../widgets/appbar/app_bar_widget.dart';
import '../../widgets/buttons/custom_button.dart';
import '../../widgets/chip/app_custom_chip_widget.dart';
import 'package:maps_launcher/maps_launcher.dart';
import 'package:provider/provider.dart';
import '../../widgets/routes/custom_page_route.dart';
@ -29,7 +29,7 @@ class ParkingSlot extends StatefulWidget {
}
class _ParkingSlotState extends State<ParkingSlot> {
void _openDirection() {
void _openDirection() async {
final lat = widget.model.latitude;
final lng = widget.model.longitude;
@ -48,7 +48,17 @@ class _ParkingSlotState extends State<ParkingSlot> {
return;
}
MapsLauncher.launchCoordinates(lat, lng);
await MapLauncher.showMarker(
mapType: MapType.google,
coords: Coords(lat, lng),
title: "Parking Location",
).catchError((err) {
MapLauncher.showMarker(
mapType: Platform.isIOS ? MapType.apple : MapType.google,
coords: Coords(lat, lng),
title: "Parking Location",
);
});
}

@ -0,0 +1,199 @@
import 'dart:async';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/services_price_list/services_price_list_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:provider/provider.dart';
import 'package:shimmer/shimmer.dart';
class ServicesPriceListPage extends StatefulWidget {
const ServicesPriceListPage({super.key});
@override
State<ServicesPriceListPage> createState() => _ServicesPriceListPageState();
}
class _ServicesPriceListPageState extends State<ServicesPriceListPage> {
late ServicesPriceListViewModel servicesPriceListViewModel;
late AppState appState;
@override
void initState() {
scheduleMicrotask(() {
servicesPriceListViewModel.initServicesPriceListProvider();
});
super.initState();
}
@override
Widget build(BuildContext context) {
servicesPriceListViewModel = Provider.of<ServicesPriceListViewModel>(context, listen: false);
appState = getIt.get<AppState>();
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView(
title: LocaleKeys.servicePriceList.tr(context: context),
child: SingleChildScrollView(
padding: EdgeInsets.all(24.h),
child: Consumer<ServicesPriceListViewModel>(builder: (context, model, child) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: true,
),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h),
child: LocaleKeys.servicePriceListDesc.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight),
),
),
SizedBox(height: 16.h),
model.isServicesPriceListLoading
? ListView.separated(
shrinkWrap: true,
padding: EdgeInsets.zero,
physics: NeverScrollableScrollPhysics(),
itemCount: 5,
separatorBuilder: (context, index) => SizedBox(height: 12.h),
itemBuilder: (context, index) => _buildLoadingCard(),
)
: model.filteredServicesPriceList.isEmpty
? Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: true,
),
padding: EdgeInsets.all(40.h),
child: Center(
child: LocaleKeys.noDataAvailable.tr(context: context).toText16(
color: AppColors.textColorLight,
),
),
)
: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: true,
),
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.serviceName.tr(context: context).toText18(weight: FontWeight.bold, color: AppColors.textColor),
SizedBox(height: 16.h),
ListView.separated(
shrinkWrap: true,
padding: EdgeInsets.zero,
physics: NeverScrollableScrollPhysics(),
itemCount: model.filteredServicesPriceList.length,
separatorBuilder: (context, index) => Divider(height: 1.h, color: AppColors.dividerColor).withVerticalPadding(),
itemBuilder: (context, index) {
final service = model.filteredServicesPriceList[index];
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: (appState.isArabic() ? service.nameAR ?? service.nameEN ?? '' : service.nameEN ?? service.nameAR ?? '').toText16(
weight: FontWeight.w500,
color: AppColors.textColor,
),
),
SizedBox(width: 12.w),
Utils.getPaymentAmountWithSymbol('${service.price ?? 0}'.toText16(isBold: true), AppColors.blackColor, 13, isSaudiCurrency: true),
],
),
],
),
),
),
);
},
),
],
),
),
SizedBox(height: 16.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: true,
),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h),
child: LocaleKeys.servicePriceListRights.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight),
),
),
],
);
}),
),
),
);
}
Widget _buildLoadingCard() {
return Shimmer.fromColors(
baseColor: AppColors.greyColor.withValues(alpha: 0.3),
highlightColor: AppColors.greyColor.withValues(alpha: 0.1),
child: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: true,
),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Container(
height: 20.h,
decoration: BoxDecoration(
color: AppColors.greyColor,
borderRadius: BorderRadius.circular(4.r),
),
),
),
SizedBox(width: 12.w),
Container(
width: 80.w,
height: 32.h,
decoration: BoxDecoration(
color: AppColors.greyColor,
borderRadius: BorderRadius.circular(12.r),
),
),
],
),
),
),
);
}
}

@ -24,8 +24,8 @@ class HealthDashboard extends StatefulWidget {
class _HealthDashboardState extends State<HealthDashboard> with SingleTickerProviderStateMixin {
late TabController _tabController;
final dateFormat = DateFormat('MMM dd, yyyy');
final timeFormat = DateFormat('hh:mm a');
final dateFormat = DateFormat('MMM dd, yyyy', "en-US");
final timeFormat = DateFormat('hh:mm a', "en-US");
@override
void initState() {

@ -9,6 +9,7 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/route_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
@ -39,13 +40,18 @@ class _UserInfoSelectionPageState extends State<UserInfoSelectionPage> {
if (appState.isAuthenticated) {
final user = appState.getAuthenticatedUser();
getIt.get<HmgServicesViewModel>().getPatientVitalSign();
if (user == null) return;
// Populate gender (gender is int: 1=Male, 2=Female)
// Use internal keys (male/female) for storage
if (user.gender != null) {
String genderKey = user.gender == 1 ? "male" : user.gender == 2 ? "female" : "other";
String genderKey = user.gender == 1
? "male"
: user.gender == 2
? "female"
: "other";
viewModel.setGender(genderKey);
}
@ -64,13 +70,14 @@ class _UserInfoSelectionPageState extends State<UserInfoSelectionPage> {
// If not authenticated or fields are empty, user will fill them manually
}
_buildEditInfoTile({
Widget _buildEditInfoTile({
required String leadingIcon,
required String title,
required String subTitle,
required VoidCallback onTap,
required String trailingIcon,
required BuildContext context,
required HmgServicesViewModel hmgServicesVM,
Color? iconColor,
}) {
return InkWell(
@ -92,7 +99,9 @@ class _UserInfoSelectionPageState extends State<UserInfoSelectionPage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
title.toText14(weight: FontWeight.w500),
subTitle.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500),
subTitle
.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500)
.toShimmer2(isShow: (leadingIcon == AppAssets.rulerIcon || leadingIcon == AppAssets.weightScale) && hmgServicesVM.isVitalSignLoading),
],
),
],
@ -144,6 +153,7 @@ class _UserInfoSelectionPageState extends State<UserInfoSelectionPage> {
age--;
}
userAgeFromDOB = age;
getIt.get<SymptomsCheckerViewModel>().setSelectedAge(userAgeFromDOB);
} catch (e) {
// If date parsing fails, ignore
}
@ -154,25 +164,20 @@ class _UserInfoSelectionPageState extends State<UserInfoSelectionPage> {
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: Consumer<SymptomsCheckerViewModel>(
builder: (context, viewModel, child) {
body: Consumer2<SymptomsCheckerViewModel, HmgServicesViewModel>(
builder: (context, viewModel, hmgServicesVM, child) {
// Check if any field is empty
bool hasEmptyFields = viewModel.selectedGender == null ||
viewModel.selectedAge == null ||
viewModel.selectedHeight == null ||
viewModel.selectedWeight == null;
bool hasEmptyFields = viewModel.selectedGender == null || viewModel.selectedAge == null || viewModel.selectedHeight == null || viewModel.selectedWeight == null;
// Get display values
String genderText = _getLocalizedGender(viewModel.selectedGender, context);
// Show age calculated from DOB (prefer viewModel's age, fallback to calculated from user's DOB)
int? displayAge = viewModel.selectedAge ?? userAgeFromDOB;
String ageText = displayAge != null ? "$displayAge ${LocaleKeys.years.tr(context: context)}" : LocaleKeys.notSet.tr(context: context);
String heightText = viewModel.selectedHeight != null
? "${viewModel.selectedHeight!.round()} ${viewModel.isHeightCm ? 'cm' : 'ft'}"
: LocaleKeys.notSet.tr(context: context);
String weightText = viewModel.selectedWeight != null
? "${viewModel.selectedWeight!.round()} ${viewModel.isWeightKg ? 'kg' : 'lbs'}"
: LocaleKeys.notSet.tr(context: context);
String heightText = viewModel.selectedHeight != null ? "${viewModel.selectedHeight!.round()} ${viewModel.isHeightCm ? 'cm' : 'ft'}" : LocaleKeys.notSet.tr(context: context);
String weightText = viewModel.selectedWeight != null ? "${viewModel.selectedWeight!.round()} ${viewModel.isWeightKg ? 'kg' : 'lbs'}" : LocaleKeys.notSet.tr(context: context);
return Column(
children: [
@ -195,53 +200,53 @@ class _UserInfoSelectionPageState extends State<UserInfoSelectionPage> {
),
SizedBox(height: 32.h),
_buildEditInfoTile(
context: context,
leadingIcon: AppAssets.genderIcon,
title: LocaleKeys.gender.tr(context: context),
subTitle: genderText,
onTap: () {
viewModel.setUserInfoPage(0, isSinglePageEdit: true);
context.navigateWithName(AppRoutes.userInfoFlowManager);
},
trailingIcon: AppAssets.edit_icon,
),
context: context,
leadingIcon: AppAssets.genderIcon,
title: LocaleKeys.gender.tr(context: context),
subTitle: genderText,
onTap: () {
viewModel.setUserInfoPage(0, isSinglePageEdit: true);
context.navigateWithName(AppRoutes.userInfoFlowManager);
},
trailingIcon: AppAssets.edit_icon,
hmgServicesVM: hmgServicesVM),
_getDivider(),
_buildEditInfoTile(
context: context,
leadingIcon: AppAssets.calendarGrey,
title: LocaleKeys.age.tr(context: context),
subTitle: ageText,
iconColor: AppColors.greyTextColor,
onTap: () {
viewModel.setUserInfoPage(1, isSinglePageEdit: true);
context.navigateWithName(AppRoutes.userInfoFlowManager);
},
trailingIcon: AppAssets.edit_icon,
),
context: context,
leadingIcon: AppAssets.calendarGrey,
title: LocaleKeys.age.tr(context: context),
subTitle: ageText,
iconColor: AppColors.greyTextColor,
onTap: () {
viewModel.setUserInfoPage(1, isSinglePageEdit: true);
context.navigateWithName(AppRoutes.userInfoFlowManager);
},
trailingIcon: AppAssets.edit_icon,
hmgServicesVM: hmgServicesVM),
_getDivider(),
_buildEditInfoTile(
context: context,
leadingIcon: AppAssets.rulerIcon,
title: LocaleKeys.height.tr(context: context),
subTitle: heightText,
onTap: () {
viewModel.setUserInfoPage(2, isSinglePageEdit: true);
context.navigateWithName(AppRoutes.userInfoFlowManager);
},
trailingIcon: AppAssets.edit_icon,
),
context: context,
leadingIcon: AppAssets.rulerIcon,
title: LocaleKeys.height.tr(context: context),
subTitle: heightText,
onTap: () {
viewModel.setUserInfoPage(2, isSinglePageEdit: true);
context.navigateWithName(AppRoutes.userInfoFlowManager);
},
trailingIcon: AppAssets.edit_icon,
hmgServicesVM: hmgServicesVM),
_getDivider(),
_buildEditInfoTile(
context: context,
leadingIcon: AppAssets.weightScale,
title: LocaleKeys.weight.tr(context: context),
subTitle: weightText,
onTap: () {
viewModel.setUserInfoPage(3, isSinglePageEdit: true);
context.navigateWithName(AppRoutes.userInfoFlowManager);
},
trailingIcon: AppAssets.edit_icon,
),
context: context,
leadingIcon: AppAssets.weightScale,
title: LocaleKeys.weight.tr(context: context),
subTitle: weightText,
onTap: () {
viewModel.setUserInfoPage(3, isSinglePageEdit: true);
context.navigateWithName(AppRoutes.userInfoFlowManager);
},
trailingIcon: AppAssets.edit_icon,
hmgServicesVM: hmgServicesVM),
],
),
),
@ -291,6 +296,7 @@ class _UserInfoSelectionPageState extends State<UserInfoSelectionPage> {
text: LocaleKeys.yesItIs.tr(context: context),
icon: AppAssets.tickIcon,
iconColor: hasEmptyFields ? AppColors.greyTextColor : AppColors.whiteColor,
isDisabled: getIt.get<HmgServicesViewModel>().isVitalSignLoading || hasEmptyFields,
onPressed: hasEmptyFields
? () {} // Empty function for disabled state
: () => context.navigateWithName(AppRoutes.organSelectorPage),

@ -158,7 +158,7 @@ class HeightSelectionPage extends StatelessWidget {
enableSound: true,
minValue: minValue,
maxValue: maxValue,
initialHeight: viewModel.selectedHeight ?? 100,
initialHeight: (viewModel.selectedHeight ?? 100).toDouble(),
isCm: viewModel.isHeightCm,
onHeightChanged: (newHeight) {
log("height: $newHeight");

@ -150,7 +150,7 @@ class WeightSelectionPage extends StatelessWidget {
enableSound: true,
minValue: minValue,
maxValue: maxValue,
initialWeight: viewModel.selectedWeight!,
initialWeight: (viewModel.selectedWeight ?? 70).toDouble(),
isKg: isKg,
onWeightChanged: (newWeight) {
log("weight: $newWeight");

@ -148,6 +148,7 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
isBold: true,
color: scheme.iconFg,
letterSpacing: -2,
isEnglishOnly: true
),
),
SizedBox(width: 4.h),

@ -3,6 +3,8 @@ import 'dart:ui';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
@ -330,6 +332,9 @@ class _VitalSignPageState extends State<VitalSignPage> {
weight: FontWeight.w600,
),
),
Utils.buildSvgWithAssets(
icon: getIt.get<AppState>().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward,
width: 18.w, height: 18.h),
],
),
SizedBox(height: 14.h),
@ -353,6 +358,7 @@ class _VitalSignPageState extends State<VitalSignPage> {
value.toText17(
isBold: true,
color: AppColors.textColor,
isEnglishOnly: true
),
if (unit.isNotEmpty) ...[
SizedBox(width: 3.w),

@ -17,6 +17,7 @@ import 'package:hmg_patient_app_new/presentation/health_trackers/health_trackers
import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
import 'package:hmg_patient_app_new/presentation/home_health_care/hhc_procedures_page.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart';
import 'package:hmg_patient_app_new/presentation/servicesPriceList/services_price_list_page.dart';
import 'package:hmg_patient_app_new/presentation/smartwatches/huawei_health_example.dart';
import 'package:hmg_patient_app_new/presentation/smartwatches/smartwatch_home_page.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/organ_selector_screen.dart';
@ -89,6 +90,9 @@ class AppRoutes {
// Emergency Services
static const String emergencyServicesPage = '/emergencyServicesPage';
// Services Price List
static const String servicesPriceListPage = '/servicesPriceListPage';
static Map<String, WidgetBuilder> get routes => {
initialRoute: (context) => SplashPage(),
loginScreen: (context) => LoginScreen(),
@ -121,6 +125,7 @@ class AppRoutes {
healthTrackersPage: (context) => HealthTrackersPage(),
vitalSign: (context) => VitalSignPage(),
emergencyServicesPage: (context) => EmergencyServicesPage(),
servicesPriceListPage: (context) => ServicesPriceListPage(),
addHealthTrackerEntryPage: (context) {
final args = ModalRoute.of(context)?.settings.arguments as HealthTrackerTypeEnum?;
return AddHealthTrackerEntryPage(

@ -23,7 +23,7 @@ abstract class DialogService {
Future<void> showExceptionBottomSheet({required String message, required Function() onOkPressed, Function()? onCancelPressed});
Future<void> showCommonBottomSheetWithoutH({String? label, required String message, required Function() onOkPressed, Function()? onCancelPressed});
Future<void> showCommonBottomSheetWithoutH({String? label, required String message, String? okLabel, String? cancelLabel, required Function() onOkPressed, Function()? onCancelPressed});
Future<void> showSuccessBottomSheetWithoutH({String? label, required String message, required Function() onOkPressed, Function()? onCancelPressed});
@ -115,13 +115,20 @@ class DialogServiceImp implements DialogService {
}
@override
Future<void> showCommonBottomSheetWithoutH({String? label, required String message, required Function() onOkPressed, Function()? onCancelPressed}) async {
Future<void> showCommonBottomSheetWithoutH({String? label, required String message, String? okLabel, String? cancelLabel, required Function() onOkPressed, Function()? onCancelPressed}) async {
final context = navigationService.navigatorKey.currentContext;
if (context == null) return;
showCommonBottomSheetWithoutHeight(
context,
title: label ?? "",
child: exceptionBottomSheetWidget(context: context, message: message, onOkPressed: onOkPressed, onCancelPressed: onCancelPressed),
child: exceptionBottomSheetWidget(
context: context,
message: message,
okLabel: okLabel,
cancelLabel: cancelLabel,
onOkPressed: onOkPressed,
onCancelPressed: onCancelPressed,
),
callBackFunc: () {},
);
}
@ -231,7 +238,7 @@ class DialogServiceImp implements DialogService {
}
}
Widget exceptionBottomSheetWidget({required BuildContext context, required String message, required Function() onOkPressed, Function()? onCancelPressed}) {
Widget exceptionBottomSheetWidget({required BuildContext context, required String message, String? okLabel, String? cancelLabel, required Function() onOkPressed, Function()? onCancelPressed}) {
return Column(
children: [
(message).toText16(isBold: false, color: AppColors.textColor),
@ -242,7 +249,7 @@ Widget exceptionBottomSheetWidget({required BuildContext context, required Strin
children: [
Expanded(
child: CustomButton(
text: LocaleKeys.cancel.tr(context: context),
text: cancelLabel ?? LocaleKeys.cancel.tr(context: context),
onPressed: () {
Navigator.of(context).pop();
},
@ -256,7 +263,7 @@ Widget exceptionBottomSheetWidget({required BuildContext context, required Strin
SizedBox(width: 10.h),
Expanded(
child: CustomButton(
text: LocaleKeys.confirm.tr(context: context),
text: okLabel ?? LocaleKeys.confirm.tr(context: context),
onPressed: onOkPressed,
backgroundColor: AppColors.bgGreenColor,
borderColor: AppColors.bgGreenColor,
@ -270,7 +277,7 @@ Widget exceptionBottomSheetWidget({required BuildContext context, required Strin
Padding(
padding: EdgeInsets.only(bottom: 10.h),
child: CustomButton(
text: LocaleKeys.cancel.tr(context: context),
text: cancelLabel ?? LocaleKeys.cancel.tr(context: context),
onPressed: (onCancelPressed == null)
? () {
context.pop();

@ -114,4 +114,6 @@ class AppColors {
static const Color lightGreyTextColor = Color(0xFF959595);
static const Color labelColorYellow = Color(0xFFFBCB6E);
static const Color tooltipColor = Color(0xFF1AACACAC);
static const LinearGradient aiLinearGradient = LinearGradient(colors: [Color(0xFF8A38F5), Color(0xFFE20BBB)], begin: Alignment.topLeft, end: Alignment.bottomRight);
}

@ -19,6 +19,7 @@ class CollapsingListView extends StatelessWidget {
VoidCallback? search;
VoidCallback? report;
VoidCallback? logout;
VoidCallback? aiOverview;
VoidCallback? history;
VoidCallback? instructions;
VoidCallback? requests;
@ -40,6 +41,7 @@ class CollapsingListView extends StatelessWidget {
this.bottomChild,
this.report,
this.logout,
this.aiOverview,
this.history,
this.instructions,
this.requests,
@ -85,9 +87,11 @@ class CollapsingListView extends StatelessWidget {
history: history,
instructions: instructions,
requests: requests,
sendEmail: sendEmail, doctorResponse: doctorResponse,
sendEmail: sendEmail,
doctorResponse: doctorResponse,
bottomChild: bottomChild,
trailing: trailing,
aiOverview: aiOverview,
),
// : null,
leading: Visibility(
@ -190,6 +194,7 @@ class ScrollAnimatedTitle extends StatefulWidget implements PreferredSizeWidget
VoidCallback? search;
VoidCallback? report;
VoidCallback? logout;
VoidCallback? aiOverview;
VoidCallback? history;
VoidCallback? instructions;
VoidCallback? requests;
@ -206,6 +211,7 @@ class ScrollAnimatedTitle extends StatefulWidget implements PreferredSizeWidget
this.search,
this.report,
this.logout,
this.aiOverview,
this.history,
this.instructions,
this.requests,
@ -288,6 +294,7 @@ class _ScrollAnimatedTitleState extends State<ScrollAnimatedTitle> {
if (widget.sendEmail != null) actionButton(context, t, title: LocaleKeys.sendEmail.tr(context: context), icon: AppAssets.email).onPress(widget.sendEmail!),
if (widget.doctorResponse != null) actionButton(context, t, title: LocaleKeys.doctorResponses.tr(context: context), icon: AppAssets.doctorResponseIcon).onPress(widget.doctorResponse!),
if (widget.search != null) Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(widget.search!),
if (widget.aiOverview != null) actionButton(context, t, title: LocaleKeys.aiOverView.tr(context: context), icon: AppAssets.aiOverView, isAiButton: true).onPress(widget.aiOverview!),
if (widget.trailing != null) widget.trailing!,
]
],
@ -295,40 +302,77 @@ class _ScrollAnimatedTitleState extends State<ScrollAnimatedTitle> {
);
}
Widget actionButton(BuildContext context, double t, {required String title, required String icon}) {
return AnimatedSize(
duration: Duration(milliseconds: 150),
child: Container(
height: 40.h,
padding: EdgeInsets.all(8.w),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.secondaryLightRedColor,
borderRadius: 10.r,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
spacing: 8.h,
children: [
Utils.buildSvgWithAssets(icon: icon, iconColor: AppColors.primaryRedColor, height: 24.h, width: 24.w),
if (t >= .5)
Text(
title,
style: context.dynamicTextStyle(
color: AppColors.primaryRedColor,
letterSpacing: -0.4,
fontSize: (12 - (2 * (1 - t))).f,
fontWeight: FontWeight.lerp(
FontWeight.w400,
FontWeight.w500,
t,
)!,
Widget actionButton(BuildContext context, double t, {required String title, required String icon, bool isAiButton = false}) {
return isAiButton
? AnimatedSize(
duration: Duration(milliseconds: 150),
child: Center(
child: Container(
height: 40.h,
padding: EdgeInsets.all(8.w),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.transparent, borderRadius: 10.r, side: BorderSide(width: 1, color: AppColors.borderGrayColor)),
child: ShaderMask(
blendMode: BlendMode.srcIn,
shaderCallback: (bounds) => AppColors.aiLinearGradient.createShader(bounds),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
spacing: 8.h,
children: [
Utils.buildSvgWithAssets(icon: icon, iconColor: AppColors.primaryRedColor, height: 24.h, width: 24.w),
Text(
title,
style: context.dynamicTextStyle(
color: AppColors.primaryRedColor,
letterSpacing: -0.4,
fontSize: (12 - (2 * (1 - t))).f,
fontWeight: FontWeight.lerp(
FontWeight.w400,
FontWeight.w500,
t,
)!,
),
),
],
),
),
),
],
),
),
);
),
)
: AnimatedSize(
duration: Duration(milliseconds: 150),
child: Container(
height: 40.h,
padding: EdgeInsets.all(8.w),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.secondaryLightRedColor,
borderRadius: 10.r,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
spacing: 8.h,
children: [
Utils.buildSvgWithAssets(icon: icon, iconColor: AppColors.primaryRedColor, height: 24.h, width: 24.w),
if (t >= .5)
Text(
title,
style: context.dynamicTextStyle(
color: AppColors.primaryRedColor,
letterSpacing: -0.4,
fontSize: (12 - (2 * (1 - t))).f,
fontWeight: FontWeight.lerp(
FontWeight.w400,
FontWeight.w500,
t,
)!,
),
),
],
),
),
);
}
}

@ -158,9 +158,10 @@ class GenericBottomSheetState extends State<GenericBottomSheet> {
prefix: widget.isForEmail ? null : widget.countryCode,
isBorderAllowed: false,
isAllowLeadingIcon: true,
fontSize: 13,
fontSize: 18.f,
isCountryDropDown: widget.isEnableCountryDropdown,
leadingIcon: widget.isForEmail ? AppAssets.email : AppAssets.smart_phone,
fontFamily: "Poppins",
)
: SizedBox(),
],

@ -100,7 +100,7 @@ class CustomCountryDropdownState extends State<CustomCountryDropdown> {
children: [
Text(
selectedCountry!.countryCode,
style: TextStyle(fontSize: 12.f, fontWeight: FontWeight.w600, letterSpacing: -0.4, height: 1.5),
style: TextStyle(fontSize: 12.f, fontWeight: FontWeight.w600, letterSpacing: -0.4, height: 1.5, fontFamily: "Poppins"),
),
SizedBox(width: 4.h),
if (widget.isEnableTextField)
@ -111,7 +111,7 @@ class CustomCountryDropdownState extends State<CustomCountryDropdown> {
alignment: Alignment.centerLeft,
child: TextField(
focusNode: textFocusNode,
style: TextStyle(fontSize: 12.f, fontWeight: FontWeight.w600, letterSpacing: -0.4, height: 1.5),
style: TextStyle(fontSize: 12.f, fontWeight: FontWeight.w600, letterSpacing: -0.4, height: 1.5, fontFamily: "Poppins"),
decoration: InputDecoration(hintText: "", isDense: true, border: InputBorder.none, contentPadding: EdgeInsets.zero),
keyboardType: TextInputType.phone,
onChanged: widget.onPhoneNumberChanged,

@ -54,6 +54,7 @@ class FamilyFileAddWidget extends StatelessWidget {
isAllowLeadingIcon: true,
autoFocus: true,
keyboardType: TextInputType.number,
fontFamily: "Poppins",
padding: EdgeInsets.symmetric(vertical: 8.h),
leadingIcon: AppAssets.student_card,
).paddingOnly(top: 8.h, bottom: 8.h),
@ -69,6 +70,7 @@ class FamilyFileAddWidget extends StatelessWidget {
isAllowLeadingIcon: true,
autoFocus: true,
keyboardType: TextInputType.number,
fontFamily: "Poppins",
padding: EdgeInsets.symmetric(vertical: 8.h),
leadingIcon: AppAssets.smart_phone,
).paddingOnly(top: 8.h, bottom: 4.h),

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hijri_gregorian_calendar/hijri_gregorian_calendar.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
@ -11,6 +12,7 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/dropdown/country_dropdown_widget.dart';
import 'package:hmg_patient_app_new/widgets/time_picker_widget.dart';
import 'dart:ui' as ui;
class TextInputWidget extends StatelessWidget {
final String labelText;
@ -48,6 +50,8 @@ class TextInputWidget extends StatelessWidget {
final bool? isHideSwitcher;
final bool? isArrowTrailing;
final String? fontFamily;
// final List<Country> countryList;
// final Function(Country)? onCountryChange;
@ -86,6 +90,7 @@ class TextInputWidget extends StatelessWidget {
this.maxLines = 6,
this.isHideSwitcher,
this.isArrowTrailing,
this.fontFamily,
// this.countryList = const [],
// this.onCountryChange,
});
@ -226,11 +231,8 @@ class TextInputWidget extends StatelessWidget {
initialDate: DateTime.now(),
showCalendarToggle: isHideSwitcher == true ? false : true,
fontFamily: appState.getLanguageCode() == "ar" ? "GESSTwo" : "Poppins",
okWidget:
Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.confirm, width: 24.h, height: 24.h)),
cancelWidget: Padding(
padding: EdgeInsets.only(right: 8.h),
child: Utils.buildSvgWithAssets(icon: AppAssets.cancel, iconColor: Colors.white, width: 24.h, height: 24.h)),
okWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.confirm, width: 24.h, height: 24.h)),
cancelWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.cancel, iconColor: Colors.white, width: 24.h, height: 24.h)),
onCalendarTypeChanged: (bool value) {
isGregorian = value;
});
@ -300,50 +302,103 @@ class TextInputWidget extends StatelessWidget {
Widget _buildTextField(BuildContext context) {
double fontS = fontSize ?? 14.f;
return TextField(
enabled: isEnable,
scrollPadding: EdgeInsets.zero,
keyboardType: isMultiline ? TextInputType.multiline : keyboardType,
controller: controller,
readOnly: isReadOnly,
textAlignVertical: TextAlignVertical.top,
textAlign: TextAlign.left,
textDirection: TextDirection.ltr,
onChanged: onChange,
focusNode: focusNode ?? _focusNode,
autofocus: autoFocus,
textInputAction: TextInputAction.done,
cursorHeight: isWalletAmountInput! ? 40.h : 20.h,
onTapOutside: (event) {
FocusManager.instance.primaryFocus?.unfocus();
return Builder(
builder: (context) {
return Localizations.override(
context: context,
locale: const Locale('en', 'US'), // Force English locale for TextField
child: TextField(
hintLocales: const [Locale('en', 'US')],
enabled: isEnable,
scrollPadding: EdgeInsets.zero,
keyboardType: isMultiline ? TextInputType.multiline : keyboardType,
controller: controller,
readOnly: isReadOnly,
textAlignVertical: TextAlignVertical.top,
textAlign: TextAlign.left,
textDirection: TextDirection.ltr,
onChanged: onChange,
focusNode: focusNode ?? _focusNode,
autofocus: autoFocus,
textInputAction: TextInputAction.done,
cursorHeight: isWalletAmountInput! ? 40.h : 20.h,
onTapOutside: (event) {
FocusManager.instance.primaryFocus?.unfocus();
},
onSubmitted: onSubmitted,
minLines: isMultiline ? minLines : 1,
maxLines: isMultiline ? maxLines : 1,
style: TextStyle(
fontSize: fontS,
height: isMultiline ? 1.2 : (isWalletAmountInput! ? 1 / 4 : 0),
fontWeight: FontWeight.w500,
color: AppColors.textColor,
letterSpacing: -1,
// fontFamily: keyboardType == TextInputType.number ? getIt.get<AppState>().isArabic() ? 'GESSTwo' : 'Poppins' : 'Poppins',
fontFamily: fontFamily,
locale: const Locale('en', 'US'), // Force English locale for text style
),
decoration: InputDecoration(
isDense: true,
hintText: hintText,
hintStyle: TextStyle(fontSize: 14.f, height: 21 / 16, fontWeight: FontWeight.w500, color: hintColor != null ? AppColors.textColor : Color(0xff898A8D), letterSpacing: -0.75),
prefixIconConstraints: BoxConstraints(minWidth: 30.h),
prefixIcon: prefix == null ? null : "+${prefix!}".toText14(letterSpacing: -1, color: AppColors.textColor, weight: FontWeight.w500),
contentPadding: EdgeInsets.zero,
border: InputBorder.none,
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
),
),
);
},
onSubmitted: onSubmitted,
minLines: isMultiline ? minLines : 1,
maxLines: isMultiline ? maxLines : 1,
style: TextStyle(
fontSize: fontS,
height: isMultiline ? 1.2 : (isWalletAmountInput! ? 1 / 4 : 0),
fontWeight: FontWeight.w500,
color: AppColors.textColor,
letterSpacing: -1,
),
decoration: InputDecoration(
isDense: true,
hintText: hintText,
hintStyle: TextStyle(
fontSize: 14.f,
height: 21 / 16,
fontWeight: FontWeight.w500,
color: hintColor != null ? AppColors.textColor : Color(0xff898A8D),
letterSpacing: -0.75),
prefixIconConstraints: BoxConstraints(minWidth: 30.h),
prefixIcon: prefix == null ? null : "+${prefix!}".toText14(letterSpacing: -1, color: AppColors.textColor, weight: FontWeight.w500),
contentPadding: EdgeInsets.zero,
border: InputBorder.none,
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
),
);
// TextField(
// hintLocales: const [Locale('en', 'US')],
// enabled: isEnable,
// scrollPadding: EdgeInsets.zero,
// keyboardType: isMultiline ? TextInputType.multiline : keyboardType,
// controller: controller,
// readOnly: isReadOnly,
// textAlignVertical: TextAlignVertical.top,
// textAlign: TextAlign.left,
// textDirection: TextDirection.ltr,
// onChanged: onChange,
// focusNode: focusNode ?? _focusNode,
// autofocus: autoFocus,
// textInputAction: TextInputAction.done,
// cursorHeight: isWalletAmountInput! ? 40.h : 20.h,
// onTapOutside: (event) {
// FocusManager.instance.primaryFocus?.unfocus();
// },
// onSubmitted: onSubmitted,
// minLines: isMultiline ? minLines : 1,
// maxLines: isMultiline ? maxLines : 1,
// style: TextStyle(
// fontSize: fontS,
// height: isMultiline ? 1.2 : (isWalletAmountInput! ? 1 / 4 : 0),
// fontWeight: FontWeight.w500,
// color: AppColors.textColor,
// letterSpacing: -1,
// ),
// decoration: InputDecoration(
// isDense: true,
// hintText: hintText,
// hintStyle: TextStyle(
// fontSize: 14.f,
// height: 21 / 16,
// fontWeight: FontWeight.w500,
// color: hintColor != null ? AppColors.textColor : Color(0xff898A8D),
// letterSpacing: -0.75),
// prefixIconConstraints: BoxConstraints(minWidth: 30.h),
// prefixIcon: prefix == null ? null : "+${prefix!}".toText14(letterSpacing: -1, color: AppColors.textColor, weight: FontWeight.w500),
// contentPadding: EdgeInsets.zero,
// border: InputBorder.none,
// focusedBorder: InputBorder.none,
// enabledBorder: InputBorder.none,
// ),
// );
}
_buildTrailingIconForSearch(BuildContext context) {

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart';
@ -8,7 +10,7 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:maps_launcher/maps_launcher.dart';
import 'package:map_launcher/map_launcher.dart';
/// A reusable location map widget that displays a static map with address and directions button
/// Can be used in any review screen (HHC, CMC, etc.)
@ -56,9 +58,19 @@ class LocationMapWidget extends StatelessWidget {
this.padding,
});
void _defaultLaunchDirections() {
void _defaultLaunchDirections() async {
if (latitude != 0.0 && longitude != 0.0) {
MapsLauncher.launchCoordinates(latitude, longitude, address);
await MapLauncher.showMarker(
mapType: MapType.google,
coords: Coords(latitude, longitude),
title: address,
).catchError((err) {
MapLauncher.showMarker(
mapType: Platform.isIOS ? MapType.apple : MapType.google,
coords: Coords(latitude, longitude),
title: address,
);
});
}
}

@ -73,7 +73,7 @@ dependencies:
web: any
flutter_staggered_animations: ^1.1.1
smooth_corner: ^1.1.1
maps_launcher: ^3.0.0+1
map_launcher: ^4.4.3
amazon_payfort: ^1.1.4
network_info_plus: ^6.1.4
flutter_nfc_kit: ^3.6.0

Loading…
Cancel
Save