Merge branch 'refs/heads/master' into faiz_dev

# Conflicts:
#	lib/presentation/symptoms_checker/user_info_selection.dart
faiz_dev
faizatflutter 1 week ago
commit 17f32e34d3

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 13C4 8.58172 7.58172 5 12 5C16.4183 5 20 8.58172 20 13C20 13.5523 20.4477 14 21 14C21.5523 14 22 13.5523 22 13C22 7.47715 17.5228 3 12 3C6.47715 3 2 7.47715 2 13C2 13.5523 2.44772 14 3 14C3.55228 14 4 13.5523 4 13Z" fill="#D48D05"/>
<path d="M12 9C12.5523 9 13 9.44772 13 10L13 15.1707C14.1652 15.5825 15 16.6938 15 18C15 19.6569 13.6569 21 12 21C10.3431 21 9 19.6569 9 18C9 16.6938 9.83481 15.5825 11 15.1707L11 10C11 9.44772 11.4477 9 12 9Z" fill="#D48D05"/>
</svg>

After

Width:  |  Height:  |  Size: 576 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.1 KiB

@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.8271 3.35515C16.8461 1.52662 14.1719 2.25537 12.5563 3.46861C12.4618 3.5396 12.3778 3.60261 12.3027 3.6585C12.1487 3.77312 12.0717 3.83043 11.9724 3.83043C11.873 3.83043 11.796 3.77312 11.642 3.6585C11.5669 3.60262 11.4829 3.5396 11.3884 3.46861C9.77284 2.25537 7.09861 1.52662 4.11763 3.35515C2.63347 4.26554 1.61665 5.88968 1.31555 7.86051C1.27161 8.14807 1.24965 8.29185 1.33944 8.39639C1.42923 8.50094 1.58234 8.50092 1.88856 8.50089L5.14448 8.50059C5.55656 8.49886 6.25061 8.49594 6.92509 8.74131C7.18957 8.83753 7.42861 8.9636 7.64109 9.10184C7.94838 9.30175 8.10203 9.40171 8.22623 9.37305C8.35044 9.34439 8.43459 9.20413 8.60289 8.92363L9.32823 7.71474C9.78963 6.94573 10.6276 6.48278 11.5242 6.50152C12.4208 6.52027 13.2387 7.01784 13.6676 7.80546L14.9648 10.1879C15.0479 10.3405 15.0895 10.4168 15.1603 10.4589C15.2311 10.501 15.318 10.501 15.4918 10.501H15.972C17.3527 10.501 18.472 11.6203 18.472 13.001C18.472 14.3817 17.3527 15.501 15.972 15.501L14.8556 15.5031C14.4407 15.5127 13.1951 15.5415 12.106 14.705C12.0741 14.6805 12.0429 14.6555 12.0124 14.6302C11.6751 14.3499 11.5064 14.2097 11.3704 14.2328C11.2345 14.2559 11.1366 14.419 10.9409 14.7453L10.6157 15.2872C10.1827 16.0089 9.41568 16.4642 8.5748 16.4989C7.73393 16.5335 6.93207 16.1427 6.44122 15.4591L5.21484 13.7511C5.12681 13.6285 5.0828 13.5672 5.01833 13.5341C4.95385 13.501 4.87839 13.501 4.72747 13.501L3.23319 13.501C2.80205 13.501 2.58648 13.501 2.49975 13.6503C2.41302 13.7997 2.51637 13.9808 2.72307 14.3432C3.7896 16.2127 5.46319 18.1083 7.86568 19.8865C9.31705 20.9614 10.3823 21.7503 11.9723 21.7503C13.5624 21.7503 14.6276 20.9614 16.079 19.8865C20.2586 16.7929 22.2322 13.3443 22.6412 10.2951C23.0481 7.2606 21.8883 4.61952 19.8271 3.35515Z" fill="#18C273"/>
<path d="M12.3509 8.5218C12.1794 8.20675 11.8522 8.00772 11.4936 8.00022C11.1349 7.99272 10.7997 8.1779 10.6152 8.48551L8.39777 12.1812L7.72833 11.2488C7.71432 11.2293 7.69962 11.2103 7.68425 11.1919C7.66865 11.1731 7.65154 11.1522 7.63294 11.1295C7.4192 10.8684 7.00838 10.3666 6.41298 10.15C5.9948 9.99782 5.53719 9.99883 5.08287 9.99984L4.97266 10L2.97266 10C2.42037 10 1.97266 10.4477 1.97266 11C1.97266 11.5523 2.42037 12 2.97266 12H4.97266C5.60991 12 5.68621 12.0141 5.72762 12.0289C5.73136 12.0307 5.76392 12.0476 5.83105 12.1122C5.91437 12.1924 5.9964 12.2905 6.12473 12.4445L7.66036 14.5832C7.8567 14.8567 8.17744 15.013 8.51379 14.9992C8.85014 14.9853 9.15695 14.8032 9.33015 14.5145L11.4306 11.0138L12.2854 12.5838C12.295 12.6013 12.305 12.6186 12.3156 12.6356L12.3573 12.7026C12.5191 12.9635 12.7156 13.2804 13.0204 13.5144C13.6863 14.0259 14.4768 14.0096 14.863 14.0016C14.9044 14.0008 14.9412 14 14.9727 14H15.9727C16.5249 14 16.9727 13.5523 16.9727 13C16.9727 12.4477 16.5249 12 15.9727 12H14.9727C14.9119 12 14.8581 12.0003 14.8096 12.0005C14.6334 12.0015 14.5272 12.002 14.416 11.9872C14.3192 11.9743 14.276 11.9545 14.248 11.9352C14.2434 11.9296 14.2355 11.9197 14.2239 11.9037C14.1807 11.8444 14.1277 11.7611 14.0275 11.601L12.3509 8.5218Z" fill="#18C273"/>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.083 8.3418C16.1804 8.26933 16.3034 8.26171 16.4092 8.32129C16.4449 8.34147 16.4818 8.37722 16.5537 8.44922C18.2206 9.92249 19.4021 12.5001 18.3545 14.9922C18.0491 15.7186 17.3429 16.2001 16.5498 16.2002L16.2725 16.1982C16.045 16.1964 15.9309 16.1958 15.8486 16.2598C15.7666 16.3238 15.7397 16.4344 15.6855 16.6553L14.7207 20.5879C14.4117 21.8473 13.2957 22.7461 11.998 22.7461C10.7005 22.746 9.5844 21.8473 9.27539 20.5879L8.31152 16.6553C8.25731 16.4342 8.2297 16.3238 8.14746 16.2598C8.06513 16.1957 7.95135 16.1964 7.72363 16.1982L7.44727 16.2002C6.6541 16.2002 5.94699 15.7187 5.6416 14.9922C4.59407 12.5 5.77638 9.92243 7.44336 8.44922C7.51481 8.37768 7.55044 8.34145 7.58594 8.32129C7.69173 8.26141 7.81555 8.26931 7.91309 8.3418C7.94584 8.36621 7.98081 8.41097 8.0498 8.5C8.14907 8.62808 8.25089 8.75011 8.35254 8.86426C8.78359 9.34823 9.29846 9.79001 9.86914 10.124C10.4188 10.4457 11.1568 10.7461 11.998 10.7461C12.8393 10.7461 13.5773 10.4457 14.127 10.124C14.6976 9.79001 15.2125 9.34823 15.6436 8.86426C15.7452 8.75013 15.847 8.62806 15.9463 8.5C16.0154 8.41081 16.0502 8.36627 16.083 8.3418ZM12 1.25C14.0711 1.25 15.75 2.92893 15.75 5C15.75 6.07361 15.1945 7.1188 14.5254 7.87012C14.1835 8.25405 13.7876 8.58923 13.3711 8.83301C12.9615 9.07273 12.4873 9.25 12 9.25C11.5127 9.25 11.0385 9.07273 10.6289 8.83301C10.2124 8.58923 9.81654 8.25405 9.47461 7.87012C8.80554 7.1188 8.25 6.07361 8.25 5C8.25 2.92893 9.92893 1.25 12 1.25Z" fill="#0B85F7"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.9999 3.125C10.9999 2.57272 11.4476 2.125 11.9999 2.125C12.5522 2.125 12.9999 2.57272 12.9999 3.125V5.25648L13.6002 5.6567C13.6063 5.64329 13.6125 5.62994 13.6188 5.61666C13.7792 5.27762 14.0126 4.95992 14.3466 4.72784C14.6864 4.4917 15.0865 4.375 15.5234 4.375C17.3868 4.375 19.2054 5.74706 20.5136 7.78194C21.844 9.8515 22.75 12.745 22.75 16.125C22.75 18.5571 21.9318 20.0447 20.8453 20.9014C19.7942 21.7302 18.6097 21.875 18.0144 21.875C16.8144 21.875 15.5948 21.6036 14.6787 20.7304C13.7563 19.8512 13.2788 18.4938 13.2788 16.625C13.2842 16.2851 13.3181 15.4637 13.4101 14.8979C13.5229 14.0671 13.7417 12.9986 14.1873 12.0938C13.709 10.979 13.3028 9.33793 13.2548 7.89942C13.254 7.87588 13.2533 7.85231 13.2527 7.82873L11.9999 6.99352L10.7473 7.82858C10.7467 7.85222 10.746 7.87583 10.7452 7.89942C10.6973 9.33793 10.291 10.979 9.81277 12.0938C10.2583 12.9986 10.4772 14.0671 10.5899 14.8979C10.682 15.4637 10.7158 16.2851 10.7213 16.625C10.7213 18.4938 10.2438 19.8512 9.32137 20.7304C8.40522 21.6036 7.18561 21.875 5.98563 21.875C5.39035 21.875 4.20581 21.7302 3.15476 20.9014C2.06825 20.0447 1.25 18.5571 1.25 16.125C1.25 12.745 2.15607 9.8515 3.48648 7.78194C4.79459 5.74706 6.61322 4.375 8.47665 4.375C8.91351 4.375 9.3136 4.4917 9.65343 4.72784C9.98742 4.95992 10.2208 5.27762 10.3812 5.61666C10.3875 5.6299 10.3937 5.64322 10.3997 5.65659L10.9999 5.25648V3.125Z" fill="#18C273"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -0,0 +1,6 @@
<svg width="67" height="74" viewBox="0 0 67 74" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M66.6078 32.2697C66.1744 30.4738 65.1769 29.2328 63.7991 28.7779C63.3117 28.6169 62.7964 28.5751 62.2712 28.6277C63.2293 24.3157 64.7466 19.7654 64.5501 15.5873C64.1645 7.35177 58.7442 0.579242 41.8415 0.0148163C31.0287 -0.346213 20.1447 5.91597 20.6036 16.0287C20.8633 21.7671 21.9015 25.0884 22.6027 28.1906L22.5955 28.6872C21.9433 28.5588 21.3005 28.5802 20.7048 28.7779C19.3245 29.2328 18.327 30.4738 17.8953 32.2697C17.4945 33.929 17.6233 35.8995 18.2581 37.8187C19.2879 40.9412 21.4426 43.2444 23.6866 43.6967C24.8086 48.1266 26.7007 52.0335 29.1423 55.1157L28.172 72.4976C28.1488 72.947 28.4967 73.3275 28.9447 73.3489C28.9581 73.3504 28.9715 73.3518 28.9853 73.3518C29.4158 73.3518 29.7757 73.0131 29.7974 72.5777L30.6851 56.8736C33.9673 60.2504 37.9975 62.271 42.3673 62.271C46.7608 62.271 50.8119 60.229 54.1046 56.8167L54.9949 72.5777C55.0166 73.0131 55.3771 73.3518 55.8074 73.3518C55.8211 73.3518 55.8345 73.3504 55.848 73.3489C56.296 73.3275 56.6436 72.947 56.6204 72.4976L55.646 55.0476C58.0739 51.9631 59.9511 48.0599 61.0666 43.6372C63.217 43.0688 65.2513 40.8234 66.2445 37.8187C66.8779 35.8995 67.0069 33.9304 66.6078 32.2697ZM19.8023 37.3084C19.2636 35.6723 19.1472 34.0197 19.4762 32.6514C19.7725 31.421 20.3896 30.5942 21.2152 30.3207C21.6323 30.184 22.0894 30.1988 22.5741 30.3493L22.5226 34.373C22.5226 36.9688 22.7961 39.47 23.2684 41.8561C21.862 41.1861 20.5169 39.474 19.8023 37.3084ZM42.3673 60.6429C32.3223 60.6429 24.1495 48.8586 24.1495 34.3838L24.2782 24.4253C24.2957 23.0704 23.6463 18.5731 28.6563 18.5731C35.6019 18.5731 35.2 23.0896 41.772 23.0896C48.4944 23.0896 49.0054 18.4254 55.848 18.4254C61.0748 18.4254 60.4495 23.4953 60.4698 25.083L60.5874 34.373C60.5874 48.8586 52.4154 60.6429 42.3673 60.6429ZM64.6994 37.3084C64.028 39.3398 62.8019 40.9779 61.4919 41.7277C61.9508 39.3764 62.2157 36.9159 62.2157 34.3635L62.1642 30.287C62.5604 30.2003 62.9383 30.2071 63.2873 30.3221C64.1118 30.5942 64.7292 31.421 65.0258 32.6514C65.3557 34.0197 65.2381 35.6723 64.6994 37.3084Z" fill="#2B353E"/>
<path d="M14.4426 56.2374C14.4426 59.3856 11.8899 61.9383 8.74195 61.9383C5.59398 61.9383 3.04248 59.3856 3.04248 56.2374C3.04248 53.0909 5.59398 50.5382 8.74195 50.5382C11.8899 50.5382 14.4426 53.0909 14.4426 56.2374Z" fill="#EC1B2A"/>
<path d="M8.74187 64.9804C3.92094 64.9804 0 61.058 0 56.2374C0 53.2991 1.50647 50.5382 3.9587 48.9287V19.423C3.9587 16.7865 6.10398 14.6387 8.74187 14.6387C11.3809 14.6387 13.5276 16.7865 13.5276 19.423V48.9302C15.9784 50.5382 17.4849 53.2991 17.4849 56.2374C17.4849 61.058 13.5617 64.9804 8.74187 64.9804ZM8.74187 16.1909C6.95934 16.1909 5.51009 17.6405 5.51009 19.423V49.807L5.1256 50.0307C2.92082 51.3189 1.55138 53.6967 1.55138 56.2374C1.55138 60.2029 4.77773 63.4293 8.74187 63.4293C12.706 63.4293 15.9326 60.2029 15.9326 56.2374C15.9326 53.6982 14.5644 51.3189 12.361 50.0318L11.9751 49.807V19.423C11.9751 17.6405 10.5258 16.1909 8.74187 16.1909Z" fill="#2B353E"/>
<path d="M10.4569 51.7886H7.02686V25.6216H10.4569V51.7886Z" fill="#EC1B2A"/>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.1459 1.27079C12.8412 1.24999 12.47 1.24999 12.0253 1.25C11.5806 1.24999 11.1588 1.24999 10.8541 1.27079C10.5368 1.29245 10.2372 1.33914 9.94665 1.45953C9.27321 1.73863 8.73814 2.2737 8.45905 2.94713C8.33865 3.23764 8.29196 3.53731 8.2703 3.85456C8.2495 4.15932 8.2495 4.53054 8.24951 4.97522L8.24951 12.6414C7.0263 13.6949 6.25 15.2569 6.25 17C6.25 20.1756 8.82436 22.75 12 22.75C15.1756 22.75 17.75 20.1756 17.75 17C17.75 15.2569 16.9737 13.6949 15.7505 12.6414L15.7505 4.97524C15.7505 4.53055 15.7505 4.15933 15.7297 3.85456C15.708 3.53731 15.6614 3.23764 15.541 2.94713C15.2619 2.2737 14.7268 1.73863 14.0534 1.45953C13.7628 1.33914 13.4632 1.29245 13.1459 1.27079ZM12 7C12.5523 7 13 7.44772 13 8L13 14.4375C14.0243 14.8375 14.75 15.834 14.75 17C14.75 18.5188 13.5188 19.75 12 19.75C10.4812 19.75 9.25 18.5188 9.25 17C9.25 15.834 9.97566 14.8375 11 14.4375L11 8C11 7.44772 11.4477 7 12 7Z" fill="#ED1C2B"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.6439 1.25L13.3561 1.25C14.259 1.24997 15.0211 1.24994 15.6249 1.32042C16.255 1.39396 16.8707 1.55847 17.3706 2.00915C17.5958 2.21222 17.7873 2.44899 17.9376 2.71113C18.2326 3.22573 18.2812 3.77334 18.2347 4.31609C19.2103 4.4287 20.0196 4.68604 20.6521 5.31966C21.2538 5.92238 21.5125 6.68113 21.6335 7.58241C21.75 8.4507 21.75 9.55477 21.75 10.9249V16.043C21.75 17.4131 21.75 18.5172 21.6335 19.3855C21.5125 20.2867 21.2538 21.0455 20.6521 21.6482C20.0502 22.2511 19.2923 22.5105 18.3919 22.6318C17.5249 22.7486 16.4225 22.7486 15.055 22.7485L8.94504 22.7485C7.57752 22.7486 6.47513 22.7486 5.60808 22.6318C4.70774 22.5105 3.94976 22.2511 3.3479 21.6482C2.74621 21.0455 2.48747 20.2867 2.3665 19.3855C2.24997 18.5172 2.24998 17.4131 2.25 16.0431L2.25 10.9248C2.24998 9.55478 2.24997 8.45069 2.3665 7.58241C2.48747 6.68113 2.74621 5.92238 3.3479 5.31966C3.98043 4.68604 4.78975 4.4287 5.76534 4.31608C5.71879 3.77334 5.76736 3.22573 6.06239 2.71113C6.21268 2.44899 6.40422 2.21222 6.62944 2.00915C7.1293 1.55847 7.74503 1.39396 8.37506 1.32042C8.97893 1.24994 9.74097 1.24997 10.6439 1.25ZM8.60067 3.2073C8.13641 3.26149 7.99252 3.35241 7.92575 3.4126C7.8493 3.48154 7.78534 3.56095 7.73568 3.64756C7.69637 3.71613 7.64027 3.8608 7.69746 4.30137C7.75673 4.75797 7.91092 5.36081 8.14823 6.27796C8.33725 7.00848 8.46246 7.48783 8.59555 7.84684C8.72171 8.18716 8.82647 8.33584 8.9287 8.43308C9.00978 8.51021 9.10016 8.57847 9.1982 8.63631C9.32461 8.71091 9.5056 8.77465 9.88389 8.81094C10.1378 8.83531 10.4406 8.84448 10.8301 8.84793L11.5514 6.68404C11.726 6.1601 12.2924 5.87694 12.8163 6.05159C13.3402 6.22624 13.6234 6.79256 13.4488 7.3165L12.9378 8.84935C13.4424 8.84743 13.8138 8.83994 14.1161 8.81094C14.4944 8.77465 14.6754 8.71091 14.8018 8.63631C14.8998 8.57847 14.9902 8.51021 15.0713 8.43308C15.1735 8.33584 15.2783 8.18716 15.4045 7.84684C15.5375 7.48783 15.6628 7.00848 15.8518 6.27796C16.0891 5.36081 16.2433 4.75797 16.3025 4.30137C16.3597 3.8608 16.3036 3.71613 16.2643 3.64756C16.2147 3.56095 16.1507 3.48153 16.0742 3.4126C16.0075 3.35241 15.8636 3.26149 15.3993 3.2073C14.9218 3.15157 14.2747 3.15 13.2979 3.15L10.7021 3.15C9.72532 3.15 9.07816 3.15157 8.60067 3.2073ZM10 17C9.44772 17 9 17.4477 9 18C9 18.5523 9.44772 19 10 19H14C14.5523 19 15 18.5523 15 18C15 17.4477 14.5523 17 14 17L10 17Z" fill="#18C273"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

@ -246,6 +246,7 @@ class AppAssets {
static const String rotateIcon = '$svgBasePath/rotate_icon.svg';
static const String refreshIcon = '$svgBasePath/refresh.svg';
static const String homeBorderedIcon = '$svgBasePath/home_bordered.svg';
static const String symptomCheckerIcon = '$svgBasePath/symptom_checker_icon.svg';
// Water Monitor
static const String waterBottle = '$svgBasePath/water_bottle.svg';
@ -284,6 +285,19 @@ class AppAssets {
static const String covid19icon = '$svgBasePath/covid_19.svg';
//vital sign
static const String heartRate = '$svgBasePath/heart_rate.svg';
static const String respRate = '$svgBasePath/resp_rate.svg';
static const String weightVital = '$svgBasePath/weight_2.svg';
static const String bmiVital = '$svgBasePath/bmi_2.svg';
static const String heightVital = '$svgBasePath/height_2.svg';
static const String bloodPressure = '$svgBasePath/blood_pressure.svg';
static const String temperature = '$svgBasePath/temperature.svg';
// PNGS //
static const String hmgLogo = '$pngBasePath/hmg_logo.png';
static const String liveCareService = '$pngBasePath/livecare_service.png';
@ -309,7 +323,7 @@ class AppAssets {
static const String fullBodyFront = '$pngBasePath/full_body_front.png';
static const String fullBodyBack = '$pngBasePath/full_body_back.png';
static const String bmiFullBody = '$pngBasePath/bmi_image_1.png';
}

@ -34,6 +34,8 @@ import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_
import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_repo.dart';
import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_view_model.dart';
import 'package:hmg_patient_app_new/features/payfort/payfort_repo.dart';
import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart';
import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_repo.dart';
@ -138,6 +140,7 @@ class AppDependencies {
getIt.registerLazySingleton<SymptomsCheckerRepo>(() => SymptomsCheckerRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<BloodDonationRepo>(() => BloodDonationRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<WaterMonitorRepo>(() => WaterMonitorRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<MyInvoicesRepo>(() => MyInvoicesRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
// ViewModels
// Global/shared VMs LazySingleton
@ -271,5 +274,7 @@ class AppDependencies {
getIt.registerLazySingleton<HealthProvider>(() => HealthProvider());
getIt.registerLazySingleton<WaterMonitorViewModel>(() => WaterMonitorViewModel(waterMonitorRepo: getIt()));
getIt.registerLazySingleton<MyInvoicesViewModel>(() => MyInvoicesViewModel(myInvoicesRepo: getIt(), errorHandlerService: getIt(), navServices: getIt()));
}
}

@ -846,6 +846,17 @@ class Utils {
isHMC: hospital.isHMC);
}
static HospitalsModel? convertToHospitalsModel(PatientDoctorAppointmentList? item) {
if (item == null) return null;
return HospitalsModel(
name: item.filterName,
nameN: item.filterName,
distanceInKilometers: item.distanceInKMs,
isHMC: item.isHMC,
);
}
static bool havePrivilege(int id) {
bool isHavePrivilege = false;
try {

@ -32,6 +32,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_vie
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/authentication/login.dart';
import 'package:hmg_patient_app_new/presentation/authentication/saved_login_screen.dart';
import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
import 'package:hmg_patient_app_new/routes/app_routes.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
@ -39,6 +40,7 @@ import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/services/localauth_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.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:sms_otp_auto_verify/sms_otp_auto_verify.dart';
import 'models/request_models/get_user_mobile_device_data.dart';
@ -565,7 +567,7 @@ class AuthenticationViewModel extends ChangeNotifier {
if (!_appState.getIsChildLoggedIn) {
await medicalVm.getFamilyFiles(status: 0);
await medicalVm.getAllPendingRecordsByResponseId();
_navigationService.popUntilNamed(AppRoutes.landingScreen);
_navigationService.replaceAllRoutesAndNavigateToLanding();
}
} else {
if (activation.list != null && activation.list!.isNotEmpty) {
@ -675,7 +677,12 @@ class AuthenticationViewModel extends ChangeNotifier {
}
Future<void> navigateToHomeScreen() async {
_navigationService.pushAndReplace(AppRoutes.landingScreen);
Navigator.pushAndRemoveUntil(
_navigationService.navigatorKey.currentContext!,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
}
Future<void> navigateToOTPScreen(

@ -935,8 +935,8 @@ class HmgServicesRepoImp implements HmgServicesRepo {
try {
List<VitalSignResModel> vitalSignList = [];
if (response['PatientVitalSignList'] != null && response['PatientVitalSignList'] is List) {
final vitalSignsList = response['PatientVitalSignList'] as List;
if (response['List_DoctorPatientVitalSign'] != null && response['List_DoctorPatientVitalSign'] is List) {
final vitalSignsList = response['List_DoctorPatientVitalSign'] as List;
for (var vitalSignJson in vitalSignsList) {
if (vitalSignJson is Map<String, dynamic>) {

@ -51,6 +51,19 @@ class HmgServicesViewModel extends ChangeNotifier {
HospitalsModel? selectedHospital;
List<VitalSignResModel> vitalSignList = [];
// Vital Sign PageView Controller
PageController _vitalSignPageController = PageController();
PageController get vitalSignPageController => _vitalSignPageController;
int _vitalSignCurrentPage = 0;
int get vitalSignCurrentPage => _vitalSignCurrentPage;
void setVitalSignCurrentPage(int page) {
_vitalSignCurrentPage = page;
notifyListeners();
}
// HHC specific lists
List<GetCMCAllOrdersResponseModel> hhcOrdersList = [];
List<GetCMCServicesResponseModel> hhcServicesList = [];
@ -896,4 +909,10 @@ class HmgServicesViewModel extends ChangeNotifier {
},
);
}
@override
void dispose() {
_vitalSignPageController.dispose();
super.dispose();
}
}

@ -0,0 +1,102 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
/// UI-only helper model for Vital Sign cards.
///
/// Keeps presentation logic (chip colors, icon colors, simple status rules)
/// in one place so it can be reused across multiple pages.
class VitalSignUiModel {
final Color iconBg;
final Color iconFg;
final Color chipBg;
final Color chipFg;
const VitalSignUiModel({
required this.iconBg,
required this.iconFg,
required this.chipBg,
required this.chipFg,
});
/// Returns a color scheme for a card based on its [status] and [label].
///
/// Rules (mirrors existing behavior in Medical File page):
/// - Height is always blue.
/// - High => red scheme.
/// - Low => yellow scheme.
/// - Otherwise => green scheme (Normal).
static VitalSignUiModel scheme({required String? status, required String label}) {
final s = (status ?? '').toLowerCase();
final l = label.toLowerCase();
// Height should always be blue.
if (l.contains('height')) {
return VitalSignUiModel(
iconBg: AppColors.infoColor.withValues(alpha: 0.12),
iconFg: AppColors.infoColor,
chipBg: AppColors.infoColor.withValues(alpha: 0.12),
chipFg: AppColors.infoColor,
);
}
if (s.contains('high')) {
return const VitalSignUiModel(
iconBg: AppColors.chipSecondaryLightRedColor,
iconFg: AppColors.primaryRedColor,
chipBg: AppColors.chipSecondaryLightRedColor,
chipFg: AppColors.primaryRedColor,
);
}
if (s.contains('low')) {
final Color yellowBg = AppColors.warningColor.withValues(alpha: 0.12);
return VitalSignUiModel(
iconBg: yellowBg,
iconFg: AppColors.warningColor,
chipBg: yellowBg,
chipFg: AppColors.warningColor,
);
}
// Normal (green)
final Color greenBg = AppColors.lightGreenColor;
return VitalSignUiModel(
iconBg: greenBg,
iconFg: AppColors.bgGreenColor,
chipBg: greenBg,
chipFg: AppColors.bgGreenColor,
);
}
/// Simple, user-friendly classification:
/// - Low: systolic < 90 OR diastolic < 60
/// - High: systolic >= 140 OR diastolic >= 90
/// - Normal: otherwise
/// Returns null if values are missing/unparseable.
static String? bloodPressureStatus({dynamic systolic, dynamic diastolic}) {
final int? s = toIntOrNull(systolic);
final int? d = toIntOrNull(diastolic);
if (s == null || d == null) return null;
if (s < 90 || d < 60) return 'Low';
if (s >= 140 || d >= 90) return 'High';
return 'Normal';
}
static int? toIntOrNull(dynamic v) {
if (v == null) return null;
if (v is int) return v;
if (v is double) return v.round();
return int.tryParse(v.toString());
}
static String bmiStatus(dynamic bmi) {
if (bmi == null) return 'N/A';
final double bmiValue = double.tryParse(bmi.toString()) ?? 0;
if (bmiValue < 18.5) return 'Underweight';
if (bmiValue < 25) return 'Normal';
if (bmiValue < 30) return 'Overweight';
return 'High';
}
}

@ -1,6 +1,10 @@
import 'package:flutter/foundation.dart' show ChangeNotifier;
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_state.dart' show AppState;
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/doctor_list_api_response.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/dental_chief_complaints_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/laser/laser_appointment.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/select_clinic_page.dart';
@ -30,7 +34,14 @@ class AppointmentViaRegionViewmodel extends ChangeNotifier {
AppointmentViaRegionState bottomSheetState =
AppointmentViaRegionState.REGION_SELECTION;
final AppState appState;
TextEditingController searchController = TextEditingController();
List<PatientDoctorAppointmentList>? hospitalList;
List<PatientDoctorAppointmentList>? hmgHospitalList;
List<PatientDoctorAppointmentList>? hmcHospitalList;
List<PatientDoctorAppointmentList>? displayList;
FacilitySelection selectedFacility = FacilitySelection.ALL;
int hmgCount = 0;
int hmcCount = 0;
RegionBottomSheetType regionBottomSheetType = RegionBottomSheetType.FOR_REGION;
AppointmentViaRegionViewmodel({required this.navigationService,required this.appState});
@ -40,6 +51,35 @@ class AppointmentViaRegionViewmodel extends ChangeNotifier {
notifyListeners();
}
void setDisplayListAndRegionHospitalList(PatientDoctorAppointmentListByRegion? registeredDoctorMap){
if(registeredDoctorMap == null) {
return;
}
selectedFacility = FacilitySelection.ALL;
hmcHospitalList = [];
hmgHospitalList = [];
hospitalList = [];
displayList = [];
for(var data in registeredDoctorMap.hmgDoctorList!){
hmgHospitalList?.add(data);
}
for(var data in registeredDoctorMap.hmcDoctorList!){
hmcHospitalList?.add(data);
}
hospitalList!.addAll(hmgHospitalList!);
hospitalList!.addAll(hmcHospitalList!);
hmcCount = registeredDoctorMap.hmcSize;
hmgCount = registeredDoctorMap.hmgSize;
getDisplayList();
}
void setFacility(String? facility) {
selectedFacilityType = facility;
notifyListeners();
@ -71,7 +111,7 @@ class AppointmentViaRegionViewmodel extends ChangeNotifier {
setSelectedRegionId(null);
break;
case AppointmentViaRegionState.HOSPITAL_SELECTION:
setBottomSheetState(AppointmentViaRegionState.TYPE_SELECTION);
setBottomSheetState(AppointmentViaRegionState.REGION_SELECTION);
break;
default:
}
@ -129,4 +169,48 @@ class AppointmentViaRegionViewmodel extends ChangeNotifier {
),
);
}
searchHospitals(String query) {
if (query.isEmpty) {
getDisplayList();
return;
}
List<PatientDoctorAppointmentList>? sourceList;
switch (selectedFacility) {
case FacilitySelection.ALL:
sourceList = hospitalList;
break;
case FacilitySelection.HMG:
sourceList = hmgHospitalList;
break;
case FacilitySelection.HMC:
sourceList = hmcHospitalList;
break;
}
displayList = sourceList?.where((hospital) => hospital.filterName != null && hospital.filterName!.toLowerCase().contains(query.toLowerCase())).toList();
notifyListeners();
}
getDisplayList() {
switch (selectedFacility) {
case FacilitySelection.ALL:
displayList = hospitalList;
break;
case FacilitySelection.HMG:
displayList = hmgHospitalList;
break;
case FacilitySelection.HMC:
displayList = hmcHospitalList;
break;
}
notifyListeners();
}
setSelectedFacility(FacilitySelection selection) {
selectedFacility = selection;
notifyListeners();
}
}

@ -127,6 +127,8 @@ class MyAppointmentsViewModel extends ChangeNotifier {
initAppointmentsViewModel() {
if (isAppointmentDataToBeLoaded) {
// Default view is grouped by clinic on first open.
isAppointmentsSortByClinic = true;
patientAppointmentsHistoryList.clear();
patientUpcomingAppointmentsHistoryList.clear();
patientArrivedAppointmentsHistoryList.clear();
@ -269,6 +271,9 @@ class MyAppointmentsViewModel extends ChangeNotifier {
patientAppointmentsHistoryList.addAll(patientArrivedAppointmentsHistoryList);
filteredAppointmentList.addAll(patientAppointmentsHistoryList);
// Build grouped list immediately so the UI has data for the default (By Clinic) view.
_groupAppointmentsByClinicAndHospital();
if (patientArrivedAppointmentsHistoryList.isNotEmpty) {
if (Utils.isDateToday(DateUtil.convertStringToDate(patientArrivedAppointmentsHistoryList.first.appointmentDate))) {
// getPatientAppointmentQueueDetails(appointmentNo: patientArrivedAppointmentsHistoryList.first.appointmentNo, patientID: patientArrivedAppointmentsHistoryList.first.patientID);
@ -280,6 +285,7 @@ class MyAppointmentsViewModel extends ChangeNotifier {
print('Arrived Appointments: ${patientArrivedAppointmentsHistoryList.length}');
print('All Appointments: ${patientAppointmentsHistoryList.length}');
getFiltersForSelectedAppointmentList(filteredAppointmentList);
notifyListeners();
}
void getFiltersForSelectedAppointmentList(List<PatientAppointmentHistoryResponseModel> filteredAppointmentList) {

@ -0,0 +1,489 @@
class GetInvoiceDetailsResponseModel {
int? projectID;
int? doctorID;
num? grandTotal;
num? quantity;
num? total;
num? discount;
num? subTotal;
int? invoiceNo;
String? createdOn;
String? procedureID;
String? procedureName;
String? procedureNameN;
num? procedurePrice;
num? patientShare;
num? companyShare;
num? totalPatientShare;
num? totalCompanyShare;
num? totalShare;
num? discountAmount;
num? vATPercentage;
num? patientVATAmount;
num? companyVATAmount;
num? totalVATAmount;
num? price;
int? patientID;
String? patientIdentificationNo;
String? patientName;
String? patientNameN;
String? nationalityID;
String? doctorName;
String? doctorNameN;
int? clinicID;
String? clinicDescription;
String? clinicDescriptionN;
String? appointmentDate;
int? appointmentNo;
String? insuranceID;
int? companyID;
String? companyName;
String? companyNameN;
String? companyAddress;
String? companyAddressN;
String? companyGroupAddress;
String? groupName;
String? groupNameN;
String? patientAddress;
String? vATNo;
String? paymentDate;
String? projectName;
num? totalDiscount;
num? totalPatientShareWithQuantity;
String? legalName;
String? legalNameN;
num? advanceAdjustment;
String? patientCityName;
String? patientCityNameN;
String? doctorImageURL;
List<ListConsultation>? listConsultation;
GetInvoiceDetailsResponseModel(
{this.projectID,
this.doctorID,
this.grandTotal,
this.quantity,
this.total,
this.discount,
this.subTotal,
this.invoiceNo,
this.createdOn,
this.procedureID,
this.procedureName,
this.procedureNameN,
this.procedurePrice,
this.patientShare,
this.companyShare,
this.totalPatientShare,
this.totalCompanyShare,
this.totalShare,
this.discountAmount,
this.vATPercentage,
this.patientVATAmount,
this.companyVATAmount,
this.totalVATAmount,
this.price,
this.patientID,
this.patientIdentificationNo,
this.patientName,
this.patientNameN,
this.nationalityID,
this.doctorName,
this.doctorNameN,
this.clinicID,
this.clinicDescription,
this.clinicDescriptionN,
this.appointmentDate,
this.appointmentNo,
this.insuranceID,
this.companyID,
this.companyName,
this.companyNameN,
this.companyAddress,
this.companyAddressN,
this.companyGroupAddress,
this.groupName,
this.groupNameN,
this.patientAddress,
this.vATNo,
this.paymentDate,
this.projectName,
this.totalDiscount,
this.totalPatientShareWithQuantity,
this.legalName,
this.legalNameN,
this.advanceAdjustment,
this.patientCityName,
this.patientCityNameN,
this.doctorImageURL,
this.listConsultation});
GetInvoiceDetailsResponseModel.fromJson(Map<String, dynamic> json) {
projectID = json['ProjectID'];
doctorID = json['DoctorID'];
grandTotal = json['GrandTotal'];
quantity = json['Quantity'];
total = json['Total'];
discount = json['Discount'];
subTotal = json['SubTotal'];
invoiceNo = json['InvoiceNo'];
createdOn = json['CreatedOn'];
procedureID = json['ProcedureID'];
procedureName = json['ProcedureName'];
procedureNameN = json['ProcedureNameN'];
procedurePrice = json['ProcedurePrice'];
patientShare = json['PatientShare'];
companyShare = json['CompanyShare'];
totalPatientShare = json['TotalPatientShare'];
totalCompanyShare = json['TotalCompanyShare'];
totalShare = json['TotalShare'];
discountAmount = json['DiscountAmount'];
vATPercentage = json['VATPercentage'];
patientVATAmount = json['PatientVATAmount'];
companyVATAmount = json['CompanyVATAmount'];
totalVATAmount = json['TotalVATAmount'];
price = json['Price'];
patientID = json['PatientID'];
patientIdentificationNo = json['PatientIdentificationNo'];
patientName = json['PatientName'];
patientNameN = json['PatientNameN'];
nationalityID = json['NationalityID'];
doctorName = json['DoctorName'];
doctorNameN = json['DoctorNameN'];
clinicID = json['ClinicID'];
clinicDescription = json['ClinicDescription'];
clinicDescriptionN = json['ClinicDescriptionN'];
appointmentDate = json['AppointmentDate'];
appointmentNo = json['AppointmentNo'];
insuranceID = json['InsuranceID'];
companyID = json['CompanyID'];
companyName = json['CompanyName'];
companyNameN = json['CompanyNameN'];
companyAddress = json['CompanyAddress'];
companyAddressN = json['CompanyAddressN'];
companyGroupAddress = json['CompanyGroupAddress'];
groupName = json['GroupName'];
groupNameN = json['GroupNameN'];
patientAddress = json['PatientAddress'];
vATNo = json['VATNo'];
paymentDate = json['PaymentDate'];
projectName = json['ProjectName'];
totalDiscount = json['TotalDiscount'];
totalPatientShareWithQuantity = json['TotalPatientShareWithQuantity'];
legalName = json['LegalName'];
legalNameN = json['LegalNameN'];
advanceAdjustment = json['AdvanceAdjustment'];
patientCityName = json['PatientCityName'];
patientCityNameN = json['PatientCityNameN'];
doctorImageURL = json['DoctorImageURL'];
if (json['listConsultation'] != null) {
listConsultation = <ListConsultation>[];
json['listConsultation'].forEach((v) {
listConsultation!.add(new ListConsultation.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['ProjectID'] = this.projectID;
data['DoctorID'] = this.doctorID;
data['GrandTotal'] = this.grandTotal;
data['Quantity'] = this.quantity;
data['Total'] = this.total;
data['Discount'] = this.discount;
data['SubTotal'] = this.subTotal;
data['InvoiceNo'] = this.invoiceNo;
data['CreatedOn'] = this.createdOn;
data['ProcedureID'] = this.procedureID;
data['ProcedureName'] = this.procedureName;
data['ProcedureNameN'] = this.procedureNameN;
data['ProcedurePrice'] = this.procedurePrice;
data['PatientShare'] = this.patientShare;
data['CompanyShare'] = this.companyShare;
data['TotalPatientShare'] = this.totalPatientShare;
data['TotalCompanyShare'] = this.totalCompanyShare;
data['TotalShare'] = this.totalShare;
data['DiscountAmount'] = this.discountAmount;
data['VATPercentage'] = this.vATPercentage;
data['PatientVATAmount'] = this.patientVATAmount;
data['CompanyVATAmount'] = this.companyVATAmount;
data['TotalVATAmount'] = this.totalVATAmount;
data['Price'] = this.price;
data['PatientID'] = this.patientID;
data['PatientIdentificationNo'] = this.patientIdentificationNo;
data['PatientName'] = this.patientName;
data['PatientNameN'] = this.patientNameN;
data['NationalityID'] = this.nationalityID;
data['DoctorName'] = this.doctorName;
data['DoctorNameN'] = this.doctorNameN;
data['ClinicID'] = this.clinicID;
data['ClinicDescription'] = this.clinicDescription;
data['ClinicDescriptionN'] = this.clinicDescriptionN;
data['AppointmentDate'] = this.appointmentDate;
data['AppointmentNo'] = this.appointmentNo;
data['InsuranceID'] = this.insuranceID;
data['CompanyID'] = this.companyID;
data['CompanyName'] = this.companyName;
data['CompanyNameN'] = this.companyNameN;
data['CompanyAddress'] = this.companyAddress;
data['CompanyAddressN'] = this.companyAddressN;
data['CompanyGroupAddress'] = this.companyGroupAddress;
data['GroupName'] = this.groupName;
data['GroupNameN'] = this.groupNameN;
data['PatientAddress'] = this.patientAddress;
data['VATNo'] = this.vATNo;
data['PaymentDate'] = this.paymentDate;
data['ProjectName'] = this.projectName;
data['TotalDiscount'] = this.totalDiscount;
data['TotalPatientShareWithQuantity'] = this.totalPatientShareWithQuantity;
data['LegalName'] = this.legalName;
data['LegalNameN'] = this.legalNameN;
data['AdvanceAdjustment'] = this.advanceAdjustment;
data['PatientCityName'] = this.patientCityName;
data['PatientCityNameN'] = this.patientCityNameN;
data['DoctorImageURL'] = this.doctorImageURL;
if (this.listConsultation != null) {
data['listConsultation'] =
this.listConsultation!.map((v) => v.toJson()).toList();
}
return data;
}
}
class ListConsultation {
int? projectID;
int? doctorID;
num? grandTotal;
int? quantity;
num? total;
num? discount;
num? subTotal;
int? invoiceNo;
String? createdOn;
String? procedureID;
String? procedureName;
String? procedureNameN;
num? procedurePrice;
num? patientShare;
num? companyShare;
num? totalPatientShare;
num? totalCompanyShare;
num? totalShare;
num? discountAmount;
num? vATPercentage;
num? patientVATAmount;
num? companyVATAmount;
num? totalVATAmount;
num? price;
int? patientID;
int? patientIdentificationNo;
String? patientName;
String? patientNameN;
String? nationalityID;
String? doctorName;
String? doctorNameN;
int? clinicID;
String? clinicDescription;
String? clinicDescriptionN;
String? appointmentDate;
dynamic appointmentNo;
dynamic insuranceID;
dynamic companyID;
String? companyName;
String? companyNameN;
String? companyAddress;
String? companyAddressN;
String? companyGroupAddress;
String? groupName;
String? groupNameN;
String? patientAddress;
String? vATNo;
String? paymentDate;
String? projectName;
num? totalDiscount;
num? totalPatientShareWithQuantity;
String? legalName;
String? legalNameN;
num? advanceAdjustment;
String? patientCityName;
String? patientCityNameN;
ListConsultation(
{this.projectID,
this.doctorID,
this.grandTotal,
this.quantity,
this.total,
this.discount,
this.subTotal,
this.invoiceNo,
this.createdOn,
this.procedureID,
this.procedureName,
this.procedureNameN,
this.procedurePrice,
this.patientShare,
this.companyShare,
this.totalPatientShare,
this.totalCompanyShare,
this.totalShare,
this.discountAmount,
this.vATPercentage,
this.patientVATAmount,
this.companyVATAmount,
this.totalVATAmount,
this.price,
this.patientID,
this.patientIdentificationNo,
this.patientName,
this.patientNameN,
this.nationalityID,
this.doctorName,
this.doctorNameN,
this.clinicID,
this.clinicDescription,
this.clinicDescriptionN,
this.appointmentDate,
this.appointmentNo,
this.insuranceID,
this.companyID,
this.companyName,
this.companyNameN,
this.companyAddress,
this.companyAddressN,
this.companyGroupAddress,
this.groupName,
this.groupNameN,
this.patientAddress,
this.vATNo,
this.paymentDate,
this.projectName,
this.totalDiscount,
this.totalPatientShareWithQuantity,
this.legalName,
this.legalNameN,
this.advanceAdjustment,
this.patientCityName,
this.patientCityNameN});
ListConsultation.fromJson(Map<String, dynamic> json) {
projectID = json['ProjectID'];
doctorID = json['DoctorID'];
grandTotal = json['GrandTotal'];
quantity = json['Quantity'];
total = json['Total'];
discount = json['Discount'];
subTotal = json['SubTotal'];
invoiceNo = json['InvoiceNo'];
createdOn = json['CreatedOn'];
procedureID = json['ProcedureID'];
procedureName = json['ProcedureName'];
procedureNameN = json['ProcedureNameN'];
procedurePrice = json['ProcedurePrice'];
patientShare = json['PatientShare'];
companyShare = json['CompanyShare'];
totalPatientShare = json['TotalPatientShare'];
totalCompanyShare = json['TotalCompanyShare'];
totalShare = json['TotalShare'];
discountAmount = json['DiscountAmount'];
vATPercentage = json['VATPercentage'];
patientVATAmount = json['PatientVATAmount'];
companyVATAmount = json['CompanyVATAmount'];
totalVATAmount = json['TotalVATAmount'];
price = json['Price'];
patientID = json['PatientID'];
patientIdentificationNo = json['PatientIdentificationNo'];
patientName = json['PatientName'];
patientNameN = json['PatientNameN'];
nationalityID = json['NationalityID'];
doctorName = json['DoctorName'];
doctorNameN = json['DoctorNameN'];
clinicID = json['ClinicID'];
clinicDescription = json['ClinicDescription'];
clinicDescriptionN = json['ClinicDescriptionN'];
appointmentDate = json['AppointmentDate'];
appointmentNo = json['AppointmentNo'];
insuranceID = json['InsuranceID'];
companyID = json['CompanyID'];
companyName = json['CompanyName'];
companyNameN = json['CompanyNameN'];
companyAddress = json['CompanyAddress'];
companyAddressN = json['CompanyAddressN'];
companyGroupAddress = json['CompanyGroupAddress'];
groupName = json['GroupName'];
groupNameN = json['GroupNameN'];
patientAddress = json['PatientAddress'];
vATNo = json['VATNo'];
paymentDate = json['PaymentDate'];
projectName = json['ProjectName'];
totalDiscount = json['TotalDiscount'];
totalPatientShareWithQuantity = json['TotalPatientShareWithQuantity'];
legalName = json['LegalName'];
legalNameN = json['LegalNameN'];
advanceAdjustment = json['AdvanceAdjustment'];
patientCityName = json['PatientCityName'];
patientCityNameN = json['PatientCityNameN'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['ProjectID'] = this.projectID;
data['DoctorID'] = this.doctorID;
data['GrandTotal'] = this.grandTotal;
data['Quantity'] = this.quantity;
data['Total'] = this.total;
data['Discount'] = this.discount;
data['SubTotal'] = this.subTotal;
data['InvoiceNo'] = this.invoiceNo;
data['CreatedOn'] = this.createdOn;
data['ProcedureID'] = this.procedureID;
data['ProcedureName'] = this.procedureName;
data['ProcedureNameN'] = this.procedureNameN;
data['ProcedurePrice'] = this.procedurePrice;
data['PatientShare'] = this.patientShare;
data['CompanyShare'] = this.companyShare;
data['TotalPatientShare'] = this.totalPatientShare;
data['TotalCompanyShare'] = this.totalCompanyShare;
data['TotalShare'] = this.totalShare;
data['DiscountAmount'] = this.discountAmount;
data['VATPercentage'] = this.vATPercentage;
data['PatientVATAmount'] = this.patientVATAmount;
data['CompanyVATAmount'] = this.companyVATAmount;
data['TotalVATAmount'] = this.totalVATAmount;
data['Price'] = this.price;
data['PatientID'] = this.patientID;
data['PatientIdentificationNo'] = this.patientIdentificationNo;
data['PatientName'] = this.patientName;
data['PatientNameN'] = this.patientNameN;
data['NationalityID'] = this.nationalityID;
data['DoctorName'] = this.doctorName;
data['DoctorNameN'] = this.doctorNameN;
data['ClinicID'] = this.clinicID;
data['ClinicDescription'] = this.clinicDescription;
data['ClinicDescriptionN'] = this.clinicDescriptionN;
data['AppointmentDate'] = this.appointmentDate;
data['AppointmentNo'] = this.appointmentNo;
data['InsuranceID'] = this.insuranceID;
data['CompanyID'] = this.companyID;
data['CompanyName'] = this.companyName;
data['CompanyNameN'] = this.companyNameN;
data['CompanyAddress'] = this.companyAddress;
data['CompanyAddressN'] = this.companyAddressN;
data['CompanyGroupAddress'] = this.companyGroupAddress;
data['GroupName'] = this.groupName;
data['GroupNameN'] = this.groupNameN;
data['PatientAddress'] = this.patientAddress;
data['VATNo'] = this.vATNo;
data['PaymentDate'] = this.paymentDate;
data['ProjectName'] = this.projectName;
data['TotalDiscount'] = this.totalDiscount;
data['TotalPatientShareWithQuantity'] = this.totalPatientShareWithQuantity;
data['LegalName'] = this.legalName;
data['LegalNameN'] = this.legalNameN;
data['AdvanceAdjustment'] = this.advanceAdjustment;
data['PatientCityName'] = this.patientCityName;
data['PatientCityNameN'] = this.patientCityNameN;
return data;
}
}

@ -0,0 +1,88 @@
class GetInvoicesListResponseModel {
String? setupId;
int? projectID;
int? patientID;
int? appointmentNo;
String? appointmentDate;
String? appointmentDateN;
int? clinicID;
int? doctorID;
int? invoiceNo;
int? status;
String? arrivedOn;
String? doctorName;
String? doctorNameN;
String? clinicName;
double? decimalDoctorRate;
String? doctorImageURL;
int? doctorRate;
int? patientNumber;
String? projectName;
GetInvoicesListResponseModel(
{this.setupId,
this.projectID,
this.patientID,
this.appointmentNo,
this.appointmentDate,
this.appointmentDateN,
this.clinicID,
this.doctorID,
this.invoiceNo,
this.status,
this.arrivedOn,
this.doctorName,
this.doctorNameN,
this.clinicName,
this.decimalDoctorRate,
this.doctorImageURL,
this.doctorRate,
this.patientNumber,
this.projectName});
GetInvoicesListResponseModel.fromJson(Map<String, dynamic> json) {
setupId = json['SetupId'];
projectID = json['ProjectID'];
patientID = json['PatientID'];
appointmentNo = json['AppointmentNo'];
appointmentDate = json['AppointmentDate'];
appointmentDateN = json['AppointmentDateN'];
clinicID = json['ClinicID'];
doctorID = json['DoctorID'];
invoiceNo = json['InvoiceNo'];
status = json['Status'];
arrivedOn = json['ArrivedOn'];
doctorName = json['DoctorName'];
doctorNameN = json['DoctorNameN'];
clinicName = json['ClinicName'];
decimalDoctorRate = json['DecimalDoctorRate'];
doctorImageURL = json['DoctorImageURL'];
doctorRate = json['DoctorRate'];
patientNumber = json['PatientNumber'];
projectName = json['ProjectName'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['SetupId'] = this.setupId;
data['ProjectID'] = this.projectID;
data['PatientID'] = this.patientID;
data['AppointmentNo'] = this.appointmentNo;
data['AppointmentDate'] = this.appointmentDate;
data['AppointmentDateN'] = this.appointmentDateN;
data['ClinicID'] = this.clinicID;
data['DoctorID'] = this.doctorID;
data['InvoiceNo'] = this.invoiceNo;
data['Status'] = this.status;
data['ArrivedOn'] = this.arrivedOn;
data['DoctorName'] = this.doctorName;
data['DoctorNameN'] = this.doctorNameN;
data['ClinicName'] = this.clinicName;
data['DecimalDoctorRate'] = this.decimalDoctorRate;
data['DoctorImageURL'] = this.doctorImageURL;
data['DoctorRate'] = this.doctorRate;
data['PatientNumber'] = this.patientNumber;
data['ProjectName'] = this.projectName;
return data;
}
}

@ -0,0 +1,141 @@
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/my_invoices/models/get_invoice_details_response_model.dart';
import 'package:hmg_patient_app_new/features/my_invoices/models/get_invoices_list_response_model.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
abstract class MyInvoicesRepo {
Future<Either<Failure, GenericApiModel<List<GetInvoicesListResponseModel>>>> getAllInvoicesList();
Future<Either<Failure, GenericApiModel<GetInvoiceDetailsResponseModel>>> getInvoiceDetails({required num appointmentNo, required num invoiceNo, required int projectID});
Future<Either<Failure, GenericApiModel<dynamic>>> sendInvoiceEmail({required num appointmentNo, required int projectID});
}
class MyInvoicesRepoImp implements MyInvoicesRepo {
final ApiClient apiClient;
final LoggerService loggerService;
MyInvoicesRepoImp({required this.loggerService, required this.apiClient});
@override
Future<Either<Failure, GenericApiModel<List<GetInvoicesListResponseModel>>>> getAllInvoicesList() async {
Map<String, dynamic> mapDevice = {};
try {
GenericApiModel<List<GetInvoicesListResponseModel>>? apiResponse;
Failure? failure;
await apiClient.post(
GET_ALL_APPOINTMENTS_FOR_DENTAL_CLINIC,
body: mapDevice,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['List_DentalAppointments'];
final invoicesList = list.map((item) => GetInvoicesListResponseModel.fromJson(item as Map<String, dynamic>)).toList().cast<GetInvoicesListResponseModel>();
apiResponse = GenericApiModel<List<GetInvoicesListResponseModel>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: invoicesList,
);
} 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<GetInvoiceDetailsResponseModel>>> getInvoiceDetails({required num appointmentNo, required num invoiceNo, required int projectID}) async {
Map<String, dynamic> mapDevice = {
"AppointmentNo": appointmentNo,
"InvoiceNo": invoiceNo,
"IsRegistered": true,
"ProjectID": projectID,
};
try {
GenericApiModel<GetInvoiceDetailsResponseModel>? apiResponse;
Failure? failure;
await apiClient.post(
GET_DENTAL_APPOINTMENT_INVOICE,
body: mapDevice,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['List_eInvoiceForDental'];
final invoicesList = GetInvoiceDetailsResponseModel.fromJson(list[0]);
apiResponse = GenericApiModel<GetInvoiceDetailsResponseModel>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: invoicesList,
);
} 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>> sendInvoiceEmail({required num appointmentNo, required int projectID}) async {
Map<String, dynamic> mapDevice = {
"AppointmentNo": appointmentNo,
"IsRegistered": true,
"ProjectID": projectID,
};
try {
GenericApiModel<GetInvoiceDetailsResponseModel>? apiResponse;
Failure? failure;
await apiClient.post(
SEND_DENTAL_APPOINTMENT_INVOICE_EMAIL,
body: mapDevice,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
apiResponse = GenericApiModel(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: response,
);
} 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,99 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/features/my_invoices/models/get_invoice_details_response_model.dart';
import 'package:hmg_patient_app_new/features/my_invoices/models/get_invoices_list_response_model.dart';
import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_repo.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
class MyInvoicesViewModel extends ChangeNotifier {
bool isInvoicesListLoading = false;
bool isInvoiceDetailsLoading = false;
MyInvoicesRepo myInvoicesRepo;
ErrorHandlerService errorHandlerService;
NavigationService navServices;
List<GetInvoicesListResponseModel> allInvoicesList = [];
late GetInvoiceDetailsResponseModel invoiceDetailsResponseModel;
MyInvoicesViewModel({required this.myInvoicesRepo, required this.errorHandlerService, required this.navServices});
setInvoicesListLoading() {
isInvoicesListLoading = true;
allInvoicesList.clear();
notifyListeners();
}
setInvoiceDetailLoading() {
isInvoiceDetailsLoading = true;
notifyListeners();
}
Future<void> getAllInvoicesList({Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await myInvoicesRepo.getAllInvoicesList();
result.fold(
(failure) async {
isInvoicesListLoading = false;
notifyListeners();
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
allInvoicesList = apiResponse.data!;
isInvoicesListLoading = false;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
Future<void> getInvoiceDetails({required num appointmentNo, required num invoiceNo, required int projectID, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await myInvoicesRepo.getInvoiceDetails(appointmentNo: appointmentNo, invoiceNo: invoiceNo, projectID: projectID);
result.fold(
(failure) async {
isInvoiceDetailsLoading = false;
notifyListeners();
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
invoiceDetailsResponseModel = apiResponse.data!;
isInvoiceDetailsLoading = false;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
Future<void> sendInvoiceEmail({required num appointmentNo, required int projectID, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await myInvoicesRepo.sendInvoiceEmail(appointmentNo: appointmentNo, projectID: projectID);
result.fold(
(failure) async {
notifyListeners();
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
}

@ -182,6 +182,7 @@ class PrescriptionsRepoImp implements PrescriptionsRepo {
"To": Utils.appState.getAuthenticatedUser()!.emailAddress,
"SetupID": prescriptionsResponseModel.setupID,
"IsDownload": true,
"isDentalAllowedBackend": false,
};
try {

@ -25,6 +25,7 @@ import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_mode
import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_view_model.dart';
import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart';
import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart';
import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart';
@ -169,6 +170,9 @@ void main() async {
),
ChangeNotifierProvider<WaterMonitorViewModel>(
create: (_) => getIt.get<WaterMonitorViewModel>(),
),
ChangeNotifierProvider<MyInvoicesViewModel>(
create: (_) => getIt.get<MyInvoicesViewModel>(),
)
], child: MyApp()),
),

@ -246,7 +246,7 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
],
),
SizedBox(height: 8.h),
Wrap(
bookAppointmentsViewModel.appointmentNearestGateResponseModel != null ? Wrap(
direction: Axis.horizontal,
spacing: 8.w,
runSpacing: 8.h,
@ -261,7 +261,7 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
"Nearest Gate: ${getIt.get<AppState>().isArabic() ? bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumberN : bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumber}")
.toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading),
],
),
) : SizedBox.shrink(),
],
),
),

@ -4,9 +4,6 @@ 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_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/date_util.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';

@ -1,8 +1,6 @@
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';
@ -43,8 +41,8 @@ class _MyDoctorsPageState extends State<MyDoctorsPage> {
bookAppointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context, listen: false);
return CollapsingListView(
title: LocaleKeys.myDoctor.tr(context: context),
child: Consumer<MyAppointmentsViewModel>(builder: (context, myAppointmentsVM, child) {
title: LocaleKeys.myDoctor.tr(context: context),
child: Consumer<MyAppointmentsViewModel>(builder: (context, myAppointmentsVM, child) {
// build grouped lists from the flat list
final clinicMap = <String, List<dynamic>>{};
final hospitalMap = <String, List<dynamic>>{};
@ -171,129 +169,121 @@ class _MyDoctorsPageState extends State<MyDoctorsPage> {
final displayName = isSortByClinic ? (group.first.clinicName ?? 'Unknown') : (group.first.projectName ?? 'Unknown');
final isExpanded = expandedIndex == index;
return Container(
key: _groupKeys.putIfAbsent(index, () => GlobalKey()),
margin: EdgeInsets.only(bottom: 12.h),
padding: EdgeInsets.all(16.h),
return AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
margin: EdgeInsets.symmetric(vertical: 8.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: true,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
onTap: () {
setState(() {
expandedIndex = isExpanded ? null : index;
});
WidgetsBinding.instance.addPostFrameCallback((_) {
final key = _groupKeys[index];
if (key != null && key.currentContext != null && expandedIndex == index) {
child: InkWell(
onTap: () {
setState(() {
expandedIndex = isExpanded ? null : index;
});
WidgetsBinding.instance.addPostFrameCallback((_) {
final key = _groupKeys.putIfAbsent(index, () => GlobalKey());
if (key.currentContext != null && expandedIndex == index) {
Future.delayed(const Duration(milliseconds: 450), () {
if (key.currentContext != null) {
Scrollable.ensureVisible(
key.currentContext!,
duration: Duration(milliseconds: 350),
duration: const Duration(milliseconds: 350),
curve: Curves.easeInOut,
alignment: 0.1,
alignment: 0.0,
);
}
});
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CustomButton(
text: "${group.length} ${'doctors'.needTranslation}",
onPressed: () {},
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 30.h,
),
Icon(isExpanded ? Icons.expand_less : Icons.chevron_right, color: AppColors.greyColor),
],
),
SizedBox(height: 8.h),
Text(
displayName,
style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600),
overflow: TextOverflow.ellipsis,
),
],
}
});
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
key: _groupKeys.putIfAbsent(index, () => GlobalKey()),
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
AppCustomChipWidget(labelText: "${group.length} ${'doctors'.needTranslation}"),
Icon(isExpanded ? Icons.expand_less : Icons.expand_more),
],
),
SizedBox(height: 8.h),
Text(
displayName,
style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600),
overflow: TextOverflow.ellipsis,
),
],
),
),
),
AnimatedSwitcher(
duration: Duration(milliseconds: 400),
child: isExpanded
? Container(
key: ValueKey<int>(index),
padding: EdgeInsets.only(top: 12.h),
child: Column(
children: group.map<Widget>((doctor) {
return Container(
margin: EdgeInsets.only(bottom: 12.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 12.h,
hasShadow: true,
),
child: Padding(
padding: EdgeInsets.all(14.h),
child: Column(
AnimatedSwitcher(
duration: const Duration(milliseconds: 500),
switchInCurve: Curves.easeIn,
switchOutCurve: Curves.easeOut,
transitionBuilder: (Widget child, Animation<double> animation) {
return FadeTransition(
opacity: animation,
child: SizeTransition(
sizeFactor: animation,
axisAlignment: 0.0,
child: child,
),
);
},
child: isExpanded
? Container(
key: ValueKey<int>(index),
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...group.map<Widget>((doctor) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Image.network(
(doctor?.doctorImageURL ?? doctor?.doctorImage ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png"),
width: 24.h,
width: 24.w,
height: 24.h,
fit: BoxFit.cover,
).circle(100),
SizedBox(width: 8.h),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
(doctor?.doctorName ?? "").toString().toText14(weight: FontWeight.w500),
SizedBox(height: 6.h),
],
),
child: (doctor?.doctorName ?? "").toString().toText14(weight: FontWeight.w500),
),
],
),
SizedBox(height: 8.h),
Row(
Wrap(
direction: Axis.horizontal,
spacing: 4.h,
runSpacing: 4.h,
children: [
CustomButton(
text: isSortByClinic ? (doctor?.clinicName ?? "") : (doctor?.projectName ?? ""),
onPressed: () {},
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 24.h,
AppCustomChipWidget(
labelText: isSortByClinic ? (doctor?.clinicName ?? "") : (doctor?.projectName ?? ""),
),
],
),
SizedBox(height: 8.h),
SizedBox(height: 12.h),
Row(
children: [
Expanded(
flex: 6,
flex: 2,
child: CustomButton(
icon: AppAssets.view_report_icon,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
text: "View Profile".needTranslation.tr(context: context),
onPressed: () async {
bookAppointmentsViewModel.setSelectedDoctor(DoctorsListResponseModel(
@ -320,75 +310,31 @@ class _MyDoctorsPageState extends State<MyDoctorsPage> {
);
});
},
backgroundColor: AppColors.bgRedLightColor,
borderColor: AppColors.primaryRedColor,
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 14,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
),
SizedBox(width: 8.h),
Expanded(
flex: 1,
child: Container(
height: 40.h,
width: 40.w,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.textColor,
borderRadius: 12,
),
child: Padding(
padding: EdgeInsets.all(12.h),
child: Transform.flip(
flipX: getIt<AppState>().isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon_small,
iconColor: AppColors.whiteColor,
fit: BoxFit.contain,
),
),
),
).onPress(() async {
bookAppointmentsViewModel.setSelectedDoctor(DoctorsListResponseModel(
clinicID: doctor?.clinicID ?? 0,
projectID: doctor?.projectID ?? 0,
doctorID: doctor?.doctorID ?? 0,
));
LoaderBottomSheet.showLoader();
await bookAppointmentsViewModel.getDoctorProfile(onSuccess: (dynamic respData) {
LoaderBottomSheet.hideLoader();
Navigator.of(context).push(
CustomPageRoute(
page: DoctorProfilePage(),
),
);
}, onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
}),
),
],
),
SizedBox(height: 12.h),
Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h),
SizedBox(height: 12.h),
],
),
),
);
}).toList(),
),
)
: SizedBox.shrink(),
),
],
);
}).toList(),
],
),
)
: const SizedBox.shrink(),
),
],
),
),
);
},

@ -179,7 +179,13 @@ class AppointmentCard extends StatelessWidget {
? '${patientAppointmentHistoryResponseModel.clinicName!.substring(0, 12)}...'
: patientAppointmentHistoryResponseModel.clinicName!),
).toShimmer2(isShow: isLoading),
AppCustomChipWidget(labelText: isLoading ? 'Olaya' : patientAppointmentHistoryResponseModel.projectName!).toShimmer2(isShow: isLoading),
AppCustomChipWidget(
labelText: isLoading
? 'Olaya'
: patientAppointmentHistoryResponseModel.projectName!.length > 15
? '${patientAppointmentHistoryResponseModel.projectName!.substring(0, 12)}...'
: patientAppointmentHistoryResponseModel.projectName!)
.toShimmer2(isShow: isLoading),
AppCustomChipWidget(
labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w),
icon: AppAssets.appointment_calendar_icon,

@ -1,58 +1,151 @@
import 'package:easy_localization/easy_localization.dart' show StringTranslateExtension;
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/debouncer.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/doctor_list_api_response.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.dart';
import 'package:hmg_patient_app_new/theme/colors.dart' show AppColors;
import 'package:hmg_patient_app_new/widgets/input_widget.dart';
import 'package:provider/provider.dart';
import '../../../../features/my_appointments/models/resp_models/hospital_model.dart' show HospitalsModel;
import '../../../emergency_services/call_ambulance/widgets/type_selection_widget.dart' show TypeSelectionWidget;
// class HospitalBottomSheetBody extends StatelessWidget {
// late BookAppointmentsViewModel appointmentsViewModel;
// late AppointmentViaRegionViewmodel regionalViewModel;
// final TextEditingController searchText = TextEditingController();
//
// HospitalBottomSheetBody({super.key});
//
// @override
// Widget build(BuildContext context) {
// appointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context);
// regionalViewModel = Provider.of<AppointmentViaRegionViewmodel>(context);
// return Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text(
// LocaleKeys.selectHospital.tr(),
// style: TextStyle(
// fontSize: 21,
// fontWeight: FontWeight.w600,
// color: AppColors.blackColor,
// ),
// ),
// Text(
// LocaleKeys.selectHospitalSubTitle.tr(),
// style: TextStyle(
// fontSize: 16,
// fontWeight: FontWeight.w500,
// color: AppColors.greyTextColor,
// ),
// ),
// SizedBox(height: 16.h),
// TextInputWidget(
// labelText: LocaleKeys.search.tr(),
// hintText: LocaleKeys.searchHospital.tr(),
// controller: searchText,
// onChange: (value) {
// appointmentsViewModel.filterHospitalListByString(
// value, regionalViewModel.selectedRegionId, regionalViewModel.selectedFacilityType == FacilitySelection.HMG.name);
// },
// isEnable: true,
// prefix: null,
// autoFocus: false,
// isBorderAllowed: false,
// keyboardType: TextInputType.text,
// isAllowLeadingIcon: true,
// selectionType: SelectionTypeEnum.search,
// padding: EdgeInsets.symmetric(
// vertical: ResponsiveExtension(10).h,
// horizontal: ResponsiveExtension(15).h,
// ),
// ),
// SizedBox(height: 24.h),
// // TypeSelectionWidget(
// // hmcCount: "0",
// // hmgCount: "0",
// // ),
// // SizedBox(height: 21.h),
// SizedBox(
// height: MediaQuery.sizeOf(context).height * .4,
// child: ListView.separated(
// itemBuilder: (_, index) {
// var hospital = regionalViewModel.selectedFacilityType == FacilitySelection.HMG.name
// ? appointmentsViewModel.filteredHospitalList!.registeredDoctorMap![regionalViewModel.selectedRegionId!]!.hmgDoctorList![index]
// : appointmentsViewModel.filteredHospitalList?.registeredDoctorMap?[regionalViewModel.selectedRegionId!]?.hmcDoctorList?[index];
// return HospitalListItem(
// hospitalData: hospital,
// isLocationEnabled: appointmentsViewModel.isLocationEnabled(),
// ).onPress(() {
// regionalViewModel.setHospitalModel(hospital);
// if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_REGION) {
// regionalViewModel.setBottomSheetState(AppointmentViaRegionState.CLINIC_SELECTION);
// regionalViewModel.handleLastStepForRegion();
// } else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_CLINIIC) {
// regionalViewModel.setBottomSheetState(AppointmentViaRegionState.DOCTOR_SELECTION);
// regionalViewModel.handleLastStepForClinic();
// } else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER) {
// regionalViewModel.setBottomSheetState(AppointmentViaRegionState.DOCTOR_SELECTION);
// regionalViewModel.handleLastStepForClinicForDentalAndLaser(appointmentsViewModel.selectedClinic.clinicID ?? -1);
// // regionalViewModel.handleLastStepForClinic();
// }
// });
// },
// separatorBuilder: (_, __) => SizedBox(
// height: 16.h,
// ),
// itemCount: (regionalViewModel.selectedFacilityType == FacilitySelection.HMG.name
// ? (appointmentsViewModel.filteredHospitalList?.registeredDoctorMap?[regionalViewModel.selectedRegionId]?.hmgDoctorList)
// : (appointmentsViewModel.filteredHospitalList?.registeredDoctorMap?[regionalViewModel.selectedRegionId]?.hmcDoctorList))
// ?.length ??
// 0),
// )
// ],
// );
// }
// }
class HospitalBottomSheetBody extends StatelessWidget {
late BookAppointmentsViewModel appointmentsViewModel;
late AppointmentViaRegionViewmodel regionalViewModel;
final TextEditingController searchText = TextEditingController();
HospitalBottomSheetBody({super.key});
final TextEditingController searchText ;
final Debouncer debouncer = Debouncer(milliseconds: 500);
final int hmcCount;
final int hmgCount;
final List<PatientDoctorAppointmentList>? displayList;
final FacilitySelection selectedFacility;
final Function(FacilitySelection) onFacilityClicked;
final Function(PatientDoctorAppointmentList) onHospitalClicked;
final Function(String) onHospitalSearch;
HospitalBottomSheetBody({super.key, required this.hmcCount, required this.hmgCount, this.displayList, required this.selectedFacility, required this.onFacilityClicked, required this.onHospitalClicked, required this.onHospitalSearch, required this.searchText});
@override
Widget build(BuildContext context) {
appointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context);
regionalViewModel = Provider.of<AppointmentViaRegionViewmodel>(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
LocaleKeys.selectHospital.tr(),
style: TextStyle(
fontSize: 21,
fontWeight: FontWeight.w600,
color: AppColors.blackColor,
),
),
Text(
LocaleKeys.selectHospitalSubTitle.tr(),
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: AppColors.greyTextColor,
),
),
SizedBox(height: 16.h),
TextInputWidget(
labelText: LocaleKeys.search.tr(),
hintText: LocaleKeys.searchHospital.tr(),
controller: searchText,
onChange: (value) {
appointmentsViewModel.filterHospitalListByString(
value, regionalViewModel.selectedRegionId, regionalViewModel.selectedFacilityType == FacilitySelection.HMG.name);
debouncer.run((){
onHospitalSearch(value??"");
});
},
isEnable: true,
prefix: null,
autoFocus: false,
isBorderAllowed: false,
keyboardType: TextInputType.text,
@ -64,46 +157,34 @@ class HospitalBottomSheetBody extends StatelessWidget {
),
),
SizedBox(height: 24.h),
// TypeSelectionWidget(
// hmcCount: "0",
// hmgCount: "0",
// ),
// SizedBox(height: 21.h),
TypeSelectionWidget(
selectedFacility:selectedFacility ,
hmcCount: hmcCount.toString(),
hmgCount: hmgCount.toString(),
onitemClicked: (selectedValue){
onFacilityClicked(selectedValue);
},
),
SizedBox(height: 21.h),
SizedBox(
height: MediaQuery.sizeOf(context).height * .4,
child: ListView.separated(
itemBuilder: (_, index) {
var hospital = regionalViewModel.selectedFacilityType == FacilitySelection.HMG.name
? appointmentsViewModel.filteredHospitalList!.registeredDoctorMap![regionalViewModel.selectedRegionId!]!.hmgDoctorList![index]
: appointmentsViewModel.filteredHospitalList?.registeredDoctorMap?[regionalViewModel.selectedRegionId!]?.hmcDoctorList?[index];
height: MediaQuery.sizeOf(context).height * .4,
child: ListView.separated(
itemBuilder: (_, index)
{
var hospital = displayList?[index];
return HospitalListItem(
hospitalData: hospital,
isLocationEnabled: appointmentsViewModel.isLocationEnabled(),
isLocationEnabled: true,
).onPress(() {
regionalViewModel.setHospitalModel(hospital);
if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_REGION) {
regionalViewModel.setBottomSheetState(AppointmentViaRegionState.CLINIC_SELECTION);
regionalViewModel.handleLastStepForRegion();
} else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_CLINIIC) {
regionalViewModel.setBottomSheetState(AppointmentViaRegionState.DOCTOR_SELECTION);
regionalViewModel.handleLastStepForClinic();
} else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER) {
regionalViewModel.setBottomSheetState(AppointmentViaRegionState.DOCTOR_SELECTION);
regionalViewModel.handleLastStepForClinicForDentalAndLaser(appointmentsViewModel.selectedClinic.clinicID ?? -1);
// regionalViewModel.handleLastStepForClinic();
}
});
},
onHospitalClicked(hospital!);
});},
separatorBuilder: (_, __) => SizedBox(
height: 16.h,
),
itemCount: (regionalViewModel.selectedFacilityType == FacilitySelection.HMG.name
? (appointmentsViewModel.filteredHospitalList?.registeredDoctorMap?[regionalViewModel.selectedRegionId]?.hmgDoctorList)
: (appointmentsViewModel.filteredHospitalList?.registeredDoctorMap?[regionalViewModel.selectedRegionId]?.hmcDoctorList))
?.length ??
0),
)
height: 16.h,
),
itemCount: displayList?.length ?? 0,
))
],
);
}
}

@ -79,7 +79,8 @@ class _RegionBottomSheetBodyState extends State<RegionBottomSheetBody> {
hmgCount: "${myAppointmentsVM.hospitalList?.registeredDoctorMap?[key]?.hmgSize ?? 0}",
).onPress(() {
regionalViewModel.setSelectedRegionId(key);
regionalViewModel.setBottomSheetState(AppointmentViaRegionState.TYPE_SELECTION);
regionalViewModel.setDisplayListAndRegionHospitalList(myAppointmentsVM.hospitalList?.registeredDoctorMap![key]);
regionalViewModel.setBottomSheetState(AppointmentViaRegionState.HOSPITAL_SELECTION);
});
},
),

@ -16,6 +16,7 @@ import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart';
import 'package:hmg_patient_app_new/widgets/bottomsheet/generic_bottom_sheet.dart';
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';
class SavedLogin extends StatefulWidget {
@ -266,9 +267,15 @@ class _SavedLogin extends State<SavedLogin> {
child: CustomButton(
text: LocaleKeys.guest.tr(),
onPressed: () {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (BuildContext context) => LandingNavigation()),
);
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
// Navigator.of(context).pushAndRemoveUntil(
// MaterialPageRoute(builder: (BuildContext context) => LandingNavigation())
// );
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),

@ -35,6 +35,7 @@ import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:provider/provider.dart';
import '../appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart';
// import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart';
class BookAppointmentPage extends StatefulWidget {
const BookAppointmentPage({super.key});
@ -511,7 +512,34 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
);
}
if (data.bottomSheetState == AppointmentViaRegionState.HOSPITAL_SELECTION) {
return HospitalBottomSheetBody();
return HospitalBottomSheetBody(
searchText: data.searchController,
displayList: data.displayList,
onFacilityClicked: (value) {
data.setSelectedFacility(value);
data.getDisplayList();
},
onHospitalClicked: (hospital) {
regionalViewModel.setHospitalModel(hospital);
if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_REGION) {
regionalViewModel.setBottomSheetState(AppointmentViaRegionState.CLINIC_SELECTION);
regionalViewModel.handleLastStepForRegion();
} else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_CLINIIC) {
regionalViewModel.setBottomSheetState(AppointmentViaRegionState.DOCTOR_SELECTION);
regionalViewModel.handleLastStepForClinic();
} else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER) {
var appointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context);
regionalViewModel.setBottomSheetState(AppointmentViaRegionState.DOCTOR_SELECTION);
regionalViewModel.handleLastStepForClinicForDentalAndLaser(appointmentsViewModel.selectedClinic.clinicID ?? -1);
}
},
onHospitalSearch: (value) {
data.searchHospitals(value ?? "");
},
selectedFacility: data.selectedFacility,
hmcCount: data.hmcCount,
hmgCount: data.hmgCount,
);
}
if (data.bottomSheetState == AppointmentViaRegionState.CLINIC_SELECTION) {
// Navigator.of(context).pop();

@ -1019,12 +1019,12 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
regionalViewModel.setBottomSheetType(type);
// AppointmentViaRegionViewmodel? viewmodel = null;
showCommonBottomSheetWithoutHeight(context, title: "", titleWidget: Consumer<AppointmentViaRegionViewmodel>(builder: (_, data, __) => getTitle(data)), isDismissible: false,
child: Consumer<AppointmentViaRegionViewmodel>(builder: (_, data, __) {
return getRegionalSelectionWidget(data);
child: Consumer<AppointmentViaRegionViewmodel>(builder: (context, data, __) {
return getRegionalSelectionWidget(data, context);
}), callBackFunc: () {});
}
Widget getRegionalSelectionWidget(AppointmentViaRegionViewmodel data) {
Widget getRegionalSelectionWidget(AppointmentViaRegionViewmodel data, BuildContext context) {
if (data.bottomSheetState == AppointmentViaRegionState.REGION_SELECTION) {
return RegionBottomSheetBody();
}
@ -1035,7 +1035,34 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
);
}
if (data.bottomSheetState == AppointmentViaRegionState.HOSPITAL_SELECTION) {
return HospitalBottomSheetBody();
return HospitalBottomSheetBody(
searchText: data.searchController,
displayList: data.displayList,
onFacilityClicked: (value) {
data.setSelectedFacility(value);
data.getDisplayList();
},
onHospitalClicked: (hospital) {
regionalViewModel.setHospitalModel(hospital);
if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_REGION) {
regionalViewModel.setBottomSheetState(AppointmentViaRegionState.CLINIC_SELECTION);
regionalViewModel.handleLastStepForRegion();
} else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_CLINIIC) {
regionalViewModel.setBottomSheetState(AppointmentViaRegionState.DOCTOR_SELECTION);
regionalViewModel.handleLastStepForClinic();
} else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER) {
var appointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context, listen: false);
regionalViewModel.setBottomSheetState(AppointmentViaRegionState.DOCTOR_SELECTION);
regionalViewModel.handleLastStepForClinicForDentalAndLaser(appointmentsViewModel.selectedClinic.clinicID ?? -1);
}
},
onHospitalSearch: (value) {
data.searchHospitals(value ?? "");
},
selectedFacility: data.selectedFacility,
hmcCount: data.hmcCount,
hmgCount: data.hmgCount,
);
}
if (data.bottomSheetState == AppointmentViaRegionState.DOCTOR_SELECTION) {
//if the region screen is opened for the dental clinic then the project id will be in the hospital list as the list is formed form the get project api

@ -10,6 +10,7 @@ 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/blood_donation/blood_donation_view_model.dart';
import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart';
import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart';
import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/hmg_services_component_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart';
@ -17,6 +18,7 @@ import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_mo
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/blood_donation/blood_donation_page.dart';
import 'package:hmg_patient_app_new/presentation/contact_us/contact_us.dart';
import 'package:hmg_patient_app_new/presentation/emergency_services/emergency_services_page.dart';
import 'package:hmg_patient_app_new/presentation/habib_wallet/habib_wallet_page.dart';
import 'package:hmg_patient_app_new/presentation/habib_wallet/recharge_wallet_page.dart';
import 'package:hmg_patient_app_new/presentation/hmg_services/services_view.dart';
@ -48,9 +50,21 @@ class ServicesPage extends StatelessWidget {
"".needTranslation,
AppAssets.emergency_services_icon,
bgColor: AppColors.primaryRedColor,
true,
route: AppRoutes.eReferralPage,
),
true, route: null, onTap: () {
getIt.get<EmergencyServicesViewModel>().flushData();
getIt.get<EmergencyServicesViewModel>().getTransportationOrders(
showLoader: false,
);
getIt.get<EmergencyServicesViewModel>().getRRTOrders(
showLoader: false,
);
Navigator.of(GetIt.instance<NavigationService>().navigatorKey.currentContext!).push(
CustomPageRoute(
page: EmergencyServicesPage(),
settings: const RouteSettings(name: '/EmergencyServicesPage'),
),
);
}),
HmgServicesComponentModel(
11,
"Book\nAppointment".needTranslation,
@ -58,7 +72,7 @@ class ServicesPage extends StatelessWidget {
AppAssets.appointment_calendar_icon,
bgColor: AppColors.bookAppointment,
true,
route: AppRoutes.eReferralPage,
route: AppRoutes.bookAppointmentPage,
),
HmgServicesComponentModel(
5,

@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:developer';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
@ -13,6 +14,7 @@ import 'package:hmg_patient_app_new/core/utils/date_util.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/int_extensions.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/authentication/authentication_view_model.dart';
@ -25,6 +27,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_
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';
import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart';
import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/appointments/appointment_queue_page.dart';
import 'package:hmg_patient_app_new/presentation/appointments/my_appointments_page.dart';
@ -43,6 +46,9 @@ import 'package:hmg_patient_app_new/presentation/home/widgets/welcome_widget.dar
import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart';
import 'package:hmg_patient_app_new/presentation/profile_settings/profile_settings.dart';
import 'package:hmg_patient_app_new/presentation/rate_appointment/rate_appointment_doctor.dart';
import 'package:hmg_patient_app_new/presentation/todo_section/ancillary_procedures_details_page.dart';
import 'package:hmg_patient_app_new/presentation/todo_section/widgets/ancillary_orders_list.dart';
import 'package:hmg_patient_app_new/routes/app_routes.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
@ -67,13 +73,13 @@ class _LandingPageState extends State<LandingPage> {
late AppState appState;
late MyAppointmentsViewModel myAppointmentsViewModel;
late PrescriptionsViewModel prescriptionsViewModel;
final CacheService cacheService = GetIt.instance<CacheService>();
late AppointmentRatingViewModel appointmentRatingViewModel;
late InsuranceViewModel insuranceViewModel;
late ImmediateLiveCareViewModel immediateLiveCareViewModel;
late BookAppointmentsViewModel bookAppointmentsViewModel;
late EmergencyServicesViewModel emergencyServicesViewModel;
late TodoSectionViewModel todoSectionViewModel;
final SwiperController _controller = SwiperController();
@ -82,10 +88,6 @@ class _LandingPageState extends State<LandingPage> {
authVM = context.read<AuthenticationViewModel>();
habibWalletVM = context.read<HabibWalletViewModel>();
appointmentRatingViewModel = context.read<AppointmentRatingViewModel>();
// myAppointmentsViewModel = context.read<MyAppointmentsViewModel>();
// prescriptionsViewModel = context.read<PrescriptionsViewModel>();
// insuranceViewModel = context.read<InsuranceViewModel>();
// immediateLiveCareViewModel = context.read<ImmediateLiveCareViewModel>();
authVM.savePushTokenToAppState();
if (mounted) {
@ -97,13 +99,11 @@ class _LandingPageState extends State<LandingPage> {
if (appState.isAuthenticated) {
habibWalletVM.initHabibWalletProvider();
habibWalletVM.getPatientBalanceAmount();
todoSectionViewModel.initializeTodoSectionViewModel();
immediateLiveCareViewModel.initImmediateLiveCare();
immediateLiveCareViewModel.getPatientLiveCareHistory();
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
myAppointmentsViewModel.getPatientMyDoctors();
prescriptionsViewModel.initPrescriptionsViewModel();
insuranceViewModel.initInsuranceProvider();
emergencyServicesViewModel.checkPatientERAdvanceBalance();
myAppointmentsViewModel.getPatientAppointmentQueueDetails();
if(!appState.isRatedVisible) {
@ -111,17 +111,16 @@ class _LandingPageState extends State<LandingPage> {
if (appointmentRatingViewModel.appointmentRatedList.isNotEmpty) {
appointmentRatingViewModel.getAppointmentDetails(appointmentRatingViewModel.appointmentRatedList.last.appointmentNo!, appointmentRatingViewModel.appointmentRatedList.last.projectID!,
onSuccess: ((response) {
appointmentRatingViewModel.setClinicOrDoctor(false);
appointmentRatingViewModel.setTitle("Rate Doctor".needTranslation);
appointmentRatingViewModel.setSubTitle("How was your last visit with doctor?".needTranslation);
openLastRating();
appState.setRatedVisible(true);
}));
}
});
}),
);
}
},
);
}
}
});
@ -132,10 +131,10 @@ class _LandingPageState extends State<LandingPage> {
Widget build(BuildContext context) {
bookAppointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context, listen: false);
myAppointmentsViewModel = Provider.of<MyAppointmentsViewModel>(context, listen: false);
prescriptionsViewModel = Provider.of<PrescriptionsViewModel>(context, listen: false);
insuranceViewModel = Provider.of<InsuranceViewModel>(context, listen: false);
immediateLiveCareViewModel = Provider.of<ImmediateLiveCareViewModel>(context, listen: false);
emergencyServicesViewModel = Provider.of<EmergencyServicesViewModel>(context, listen: false);
todoSectionViewModel = Provider.of<TodoSectionViewModel>(context, listen: false);
appState = getIt.get<AppState>();
return PopScope(
canPop: false,
@ -214,6 +213,50 @@ class _LandingPageState extends State<LandingPage> {
),
],
).paddingSymmetrical(24.h, 0.h),
!appState.isAuthenticated
? Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: false,
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Row(
children: [
Utils.buildSvgWithAssets(
width: 50.w,
height: 60.h,
icon: AppAssets.symptomCheckerIcon,
fit: BoxFit.contain,
),
SizedBox(width: 12.w),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"How are you feeling today?".needTranslation.toText14(isBold: true),
"Check your symptoms with this scale".needTranslation.toText12(fontWeight: FontWeight.w500),
SizedBox(height: 14.h),
CustomButton(
text: "Check your symptoms".needTranslation,
onPressed: () async {
context.navigateWithName(AppRoutes.userInfoSelection);
},
backgroundColor: Color(0xFF2B353E),
borderColor: Color(0xFF2B353E),
textColor: AppColors.whiteColor,
fontSize: 14,
fontWeight: FontWeight.w600,
borderRadius: 12,
height: 40.h,
),
],
)
],
),
),
).paddingSymmetrical(24.w, 0.h)
: SizedBox.shrink(),
appState.isAuthenticated
? Column(
children: [
@ -234,8 +277,8 @@ class _LandingPageState extends State<LandingPage> {
Navigator.of(context).push(CustomPageRoute(page: MyAppointmentsPage()));
}),
SizedBox(height: 16.h),
Consumer2<MyAppointmentsViewModel, ImmediateLiveCareViewModel>(
builder: (context, myAppointmentsVM, immediateLiveCareVM, child) {
Consumer3<MyAppointmentsViewModel, ImmediateLiveCareViewModel, TodoSectionViewModel>(
builder: (context, myAppointmentsVM, immediateLiveCareVM, todoSectionVM, child) {
return myAppointmentsVM.isMyAppointmentsLoading
? Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
@ -452,20 +495,41 @@ class _LandingPageState extends State<LandingPage> {
SizedBox(height: 12.h),
],
)
: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: true,
),
child: AppointmentCard(
patientAppointmentHistoryResponseModel:
myAppointmentsVM.patientAppointmentsHistoryList[immediateLiveCareViewModel.patientHasPendingLiveCareRequest ? --index : index],
myAppointmentsViewModel: myAppointmentsViewModel,
bookAppointmentsViewModel: bookAppointmentsViewModel,
isLoading: false,
isFromHomePage: true,
),
: (todoSectionVM.patientAncillaryOrdersList.isNotEmpty && index == 1)
? AncillaryOrderCard(
order: todoSectionVM.patientAncillaryOrdersList.first,
isLoading: false,
isOrdersList: false,
onCheckIn: () {
log("Check-in for order: ${todoSectionVM.patientAncillaryOrdersList.first.orderNo}");
},
onViewDetails: () {
Navigator.of(context).push(
CustomPageRoute(
page: AncillaryOrderDetailsList(
appointmentNoVida: todoSectionVM.patientAncillaryOrdersList.first.appointmentNo ?? 0,
orderNo: todoSectionVM.patientAncillaryOrdersList.first.orderNo ?? 0,
projectID: todoSectionVM.patientAncillaryOrdersList.first.projectID ?? 0,
projectName: todoSectionVM.patientAncillaryOrdersList.first.projectName ?? "",
),
),
);
},
)
: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: true,
),
child: AppointmentCard(
patientAppointmentHistoryResponseModel:
myAppointmentsVM.patientAppointmentsHistoryList[immediateLiveCareViewModel.patientHasPendingLiveCareRequest ? --index : index],
myAppointmentsViewModel: myAppointmentsViewModel,
bookAppointmentsViewModel: bookAppointmentsViewModel,
isLoading: false,
isFromHomePage: true,
),
);
},
)

@ -1,15 +1,18 @@
import 'package:flutter/material.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/utils.dart';
import 'package:hmg_patient_app_new/extensions/route_extensions.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/presentation/contact_us/feedback_page.dart';
import 'package:hmg_patient_app_new/presentation/hmg_services/services_page.dart';
import 'package:hmg_patient_app_new/presentation/home/landing_page.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_selection.dart';
import 'package:hmg_patient_app_new/presentation/todo_section/todo_page.dart';
import 'package:hmg_patient_app_new/routes/app_routes.dart';
import 'package:hmg_patient_app_new/widgets/bottom_navigation/bottom_navigation.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
class LandingNavigation extends StatefulWidget {
const LandingNavigation({super.key});
@ -33,7 +36,9 @@ class _LandingNavigationState extends State<LandingNavigation> {
const LandingPage(),
appState.isAuthenticated ? MedicalFilePage() : /* need add feedback page */ FeedbackPage(),
SizedBox(),
const ToDoPage(),
// const ToDoPage(),
// appState.isAuthenticated ? UserInfoSelectionScreen() : /* need add news page */ SizedBox(),
SizedBox(),
ServicesPage(),
],
),
@ -46,6 +51,20 @@ class _LandingNavigationState extends State<LandingNavigation> {
context.navigateWithName(AppRoutes.bookAppointmentPage);
return;
}
if (_currentIndex == 3) {
if (appState.isAuthenticated) {
Navigator.of(context).push(
CustomPageRoute(
page: UserInfoSelectionScreen(),
),
);
} else {
Utils.openWebView(
url: 'https://x.com/HMG',
);
}
return;
}
_pageController.animateToPage(index, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut);
},
),

@ -146,7 +146,7 @@ class InsuranceApprovalDetailsPage extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"${LocaleKeys.usageStatus.tr(context: context)}: ".toText14(isBold: true),
insuranceApprovalResponseModel.apporvalDetails!.isInvoicedDesc!.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor),
(insuranceApprovalResponseModel.apporvalDetails!.isInvoicedDesc ?? "").toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor),
],
),
],

@ -48,8 +48,9 @@ class PatientInsuranceCard extends StatelessWidget {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}".toText18(isBold: true),
"Policy: ${insuranceCardDetailsModel.insurancePolicyNo}".toText12(isBold: true, color: AppColors.lightGrayColor),
SizedBox(
width: MediaQuery.of(context).size.width * 0.45, child: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}".toText18(isBold: true)),
"Policy: ${insuranceCardDetailsModel.insurancePolicyNo}".needTranslation.toText12(isBold: true, color: AppColors.lightGrayColor),
],
),
AppCustomChipWidget(

@ -15,6 +15,9 @@ 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/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart';
import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_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/hmg_services/models/ui_models/vital_sign_ui_model.dart';
import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart';
import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart';
@ -22,6 +25,7 @@ import 'package:hmg_patient_app_new/features/medical_file/models/family_file_res
import 'package:hmg_patient_app_new/features/medical_file/models/patient_sickleave_response_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';
import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_view_model.dart';
import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/allergies/allergies_list_page.dart';
@ -46,8 +50,11 @@ import 'package:hmg_patient_app_new/presentation/medical_file/widgets/medical_fi
import 'package:hmg_patient_app_new/presentation/medical_file/widgets/patient_sick_leave_card.dart';
import 'package:hmg_patient_app_new/presentation/medical_report/medical_reports_page.dart';
import 'package:hmg_patient_app_new/presentation/my_family/my_family.dart';
import 'package:hmg_patient_app_new/presentation/my_invoices/my_invoices_list.dart';
import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart';
import 'package:hmg_patient_app_new/presentation/radiology/radiology_orders_page.dart';
import 'package:hmg_patient_app_new/presentation/todo_section/todo_page.dart';
import 'package:hmg_patient_app_new/presentation/vital_sign/vital_sign_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';
@ -79,24 +86,50 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
late MedicalFileViewModel medicalFileViewModel;
late BookAppointmentsViewModel bookAppointmentsViewModel;
late LabViewModel labViewModel;
late MyInvoicesViewModel myInvoicesViewModel;
late HmgServicesViewModel hmgServicesViewModel;
late PrescriptionsViewModel prescriptionsViewModel;
int currentIndex = 0;
// Used to make the PageView height follow the card's intrinsic height
final GlobalKey _vitalSignMeasureKey = GlobalKey();
double? _vitalSignMeasuredHeight;
@override
void initState() {
appState = getIt.get<AppState>();
scheduleMicrotask(() {
if (appState.isAuthenticated) {
labViewModel.initLabProvider();
myAppointmentsViewModel.getPatientMyDoctors();
prescriptionsViewModel.initPrescriptionsViewModel();
insuranceViewModel.initInsuranceProvider();
medicalFileViewModel.setIsPatientSickLeaveListLoading(true);
medicalFileViewModel.getPatientSickLeaveList();
medicalFileViewModel.onTabChanged(0);
// Load vital signs
hmgServicesViewModel.getPatientVitalSign();
}
});
super.initState();
}
void _scheduleVitalSignMeasure() {
WidgetsBinding.instance.addPostFrameCallback((_) {
final ctx = _vitalSignMeasureKey.currentContext;
if (ctx == null) return;
final box = ctx.findRenderObject();
if (box is RenderBox) {
final h = box.size.height;
if (h > 0 && h != _vitalSignMeasuredHeight) {
setState(() {
_vitalSignMeasuredHeight = h;
});
}
}
});
}
@override
Widget build(BuildContext context) {
labViewModel = Provider.of<LabViewModel>(context, listen: false);
@ -104,9 +137,13 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
myAppointmentsViewModel = Provider.of<MyAppointmentsViewModel>(context, listen: false);
medicalFileViewModel = Provider.of<MedicalFileViewModel>(context, listen: false);
bookAppointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context, listen: false);
myInvoicesViewModel = Provider.of<MyInvoicesViewModel>(context, listen: false);
hmgServicesViewModel = Provider.of<HmgServicesViewModel>(context, listen: false);
prescriptionsViewModel = Provider.of<PrescriptionsViewModel>(context, listen: false);
NavigationService navigationService = getIt.get<NavigationService>();
return CollapsingListView(
title: "Medical File".needTranslation,
// title: "Medical File".needTranslation,
title: LocaleKeys.medicalFile.tr(context: context),
trailing: Row(
children: [
Wrap(
@ -180,8 +217,11 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}"
.toText18(isBold: true, weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1),
SizedBox(
width: MediaQuery.of(context).size.width * 0.6,
child: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}"
.toText18(isBold: true, weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 2),
),
SizedBox(height: 4.h),
Wrap(
direction: Axis.horizontal,
@ -250,6 +290,111 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
),
).paddingSymmetrical(24.w, 0.0),
SizedBox(height: 16.h),
// Vital Signs Section
Consumer<HmgServicesViewModel>(builder: (context, hmgServicesVM, child) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"Vital Signs".needTranslation.toText16(weight: FontWeight.w500, letterSpacing: -0.2),
Row(
children: [
LocaleKeys.viewAll.tr().toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500),
SizedBox(width: 2.h),
Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h),
],
),
],
).paddingSymmetrical(0.w, 0.h).onPress(() {
Navigator.of(context).push(
CustomPageRoute(
page: VitalSignPage(),
),
);
}),
SizedBox(height: 16.h),
// Make this section dynamic-height (no fixed 160.h)
LayoutBuilder(
builder: (context, constraints) {
if (hmgServicesVM.isVitalSignLoading) {
return _buildVitalSignShimmer();
}
if (hmgServicesVM.vitalSignList.isEmpty) {
return Container(
padding: EdgeInsets.all(16.w),
width: MediaQuery.of(context).size.width,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 12.r,
hasShadow: false,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Utils.buildSvgWithAssets(icon: AppAssets.call_for_vitals, width: 32.h, height: 32.h),
SizedBox(height: 12.h),
"No vital signs recorded yet".needTranslation.toText12(isCenter: true),
],
),
);
}
// The cards define their own height; measure the first rendered page once
_scheduleVitalSignMeasure();
final double hostHeight = _vitalSignMeasuredHeight ?? (160.h);
return SizedBox(
height: hostHeight,
child: PageView(
controller: hmgServicesVM.vitalSignPageController,
onPageChanged: (index) {
hmgServicesVM.setVitalSignCurrentPage(index);
_scheduleVitalSignMeasure();
},
children: _buildVitalSignPages(
vitalSign: hmgServicesVM.vitalSignList.first,
onTap: () {
Navigator.of(context).push(
CustomPageRoute(
page: VitalSignPage(),
),
);
},
measureKey: _vitalSignMeasureKey,
currentPageIndex: hmgServicesVM.vitalSignCurrentPage,
),
),
);
},
),
if (!hmgServicesVM.isVitalSignLoading && hmgServicesVM.vitalSignList.isNotEmpty) ...[
SizedBox(height: 12.h),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
2, // 2 pages (BMI+Height on page 1, Weight+BP on page 2)
(index) => Container(
margin: EdgeInsets.symmetric(horizontal: 3.w),
width: hmgServicesVM.vitalSignCurrentPage == index ? 24.w : 8.w,
height: 8.h,
decoration: BoxDecoration(
color: hmgServicesVM.vitalSignCurrentPage == index
? AppColors.primaryRedColor
: AppColors.dividerColor,
borderRadius: BorderRadius.circular(4.r),
),
),
),
),
],
],
).paddingSymmetrical(24.w, 0.0);
}),
SizedBox(height: 16.h),
TextInputWidget(
labelText: LocaleKeys.search.tr(context: context),
hintText: "Type any record".needTranslation,
@ -268,7 +413,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
// Using CustomExpandableList
CustomExpandableList(
expansionMode: ExpansionMode.exactlyOne,
dividerColor: Color(0xFF2B353E1A),
dividerColor: Color(0xff2b353e1a),
itemPadding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 14.h),
items: [
ExpandableListItem(
@ -485,47 +630,51 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
horizontalOffset: 100.0,
child: FadeInAnimation(
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
child: MedicalFileAppointmentCard(
patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index],
myAppointmentsViewModel: myAppointmentsViewModel,
onRescheduleTap: () {
openDoctorScheduleCalendar(myAppointmentsVM.patientAppointmentsHistoryList[index]);
},
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
child: MedicalFileAppointmentCard(
patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index],
myAppointmentsViewModel: myAppointmentsViewModel,
onRescheduleTap: () {
openDoctorScheduleCalendar(myAppointmentsVM.patientAppointmentsHistoryList[index]);
},
onAskDoctorTap: () async {
LoaderBottomSheet.showLoader(loadingText: "Checking doctor availability...".needTranslation);
await myAppointmentsViewModel.isDoctorAvailable(
projectID: myAppointmentsVM.patientAppointmentsHistoryList[index].projectID,
doctorId: myAppointmentsVM.patientAppointmentsHistoryList[index].doctorID,
clinicId: myAppointmentsVM.patientAppointmentsHistoryList[index].clinicID,
onSuccess: (value) async {
if (value) {
await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
title: LocaleKeys.askDoctor.tr(context: context),
child: AskDoctorRequestTypeSelect(
askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList,
myAppointmentsViewModel: myAppointmentsViewModel,
patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index],
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else {
print("Doctor is not available");
}
});
projectID: myAppointmentsVM.patientAppointmentsHistoryList[index].projectID,
doctorId: myAppointmentsVM.patientAppointmentsHistoryList[index].doctorID,
clinicId: myAppointmentsVM.patientAppointmentsHistoryList[index].clinicID,
onSuccess: (value) async {
if (value) {
await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
title: LocaleKeys.askDoctor.tr(context: context),
child: AskDoctorRequestTypeSelect(
askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList,
myAppointmentsViewModel: myAppointmentsViewModel,
patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index],
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else {
LoaderBottomSheet.hideLoader();
print("Doctor is not available");
}
},
onError: (_) {
LoaderBottomSheet.hideLoader();
},
);
},
),
),
),
),
);
));
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 12.h),
),
@ -643,58 +792,57 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
);
}),
),
));
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
),
SizedBox(height: 16.h),
const Divider(color: AppColors.dividerColor),
SizedBox(height: 16.h),
Row(
children: [
Expanded(
child: CustomButton(
text: "All Prescriptions".needTranslation,
onPressed: () {
Navigator.of(context).push(
CustomPageRoute(
page: PrescriptionsListPage(),
),
);
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 12.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
height: 40.h,
icon: AppAssets.requests,
iconColor: AppColors.primaryRedColor,
iconSize: 16.w,
),
),
);
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
),
SizedBox(height: 16.h),
const Divider(color: AppColors.dividerColor),
SizedBox(height: 16.h),
Row(
children: [
Expanded(
child: CustomButton(
text: "All Prescriptions".needTranslation,
onPressed: () {
Navigator.of(context).push(
CustomPageRoute(
page: PrescriptionsListPage(),
),
);
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 12.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
height: 40.h,
icon: AppAssets.requests,
iconColor: AppColors.primaryRedColor,
iconSize: 16.w,
),
),
SizedBox(width: 6.w),
Expanded(
child: CustomButton(
text: "All Medications".needTranslation,
onPressed: () {},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 12.f,
fontWeight: FontWeight.w500,
borderRadius: 12.h,
height: 40.h,
icon: AppAssets.all_medications_icon,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
SizedBox(width: 6.w),
Expanded(
child: CustomButton(
text: "All Medications".needTranslation,
onPressed: () {},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 12.f,
fontWeight: FontWeight.w500,
borderRadius: 12.h,
height: 40.h,
icon: AppAssets.all_medications_icon,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
),
),
),
],
),
],
],
),
],
),
),
).paddingSymmetrical(0.w, 0.h)
@ -826,8 +974,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
});
}),
),
),
);
));
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 8.h),
),
@ -995,7 +1142,13 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
svgIcon: AppAssets.invoices_list_icon,
isLargeText: true,
iconSize: 36.w,
),
).onPress(() {
Navigator.of(context).push(
CustomPageRoute(
page: MyInvoicesList(),
),
);
}),
MedicalFileCard(
label: "Ancillary Orders List".needTranslation,
textColor: AppColors.blackColor,
@ -1003,7 +1156,13 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
svgIcon: AppAssets.ancillary_orders_list_icon,
isLargeText: true,
iconSize: 36.w,
),
).onPress(() {
Navigator.of(context).push(
CustomPageRoute(
page: ToDoPage(),
),
);
}),
],
).paddingSymmetrical(0.w, 0.0),
SizedBox(height: 16.h),
@ -1065,7 +1224,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.medical_reports_icon,
isLargeText: true,
iconSize: 36.h,
iconSize: 36.w,
).onPress(() {
medicalFileViewModel.setIsPatientMedicalReportsLoading(true);
medicalFileViewModel.getPatientMedicalReportList();
@ -1185,4 +1344,265 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
return Container();
}
}
// Build shimmer for vital signs
Widget _buildVitalSignShimmer() {
return Row(
children: [
Expanded(child: _buildSingleShimmerCard()),
SizedBox(width: 12.w),
Expanded(child: _buildSingleShimmerCard()),
],
);
}
Widget _buildSingleShimmerCard() {
return Container(
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.circular(16.r),
),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Icon shimmer at top
Container(
width: 44.w,
height: 44.h,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.r),
),
).toShimmer(),
SizedBox(height: 16.h),
// Label shimmer
Container(
width: 70.w,
height: 12.h,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4.r),
),
).toShimmer(),
SizedBox(height: 8.h),
// Value shimmer (larger)
Container(
width: 60.w,
height: 32.h,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4.r),
),
).toShimmer(),
SizedBox(height: 12.h),
// Bottom row with chip and arrow
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
width: 60.w,
height: 20.h,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.r),
),
).toShimmer(),
Container(
width: 16.w,
height: 16.h,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(2.r),
),
).toShimmer(),
],
),
],
),
),
);
}
// Build pages with 2 cards each
List<Widget> _buildVitalSignPages({
required VitalSignResModel vitalSign,
required VoidCallback onTap,
required GlobalKey measureKey,
required int currentPageIndex,
}) {
return [
// Page 1: BMI + Height
Row(
children: [
Expanded(
child: _buildVitalSignCard(
icon: AppAssets.bmiVital,
label: "BMI",
value: vitalSign.bodyMassIndex?.toString() ?? '--',
unit: '',
status: vitalSign.bodyMassIndex != null ? _getBMIStatus(vitalSign.bodyMassIndex) : null,
onTap: onTap,
),
),
SizedBox(width: 12.w),
Expanded(
child: _buildVitalSignCard(
icon: AppAssets.heightVital,
label: "Height",
value: vitalSign.heightCm?.toString() ?? '--',
unit: 'cm',
status: null,
onTap: onTap,
),
),
],
),
// Page 2: Weight + Blood Pressure
Row(
children: [
Expanded(
child: _buildVitalSignCard(
icon: AppAssets.weightVital,
label: "Weight",
value: vitalSign.weightKg?.toString() ?? '--',
unit: 'kg',
status: vitalSign.weightKg != null ? "Normal" : null,
onTap: onTap,
),
),
SizedBox(width: 12.w),
Expanded(
child: _buildVitalSignCard(
icon: AppAssets.bloodPressure,
label: "Blood Pressure",
value: vitalSign.bloodPressureLower != null && vitalSign.bloodPressureHigher != null
? "${vitalSign.bloodPressureHigher}/${vitalSign.bloodPressureLower}"
: '--',
unit: '',
status: _getBloodPressureStatus(
systolic: vitalSign.bloodPressureHigher,
diastolic: vitalSign.bloodPressureLower,
),
onTap: onTap,
),
),
],
),
];
}
String _getBMIStatus(dynamic bmi) {
return VitalSignUiModel.bmiStatus(bmi);
}
String? _getBloodPressureStatus({dynamic systolic, dynamic diastolic}) {
return VitalSignUiModel.bloodPressureStatus(systolic: systolic, diastolic: diastolic);
}
Widget _buildVitalSignCard({
required String icon,
required String label,
required String value,
required String unit,
required String? status,
required VoidCallback onTap,
}) {
final VitalSignUiModel scheme = VitalSignUiModel.scheme(status: status, label: label);
return GestureDetector(
onTap: onTap,
child: Container(
// Same styling used originally for vitals in MedicalFilePage
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 16.r,
hasShadow: false,
),
child: Padding(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: EdgeInsets.all(10.h),
decoration: BoxDecoration(
color: scheme.iconBg,
borderRadius: BorderRadius.circular(12.r),
),
child: Utils.buildSvgWithAssets(
icon: icon,
width: 20.w,
height: 20.h,
iconColor: scheme.iconFg,
fit: BoxFit.contain,
),
),
SizedBox(width: 10.w),
Expanded(
child: label.toText14(
color: AppColors.textColor,
weight: FontWeight.w600,
),
),
],
),
SizedBox(height: 14.h),
Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 6.h),
decoration: BoxDecoration(
color: AppColors.bgScaffoldColor,
borderRadius: BorderRadius.circular(10.r),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
value.toText17(
isBold: true,
color: AppColors.textColor,
),
if (unit.isNotEmpty) ...[
SizedBox(width: 3.w),
unit.toText12(
color: AppColors.textColor,
fontWeight: FontWeight.w500,
),
],
],
),
if (status != null)
AppCustomChipWidget(
labelText: status,
backgroundColor: scheme.chipBg,
textColor: scheme.chipFg,
)
else
const SizedBox.shrink(),
],
),
),
SizedBox(height: 8.h),
Align(
alignment: AlignmentDirectional.centerEnd,
child: Utils.buildSvgWithAssets(
icon: AppAssets.arrow_forward,
width: 18.w,
height: 18.h,
iconColor: AppColors.textColorLight,
fit: BoxFit.contain,
),
),
],
),
),
),
);
}
}

@ -84,7 +84,7 @@ class _MedicalReportsPageState extends State<MedicalReportsPage> {
// ),
// ],
// ).paddingSymmetrical(24.h, 0.h),
SizedBox(height: 20.h),
SizedBox(height: 8.h),
Row(
children: [
CustomButton(
@ -162,7 +162,7 @@ class _MedicalReportsPageState extends State<MedicalReportsPage> {
patientMedicalReportResponseModel: PatientMedicalReportResponseModel(),
medicalFileViewModel: medicalFileVM,
isLoading: true,
).paddingSymmetrical(24.h, 8.h)
).paddingSymmetrical(0.h, 8.h)
: medicalFileViewModel.patientMedicalReportList.isNotEmpty
? AnimationConfiguration.staggeredList(
position: index,

@ -0,0 +1,272 @@
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/date_util.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/my_invoices/models/get_invoice_details_response_model.dart';
import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_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/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:provider/provider.dart';
class MyInvoicesDetailsPage extends StatefulWidget {
GetInvoiceDetailsResponseModel getInvoiceDetailsResponseModel;
MyInvoicesDetailsPage({super.key, required this.getInvoiceDetailsResponseModel});
@override
State<MyInvoicesDetailsPage> createState() => _MyInvoicesDetailsPageState();
}
class _MyInvoicesDetailsPageState extends State<MyInvoicesDetailsPage> {
late MyInvoicesViewModel myInvoicesViewModel;
@override
Widget build(BuildContext context) {
myInvoicesViewModel = Provider.of<MyInvoicesViewModel>(context, listen: false);
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: Column(
children: [
Expanded(
child: CollapsingListView(
title: "Invoice Details".needTranslation,
sendEmail: () async {
LoaderBottomSheet.showLoader(loadingText: "Sending email, Please wait...".needTranslation);
await myInvoicesViewModel.sendInvoiceEmail(
appointmentNo: widget.getInvoiceDetailsResponseModel.appointmentNo!,
projectID: widget.getInvoiceDetailsResponseModel.projectID!,
onSuccess: (val) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getSuccessWidget(loadingText: "Email sent successfully.".needTranslation),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
},
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
},
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(24.h),
child: Column(
children: [
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: true,
),
child: Padding(
padding: EdgeInsets.all(14.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
children: [
Image.network(
widget.getInvoiceDetailsResponseModel.doctorImageURL!,
width: 63.h,
height: 63.h,
fit: BoxFit.cover,
).circle(100.r),
],
),
SizedBox(width: 16.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
(getIt<AppState>().isArabic() ? widget.getInvoiceDetailsResponseModel.doctorNameN! : widget.getInvoiceDetailsResponseModel.doctorName!).toText16(isBold: true),
SizedBox(height: 8.h),
Wrap(
direction: Axis.horizontal,
spacing: 6.w,
runSpacing: 6.h,
children: [
AppCustomChipWidget(
labelText: "${LocaleKeys.invoiceNo}: ${widget.getInvoiceDetailsResponseModel.invoiceNo!}",
labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w),
),
AppCustomChipWidget(
labelText: (widget.getInvoiceDetailsResponseModel.clinicDescription!.length > 15
? '${widget.getInvoiceDetailsResponseModel.clinicDescription!.substring(0, 12)}...'
: widget.getInvoiceDetailsResponseModel.clinicDescription!),
labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w),
),
AppCustomChipWidget(
labelText: widget.getInvoiceDetailsResponseModel.projectName!,
labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w),
),
AppCustomChipWidget(
labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w),
icon: AppAssets.doctor_calendar_icon,
labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.getInvoiceDetailsResponseModel.appointmentDate), false),
),
],
),
],
),
),
],
),
],
),
),
),
SizedBox(height: 16.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: true,
),
child: Padding(
padding: EdgeInsets.all(14.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
widget.getInvoiceDetailsResponseModel.listConsultation!.first.procedureName!.toText16(isBold: true),
SizedBox(height: 16.h),
Wrap(
direction: Axis.horizontal,
spacing: 6.w,
runSpacing: 6.h,
children: [
AppCustomChipWidget(
labelText: "${LocaleKeys.quantity.tr()}: ${widget.getInvoiceDetailsResponseModel.listConsultation!.first.quantity!}",
labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w),
),
AppCustomChipWidget(
labelText: "${LocaleKeys.price.tr()}: ${widget.getInvoiceDetailsResponseModel.listConsultation!.first.price!} ${LocaleKeys.sar.tr()}",
labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w),
),
AppCustomChipWidget(
labelText: "${LocaleKeys.total.tr()}: ${widget.getInvoiceDetailsResponseModel.listConsultation!.first.total!} ${LocaleKeys.sar.tr()}",
labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w),
),
],
),
],
),
),
),
SizedBox(height: 16.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: true,
),
child: Padding(
padding: EdgeInsets.all(14.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Insurance Details".toText16(isBold: true),
SizedBox(height: 16.h),
widget.getInvoiceDetailsResponseModel.groupName!.toText14(isBold: true),
Row(
children: [
Expanded(child: widget.getInvoiceDetailsResponseModel.companyName!.toText14(isBold: true)),
],
),
SizedBox(height: 12.h),
Row(
children: [
AppCustomChipWidget(
labelText: "Insurance ID: ${widget.getInvoiceDetailsResponseModel.insuranceID ?? "-"}",
labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w),
),
],
),
],
),
),
),
],
),
),
),
),
),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: false,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 24.h),
"Total Balance".needTranslation.toText18(isBold: true).paddingSymmetrical(24.h, 0.h),
SizedBox(height: 17.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"Amount before tax".needTranslation.toText14(isBold: true),
Utils.getPaymentAmountWithSymbol(widget.getInvoiceDetailsResponseModel.listConsultation!.first.totalShare.toString().toText16(isBold: true), AppColors.blackColor, 13,
isSaudiCurrency: true),
],
).paddingSymmetrical(24.h, 0.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"VAT 15%".needTranslation.toText14(isBold: true, color: AppColors.greyTextColor),
Utils.getPaymentAmountWithSymbol(
widget.getInvoiceDetailsResponseModel.listConsultation!.first.totalVATAmount!.toString().toText14(isBold: true, color: AppColors.greyTextColor), AppColors.greyTextColor, 13,
isSaudiCurrency: true),
],
).paddingSymmetrical(24.h, 0.h),
SizedBox(height: 16.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"Discount".needTranslation.toText14(isBold: true),
Utils.getPaymentAmountWithSymbol(widget.getInvoiceDetailsResponseModel.listConsultation!.first.discountAmount!.toString().toText14(isBold: true, color: AppColors.primaryRedColor),
AppColors.primaryRedColor, 13,
isSaudiCurrency: true),
],
).paddingSymmetrical(24.h, 0.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"Paid".needTranslation.toText14(isBold: true),
Utils.getPaymentAmountWithSymbol(
widget.getInvoiceDetailsResponseModel.listConsultation!.first.grandTotal!.toString().toText14(isBold: true, color: AppColors.textColor), AppColors.textColor, 13,
isSaudiCurrency: true),
],
).paddingSymmetrical(24.h, 0.h),
SizedBox(height: 24.h),
],
),
),
],
),
);
}
}

@ -0,0 +1,116 @@
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/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/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_card.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart';
import 'package:hmg_patient_app_new/presentation/my_invoices/my_invoices_details_page.dart';
import 'package:hmg_patient_app_new/presentation/my_invoices/widgets/invoice_list_card.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/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:provider/provider.dart';
class MyInvoicesList extends StatefulWidget {
const MyInvoicesList({super.key});
@override
State<MyInvoicesList> createState() => _MyInvoicesListState();
}
class _MyInvoicesListState extends State<MyInvoicesList> {
late MyInvoicesViewModel myInvoicesViewModel;
@override
void initState() {
scheduleMicrotask(() {
myInvoicesViewModel.setInvoicesListLoading();
myInvoicesViewModel.getAllInvoicesList();
});
super.initState();
}
@override
Widget build(BuildContext context) {
myInvoicesViewModel = Provider.of<MyInvoicesViewModel>(context, listen: false);
return CollapsingListView(
title: LocaleKeys.invoiceList.tr(context: context),
child: SingleChildScrollView(
child: Consumer<MyInvoicesViewModel>(builder: (context, myInvoicesVM, child) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 24.h),
ListView.builder(
itemCount: myInvoicesVM.isInvoicesListLoading ? 4 : myInvoicesVM.allInvoicesList.length,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
padding: EdgeInsetsGeometry.zero,
itemBuilder: (context, index) {
return myInvoicesVM.isInvoicesListLoading
? LabResultItemView(
onTap: () {},
labOrder: null,
index: index,
isLoading: true,
)
: AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
child: InvoiceListCard(
getInvoicesListResponseModel: myInvoicesVM.allInvoicesList[index],
onTap: () async {
myInvoicesVM.setInvoiceDetailLoading();
LoaderBottomSheet.showLoader(loadingText: "Fetching invoice details, Please wait...".needTranslation);
await myInvoicesVM.getInvoiceDetails(
appointmentNo: myInvoicesVM.allInvoicesList[index].appointmentNo!,
invoiceNo: myInvoicesVM.allInvoicesList[index].invoiceNo!,
projectID: myInvoicesVM.allInvoicesList[index].projectID!,
onSuccess: (val) {
LoaderBottomSheet.hideLoader();
Navigator.of(context).push(
CustomPageRoute(
page: MyInvoicesDetailsPage(getInvoiceDetailsResponseModel: myInvoicesVM.invoiceDetailsResponseModel),
),
);
},
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
},
),
),
),
),
);
}).paddingSymmetrical(24.w, 0.h),
],
);
}),
),
);
}
}

@ -0,0 +1,151 @@
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/date_util.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/my_invoices/models/get_invoices_list_response_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/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
class InvoiceListCard extends StatelessWidget {
final GetInvoicesListResponseModel getInvoicesListResponseModel;
Function? onTap;
InvoiceListCard({super.key, required this.getInvoicesListResponseModel, required this.onTap});
@override
Widget build(BuildContext context) {
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: true,
),
child: Padding(
padding: EdgeInsets.all(14.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Wrap(
alignment: WrapAlignment.start,
direction: Axis.horizontal,
spacing: 6.w,
runSpacing: 6.h,
children: [
AppCustomChipWidget(
icon: AppAssets.walkin_appointment_icon,
iconColor: AppColors.textColor,
labelText: 'Walk In'.needTranslation,
textColor: AppColors.textColor,
),
AppCustomChipWidget(
labelText: 'OutPatient'.needTranslation,
backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.1),
textColor: AppColors.primaryRedColor,
),
],
),
SizedBox(height: 16.h),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
children: [
Image.network(
getInvoicesListResponseModel.doctorImageURL!,
width: 63.h,
height: 63.h,
fit: BoxFit.cover,
).circle(100.r),
Transform.translate(
offset: Offset(0.0, -20.h),
child: Container(
width: 40.w,
height: 40.h,
decoration: BoxDecoration(
color: AppColors.whiteColor,
shape: BoxShape.circle, // Makes the container circular
border: Border.all(
color: AppColors.scaffoldBgColor, // Color of the border
width: 1.5.w, // Width of the border
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 15.w, height: 15.h),
SizedBox(height: 2.h),
"${getInvoicesListResponseModel.decimalDoctorRate}".toText11(isBold: true, color: AppColors.textColor),
],
),
).circle(100),
),
],
),
SizedBox(width: 16.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
(getIt<AppState>().isArabic() ? getInvoicesListResponseModel.doctorNameN! : getInvoicesListResponseModel.doctorName!).toText16(isBold: true),
SizedBox(height: 8.h),
Wrap(
direction: Axis.horizontal,
spacing: 6.w,
runSpacing: 6.h,
children: [
AppCustomChipWidget(
labelText: "${LocaleKeys.invoiceNo}: ${getInvoicesListResponseModel.invoiceNo!}",
labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w),
),
AppCustomChipWidget(
labelText:
(getInvoicesListResponseModel.clinicName!.length > 15 ? '${getInvoicesListResponseModel.clinicName!.substring(0, 12)}...' : getInvoicesListResponseModel.clinicName!),
labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w),
),
AppCustomChipWidget(
labelText: getInvoicesListResponseModel.projectName!,
labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w),
),
AppCustomChipWidget(
labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w),
icon: AppAssets.doctor_calendar_icon,
labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(getInvoicesListResponseModel.appointmentDate), false),
),
],
),
],
),
),
],
),
SizedBox(height: 16.h),
CustomButton(
text: "View invoice details".needTranslation,
onPressed: () {
if (onTap != null) {
onTap!();
}
},
backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.1),
borderColor: AppColors.primaryRedColor.withValues(alpha: 0.01),
textColor: AppColors.primaryRedColor,
fontSize: 14.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
height: 40.h,
iconSize: 14.h,
),
],
),
),
).paddingOnly(bottom: 16.h);
}
}

@ -9,6 +9,7 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/presentation/home/navigation_screen.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/routes/custom_page_route.dart';
import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart';
import 'package:lottie/lottie.dart';
@ -30,7 +31,13 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
void goToHomePage() {
Utils.saveBoolFromPrefs(CacheConst.firstLaunch, false);
Navigator.of(context).pushReplacement(FadePage(page: LandingNavigation()));
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
// Navigator.of(context).pushReplacement(FadePage(page: LandingNavigation()));
}
@override

@ -219,7 +219,15 @@ class _PrescriptionDetailPageState extends State<PrescriptionDetailPage> {
text: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported!
? LocaleKeys.resendOrder.tr(context: context)
: LocaleKeys.prescriptionDeliveryError.tr(context: context),
onPressed: () {},
onPressed: () async {
if (widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported!) {
LoaderBottomSheet.showLoader(loadingText: "Fetching prescription details...".needTranslation);
await prescriptionsViewModel.getPrescriptionDetails(widget.prescriptionsResponseModel, onSuccess: (val) {
LoaderBottomSheet.hideLoader();
prescriptionsViewModel.initiatePrescriptionDelivery();
});
}
},
backgroundColor: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.greyF7Color,
borderColor: AppColors.successColor.withOpacity(0.01),
textColor:

@ -190,231 +190,173 @@ class _RadiologyOrdersPageState extends State<RadiologyOrdersPage> {
itemBuilder: (context, index) {
final group = model.patientRadiologyOrdersViewList[index];
final displayName = model.isSortByClinic ? (group.first.clinicDescription ?? 'Unknown') : (group.first.projectName ?? 'Unknown');
final isExpanded = expandedIndex == index;
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 400),
child: SlideAnimation(
verticalOffset: 50.0,
child: FadeInAnimation(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Group header container with key so we can scroll to it
GestureDetector(
onTap: () {
setState(() {
expandedIndex = expandedIndex == index ? null : index;
});
WidgetsBinding.instance.addPostFrameCallback((_) {
final key = _groupKeys.putIfAbsent(index, () => GlobalKey());
if (key.currentContext != null && expandedIndex == index) {
// Delay scrolling to wait for expansion animation
Future.delayed(Duration(milliseconds: 450), () {
if (key.currentContext != null) {
Scrollable.ensureVisible(
key.currentContext!,
duration: Duration(milliseconds: 350),
curve: Curves.easeInOut,
alignment: 0.0,
);
}
});
}
});
},
child: Container(
key: _groupKeys.putIfAbsent(index, () => GlobalKey()),
margin: EdgeInsets.only(bottom: 8.h),
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8.h),
boxShadow: [
BoxShadow(
color: AppColors.blackColor.withValues(alpha: 0.03),
blurRadius: 6,
offset: Offset(0, 2),
)
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
margin: EdgeInsets.symmetric(vertical: 8.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: true,
),
child: InkWell(
onTap: () {
setState(() {
expandedIndex = isExpanded ? null : index;
});
WidgetsBinding.instance.addPostFrameCallback((_) {
final key = _groupKeys.putIfAbsent(index, () => GlobalKey());
if (key.currentContext != null && expandedIndex == index) {
Future.delayed(const Duration(milliseconds: 450), () {
if (key.currentContext != null) {
Scrollable.ensureVisible(
key.currentContext!,
duration: const Duration(milliseconds: 350),
curve: Curves.easeInOut,
alignment: 0.0,
);
}
});
}
});
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
key: _groupKeys.putIfAbsent(index, () => GlobalKey()),
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CustomButton(
text: "${group.length} ${'results'.needTranslation}",
onPressed: () {},
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 30.h,
),
Icon(expandedIndex == index ? Icons.expand_less : Icons.expand_more),
],
),
SizedBox(height: 8.h),
Text(
displayName,
style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600),
overflow: TextOverflow.ellipsis,
),
AppCustomChipWidget(labelText: "${group.length} ${'results'.needTranslation}"),
Icon(isExpanded ? Icons.expand_less : Icons.expand_more),
],
),
),
],
SizedBox(height: 8.h),
Text(
displayName,
style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600),
overflow: TextOverflow.ellipsis,
),
],
),
),
),
),
AnimatedSwitcher(
duration: Duration(milliseconds: 400),
child: expandedIndex == index
? Container(
key: ValueKey<int>(index),
padding: EdgeInsets.only(top: 12.h),
child: Column(
children: group.map<Widget>((order) {
return Container(
margin: EdgeInsets.only(bottom: 12.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 12.h,
hasShadow: true,
),
child: Padding(
padding: EdgeInsets.all(14.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.network(
order.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png",
width: 24.h,
height: 24.h,
fit: BoxFit.cover,
).circle(100),
SizedBox(width: 8.h),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
(order.doctorName ?? "").toString().toText14(weight: FontWeight.w500),
SizedBox(height: 6.h),
],
AnimatedSwitcher(
duration: const Duration(milliseconds: 500),
switchInCurve: Curves.easeIn,
switchOutCurve: Curves.easeOut,
transitionBuilder: (Widget child, Animation<double> animation) {
return FadeTransition(
opacity: animation,
child: SizeTransition(
sizeFactor: animation,
axisAlignment: 0.0,
child: child,
),
);
},
child: isExpanded
? Container(
key: ValueKey<int>(index),
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...group.map<Widget>((order) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
Image.network(
order.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png",
width: 24.w,
height: 24.h,
fit: BoxFit.cover,
).circle(100),
SizedBox(width: 8.h),
Expanded(
child: (order.doctorName ?? '').toString().toText14(weight: FontWeight.w500),
),
),
],
),
SizedBox(height: 8.h),
Row(children: [
CustomButton(
text: order.description!,
onPressed: () {},
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 24.h,
)
]
),
SizedBox(height: 6.h),
Row(
children: [
CustomButton(
text: DateUtil.formatDateToDate(order.orderDate ?? order.appointmentDate ?? "", false),
onPressed: () {},
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 24.h,
),
SizedBox(width: 8.h),
CustomButton(
text: model.isSortByClinic ? (order.clinicDescription ?? "") : (order.projectName ?? ""),
onPressed: () {},
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 24.h,
),
],
),
SizedBox(height: 8.h),
Row(
children: [
Expanded(
flex: 6,
child: SizedBox(),
),
SizedBox(width: 8.h),
Expanded(
flex: 1,
child: Container(
height: 40.h,
width: 40.w,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.textColor,
borderRadius: 12,
],
),
SizedBox(height: 8.h),
Wrap(
direction: Axis.horizontal,
spacing: 4.h,
runSpacing: 4.h,
children: [
if ((order.description ?? '').isNotEmpty)
AppCustomChipWidget(
labelText: (order.description ?? '').toString(),
),
child: Padding(
padding: EdgeInsets.all(12.h),
child: Transform.flip(
flipX: false,
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon_small,
iconColor: AppColors.whiteColor,
fit: BoxFit.contain,
),
),
AppCustomChipWidget(
labelText: DateUtil.formatDateToDate(
(order.orderDate ?? order.appointmentDate),
false,
),
).onPress(() {
model.navigationService.push(
CustomPageRoute(
page: RadiologyResultPage(patientRadiologyResponseModel: order),
),
);
}),
),
],
),
],
),
),
);
}).toList(),
),
)
: SizedBox.shrink(),
),
AppCustomChipWidget(
labelText: model.isSortByClinic ? (order.clinicDescription ?? '') : (order.projectName ?? ''),
),
],
),
SizedBox(height: 12.h),
Row(
children: [
Expanded(flex: 2, child: const SizedBox()),
Expanded(
flex: 2,
child: CustomButton(
icon: AppAssets.view_report_icon,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
text: "View Results".needTranslation,
onPressed: () {
model.navigationService.push(
CustomPageRoute(
page: RadiologyResultPage(patientRadiologyResponseModel: order),
),
);
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 14,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
),
],
),
SizedBox(height: 12.h),
Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h),
SizedBox(height: 12.h),
],
);
}).toList(),
],
),
)
: const SizedBox.shrink(),
),
],
),
],
),
),
),
),

@ -3,7 +3,6 @@ import 'package:hmg_patient_app_new/core/app_assets.dart';
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/route_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
@ -18,8 +17,7 @@ class UserInfoSelectionScreen extends StatefulWidget {
const UserInfoSelectionScreen({super.key});
@override
State<UserInfoSelectionScreen> createState() =>
_UserInfoSelectionScreenState();
State<UserInfoSelectionScreen> createState() => _UserInfoSelectionScreenState();
}
class _UserInfoSelectionScreenState extends State<UserInfoSelectionScreen> {
@ -53,7 +51,7 @@ class _UserInfoSelectionScreenState extends State<UserInfoSelectionScreen> {
if (user.dateofBirth != null && user.dateofBirth!.isNotEmpty) {
try {
DateTime dob = DateUtil.convertStringToDate(user.dateofBirth!);
DateTime dob = DateTime.parse(user.dateofBirth!);
viewModel.setDateOfBirth(dob);
} catch (e) {
// If date parsing fails, ignore and let user fill manually
@ -87,25 +85,19 @@ class _UserInfoSelectionScreenState extends State<UserInfoSelectionScreen> {
width: 40.h,
margin: EdgeInsets.only(right: 10.h),
padding: EdgeInsets.all(8.h),
decoration: RoundedRectangleBorder()
.toSmoothCornerDecoration(
borderRadius: 12.r, color: AppColors.greyColor),
child: Utils.buildSvgWithAssets(
icon: leadingIcon, iconColor: iconColor)),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 12.r, color: AppColors.greyColor),
child: Utils.buildSvgWithAssets(icon: leadingIcon, iconColor: iconColor)),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
title.toText16(weight: FontWeight.w500),
subTitle.toText14(
color: AppColors.primaryRedColor,
weight: FontWeight.w500),
subTitle.toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500),
],
),
],
),
),
Utils.buildSvgWithAssets(
icon: trailingIcon, height: 24.h, width: 24.h),
Utils.buildSvgWithAssets(icon: trailingIcon, height: 24.h, width: 24.h),
],
),
);
@ -122,27 +114,8 @@ class _UserInfoSelectionScreenState extends State<UserInfoSelectionScreen> {
AppState appState = getIt.get<AppState>();
String name = "";
int? userAgeFromDOB;
if (appState.isAuthenticated) {
final user = appState.getAuthenticatedUser();
name = "${user!.firstName!} ${user.lastName!} ";
// Calculate age from authenticated user's DOB if available
if (user.dateofBirth != null && user.dateofBirth!.isNotEmpty) {
try {
DateTime dob = DateUtil.convertStringToDate(user.dateofBirth!);
final now = DateTime.now();
int age = now.year - dob.year;
if (now.month < dob.month ||
(now.month == dob.month && now.day < dob.day)) {
age--;
}
userAgeFromDOB = age;
} catch (e) {
// If date parsing fails, ignore
}
}
name = "${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!} ";
} else {
name = "Guest";
}
@ -159,15 +132,12 @@ class _UserInfoSelectionScreenState extends State<UserInfoSelectionScreen> {
// Get display values
String genderText = viewModel.selectedGender ?? "Not set";
// 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 Years" : "Not set";
String heightText = viewModel.selectedHeight != null
? "${viewModel.selectedHeight!.round()} ${viewModel.isHeightCm ? 'cm' : 'ft'}"
: "Not set";
String weightText = viewModel.selectedWeight != null
? "${viewModel.selectedWeight!.round()} ${viewModel.isWeightKg ? 'kg' : 'lbs'}"
: "Not set";
// Show age calculated from DOB, not the DOB itself
String ageText = viewModel.selectedAge != null ? "${viewModel.selectedAge} Years" : "Not set";
String heightText =
viewModel.selectedHeight != null ? "${viewModel.selectedHeight!.round()} ${viewModel.isHeightCm ? 'cm' : 'ft'}" : "Not set";
String weightText =
viewModel.selectedWeight != null ? "${viewModel.selectedWeight!.round()} ${viewModel.isWeightKg ? 'kg' : 'lbs'}" : "Not set";
return Column(
children: [
@ -180,17 +150,11 @@ class _UserInfoSelectionScreenState extends State<UserInfoSelectionScreen> {
children: [
Container(
width: double.infinity,
decoration: RoundedRectangleBorder()
.toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r),
padding: EdgeInsets.symmetric(
vertical: 24.h, horizontal: 16.w),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r),
padding: EdgeInsets.symmetric(vertical: 24.h, horizontal: 16.w),
child: Column(
children: [
"Hello $name, Is your information up to date?"
.needTranslation
.toText18(
"Hello $name, Is your information up to date?".needTranslation.toText18(
weight: FontWeight.w600,
color: AppColors.textColor,
),
@ -201,10 +165,8 @@ class _UserInfoSelectionScreenState extends State<UserInfoSelectionScreen> {
title: "Gender".needTranslation,
subTitle: genderText,
onTap: () {
viewModel.setUserInfoPage(0,
isSinglePageEdit: true);
context.navigateWithName(
AppRoutes.userInfoFlowManager);
viewModel.setUserInfoPage(0, isSinglePageEdit: true);
context.navigateWithName(AppRoutes.userInfoFlowManager);
},
trailingIcon: AppAssets.edit_icon,
),
@ -216,10 +178,8 @@ class _UserInfoSelectionScreenState extends State<UserInfoSelectionScreen> {
subTitle: ageText,
iconColor: AppColors.greyTextColor,
onTap: () {
viewModel.setUserInfoPage(1,
isSinglePageEdit: true);
context.navigateWithName(
AppRoutes.userInfoFlowManager);
viewModel.setUserInfoPage(1, isSinglePageEdit: true);
context.navigateWithName(AppRoutes.userInfoFlowManager);
},
trailingIcon: AppAssets.edit_icon,
),
@ -230,10 +190,8 @@ class _UserInfoSelectionScreenState extends State<UserInfoSelectionScreen> {
title: "Height".needTranslation,
subTitle: heightText,
onTap: () {
viewModel.setUserInfoPage(2,
isSinglePageEdit: true);
context.navigateWithName(
AppRoutes.userInfoFlowManager);
viewModel.setUserInfoPage(2, isSinglePageEdit: true);
context.navigateWithName(AppRoutes.userInfoFlowManager);
},
trailingIcon: AppAssets.edit_icon,
),
@ -244,10 +202,8 @@ class _UserInfoSelectionScreenState extends State<UserInfoSelectionScreen> {
title: "Weight".needTranslation,
subTitle: weightText,
onTap: () {
viewModel.setUserInfoPage(3,
isSinglePageEdit: true);
context.navigateWithName(
AppRoutes.userInfoFlowManager);
viewModel.setUserInfoPage(3, isSinglePageEdit: true);
context.navigateWithName(AppRoutes.userInfoFlowManager);
},
trailingIcon: AppAssets.edit_icon,
),
@ -269,8 +225,7 @@ class _UserInfoSelectionScreenState extends State<UserInfoSelectionScreen> {
Widget _buildBottomCard(BuildContext context, bool hasEmptyFields) {
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor, borderRadius: 24.r),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r),
child: SafeArea(
top: false,
child: Column(

@ -47,6 +47,7 @@ class _ToDoPageState extends State<ToDoPage> {
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: 3,
padding: EdgeInsetsGeometry.zero,
itemBuilder: (context, index) {
return AncillaryOrderCard(
order: AncillaryOrderItem(),
@ -60,15 +61,13 @@ class _ToDoPageState extends State<ToDoPage> {
Widget build(BuildContext context) {
appState = getIt.get<AppState>();
return CollapsingListView(
title: "ToDo List".needTranslation,
isLeading: false,
title: "Ancillary Orders".needTranslation,
isLeading: true,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 16.h),
"Ancillary Orders".needTranslation.toText18(isBold: true),
SizedBox(height: 24.h),
Consumer<TodoSectionViewModel>(
builder: (BuildContext context, TodoSectionViewModel todoSectionViewModel, Widget? child) {
return todoSectionViewModel.isAncillaryOrdersLoading

@ -7,9 +7,13 @@ 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/todo_section/models/resp_models/ancillary_order_list_response_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/appointments/my_appointments_page.dart';
import 'package:hmg_patient_app_new/presentation/todo_section/todo_page.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 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
class AncillaryOrdersList extends StatelessWidget {
final List<AncillaryOrderItem> orders;
@ -52,6 +56,7 @@ class AncillaryOrdersList extends StatelessWidget {
child: AncillaryOrderCard(
order: order,
isLoading: false,
isOrdersList: true,
onCheckIn: onCheckIn != null ? () => onCheckIn!(order) : null,
onViewDetails: onViewDetails != null ? () => onViewDetails!(order) : null,
)),
@ -90,12 +95,14 @@ class AncillaryOrderCard extends StatelessWidget {
super.key,
required this.order,
this.isLoading = false,
this.isOrdersList = false,
this.onCheckIn,
this.onViewDetails,
});
final AncillaryOrderItem order;
final bool isLoading;
final bool isOrdersList;
final VoidCallback? onCheckIn;
final VoidCallback? onViewDetails;
@ -113,30 +120,24 @@ class AncillaryOrderCard extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header Row with Order Number and Date
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Row(
// children: [
// if (!isLoading)
// "Order #".needTranslation.toText14(
// color: AppColors.textColorLight,
// weight: FontWeight.w500,
// ),
// SizedBox(width: 4.w),
// (isLoading ? "12345" : "${order.orderNo ?? '-'}").toText16(isBold: true).toShimmer2(isShow: isLoading),
// ],
// ),
// if (order.orderDate != null || isLoading)
// (isLoading ? "Jan 15, 2024" : DateFormat('MMM dd, yyyy').format(order.orderDate!))
// .toText12(color: AppColors.textColorLight)
// .toShimmer2(isShow: isLoading),
// ],
// ),
SizedBox(height: 12.h),
isOrdersList
? SizedBox.shrink()
: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"Ancillary Orders".toText14(isBold: true),
Row(
children: [
LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor),
SizedBox(width: 2.h),
Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h),
],
),
],
).toShimmer2(isShow: isLoading).onPress(() {
Navigator.of(context).push(CustomPageRoute(page: ToDoPage()));
}),
SizedBox(height: 10.h),
// Doctor and Clinic Info
Row(
crossAxisAlignment: CrossAxisAlignment.center,
@ -144,8 +145,8 @@ class AncillaryOrderCard extends StatelessWidget {
if (!isLoading) ...[
Image.network(
"https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown.png",
width: 40.w,
height: 40.h,
width: 63.w,
height: 63.h,
fit: BoxFit.cover,
).circle(100.r),
SizedBox(width: 12.w),
@ -155,11 +156,7 @@ class AncillaryOrderCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Doctor Name
if (order.doctorName != null || isLoading)
(isLoading ? "Dr. John Smith" : order.doctorName!)
.toString()
.toText14(isBold: true, maxlines: 2)
.toShimmer2(isShow: isLoading),
if (order.doctorName != null || isLoading) (isLoading ? "Dr. John Smith" : order.doctorName!).toString().toText16(isBold: true, maxlines: 2).toShimmer2(isShow: isLoading),
SizedBox(height: 4.h),
],
@ -167,9 +164,7 @@ class AncillaryOrderCard extends StatelessWidget {
),
],
),
SizedBox(height: 12.h),
SizedBox(height: 8.h),
// Chips for Appointment Info and Status
Wrap(
direction: Axis.horizontal,
@ -191,9 +186,8 @@ class AncillaryOrderCard extends StatelessWidget {
// Appointment Date
if (order.appointmentDate != null || isLoading)
AppCustomChipWidget(
icon: AppAssets.calendar,
labelText:
isLoading ? "Date: Jan 20, 2024" : "Date: ${DateFormat('MMM dd, yyyy').format(order.appointmentDate!)}".needTranslation,
icon: AppAssets.appointment_calendar_icon,
labelText: isLoading ? "Date: Jan 20, 2024" : DateFormat('MMM dd, yyyy').format(order.appointmentDate!).needTranslation,
).toShimmer2(isShow: isLoading),
// Appointment Number

@ -1,4 +1,4 @@
import 'dart:async';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
@ -11,7 +11,7 @@ import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/vit
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/chip/app_custom_chip_widget.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/vital_sign_ui_model.dart';
import 'package:provider/provider.dart';
class VitalSignPage extends StatefulWidget {
@ -22,22 +22,10 @@ class VitalSignPage extends StatefulWidget {
}
class _VitalSignPageState extends State<VitalSignPage> {
@override
void initState() {
super.initState();
final HmgServicesViewModel hmgServicesViewModel = context.read<HmgServicesViewModel>();
scheduleMicrotask(() async {
LoaderBottomSheet.showLoader(loadingText: 'Loading Vital Signs...');
await hmgServicesViewModel.getPatientVitalSign(
onSuccess: (_) {
LoaderBottomSheet.hideLoader();
},
onError: (_) {
LoaderBottomSheet.hideLoader();
},
);
});
}
@override
@ -72,48 +60,40 @@ class _VitalSignPageState extends State<VitalSignPage> {
children: [
// BMI Card
_buildVitalSignCard(
icon: AppAssets.activity,
iconColor: AppColors.successColor,
iconBgColor: AppColors.successColor.withValues(alpha: 0.1),
icon: AppAssets.bmiVital,
label: 'BMI',
value: latestVitalSign?.bodyMassIndex?.toString() ?? '--',
unit: '',
chipText: _getBMIStatus(latestVitalSign?.bodyMassIndex),
chipBgColor: AppColors.successColor.withValues(alpha: 0.1),
chipTextColor: AppColors.successColor,
status: VitalSignUiModel.bmiStatus(latestVitalSign?.bodyMassIndex),
onTap: () {},
),
SizedBox(height: 16.h),
// Height Card
_buildVitalSignCard(
icon: AppAssets.height,
iconColor: AppColors.infoColor,
iconBgColor: AppColors.infoColor.withValues(alpha: 0.1),
icon: AppAssets.heightVital,
label: 'Height',
value: latestVitalSign?.heightCm?.toString() ?? '--',
unit: 'cm',
status: null,
onTap: () {},
),
SizedBox(height: 16.h),
// Weight Card
_buildVitalSignCard(
icon: AppAssets.weight,
iconColor: AppColors.successColor,
iconBgColor: AppColors.successColor.withValues(alpha: 0.1),
icon: AppAssets.weightVital,
label: 'Weight',
value: latestVitalSign?.weightKg?.toString() ?? '--',
unit: 'kg',
chipText: 'Normal',
chipBgColor: AppColors.successColor.withValues(alpha: 0.1),
chipTextColor: AppColors.successColor,
status: (latestVitalSign?.weightKg != null) ? 'Normal' : null,
onTap: () {},
),
SizedBox(height: 16.h),
// Blood Pressure Card
_buildVitalSignCard(
icon: AppAssets.activity,
iconColor: AppColors.warningColor,
iconBgColor: AppColors.warningColor.withValues(alpha: 0.1),
icon: AppAssets.bloodPressure,
label: 'Blood Pressure',
value: latestVitalSign != null &&
latestVitalSign.bloodPressureHigher != null &&
@ -121,17 +101,22 @@ class _VitalSignPageState extends State<VitalSignPage> {
? '${latestVitalSign.bloodPressureHigher}/${latestVitalSign.bloodPressureLower}'
: '--',
unit: '',
status: VitalSignUiModel.bloodPressureStatus(
systolic: latestVitalSign?.bloodPressureHigher,
diastolic: latestVitalSign?.bloodPressureLower,
),
onTap: () {},
),
SizedBox(height: 16.h),
// Temperature Card
_buildVitalSignCard(
icon: AppAssets.activity,
iconColor: AppColors.errorColor,
iconBgColor: AppColors.errorColor.withValues(alpha: 0.1),
icon: AppAssets.temperature,
label: 'Temperature',
value: latestVitalSign?.temperatureCelcius?.toString() ?? '--',
unit: '°C',
status: null,
onTap: () {},
),
],
),
@ -143,54 +128,80 @@ class _VitalSignPageState extends State<VitalSignPage> {
Expanded(
child: Column(
children: [
// Body anatomy image
Container(
height: 280.h,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: true,
),
child: Center(
child: Image.asset(
AppAssets.fullBodyFront,
height: 260.h,
fit: BoxFit.contain,
),
),
),
SizedBox(height: 16.h),
// Body anatomy image with Heart Rate card overlaid at bottom
SizedBox(
height: 480.h,
width: double.infinity,
child: Stack(
clipBehavior: Clip.none,
children: [
// Image
Positioned.fill(
child: Stack(
fit: StackFit.expand,
children: [
Image.asset(
AppAssets.bmiFullBody,
fit: BoxFit.cover,
alignment: Alignment.topCenter,
),
Align(
alignment: Alignment.bottomCenter,
child: SizedBox(
height: 420.h,
child: ImageFiltered(
imageFilter: ImageFilter.blur(sigmaX: 6, sigmaY: 6),
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
AppColors.whiteColor.withValues(alpha: 0.0),
AppColors.whiteColor.withValues(alpha: 0.97),
AppColors.whiteColor,
],
),
),
),
),
),
),
],
),
),
// Heart Rate Card
_buildVitalSignCard(
icon: AppAssets.heart,
iconColor: AppColors.errorColor,
iconBgColor: AppColors.errorColor.withValues(alpha: 0.1),
label: 'Heart Rate',
value: latestVitalSign?.heartRate?.toString() ??
latestVitalSign?.pulseBeatPerMinute?.toString() ?? '--',
unit: 'bpm',
chipText: 'Normal',
chipBgColor: AppColors.successColor.withValues(alpha: 0.1),
chipTextColor: AppColors.successColor,
// Overlay Heart Rate card
Positioned(
left: 0,
right: 0,
bottom: 12.h,
child: _buildVitalSignCard(
icon: AppAssets.heart,
label: 'Heart Rate',
value: latestVitalSign?.heartRate?.toString() ?? latestVitalSign?.pulseBeatPerMinute?.toString() ?? '--',
unit: 'bpm',
status: 'Normal',
onTap: () {},
),
),
],
),
),
SizedBox(height: 16.h),
SizedBox(height: 12.h),
// Respiratory rate Card
_buildVitalSignCard(
icon: AppAssets.activity,
iconColor: AppColors.successColor,
iconBgColor: AppColors.successColor.withValues(alpha: 0.1),
label: 'Respiratory rate',
value: latestVitalSign?.respirationBeatPerMinute?.toString() ?? '--',
unit: 'bpm',
chipText: 'Normal',
chipBgColor: AppColors.successColor.withValues(alpha: 0.1),
chipTextColor: AppColors.successColor,
),
],
),
),
// Respiratory rate Card
_buildVitalSignCard(
icon: AppAssets.respRate,
label: 'Respiratory rate',
value: latestVitalSign?.respirationBeatPerMinute?.toString() ?? '--',
unit: 'bpm',
status: 'Normal',
onTap: () {},
),
],
),
),
],
),
),
@ -205,104 +216,106 @@ class _VitalSignPageState extends State<VitalSignPage> {
);
}
String? _getBMIStatus(dynamic bmi) {
if (bmi == null) return null;
double bmiValue = double.tryParse(bmi.toString()) ?? 0;
if (bmiValue < 18.5) return 'Underweight';
if (bmiValue < 25) return 'Normal';
if (bmiValue < 30) return 'Overweight';
return 'Obese';
}
Widget _buildVitalSignCard({
required String icon,
required Color iconColor,
required Color iconBgColor,
required String label,
required String value,
required String unit,
String? chipText,
Color? chipBgColor,
Color? chipTextColor,
required String? status,
required VoidCallback onTap,
}) {
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: true,
),
child: Padding(
padding: EdgeInsets.all(12.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
// Icon with background
Container(
padding: EdgeInsets.all(8.h),
decoration: BoxDecoration(
color: iconBgColor,
borderRadius: BorderRadius.circular(12.r),
final VitalSignUiModel scheme = VitalSignUiModel.scheme(status: status, label: label);
return GestureDetector(
onTap: onTap,
child: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 16.r,
hasShadow: false,
),
child: Padding(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: EdgeInsets.all(10.h),
decoration: BoxDecoration(
color: scheme.iconBg,
borderRadius: BorderRadius.circular(12.r),
),
child: Utils.buildSvgWithAssets(
icon: icon,
width: 20.w,
height: 20.h,
iconColor: scheme.iconFg,
fit: BoxFit.contain,
),
),
child: Utils.buildSvgWithAssets(
icon: icon,
width: 16.w,
height: 16.h,
iconColor: iconColor,
fit: BoxFit.contain,
SizedBox(width: 10.w),
Expanded(
child: label.toText14(
color: AppColors.textColor,
weight: FontWeight.w600,
),
),
],
),
SizedBox(height: 14.h),
Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 6.h),
decoration: BoxDecoration(
color: AppColors.bgScaffoldColor,
borderRadius: BorderRadius.circular(10.r),
),
SizedBox(width: 8.w),
Expanded(
child: label.toText10(
color: AppColors.textColorLight,
weight: FontWeight.w500,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
value.toText17(
isBold: true,
color: AppColors.textColor,
),
if (unit.isNotEmpty) ...[
SizedBox(width: 3.w),
unit.toText12(
color: AppColors.textColor,
fontWeight: FontWeight.w500,
),
],
],
),
if (status != null)
AppCustomChipWidget(
labelText: status,
backgroundColor: scheme.chipBg,
textColor: scheme.chipFg,
)
else
const SizedBox.shrink(),
],
),
// Forward arrow
Utils.buildSvgWithAssets(
),
SizedBox(height: 8.h),
Align(
alignment: AlignmentDirectional.centerEnd,
child: Utils.buildSvgWithAssets(
icon: AppAssets.arrow_forward,
width: 16.w,
height: 16.h,
width: 18.w,
height: 18.h,
iconColor: AppColors.textColorLight,
fit: BoxFit.contain,
),
],
),
SizedBox(height: 12.h),
// Value
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
value.toText18(
isBold: true,
color: AppColors.textColor,
),
if (unit.isNotEmpty) ...[
SizedBox(width: 4.w),
unit.toText12(
color: AppColors.textColorLight,
),
],
],
),
// Chip if available
if (chipText != null) ...[
SizedBox(height: 8.h),
AppCustomChipWidget(
labelText: chipText,
backgroundColor: chipBgColor,
textColor: chipTextColor,
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
),
],
],
),
),
),
);
}
}

@ -21,6 +21,7 @@ class CollapsingListView extends StatelessWidget {
VoidCallback? history;
VoidCallback? instructions;
VoidCallback? requests;
VoidCallback? sendEmail;
Widget? bottomChild;
Widget? trailing;
bool isClose;
@ -40,6 +41,7 @@ class CollapsingListView extends StatelessWidget {
this.history,
this.instructions,
this.requests,
this.sendEmail,
this.isLeading = true,
this.trailing,
this.leadingCallback,
@ -78,6 +80,7 @@ class CollapsingListView extends StatelessWidget {
history: history,
instructions: instructions,
requests: requests,
sendEmail: sendEmail,
bottomChild: bottomChild,
trailing: trailing,
)
@ -108,6 +111,7 @@ class CollapsingListView extends StatelessWidget {
history: history,
instructions: instructions,
requests: requests,
sendEmail: sendEmail,
bottomChild: bottomChild,
trailing: trailing,
),
@ -181,6 +185,7 @@ class ScrollAnimatedTitle extends StatefulWidget implements PreferredSizeWidget
VoidCallback? history;
VoidCallback? instructions;
VoidCallback? requests;
VoidCallback? sendEmail;
Widget? bottomChild;
Widget? trailing;
@ -195,6 +200,7 @@ class ScrollAnimatedTitle extends StatefulWidget implements PreferredSizeWidget
this.history,
this.instructions,
this.requests,
this.sendEmail,
this.bottomChild,
this.trailing,
});
@ -267,6 +273,7 @@ class _ScrollAnimatedTitleState extends State<ScrollAnimatedTitle> {
if (widget.history != null) actionButton(context, t, title: "History".needTranslation, icon: AppAssets.insurance_history_icon).onPress(widget.history!),
if (widget.instructions != null) actionButton(context, t, title: "Instructions".needTranslation, icon: AppAssets.requests).onPress(widget.instructions!),
if (widget.requests != null) actionButton(context, t, title: "Requests".needTranslation, icon: AppAssets.insurance_history_icon).onPress(widget.requests!),
if (widget.sendEmail != null) actionButton(context, t, title: "Send Email".needTranslation, icon: AppAssets.email).onPress(widget.sendEmail!),
if (widget.search != null) Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(widget.search!),
if (widget.trailing != null) widget.trailing!,
]

@ -30,7 +30,8 @@ class BottomNavigation extends StatelessWidget {
isSpecial: true,
),
appState.isAuthenticated
? BottomNavItem(icon: AppAssets.toDoBottom, label: LocaleKeys.todoList.tr(context: context))
// ? BottomNavItem(icon: AppAssets.toDoBottom, label: LocaleKeys.todoList.tr(context: context))
? BottomNavItem(icon: AppAssets.toDoBottom, label: "Symptoms")
: BottomNavItem(icon: AppAssets.news, label: LocaleKeys.news.tr()),
BottomNavItem(icon: AppAssets.servicesBottom, label: LocaleKeys.services2.tr(context: context)),
];
@ -39,7 +40,7 @@ class BottomNavigation extends StatelessWidget {
decoration: _containerDecoration,
padding: _containerPadding,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List.generate(
items.length,
(index) => _buildNavItem(items[index], index),

@ -0,0 +1,93 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
class VitalSignShimmerWidget extends StatelessWidget {
const VitalSignShimmerWidget({super.key});
@override
Widget build(BuildContext context) {
return Row(
children: [
// BMI Card Shimmer
Expanded(child: _buildShimmerCard()),
SizedBox(width: 8.w),
// Height Card Shimmer
Expanded(child: _buildShimmerCard()),
SizedBox(width: 8.w),
// Weight Card Shimmer
Expanded(child: _buildShimmerCard()),
SizedBox(width: 8.w),
// Blood Pressure Card Shimmer
Expanded(child: _buildShimmerCard()),
],
);
}
Widget _buildShimmerCard() {
return Container(
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.circular(12.r),
),
child: Padding(
padding: EdgeInsets.all(12.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
// Icon shimmer
Container(
width: 32.w,
height: 32.h,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.r),
),
).toShimmer(),
SizedBox(height: 8.h),
// Label shimmer
Container(
width: 50.w,
height: 10.h,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4.r),
),
).toShimmer(),
SizedBox(height: 4.h),
// Value shimmer
Container(
width: 40.w,
height: 16.h,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4.r),
),
).toShimmer(),
SizedBox(height: 4.h),
// Chip shimmer
Container(
width: 45.w,
height: 18.h,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.r),
),
).toShimmer(),
SizedBox(height: 4.h),
// Arrow shimmer
Align(
alignment: AlignmentDirectional.centerEnd,
child: Container(
width: 10.w,
height: 10.h,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(2.r),
),
).toShimmer(),
),
],
),
),
);
}
}
Loading…
Cancel
Save