Merge branch 'master' into faiz_dev

# Conflicts:
#	ios/Podfile.lock
faiz_dev
faizatflutter 1 day ago
commit 8c925a7f5e

@ -39,7 +39,6 @@
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE"
tools:node="remove" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" tools:node="remove" />
<!-- Gallery/Photo permissions for Android 13+ (API 33+) -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
@ -52,7 +51,7 @@
<uses-permission
android:name="android.permission.ACCESS_BACKGROUND_LOCATION"
tools:node="remove" /> <!-- <uses-permission android:name="android.permission.INTERNET" /> -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" tools:node="remove" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 573 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 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 fill-rule="evenodd" clip-rule="evenodd" d="M12.7501 1.97257C12.7352 1.56461 12.3967 1.24352 11.9885 1.2501C11.5804 1.25668 11.2524 1.58851 11.2506 1.99674C11.2485 2.4854 10.9063 3.79916 9.52097 5.66527C9.37283 5.86481 9.44668 6.1691 9.68599 6.23616C9.86992 6.28771 10.1456 6.22765 10.2355 6.05914C10.5779 5.4179 11.2584 4.98778 12.0331 5.00026C13.1216 5.01781 13.9961 5.9027 14.0008 6.99131C14.0014 7.00025 14.0076 7.09332 14.0573 7.27852C14.1079 7.46728 14.1966 7.72504 14.3438 8.04628C14.6377 8.68804 15.1562 9.56385 16.038 10.6233C16.0555 10.6443 16.0726 10.6657 16.0892 10.6873C17.2456 12.1985 18.0009 14.0834 18.0009 16C18.0009 16.0825 17.9995 16.1645 17.9969 16.246C17.9813 16.7261 17.9735 16.9661 18.1166 17.0581C18.2597 17.1502 18.4513 17.0552 18.8347 16.8653C21.1457 15.7203 22.7506 13.565 22.7506 11C22.7506 9.40543 22.1171 7.78106 21.0962 6.44698C21.09 6.43884 21.0836 6.43082 21.0771 6.42294C19.19 4.15586 18.753 2.54913 18.7506 1.99674C18.7488 1.58851 18.4209 1.25668 18.0127 1.2501C17.6045 1.24352 17.2661 1.56461 17.2511 1.97257C17.233 2.46736 16.9266 2.82542 16.5831 2.95502C16.2811 3.06897 15.9181 3.02413 15.6247 2.58398C15.4856 2.37533 15.2514 2.25 15.0006 2.25C14.7499 2.25 14.5157 2.37533 14.3766 2.58398C14.0831 3.02413 13.7201 3.06897 13.4181 2.95502C13.0746 2.82542 12.7682 2.46736 12.7501 1.97257ZM16.1637 5.82934C15.7932 6.01459 15.643 6.46509 15.8283 6.83557L16.3283 7.83557C16.5135 8.20606 16.964 8.35623 17.3345 8.17098C17.705 7.98574 17.8552 7.53524 17.6699 7.16475L17.1699 6.16475C16.9847 5.79427 16.5342 5.6441 16.1637 5.82934Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.98718 6.2501C6.39536 6.24352 6.73384 6.56461 6.74877 6.97257C6.76687 7.46736 7.07325 7.82542 7.41675 7.95502C7.71875 8.06897 8.08179 8.02413 8.37523 7.58398C8.51433 7.37533 8.7485 7.25 8.99927 7.25C9.25003 7.25 9.48421 7.37533 9.62331 7.58398C9.91674 8.02413 10.2798 8.06897 10.5818 7.95502C10.9253 7.82542 11.2317 7.46736 11.2498 6.97257C11.2647 6.56461 11.6032 6.24352 12.0114 6.2501C12.4195 6.25668 12.7475 6.58851 12.7493 6.99674C12.7517 7.54913 13.1886 9.15586 15.0757 11.4229C15.0823 11.4308 15.0887 11.4388 15.0949 11.447C16.1157 12.7811 16.7493 14.4054 16.7493 16C16.7493 19.8322 13.1668 22.75 8.99927 22.75C4.83169 22.75 1.24927 19.8322 1.24927 16C1.24927 14.4054 1.88281 12.7811 2.90364 11.447C2.90987 11.4388 2.91627 11.4308 2.92283 11.4229C4.8099 9.15586 5.24687 7.54913 5.24927 6.99674C5.25105 6.58851 5.579 6.25668 5.98718 6.2501ZM7.83484 10.8292C8.20533 11.0144 8.35549 11.4649 8.17025 11.8354L7.67025 12.8354C7.48501 13.2059 7.03451 13.356 6.66402 13.1708C6.29354 12.9856 6.14337 12.5351 6.32861 12.1646L6.82861 11.1646C7.01385 10.7941 7.46436 10.6439 7.83484 10.8292ZM10.164 10.8292C10.5345 10.6439 10.985 10.7941 11.1703 11.1646L11.6703 12.1646C11.8555 12.5351 11.7053 12.9856 11.3348 13.1708C10.9644 13.356 10.5139 13.2059 10.3286 12.8354L9.82861 11.8354C9.64337 11.4649 9.79354 11.0144 10.164 10.8292Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 3.0 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="M12.6464 4.85841C12.9445 4.48673 13.1271 4.01976 13.1271 3.5C13.1271 2.20288 12.0081 1.25 10.7543 1.25H5.24524C3.99144 1.25 2.87244 2.20288 2.87244 3.5C2.87244 4.01976 3.055 4.48673 3.35316 4.85841C3.52079 5.06738 3.6046 5.17187 3.61356 5.26012C3.62251 5.34837 3.57237 5.44614 3.47209 5.64169C2.15246 8.21494 1.24963 10.6245 1.24963 13.7771V18C1.24963 19.6296 1.41033 20.9586 2.31169 21.7918C2.76397 22.2098 3.3378 22.4427 3.99127 22.5754C4.63937 22.707 5.41717 22.75 6.32711 22.75H9.67242C10.0971 22.75 10.536 22.7311 10.9492 22.6976C11.458 22.6563 11.7124 22.6356 11.7825 22.466C11.8525 22.2965 11.6681 22.0801 11.2993 21.6473C10.355 20.5392 9.74963 19.1021 9.74963 17.3845C9.74963 15.5039 10.3683 13.8652 11.2997 12.6615C11.9146 11.867 12.7887 11.1221 13.8271 10.7559C14.1207 10.6524 14.2674 10.6007 14.3223 10.4975C14.3772 10.3943 14.343 10.2627 14.2747 9.9996C13.8827 8.49067 13.2713 7.09213 12.5274 5.64158C12.4271 5.44611 12.377 5.34837 12.386 5.26012C12.3949 5.17187 12.4787 5.06738 12.6464 4.85841Z" fill="white"/>
<path d="M20.7264 10C20.7264 9.44771 20.2787 9 19.7264 9C18.4312 9 17.4699 9.67339 16.8679 10.5139C16.515 11.0066 16.2718 11.5713 16.1343 12.1362C15.845 12.0691 15.5182 12.0195 15.1816 12.0195C14.1838 12.0195 13.1925 12.667 12.4862 13.5798C11.7591 14.5195 11.2498 15.8349 11.2498 17.3848C11.2498 20.6682 14.1829 22.7503 16.9998 22.7503C19.8167 22.7503 22.7498 20.6682 22.7498 17.3848C22.7498 15.8349 22.2406 14.5195 21.5134 13.5798C20.8071 12.667 19.8158 12.0195 18.818 12.0196C18.6312 12.0195 18.4473 12.0348 18.2712 12.0599C18.3347 11.9256 18.409 11.7969 18.4939 11.6785C18.801 11.2497 19.2033 11 19.7264 11C20.2787 11 20.7264 10.5523 20.7264 10Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 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="white"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 209 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 30 KiB

@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.25 8C9.25 7.58579 9.58579 7.25 10 7.25H10.0081C10.4223 7.25 10.7581 7.58579 10.7581 8C10.7581 8.41421 10.4223 8.75 10.0081 8.75H10C9.58579 8.75 9.25 8.41421 9.25 8Z" fill="white"/>
<path d="M13.25 8C13.25 7.58579 13.5858 7.25 14 7.25H14.0081C14.4223 7.25 14.7581 7.58579 14.7581 8C14.7581 8.41421 14.4223 8.75 14.0081 8.75H14C13.5858 8.75 13.25 8.41421 13.25 8Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.0039 1.25C8.27598 1.25 5.25391 4.27208 5.25391 8C5.25391 9.38824 5.67299 10.6786 6.39155 11.7515C5.6694 12.8779 5.25 14.2322 5.25 15.6847C5.25 19.5503 8.23629 22.7505 12 22.7505C14.6194 22.7505 16.868 21.1942 17.9876 18.9496C16.4713 18.291 14.8272 17.3477 13.3318 16.1767C12.767 15.7344 12.2184 15.2558 11.7036 14.7434C11.8031 14.7478 11.9033 14.75 12.0039 14.75C12.5875 14.75 13.1538 14.6759 13.694 14.5367C13.8779 14.6931 14.0656 14.8462 14.2566 14.9957C15.6278 16.0694 17.1339 16.9362 18.5141 17.5427C18.668 16.9498 18.75 16.3267 18.75 15.6847C18.75 14.2348 18.3321 12.8827 17.6123 11.7574C18.3333 10.6834 18.7539 9.39081 18.7539 8C18.7539 4.27208 15.7318 1.25 12.0039 1.25ZM9.30582 4.0036C8.02465 4.87025 7.18248 6.33678 7.18248 8C7.18248 10.6628 9.3411 12.8214 12.0039 12.8214C14.6667 12.8214 16.8253 10.6628 16.8253 8C16.8253 5.3372 14.6667 3.17857 12.0039 3.17857C11.5521 3.17857 11.2325 3.5278 11.2325 3.90179C11.2325 4.27578 11.5521 4.625 12.0039 4.625C12.2101 4.625 12.3921 4.55043 12.528 4.43216C12.9298 4.08256 13.5389 4.12485 13.8885 4.5266C14.2381 4.92836 14.1958 5.53744 13.794 5.88704C13.3158 6.30318 12.6868 6.55357 12.0039 6.55357C10.5721 6.55357 9.36055 5.44435 9.30582 4.0036Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@ -0,0 +1,5 @@
<svg width="11" height="13" viewBox="0 0 11 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.3125 9.18754C8.3125 9.5097 8.05133 9.77087 7.72917 9.77087H3.64583C3.32367 9.77087 3.0625 9.5097 3.0625 9.18753C3.0625 8.86537 3.32367 8.6042 3.64583 8.6042L7.72917 8.6042C8.05133 8.6042 8.3125 8.86537 8.3125 9.18754Z" fill="#D48D05"/>
<path d="M7.72917 7.43754C8.05133 7.43754 8.3125 7.17637 8.3125 6.8542C8.3125 6.53204 8.05133 6.27087 7.72917 6.27087H5.97917C5.657 6.27087 5.39583 6.53204 5.39583 6.8542C5.39583 7.17637 5.657 7.43754 5.97917 7.43754H7.72917Z" fill="#D48D05"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.73382 12.4817C3.3068 12.5417 4.02593 12.5417 4.92491 12.5416H5.43552C6.53974 12.5417 7.42285 12.5417 8.11587 12.4537C8.83017 12.3629 9.42828 12.17 9.90594 11.7188C10.3885 11.263 10.5993 10.6844 10.6976 9.99371C10.7917 9.33241 10.7917 8.49251 10.7917 7.45491V3.86752C10.7917 3.40135 10.7917 3.02361 10.7714 2.71527C10.7505 2.39757 10.7064 2.11426 10.5995 1.8435C10.291 1.06224 9.64446 0.458909 8.84147 0.17599C8.3405 -0.000517334 7.48179 -0.000290103 6.5747 2.63095e-05C4.91826 -0.000183545 3.93438 -0.000307483 3.12796 0.28382C1.83296 0.74009 0.798282 1.71002 0.3063 2.95606C0.142319 3.37137 0.0696729 3.81745 0.034526 4.35227C-2.24045e-06 4.87769 -1.10714e-06 5.52671 3.38423e-07 6.35404L3.38423e-07 7.8611C-1.19404e-05 8.70582 -2.16874e-05 9.38986 0.0640774 9.93664C0.130643 10.5045 0.272541 10.9932 0.604942 11.4127C0.777131 11.63 0.98045 11.8217 1.20816 11.9828C1.64261 12.29 2.14499 12.4201 2.73382 12.4817ZM9.12738 10.8885C8.89802 11.1052 8.57364 11.2463 7.97325 11.3225C7.35988 11.4004 6.5489 11.4015 5.39582 11.4015H4.95693C4.01857 11.4015 3.35874 11.4008 2.85157 11.3477C2.35344 11.2956 2.07116 11.1983 1.86242 11.0507C1.72157 10.9511 1.59769 10.8338 1.49397 10.7029C1.3449 10.5148 1.24605 10.2623 1.19225 9.80338C1.13687 9.33102 1.13596 8.71425 1.13596 7.82557L1.13591 6.13379C1.1359 5.97803 1.136 5.60554 1.29969 5.33011C1.40057 5.16037 1.52973 5.01856 1.66241 4.94633C1.87621 4.82994 2.12132 4.76381 2.38188 4.76381L2.98876 4.7851C3.21048 4.7897 3.46921 4.7827 3.71896 4.71578C4.20545 4.58542 4.58543 4.20544 4.71579 3.71895C4.78271 3.4692 4.78971 3.21047 4.78511 2.98875L4.76382 2.38187C4.76382 2.10905 4.83638 1.87156 4.96316 1.65082C5.03707 1.52214 5.16584 1.40437 5.34361 1.30094C5.61319 1.14408 5.93601 1.14262 6.13327 1.14173C6.33053 1.14084 6.53865 1.14016 6.71251 1.14016C7.78242 1.14016 8.17157 1.1483 8.4652 1.25175C8.97423 1.4311 9.36256 1.8054 9.54344 2.26352C9.58874 2.37823 9.62073 2.52898 9.6379 2.79028C9.6554 3.05668 9.65569 3.39673 9.65569 3.88688V7.41097C9.65569 8.503 9.65433 9.26115 9.57302 9.83245C9.49457 10.3836 9.35187 10.6765 9.12738 10.8885Z" fill="#D48D05"/>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

@ -0,0 +1,9 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.00439 1.5C9.41861 1.5 9.75439 1.83579 9.75439 2.25V4.50003L10.1913 4.50001C10.323 4.49992 10.4848 4.4998 10.6171 4.51636L10.6196 4.51667C10.7144 4.5285 11.1465 4.58237 11.3522 5.00657C11.5585 5.43169 11.332 5.80679 11.283 5.88795L11.0287 6.23932C10.8077 6.52119 10.5034 6.90703 10.2181 7.20028C10.0757 7.34661 9.91277 7.49754 9.73959 7.61668C9.58565 7.72258 9.323 7.87503 9 7.87503C8.677 7.87503 8.41436 7.72258 8.26041 7.61668C8.08723 7.49754 7.92428 7.34661 7.78191 7.20028C7.49659 6.90703 7.19231 6.52118 6.97134 6.23931L6.71698 5.88795C6.66799 5.80679 6.44154 5.43169 6.64776 5.00657C6.85353 4.58237 7.28556 4.5285 7.38039 4.51667L7.3829 4.51636C7.51523 4.49981 7.67697 4.49992 7.8087 4.50001L8.25439 4.50003V2.25C8.25439 1.83579 8.59018 1.5 9.00439 1.5Z" fill="#2E3039"/>
<path d="M14.3028 8.50787C14.5957 8.21498 14.5957 7.7401 14.3028 7.44721C14.0099 7.15432 13.535 7.15432 13.2421 7.44721L12.4466 8.2427C12.1537 8.5356 12.1537 9.01047 12.4466 9.30336C12.7395 9.59626 13.2144 9.59626 13.5073 9.30336L14.3028 8.50787Z" fill="#2E3039"/>
<path d="M4.75778 7.44767C4.46489 7.15478 3.99002 7.15478 3.69712 7.44767C3.40423 7.74057 3.40423 8.21544 3.69712 8.50833L4.49262 9.30383C4.78551 9.59672 5.26039 9.59672 5.55328 9.30383C5.84617 9.01094 5.84617 8.53606 5.55328 8.24317L4.75778 7.44767Z" fill="#2E3039"/>
<path d="M2.25 12.0005C1.83579 12.0005 1.5 12.3363 1.5 12.7505C1.5 13.1648 1.83579 13.5005 2.25 13.5005H3.375C3.78921 13.5005 4.125 13.1648 4.125 12.7505C4.125 12.3363 3.78921 12.0005 3.375 12.0005H2.25Z" fill="#2E3039"/>
<path d="M14.625 12.0005C14.2108 12.0005 13.875 12.3363 13.875 12.7505C13.875 13.1648 14.2108 13.5005 14.625 13.5005H15.75C16.1642 13.5005 16.5 13.1648 16.5 12.7505C16.5 12.3363 16.1642 12.0005 15.75 12.0005H14.625Z" fill="#2E3039"/>
<path d="M16.5 15.75C16.5 16.1642 16.1642 16.5 15.75 16.5L2.25 16.5C1.83579 16.5 1.5 16.1642 1.5 15.75C1.5 15.3358 1.83579 15 2.25 15L15.75 15C16.1642 15 16.5 15.3358 16.5 15.75Z" fill="#2E3039"/>
<path d="M9 9C6.92893 9 5.25 10.6789 5.25 12.75C5.25 13.1642 5.58579 13.5 6 13.5H12C12.4142 13.5 12.75 13.1642 12.75 12.75C12.75 10.6789 11.0711 9 9 9Z" fill="#2E3039"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 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="M9.22103 1.7795C9.81474 1.35942 10.537 1.15927 11.4457 1.41276L13.8325 2.07854C13.8415 2.08106 13.8506 2.08358 13.8596 2.08609C14.1283 2.16063 14.401 2.23626 14.6357 2.39226C14.867 2.54596 15.0425 2.76342 15.2153 2.9775C15.2248 2.98922 15.2343 3.00093 15.2437 3.01262C17.8745 6.25803 19.615 8.80922 21.0951 12.0611L21.1211 12.1182C21.683 13.3527 22.1426 14.3625 22.3793 15.2434C22.6308 16.1796 22.6491 17.0304 22.2891 17.9326C21.9294 18.8342 21.3483 19.4157 20.5396 19.8663C19.7934 20.2819 18.8182 20.603 17.6511 20.9872L17.5852 21.0089C15.2201 21.7876 13.0748 21.9411 10.669 21.1702C10.5551 21.1337 10.4982 21.1155 10.4464 21.1264C10.3946 21.1373 10.3485 21.178 10.2564 21.2596C9.06151 22.3173 7.45354 22.815 5.89016 22.8005C4.19123 22.7847 2.45139 22.163 1.26272 20.8727C1.13515 20.7342 1.06433 20.5528 1.06433 20.3646V12.9848C1.06433 12.7866 1.14282 12.5964 1.28264 12.4558C2.54568 11.1863 4.38273 10.5793 6.17069 10.5601C7.07426 10.5504 7.99402 10.6905 8.85052 10.9896C9.09931 11.0765 9.22371 11.1199 9.25049 11.2234C9.27728 11.3268 9.18109 11.4347 8.98871 11.6505C8.81551 11.8448 8.65167 12.0526 8.50126 12.2661C8.0296 12.9357 7.62672 13.7512 7.4766 14.5559C7.40064 14.9631 7.66916 15.3548 8.07634 15.4308C8.48353 15.5067 8.8752 15.2382 8.95116 14.831C9.0507 14.2974 9.33974 13.6805 9.72756 13.1299C9.99274 12.7535 10.2755 12.4484 10.5286 12.2379C10.6156 12.1656 10.6592 12.1294 10.7767 12.0747C10.8942 12.02 10.9653 12.0072 11.1074 11.9817C11.9593 11.8286 12.7692 11.8715 13.5198 12.0602C13.6644 12.0966 13.7367 12.1147 13.7763 12.0789C13.8159 12.0432 13.8053 11.9704 13.7841 11.8248C13.7289 11.4458 13.6909 11.0636 13.6531 10.6817C13.626 10.409 13.599 10.1364 13.5656 9.86524C13.5521 9.75498 13.5275 9.58286 13.5086 9.45334C13.4917 9.33746 13.4511 9.22589 13.3827 9.13085C13.2693 8.97333 13.1507 8.81947 13.0322 8.6657C13.0045 8.62974 12.9768 8.59379 12.9492 8.5578C12.877 8.46331 12.8062 8.36863 12.7357 8.27426C12.5251 7.9923 12.3165 7.7131 12.0765 7.45006C12.0344 7.40383 11.9726 7.38127 11.9104 7.38829C11.6833 7.41395 11.119 7.47937 10.8177 7.52912C10.2109 7.62929 9.32812 7.58686 8.55022 7.34492C7.81147 7.11515 6.86667 6.60136 6.68264 5.54605C6.59164 5.02417 6.73496 4.58765 6.98353 4.19587C7.19707 3.8593 7.52123 3.50757 7.86706 3.13234C7.95848 3.03313 8.04835 2.93256 8.13781 2.83158C8.44695 2.48267 8.81614 2.06598 9.22103 1.7795Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 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 fill-rule="evenodd" clip-rule="evenodd" d="M2.70711 1.29289C2.31658 0.902369 1.68342 0.902369 1.29289 1.29289C0.902369 1.68342 0.902369 2.31658 1.29289 2.70711L4.01381 5.42803C2.9068 6.9926 2.25 8.88897 2.25 10.9258C2.25 13.6482 3.40187 15.9241 5.01689 17.8025C6.62496 19.6728 8.71834 21.1811 10.6782 22.3944L10.6911 22.4024L10.7043 22.4098C11.0996 22.6329 11.546 22.75 12 22.75C12.454 22.75 12.9004 22.6329 13.2957 22.4098L13.3072 22.4033L13.3185 22.3964C14.7993 21.4903 16.352 20.4087 17.7149 19.1291L21.2929 22.7071C21.6834 23.0976 22.3166 23.0976 22.7071 22.7071C23.0976 22.3166 23.0976 21.6834 22.7071 21.2929L2.70711 1.29289ZM13.3517 14.7659L8.23413 9.64835C8.08257 10.0706 8 10.5256 8 11C8 13.2091 9.79086 15 12 15C12.4744 15 12.9294 14.9174 13.3517 14.7659Z" fill="#8F9AA3"/>
<path d="M12 1.25C17.2755 1.25 21.75 5.60954 21.75 10.9258C21.75 12.7582 21.2256 14.3937 20.3882 15.8424C20.2225 16.129 20.1397 16.2723 19.993 16.2916C19.8464 16.3109 19.7251 16.1896 19.4826 15.9471L16.0589 12.5234C15.9357 12.4002 15.8742 12.3386 15.8528 12.264C15.8314 12.1894 15.8533 12.0946 15.8972 11.9051C15.9644 11.6143 16 11.3113 16 11C16 8.79086 14.2091 7 12 7C11.6887 7 11.3857 7.03556 11.0949 7.10283C10.9054 7.14667 10.8106 7.16859 10.736 7.14721C10.6614 7.12584 10.5998 7.06426 10.4766 6.94111L7.01667 3.48113C6.77751 3.24197 6.65793 3.12239 6.67673 2.97621C6.69553 2.83003 6.83548 2.74811 7.11539 2.58426C8.56262 1.73712 10.2366 1.25 12 1.25Z" fill="#8F9AA3"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -0,0 +1,5 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" rx="10" fill="#EFEFF0"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.7071 9.29289C10.3166 8.90237 9.68342 8.90237 9.29289 9.29289C8.90237 9.68342 8.90237 10.3166 9.29289 10.7071L12.0138 13.428C10.9068 14.9926 10.25 16.889 10.25 18.9258C10.25 21.6482 11.4019 23.9241 13.0169 25.8025C14.625 27.6728 16.7183 29.1811 18.6782 30.3944L18.6911 30.4024L18.7043 30.4098C19.0996 30.6329 19.546 30.75 20 30.75C20.454 30.75 20.9004 30.6329 21.2957 30.4098L21.3072 30.4033L21.3185 30.3964C22.7993 29.4903 24.352 28.4087 25.7149 27.1291L29.2929 30.7071C29.6834 31.0976 30.3166 31.0976 30.7071 30.7071C31.0976 30.3166 31.0976 29.6834 30.7071 29.2929L10.7071 9.29289ZM21.3517 22.7659L16.2341 17.6483C16.0826 18.0706 16 18.5256 16 19C16 21.2091 17.7909 23 20 23C20.4744 23 20.9294 22.9174 21.3517 22.7659Z" fill="#8F9AA3"/>
<path d="M20 9.25C25.2755 9.25 29.75 13.6095 29.75 18.9258C29.75 20.7582 29.2256 22.3937 28.3882 23.8424C28.2225 24.129 28.1397 24.2723 27.993 24.2916C27.8464 24.3109 27.7251 24.1896 27.4826 23.9471L24.0589 20.5234C23.9357 20.4002 23.8742 20.3386 23.8528 20.264C23.8314 20.1894 23.8533 20.0946 23.8972 19.9051C23.9644 19.6143 24 19.3113 24 19C24 16.7909 22.2091 15 20 15C19.6887 15 19.3857 15.0356 19.0949 15.1028C18.9054 15.1467 18.8106 15.1686 18.736 15.1472C18.6614 15.1258 18.5998 15.0643 18.4766 14.9411L15.0167 11.4811C14.7775 11.242 14.6579 11.1224 14.6767 10.9762C14.6955 10.83 14.8355 10.7481 15.1154 10.5843C16.5626 9.73712 18.2366 9.25 20 9.25Z" fill="#8F9AA3"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@ -0,0 +1,9 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.00439 7.87503C9.41861 7.87503 9.75439 7.53924 9.75439 7.12503L9.75439 4.875L10.1913 4.87501C10.323 4.87511 10.4848 4.87522 10.6171 4.85867L10.6196 4.85836C10.7144 4.84653 11.1465 4.79266 11.3522 4.36846C11.5585 3.94334 11.332 3.56824 11.283 3.48708L11.0287 3.13571C10.8077 2.85384 10.5034 2.468 10.2181 2.17474C10.0757 2.02841 9.91277 1.87749 9.73959 1.75835C9.58565 1.65245 9.323 1.5 9 1.5C8.677 1.5 8.41436 1.65245 8.26041 1.75835C8.08723 1.87749 7.92428 2.02841 7.78191 2.17474C7.49658 2.468 7.19231 2.85385 6.97134 3.13572L6.71698 3.48708C6.66799 3.56823 6.44154 3.94334 6.64776 4.36846C6.85353 4.79265 7.28556 4.84653 7.38039 4.85836L7.38291 4.85867C7.51523 4.87522 7.67697 4.87511 7.8087 4.87501L8.25439 4.875L8.25439 7.12503C8.25439 7.53924 8.59018 7.87503 9.00439 7.87503Z" fill="#2E3039"/>
<path d="M14.3028 8.5079C14.5957 8.215 14.5957 7.74013 14.3028 7.44724C14.0099 7.15434 13.535 7.15434 13.2421 7.44724L12.4466 8.24273C12.1537 8.53563 12.1537 9.0105 12.4466 9.30339C12.7395 9.59629 13.2144 9.59629 13.5073 9.30339L14.3028 8.5079Z" fill="#2E3039"/>
<path d="M4.75778 7.4477C4.46489 7.15481 3.99002 7.15481 3.69712 7.4477C3.40423 7.74059 3.40423 8.21547 3.69712 8.50836L4.49262 9.30386C4.78551 9.59675 5.26039 9.59675 5.55328 9.30386C5.84617 9.01096 5.84617 8.53609 5.55328 8.2432L4.75778 7.4477Z" fill="#2E3039"/>
<path d="M2.25 12.0006C1.83579 12.0006 1.5 12.3364 1.5 12.7506C1.5 13.1648 1.83579 13.5006 2.25 13.5006H3.375C3.78921 13.5006 4.125 13.1648 4.125 12.7506C4.125 12.3364 3.78921 12.0006 3.375 12.0006H2.25Z" fill="#2E3039"/>
<path d="M14.625 12.0006C14.2108 12.0006 13.875 12.3364 13.875 12.7506C13.875 13.1648 14.2108 13.5006 14.625 13.5006H15.75C16.1642 13.5006 16.5 13.1648 16.5 12.7506C16.5 12.3364 16.1642 12.0006 15.75 12.0006H14.625Z" fill="#2E3039"/>
<path d="M16.5 15.75C16.5 16.1642 16.1642 16.5 15.75 16.5L2.25 16.5C1.83579 16.5 1.5 16.1642 1.5 15.75C1.5 15.3358 1.83579 15 2.25 15L15.75 15C16.1642 15 16.5 15.3358 16.5 15.75Z" fill="#2E3039"/>
<path d="M9 9.00003C6.92893 9.00003 5.25 10.679 5.25 12.75C5.25 13.1642 5.58579 13.5 6 13.5H12C12.4142 13.5 12.75 13.1642 12.75 12.75C12.75 10.679 11.0711 9.00003 9 9.00003Z" fill="#2E3039"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@ -0,0 +1,8 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 11.75C7.30964 11.75 6.75 12.3096 6.75 13C6.75 13.6904 7.30964 14.25 8 14.25H8.00897C8.69933 14.25 9.25897 13.6904 9.25897 13C9.25897 12.3096 8.69933 11.75 8.00897 11.75H8Z" fill="white"/>
<path d="M11.9955 11.75C11.3052 11.75 10.7455 12.3096 10.7455 13C10.7455 13.6904 11.3052 14.25 11.9955 14.25H12.0045C12.6948 14.25 13.2545 13.6904 13.2545 13C13.2545 12.3096 12.6948 11.75 12.0045 11.75H11.9955Z" fill="white"/>
<path d="M15.991 11.75C15.3007 11.75 14.741 12.3096 14.741 13C14.741 13.6904 15.3007 14.25 15.991 14.25H16C16.6904 14.25 17.25 13.6904 17.25 13C17.25 12.3096 16.6904 11.75 16 11.75H15.991Z" fill="white"/>
<path d="M8 15.75C7.30964 15.75 6.75 16.3096 6.75 17C6.75 17.6904 7.30964 18.25 8 18.25H8.00897C8.69933 18.25 9.25897 17.6904 9.25897 17C9.25897 16.3096 8.69933 15.75 8.00897 15.75H8Z" fill="white"/>
<path d="M11.9955 15.75C11.3052 15.75 10.7455 16.3096 10.7455 17C10.7455 17.6904 11.3052 18.25 11.9955 18.25H12.0045C12.6948 18.25 13.2545 17.6904 13.2545 17C13.2545 16.3096 12.6948 15.75 12.0045 15.75H11.9955Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7 2C7 1.44772 6.55229 1 6 1C5.44772 1 5 1.44772 5 2V2.44885C4.23882 2.6903 3.57734 3.06994 3.01802 3.6746C2.18949 4.57031 1.83279 5.69272 1.66416 7.04866C1.49997 8.36894 1.49998 10.0541 1.5 12.1739L1.5 12.8261C1.49998 14.9459 1.49997 16.6311 1.66416 17.9513C1.83279 19.3073 2.18949 20.4297 3.01802 21.3254C3.8568 22.2322 4.92535 22.6329 6.21533 22.8204C7.45141 23.0001 9.02291 23 10.9712 23H13.0288C14.977 23 16.5486 23.0001 17.7847 22.8204C19.0747 22.6329 20.1432 22.2322 20.982 21.3254C21.8105 20.4297 22.1672 19.3073 22.3358 17.9513C22.5 16.6311 22.5 14.9459 22.5 12.8261V12.1739C22.5 10.0541 22.5 8.36895 22.3358 7.04866C22.1672 5.69272 21.8105 4.57031 20.982 3.6746C20.4227 3.06993 19.7612 2.6903 19 2.44885V2C19 1.44772 18.5523 1 18 1C17.4477 1 17 1.44772 17 2V2.09173C15.903 1.99995 14.5863 1.99998 13.0288 2L10.9712 2C9.41376 1.99998 8.09704 1.99995 7 2.09173V2ZM4.49783 9C4.03921 9 3.8099 9 3.66385 9.14417C3.51781 9.28833 3.51487 9.51472 3.509 9.96751C3.50027 10.6407 3.5 11.3942 3.5 12.2432V12.7568C3.5 14.9616 3.50182 16.5221 3.64887 17.7045C3.79327 18.8656 4.06263 19.5094 4.48622 19.9673C4.89956 20.4142 5.4647 20.6903 6.503 20.8412C7.57858 20.9975 9.00425 21 11.05 21H12.95C14.9957 21 16.4214 20.9975 17.497 20.8412C18.5353 20.6903 19.1004 20.4142 19.5138 19.9673C19.9374 19.5094 20.2067 18.8656 20.3511 17.7045C20.4982 16.5221 20.5 14.9616 20.5 12.7568V12.2432C20.5 11.3942 20.4997 10.6407 20.491 9.96751C20.4851 9.51472 20.4822 9.28833 20.3362 9.14417C20.1901 9 19.9608 9 19.5022 9L4.49783 9Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

@ -0,0 +1,4 @@
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.32954 16.125C6.17371 16.125 4.2516 16.125 3.5149 16.0479C2.75784 15.9687 2.11193 15.8014 1.55335 15.4064C1.26058 15.1994 0.999168 14.9528 0.777782 14.6734C0.35041 14.1341 0.16797 13.5057 0.082385 12.7757C-2.81744e-05 12.0727 -1.53024e-05 11.1932 4.33207e-07 10.1071V8.16951C-9.97304e-07 7.10582 -2.42796e-06 6.27131 0.0443906 5.59578C0.0895791 4.90815 0.182981 4.33462 0.393815 3.80065C1.02636 2.1986 2.35666 0.951546 4.02166 0.364913C5.05849 -0.000393972 6.32348 -0.000236608 8.45319 3.37585e-05C9.61944 -0.000372507 10.7235 -0.000664338 11.3676 0.226273C12.4 0.590027 13.2313 1.36573 13.6279 2.37021C13.7654 2.71833 13.8221 3.08259 13.849 3.49106C13.875 3.88749 13.875 4.37316 13.875 4.97253V5.83223C13.875 6.23555 13.548 6.5625 13.1447 6.5625C12.7414 6.5625 12.4145 6.23555 12.4145 5.83223V4.99742C12.4145 4.36722 12.4141 3.93001 12.3916 3.58751C12.3695 3.25154 12.3284 3.05773 12.2701 2.91025C12.0376 2.32122 11.5383 1.83999 10.8838 1.6094C10.5063 1.47639 10.006 1.46592 8.63037 1.46592C8.40683 1.46592 8.13925 1.4668 7.88563 1.46794C7.63201 1.46909 7.21696 1.47096 6.87035 1.67263C6.6418 1.80562 6.47623 1.95704 6.38121 2.12248C6.2182 2.40629 6.12491 2.71164 6.12491 3.0624L6.15228 3.84268C6.1582 4.12775 6.14919 4.4604 6.06315 4.78151C5.89556 5.40699 5.407 5.89555 4.78152 6.06314C4.46041 6.14919 4.12776 6.15819 3.84268 6.15227L3.06241 6.1249C2.72741 6.1249 2.41226 6.20992 2.13738 6.35957C1.96679 6.45243 1.80073 6.63476 1.67103 6.85299C1.46057 7.20712 1.46044 7.68604 1.46045 7.88631L1.46052 10.0614C1.46052 11.204 1.4617 11.997 1.53289 12.6044C1.60207 13.1944 1.72915 13.519 1.92082 13.7609C2.05417 13.9292 2.21345 14.08 2.39454 14.2081C2.66291 14.3978 3.02586 14.5229 3.6663 14.5899C4.31838 14.6581 6.12307 14.6591 7.32954 14.6591C7.73434 14.6591 8.0625 14.9872 8.0625 15.3921C8.0625 15.7969 7.73434 16.125 7.32954 16.125Z" fill="#F43333"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.1909 7.74268C12.0592 7.4633 11.7697 7.29324 11.4615 7.31423C11.1534 7.33522 10.8896 7.54297 10.797 7.83762L9.72246 11.2566L9.33448 10.4336C9.23436 10.2212 9.13716 10.0149 9.03714 9.84819C8.92509 9.6614 8.77697 9.46633 8.54751 9.31592C8.31524 9.16367 8.07628 9.10728 7.8576 9.0833C7.66706 9.0624 7.44476 9.06245 7.22295 9.06249L6.5625 9.06249C6.14829 9.06249 5.8125 9.39828 5.8125 9.81249C5.8125 10.2267 6.14829 10.5625 6.5625 10.5625H7.19274C7.45714 10.5625 7.59536 10.5635 7.69409 10.5744C7.70631 10.5757 7.71628 10.577 7.72429 10.5783C7.73108 10.5881 7.73992 10.6016 7.75086 10.6199C7.80748 10.7142 7.8728 10.8507 7.99016 11.0997L9.18411 13.6323C9.31581 13.9117 9.60532 14.0817 9.91347 14.0608C10.2216 14.0398 10.4854 13.832 10.578 13.5374L11.6525 10.1184L12.0405 10.9414C12.1406 11.1538 12.2378 11.3601 12.3379 11.5268C12.4499 11.7136 12.598 11.9087 12.8275 12.0591C13.0598 12.2113 13.2987 12.2677 13.5174 12.2917C13.7079 12.3126 13.9302 12.3125 14.1521 12.3125L14.8125 12.3125C15.2267 12.3125 15.5625 11.9767 15.5625 11.5625C15.5625 11.1483 15.2267 10.8125 14.8125 10.8125H14.1823C13.9179 10.8125 13.7796 10.8115 13.6809 10.8006C13.6687 10.7993 13.6587 10.7979 13.6507 10.7967C13.6439 10.7869 13.6351 10.7734 13.6241 10.7551C13.5675 10.6608 13.5022 10.5242 13.3848 10.2753L12.1909 7.74268ZM7.70832 10.5571L7.70719 10.5559C7.70719 10.5559 7.70757 10.5563 7.70832 10.5571ZM13.6667 10.8179L13.6678 10.8191C13.6678 10.8191 13.6674 10.8187 13.6667 10.8179Z" fill="#F43333"/>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

@ -0,0 +1,3 @@
<svg width="13" height="13" viewBox="0 0 13 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 6.27083C0 9.73408 2.80758 12.5417 6.27083 12.5417C9.73408 12.5417 12.5417 9.73408 12.5417 6.27083C12.5417 2.80758 9.73408 0 6.27083 0C2.80758 0 0 2.80758 0 6.27083ZM8.9985 4.09091C9.23592 4.30849 9.25167 4.67774 9.03409 4.91515L5.82576 8.41515C5.71842 8.5324 5.56733 8.60067 5.40867 8.60417C5.24941 8.60767 5.096 8.54585 4.98341 8.43327L3.52508 6.97494C3.297 6.74744 3.297 6.37756 3.52508 6.15006C3.75258 5.92198 4.12242 5.92198 4.34992 6.15006L5.37775 7.17734L8.17424 4.12651C8.39183 3.8891 8.76108 3.87333 8.9985 4.09091Z" fill="#18C273"/>
</svg>

After

Width:  |  Height:  |  Size: 697 B

@ -0,0 +1,8 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 11.75C7.30964 11.75 6.75 12.3096 6.75 13C6.75 13.6904 7.30964 14.25 8 14.25H8.00897C8.69933 14.25 9.25897 13.6904 9.25897 13C9.25897 12.3096 8.69933 11.75 8.00897 11.75H8Z" fill="white"/>
<path d="M11.9955 11.75C11.3052 11.75 10.7455 12.3096 10.7455 13C10.7455 13.6904 11.3052 14.25 11.9955 14.25H12.0045C12.6948 14.25 13.2545 13.6904 13.2545 13C13.2545 12.3096 12.6948 11.75 12.0045 11.75H11.9955Z" fill="white"/>
<path d="M15.991 11.75C15.3007 11.75 14.741 12.3096 14.741 13C14.741 13.6904 15.3007 14.25 15.991 14.25H16C16.6904 14.25 17.25 13.6904 17.25 13C17.25 12.3096 16.6904 11.75 16 11.75H15.991Z" fill="white"/>
<path d="M8 15.75C7.30964 15.75 6.75 16.3096 6.75 17C6.75 17.6904 7.30964 18.25 8 18.25H8.00897C8.69933 18.25 9.25897 17.6904 9.25897 17C9.25897 16.3096 8.69933 15.75 8.00897 15.75H8Z" fill="white"/>
<path d="M11.9955 15.75C11.3052 15.75 10.7455 16.3096 10.7455 17C10.7455 17.6904 11.3052 18.25 11.9955 18.25H12.0045C12.6948 18.25 13.2545 17.6904 13.2545 17C13.2545 16.3096 12.6948 15.75 12.0045 15.75H11.9955Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7 2C7 1.44772 6.55229 1 6 1C5.44772 1 5 1.44772 5 2V2.44885C4.23882 2.6903 3.57734 3.06994 3.01802 3.6746C2.18949 4.57031 1.83279 5.69272 1.66416 7.04866C1.49997 8.36894 1.49998 10.0541 1.5 12.1739L1.5 12.8261C1.49998 14.9459 1.49997 16.6311 1.66416 17.9513C1.83279 19.3073 2.18949 20.4297 3.01802 21.3254C3.8568 22.2322 4.92535 22.6329 6.21533 22.8204C7.45141 23.0001 9.02291 23 10.9712 23H13.0288C14.977 23 16.5486 23.0001 17.7847 22.8204C19.0747 22.6329 20.1432 22.2322 20.982 21.3254C21.8105 20.4297 22.1672 19.3073 22.3358 17.9513C22.5 16.6311 22.5 14.9459 22.5 12.8261V12.1739C22.5 10.0541 22.5 8.36895 22.3358 7.04866C22.1672 5.69272 21.8105 4.57031 20.982 3.6746C20.4227 3.06993 19.7612 2.6903 19 2.44885V2C19 1.44772 18.5523 1 18 1C17.4477 1 17 1.44772 17 2V2.09173C15.903 1.99995 14.5863 1.99998 13.0288 2L10.9712 2C9.41376 1.99998 8.09704 1.99995 7 2.09173V2ZM4.49783 9C4.03921 9 3.8099 9 3.66385 9.14417C3.51781 9.28833 3.51487 9.51472 3.509 9.96751C3.50027 10.6407 3.5 11.3942 3.5 12.2432V12.7568C3.5 14.9616 3.50182 16.5221 3.64887 17.7045C3.79327 18.8656 4.06263 19.5094 4.48622 19.9673C4.89956 20.4142 5.4647 20.6903 6.503 20.8412C7.57858 20.9975 9.00425 21 11.05 21H12.95C14.9957 21 16.4214 20.9975 17.497 20.8412C18.5353 20.6903 19.1004 20.4142 19.5138 19.9673C19.9374 19.5094 20.2067 18.8656 20.3511 17.7045C20.4982 16.5221 20.5 14.9616 20.5 12.7568V12.2432C20.5 11.3942 20.4997 10.6407 20.491 9.96751C20.4851 9.51472 20.4822 9.28833 20.3362 9.14417C20.1901 9 19.9608 9 19.5022 9L4.49783 9Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

File diff suppressed because it is too large Load Diff

@ -6,7 +6,7 @@
"noRatingAvailable": "No Rating Available",
"doctorDoesNotHaveRating": "This doctor does not have any ratings yet.",
"confirm": "Confirm",
"loadingText": "Loading, please wait...",
"loadingText": "Please wait...",
"kilometerUnit": "KM",
"dashboardScreenToolbarTitle": "Home",
"settings": "Settings",
@ -21,7 +21,7 @@
"mySchedule": "My Schedule",
"logout": "Logout",
"respirationRate": "Respiration Rate",
"bookAppo": "Book Appointment",
"bookAppo": "New Appointment",
"searchBy": "Search By:",
"clinic": "Clinic",
"byClinic": "By Clinic",
@ -40,7 +40,7 @@
"noResultFound": "No Result Found",
"noSearchResultFound": "No Result Found",
"pleaseEnterProductName": "Please Enter Product Name",
"bookNow": "Book Now",
"bookNow": "New Appointment",
"docInfo": "Doctor Information",
"appoInfo": "Appointment Information",
"availableAppo": "Available Appointments",
@ -69,7 +69,7 @@
"date": "Date",
"time": "Time",
"pendingPayment": "Pending for payment",
"payNow": "Pay Now",
"payNow": "Payment",
"viewQR": "View QR Code",
"instruction": "Instructions",
"livecare": "LiveCare",
@ -99,7 +99,7 @@
"onlinePaymentSubtitle": "Payment",
"cancelNocaps": "Cancel",
"setReminder": "Set Reminder",
"nationalIdNumber": "National ID Number",
"nationalIdNumber": "National ID / Iqama",
"medicalFileNumber": "Medical File Number",
"loginregister": "Login / Register",
"poweredBy": "Powered by",
@ -112,7 +112,7 @@
"yes": "Yes",
"no": "No",
"logintyperadio": " Choose from the below options to login to your medical file.",
"registernow": "Register Now",
"registernow": "Register",
"nationalID": "Enter the Identification Number",
"nationalId": "National ID",
"fileNo": "File Number",
@ -123,7 +123,7 @@
"profileInfo": "Please enter profile information",
"submit": "Submit",
"forgotDesc": "Enter the mobile number to receive the Medical File Number via SMS",
"dob": "Birth Date:",
"dob": "Birth Date",
"hijriDate": "Hijri Date",
"gregorianDate": "Gregorian Date",
"verifyLoginWith": "Please choose one of the following options to verify",
@ -196,6 +196,7 @@
"enterEmail": "Please Enter Email",
"family": "My Family",
"familyTitle": "My Family Files",
"familyTitle2": "My Family Files",
"addNewMember": "Add Family Member",
"sentRequests": "Sent Requests",
"recievedRequests": "Recieved Requests",
@ -268,6 +269,7 @@
"userViewRequester": "User Wants to View Your Medical File",
"userView": "User Can View Your Medical File",
"parking": "Parking",
"parking2": "Parking",
"alhabiServices": "HMG Service",
"parkingTitle": "Car service, car service, service to save parking information, return to it later, 1- By clicking on (Read the code), save the parking data. 2- By clicking on the button (view my car park), it shows you the cars location in Google Maps. 3- Read another position by pressing the Clear Position Data button.",
"parkingDescription": "Parking service is for you to scan the car location so you can find it easy on you way out. click on (scan parking) button to save the parking location, then (show my park) button will appear to show you the way for the parking. if you want to rescan parking QR just click on (Clear My Data).",
@ -306,10 +308,12 @@
"vitalSignObese": "Obese",
"vitalSignOverweight": "Overweight",
"vitalSignUnderweight": "Underweight",
"vitalSignSkinny": "Underweight",
"myMedical": "Active",
"myMedicalSubtitle": "Medications",
"myDoctor": "My Doctors",
"myDoctorSubtitle": "List",
"myDoctorSubtitle2": "List",
"eye": "Eye",
"eyeSubtitle": "Measurement",
"insurance": "Insurance",
@ -327,6 +331,7 @@
"monthly": "Monthly",
"monthlySubtitle": "Report",
"sick": "Sick",
"sick2": "Sick",
"sickSubtitle": "Leaves",
"myBalance": "My Balance",
"myBalanceSubtitle": "Credit",
@ -438,7 +443,7 @@
"serviceInformation": "Service Information",
"homeHealthCare": "Home Health Care",
"noAppointmentAvailable": "No Available Appointments",
"homeHealthCareText": "This service provides a set of home health care services, continuous and comprehensive follow-up in their places of residence for those who cannot access health facilities, such as (laboratory analyzes - radiology - vaccinations - physical therapy), etc.",
"homeHealthCareText": "Clinical excellence and specialized care in the comfort of your home",
"loginRegister": "Login/Register",
"orderLog": "Order Log",
"infoLab": "This service allows you to view the results of all laboratory tests performed in Al Habib Medical Group as well as sending the report via e-mail.",
@ -484,8 +489,10 @@
"refferalSubTitle": "Service",
"healthCare": "Health Care",
"emergency": "Emergency",
"emergency2": "Emergency",
"erservices": "Emergency",
"services2": "Services",
"services3": "Services",
"cantSeeProfile": "To view your medical profile, please log in or register now",
"loginRegisterNow": "Login or Register Now",
"hmgPharmacy": "HMG Pharmacy",
@ -694,11 +701,11 @@
"bikini": "Bikini",
"totalMinutes": "Total Minutes",
"feedback": "Feedback",
"likeToHear": "We would love to hear the feedback, concerns on healthcare services and eServices experience. Please use the below form",
"likeToHear": "We would love to hear your feedback. If you have any concerns about healthcare services or eServices experience, please let us in the form below.",
"emptySubject": "Please enter the subject",
"emptyMessage": "Please enter message",
"selectAttachment": "Select Attachment",
"complainAppo": "Complaint for appointment",
"complainAppo": "Complaint with appointment",
"complainAppoAlt": "Complaint without appointment",
"messageType": "Message Type",
"feedbackType": "Feedback Type",
@ -757,6 +764,7 @@
"erConsultation": "This service allows you to make an online virtual consultation via video call directly with the doctor from anywhere at any time.",
"myInvoice": "List",
"invoiceList": "My Invoice",
"invoiceList2": "My Invoice",
"allInvoices": "All Invoices",
"hospitals": "Hospitals",
"clinics": "Clinics",
@ -769,7 +777,7 @@
"livecarePoint2": "A Specialized doctor will contact you",
"livecarePoint3": "A Doctor will be able to see your full medical file history",
"livecarePoint4": "Free Medicine Delivery Available",
"livecarePoint5": "No need to visit the service is in your place",
"livecarePoint5": "Consult our specialists remotely and manage your health whenever and wherever you are.",
"bookImmediateLivecare": "I accept to get the service immediately",
"bookVideoLivecare2": "I don't need to visit the clinic",
"noThankyou": "No Thanks",
@ -783,11 +791,11 @@
"aboutApp": "About the app",
"aboutPoints": "Online Appointment Booking & rescheduling, Insurance approval status, Find A doctor, Ask your doctor, Medical prescriptions, Lab results, Hospitals contact numbers, Doctor profiles, Hospitals locations, Pharmacies Locations, Hospital's Virtual Tour, Official Social Media, Vaccines Schedule, Health Calculators, Other Services",
"termsConditions": "These Online Services Terms of Use (Service Terms) govern certain online services provided by Dr Sulaiman Al Habib Medical Services Group Company (HMG, we, us, our)...",
"receiveOtpToast": "Where would you like to receive OTP?",
"receiveOtpToast": "OTP",
"enterPhoneNumber": "Enter Phone Number",
"enterEmailDesc": "Enter your email address to complete the process of creating a medical file",
"enterPhoneDesc": "Enter your phone number to receive OTP verification code",
"pleaseChooseOption": "Please select from the below options to receive OTP",
"pleaseChooseOption": "Please select an option below to receive your OTP.",
"dontHaveAccount": "Don't have an account?",
"loginOrRegister": "Login or Register",
"myFiles": "My Files",
@ -798,14 +806,14 @@
"readInstructions": "Read Instructions",
"searchLabReport": "Search Lab Report",
"prescriptionDeliveryError": "This clinic doesn't support refill",
"prepareToElevate": "Prepared to elevate your health and well-being?",
"prepareToElevate": "Your wellness journey starts here.",
"iAcceptTermsConditions": "I Accept the Terms and Conditions",
"alreadyHaveAccount": "Already have an account?",
"loginNow": "Login Now",
"notice": "Notice",
"oR": "OR",
"sendOTPWHATSAPP": "Send me OTP on Whatsapp",
"sendOTPSMS": "Send me OTP on SMS",
"sendOTPWHATSAPP": "Whatsapp",
"sendOTPSMS": "SMS",
"fullName": "Full Name",
"married": "Married",
"uae": "United Arab Emirates",
@ -814,8 +822,10 @@
"loginByOTP": "Login By OTP",
"guest": "Guest",
"switchAccount": "Switch Account",
"allSet": "All Set! Now you can login with Face ID or Biometric",
"allSet": "Now you can login with Face ID or Biometric",
"quickLogin": "Quick Login",
"enableQuickLogin": "Enable Quick Login",
"enableQuickLoginBtn": "Enable Quick Login",
"enableMsg": "Enabling the quick login will verify through your existing device Face ID / Biometric",
"notNow": "Not Now",
"pendingActivation": "Pending Activation",
@ -841,7 +851,7 @@
"didntReceiveIt": "Didn't receive it?",
"resendOTP": "Resend",
"resendIn": "resend in",
"pleaseEnterAnationalID": "Please enter a national ID",
"pleaseEnterAnationalID": "ID or Iqama number is required",
"pleaseEnterAFileNumber": "Please enter a file number",
"pleaseEnterAValidEmail": "Please enter a valid email",
"pleaseEnterFullName": "Please enter full name",
@ -870,6 +880,7 @@
"monitor": "Monitor",
"noSpecialResult": "No Special Results",
"setTheDateRange": "Set The Date Range",
"selectDate": "Select Date",
"historyFlowchart": "History FlowChart",
"to": "to",
"startDate" : "Start Date",
@ -882,7 +893,7 @@
"clearAllFilters": "Clear all filters",
"filters": "Filters",
"searchClinic": "Search Clinic",
"walkin": "Walk In",
"walkin": "Booked",
"continueString": "Continue",
"laserClinic": "Laser Clinic",
"covid_info" :"Dr. Sulaiman Al Habib hospitals are conducting a test for the emerging corona virus and issuing travel certificates 24/7 in a short time and with high accuracy. Those wishing to benefit from this service can visit one of Dr. Sulaiman Al Habib branches to conduct a corona test within few minutes, and obtain the result within several hours. Corona Virus Covid 19 testing service with PCR technology to detect the virus according to the highest international standards and with the latest high-precision RT-PCR devices (American GeneXpert and others), That is approved by the Food and Drug Authority as well as by the Saudi Center for Infectious Diseases Prevention.",
@ -910,7 +921,7 @@
"paymentFailedPleaseTryAgain": "Payment Failed! Please try again.",
"appointmentCheckIn": "Appointment check in",
"insuranceExpiredOrInactive": "Insurance expired or inactive",
"totalAmountToPay": "Total amount to pay",
"totalAmountToPay": "Total Amount",
"vat15": "VAT 15%",
"general": "General",
"liveCare": "LiveCare",
@ -925,15 +936,15 @@
"immediateConsultation": "Immediate Consultation",
"scheduledConsultation": "Scheduled Consultation",
"pharmaLiveCare": "Pharma LiveCare",
"notSureHelpMeChooseClinic": "Not sure? help me choose a clinic!",
"mentionYourSymptomsAndFindDoctors": "Mention your symptoms and find the list of doctors accordingly",
"notSureHelpMeChooseClinic": "Need help choosing a clinic?",
"mentionYourSymptomsAndFindDoctors": "Tell us your symptoms, and well recommend the best-suited specialists for your care",
"immediateService": "Immediate service",
"noNeedToWaitGetMedicalConsultation": "No need to wait, you will get medical consultation immediately via video call",
"noVisitRequired": "No visit required",
"doctorWillContact": "Doctor will contact",
"specialisedDoctorWillContactYou": "A specialised doctor will contact you and will be able to view your medical history",
"doctorWillContact": "Doctor will contact you",
"specialisedDoctorWillContactYou": "A specialized doctor will contact you and will be able to view your medical history",
"freeMedicineDelivery": "Free medicine delivery",
"offersFreeMedicineDelivery": "Offers free medicine delivery for the LiveCare appointment",
"offersFreeMedicineDelivery": "Receive free medicine delivery with your LiveCare appointment.",
"dentalChiefComplaints": "Dental Chief Complaints",
"viewAvailableAppointments": "View available appointments",
"doctorProfile": "Doctor Profile",
@ -952,22 +963,22 @@
"viewNearestAppos": "View nearest available appointments",
"noDoctorFound": "No Doctor found for selected criteria...",
"yesPleasImInAHurry": "Yes please, I am in a hurry",
"fetchingFeesPleaseWait": "Fetching fees, Please wait...",
"fetchingFeesPleaseWait": "Please wait...",
"noThanksPhysicalVisit": "No, Thanks. I would like a physical visit",
"offline": "Offline",
"videoCall": "Video Call",
"liveVideoCallWithHMGDoctors": "Live Video Call with HMG Doctors",
"liveAudioCallWithHMGDoctors": "Live Audio Call with HMG Doctors",
"liveVideoCallWithHMGDoctors": "Live video call with a specialist",
"liveAudioCallWithHMGDoctors": "Live audio call with a specialist",
"audioCall": "Audio Call",
"phoneCall": "Phone Call",
"livePhoneCallWithHMGDoctors": "Live Phone Call with HMG Doctors",
"reviewLiveCareRequest": "Review LiveCare Request",
"selectedLiveCareType": "Selected LiveCare Type",
"selectLiveCareCallType": "Select LiveCare call type",
"livePhoneCallWithHMGDoctors": "Live phone call with a specialist",
"reviewLiveCareRequest": "LiveCare Request Details",
"selectedLiveCareType": "LiveCare Type",
"selectLiveCareCallType": "LiveCare Call Type",
"confirmingLiveCareRequest": "Confirming LiveCare request, Please wait...",
"unknownErrorOccurred": "Unknown error occurred...",
"liveCarePermissionsMessage": "LiveCare requires Camera, Microphone, Location & Notifications permissions to enable virtual consultation between patient & doctor, Please allow these to proceed.",
"liveCarePayment": "LiveCare Payment",
"liveCarePayment": "Payment",
"mada": "Mada",
"visaOrMastercard": "Visa or Mastercard",
"tamara": "Tamara",
@ -990,7 +1001,7 @@
"bookingWaitingAppointment": "Booking Waiting Appointment, Please wait...",
"enterValidIDorIqama": "Please enter a valid national ID or file number",
"selectAppointment": "Select Appointment",
"rebookSameDoctor": "Rebook with same doctor",
"rebookSameDoctor": "New Appointment",
"queueing": "Queueing",
"inQueue": "In Queue",
"yourTurn": "Your Turn",
@ -1051,7 +1062,7 @@
"insuranceDocument": "Insurance Document",
"selectBranch": "Select Branch",
"patientInformation": "Patient information",
"patientLocation": "Where the patient located",
"patientLocation": "Select the location of the patient",
"identificationNumber": "Identification Number",
"enterIdentificationNumber": "Enter Identification Number*",
"patientName": "Patient Name*",
@ -1079,7 +1090,7 @@
"hereIsYourReferralNumber": "Here is your Referral #: ",
"searchEReferral": "Search E-Referral",
"enterRequiredInfoToSearch": "Please enter the required information to search for an e-referral",
"selectPickupDirection": "Select Pickup Direction",
"selectPickupDirection": "Pickup Direction",
"selectDirection": "Select Direction",
"toHospital": "To Hospital",
"fromHospital": "From Hospital",
@ -1090,9 +1101,9 @@
"pleaseSelectDetailsOfPickup": " Please select the details of pickup",
"selectDetails": "Select Details",
"work": "Work",
"pick": "Pick",
"insideTheHome": "Inside the home",
"haveAnyAppointment": "Have any appointment",
"pick": "Pick up",
"insideTheHome": "From your home/Selected location",
"haveAnyAppointment": "Select if you have any appointment?",
"amountPaidAtHospital": "Amount will be paid at the hospital",
"submitRequest": "Submit Request",
"enterPickupLocationManually": "Enter Pickup Location Manually",
@ -1125,26 +1136,33 @@
"allFacilities": "All Facilities",
"selectLocation": "Select Location",
"pleaseSelectTheLocation": "Please select the location",
"viewLocationGoogleMaps": "View Location on Google Maps",
"viewLocationGoogleMaps": "Get Directions on Google Maps",
"callAmbulance": "Call Ambulance",
"requestAmbulanceInEmergency": "Request an ambulance in emergency from home or hospital",
"requestAmbulanceInEmergency": "Request an ambulance for immediate assistance in cases of emergency.",
"confirmation": "Confirmation",
"areYouSureYouWantToCallAmbulance": "Are you sure you want to call an ambulance?",
"getDetailsOfNearestBranch": "Get the details of nearest branch including directions",
"getDetailsOfNearestBranch": "Find the nearest ER and get directions.",
"areYouSureYouWantToCallRRT": "Are you sure you want to call Rapid Response Team (RRT)?",
"priorERCheckInToSkipLine": "Prior ER Check-In to skip the line & payment at the reception.",
"areYouSureYouWantToMakeERCheckIn": "Are you sure you want to make ER Check-In?",
"checkingYourERAppointmentStatus": "Checking your ER Appointment status...",
"priorERCheckInToSkipLine": "Check in before you arrive to the ER to skip the line & payment at reception.",
"areYouSureYouWantToMakeERCheckIn": "Are you sure you want to request an online ER check-in?",
"checkingYourERAppointmentStatus": "Please Wait...",
"transportOptions": "Transport Options",
"selectHospitalForAdvancePayment": "Please select the hospital you want to make an advance payment for.",
"recharge": "Recharge",
"recharge": "Add Funds",
"activityLevel": "Activity Level",
"selectActivityLevel": "Select Activity Level",
"almostInactive": "Almost Inactive (no exercise)",
"lightlyActive": "Lightly active",
"lightlyActive13Days": "Lightly active (1-3) days per week",
"superActive": "Super active (very hard exercise)",
"caloriesPerDay": "Calories Per Day",
"dietType": "Diet Type",
"selectDietType": "Select Diet Type",
"bodyFrameSize": "Body Frame Size",
"selectBodyFrameSize": "Select Body Frame Size",
"bodyFrameSmall": "Small (fingers overlaps)",
"bodyFrameMedium": "Medium (fingers touch)",
"bodyFrameLarge": "Large (fingers don't touch)",
"averageCycleLength": "Average Cycle Length (Usually 28 days)",
"averageLutealPhase": "Average Luteal Phase Length(Usually 14 days)",
"convert": "Convert",
@ -1162,14 +1180,22 @@
"monitorCholesterolLevels": "Monitor cholesterol levels, assess heart health risks, and take proactive steps for well-being.",
"triglyceridesFatBlood": "Triglycerides Fat Blood",
"understandTriglyceridesImpact": "Understand triglycerides' impact on heart health with personalized insights and expert recommendations.",
"bmiCalculator": "BMI\nCalculator",
"caloriesCalculator": "Calories\nCalculator",
"bmrCalculator": "BMR\nCalculator",
"idealBodyWeight": "Ideal Body\nWeight",
"bodyFatCalculator": "Body Fat\nCalculator",
"carbsProteinFat": "Carbs\nProtein & Fat",
"ovulationPeriod": "Ovulation\nPeriod",
"deliveryDueDate": "Delivery\nDue Date",
"bmiCalculator": "BMI Calculator",
"bmiCalculatorDesc": "Calculate your body mass index to understand your healthy weight range.",
"caloriesCalculator": "Calories Calculator",
"caloriesCalculatorDesc": "Find out how many calories you need daily to maintain or lose weight.",
"bmrCalculator": "BMR Calculator",
"bmrCalculatorDesc": "Determine your basal metabolic rate and daily energy requirements.",
"idealBodyWeight": "Ideal Body Weight",
"idealBodyWeightDesc": "Discover your ideal weight based on height, age, and body structure.",
"bodyFatCalculator": "Body Fat Calculator",
"bodyFatCalculatorDesc": "Estimate your body fat percentage to track fitness and health goals.",
"carbsProteinFat": "Carbs Protein & Fat",
"carbsProteinFatDesc": "Balance your macros with the right carbs, protein, and fat ratios.",
"ovulationPeriod": "Ovulation Period",
"ovulationPeriodDesc": "Track your fertile window and plan for conception with accuracy.",
"deliveryDueDate": "Delivery Due Date",
"deliveryDueDateDesc": "Calculate your expected delivery date and monitor pregnancy milestones.",
"low": "Low",
"preDiabetic": "Pre-diabetic",
"high": "High",
@ -1200,7 +1226,7 @@
"healthTrackers": "Health Trackers",
"monitorBloodPressureLevels": "Monitor your blood pressure levels, track systolic and diastolic readings, and maintain a healthy heart.",
"trackWeightProgress": "Track your weight progress, set goals, and maintain a healthy body mass for overall wellness.",
"bookAppointment": "Book\nAppointment",
"bookAppointment": "New\nAppointment",
"completeCheckup": "Complete Checkup",
"indoorNavigation": "Indoor Navigation",
"eReferralServices": "E-Referral Services",
@ -1210,10 +1236,11 @@
"healthConvertersServices": "Health\nConverters",
"smartWatchesServices": "Smart\nWatches",
"exploreServices": "Explore Services",
"medicalAndCareServices": "Medical & Care Services",
"medicalAndCareServices": "Medical & Healthcare Services",
"hmgServices": "HMG Services",
"personalServices": "Personal Services",
"habibWallet": "Habib Wallet",
"habibWallet": "HMG Wallet",
"habibWallet2": "Habib Wallet",
"loginToViewWalletBalance": "Login to view your wallet balance",
"loginToViewMedicalFile": "Login to view your medical file",
"addMember": "Add Member",
@ -1245,8 +1272,8 @@
"stayHydratedDrinkWater": "Stay hydrated! Drink {amount}ml of water.",
"visitPharmacyOnline": "Visit Pharmacy Online",
"howAreYouFeelingToday": "How are you feeling today?",
"checkYourSymptomsWithScale": "Check your symptoms with this scale",
"checkYourSymptoms": "Check your symptoms",
"checkYourSymptomsWithScale": "Evaluate your symptoms and find the right care",
"checkYourSymptoms": "Symptom Checker",
"noUpcomingAppointmentPleaseBook": "You do not have any upcoming appointment. Please book an appointment",
"youHaveEROnlineCheckInRequest": "You have ER Online Check-In Request",
"quickLinks": "Quick Links",
@ -1258,9 +1285,9 @@
"requestedServices": "Requested Services",
"selectServices": "Select Services",
"selectedServices": "Selected Services",
"createNewRequest": "Create new request",
"youHaveNoPendingRequests": "You have no pending requests.",
"noInsuranceDataFound": "No insurance data found...",
"createNewRequest": "New Request",
"youHaveNoPendingRequests": "You have no pending requests",
"noInsuranceinsurance2DataFound": "No insurance data found...",
"noInsuranceUpdateRequest": "No insurance update requests found.",
"policyNumberInsurancePage": "Policy: {number}",
"insuranceExpired": "Insurance Expired",
@ -1278,7 +1305,7 @@
"lastTested": "Last Tested",
"byVisit": "By Visit",
"byTest": "By Test",
"results": "results",
"results": "Results",
"viewResults": "View Results",
"rebook": "Rebook",
"noOphthalmologyAppointments": "No Ophthalmology appointments found...",
@ -1293,7 +1320,7 @@
"others": "Others",
"allergyInfo": "Allergy Info",
"vaccineInfo": "Vaccine Info",
"updateInsuranceInfo": "Update Insurance",
"updateInsuranceInfo": "Insurance Update",
"myInvoicesList": "My Invoices List",
"ancillaryOrdersList": "Ancillary Orders List",
"youDontHaveAnySickLeavesYet": "You don't have any sick leaves yet.",
@ -1303,6 +1330,7 @@
"askYourDoctor": "Ask Your Doctor",
"internetPairing": "Internet Pairing",
"requested": "Requested",
"readyToDownload": "Ready to download",
"youDontHaveAnyMedicalReportsYet": "You don't have any medical reports yet.",
"requestMedicalReport": "Request medical report",
"youDoNotHaveAnyAppointmentsToRequestMedicalReport": "You do not have any appointments to request a medical report.",
@ -1344,7 +1372,7 @@
"downloadPrescription": "Download Prescription",
"fetchingPrescriptionDetails": "Fetching prescription details...",
"switchBackFamilyFile": "Switch Back To Family File",
"profileAndSettings": "Profile & Settings",
"profileAndSettings": "Settings",
"quickActions": "Quick Actions",
"notificationsSettings": "Notifications Settings",
"touchIDFaceIDServices": "Touch ID / Face ID Services",
@ -1448,6 +1476,8 @@
"setTimerOfReminder": "Set the timer of reminder",
"youHaveAppointmentWithDr": "You have appointment with Dr. ",
"hours": "Hours",
"minutes": "Minutes",
"seconds": "Seconds",
"secs": "Secs",
"noAllergiesDataFound": "No allergies data found...",
"heartRateDescription": "Your heart rate indicates how many times your heart beats per minute",
@ -1457,6 +1487,7 @@
"distanceDescription": "Distance covered throughout the day",
"overview": "Overview",
"details": "Details",
"details2": "Details",
"healthy": "Healthy",
"warning": "Warning",
"vitalSignDetails": "Vital Sign Details",
@ -1469,7 +1500,7 @@
"heightDescription": "Height is measured in centimeters and is used to calculate BMI and dosage recommendations.",
"weightDescription": "Weight helps track overall health, nutrition, and changes over time.",
"bloodPressureDescription": "Blood pressure reflects the force of blood against artery walls. It is shown as systolic/diastolic.",
"temperatureDescription": "Body temperature reflects how hot your body is and may change with infection or inflammation.",
"temperatureDescription": "Body temperature is a vital sign that indicates how warm the body is. It may increase in response to immune triggers like infection or inflammation",
"heartRateDescriptionVital": "Heart rate refers to the number of heart beats per minute.",
"respiratoryRateDescription": "Respiratory rate is the number of breaths taken per minute.",
"bmiAdvice": "Maintain a balanced diet and regular activity. If your BMI is high or low, consider consulting your doctor.",
@ -1509,7 +1540,7 @@
"route": "Route",
"frequency": "Frequency",
"reminders": "Reminders",
"reminderAddedToCalendar": "Reminder added to calendar",
"reminderAddedToCalendar": "Reminder added to calendar",
"errorWhileSettingCalendar": "Error while setting calendar: {error}",
"instructions": "Instructions",
"requests": "Requests",
@ -1522,18 +1553,18 @@
"pleaseVisitRoomToTheDoctor": "Please visit Room {roomNumber} to the Doctor",
"erOnlineCheckInRequest": "ER Online Check-In Request",
"indoor": "Indoor",
"navigation": "Navigation",
"thehospital": "Navigation",
"health": "Health",
"calculators": "Calculators",
"converters": "Converters",
"guide": "Guide",
"liveCareServiceDesc": "Get medical advice with a specialist doctor Via a video call",
"liveCareServiceDesc": "Get medical advice from a specialist doctor via video call",
"bloodGroup": "Blood: {bloodType}",
"medicalServices": "Medical Services",
"insuranceAndPayments": "Insurance & Payments",
"trackerAndOthers": "Tracker & Others",
"bmi": "BMI",
"yesPleaseINeedHelp": "Yes, Please I need help",
"yesPleaseINeedHelp": "Yes, Please",
"confirmYourLocation": "Confirm Your Location",
"confirmYourLocationDesc": "Please confirm your present location to view appropriate directions.",
"insideHospital": "I am inside the hospital",
@ -1543,7 +1574,7 @@
"servicePriceListRights": "The patient has the right to a free follow-up within 14 days of initial visit",
"serviceName": "Service Name",
"noFreeSlot": "No free slot available for the selected date. Please select another date or check back later.",
"noThanksIKnowTheClinic": "No, Thanks. I Know the clinic",
"noThanksIKnowTheClinic": "No, Thanks",
"unableToSendOTP": "Unable to send Verification OTP",
"aiOverView": "AI Overview",
"generateAiAnalysis": "Generate AI analysis for this order",
@ -1551,7 +1582,7 @@
"prediction": "Prediction",
"prevention": "Prevention",
"disclaimer": "Disclaimer",
"thisAboveInfo": "The above information is generated by AI based on your lab results and is intended for informational purposes only. It should not be considered a medical diagnosis or treatment plan. Always consult with your healthcare provider for personalized medical advice and before making any health-related decisions.",
"thisAboveInfo": "This Lab result was analyzed by AI, and it is not medical advice. Consult your healthcare provider for diagnosis and treatment.",
"aiDisclaimer": "Your lab result data will be securely shared with our AI Analyzer for analysis. This helps provide personalized health insights. Do you want to proceed?",
"consent": "Consent",
"bmiNormalDescription": "Your BMI is within the healthy range. Continue a balanced diet, regular physical activity, and routine checkups to maintain good health. Monitor any significant changes over time.",
@ -1613,13 +1644,13 @@
"darkMode": "Dark Mode",
"generateAiAnalysisResult": "Generate AI analysis for this result",
"ratings": "Ratings",
"hmgPharmacyText": "Al Habib Pharmacy, the complete online Pharmaceutical store brought to you by Dr. Sulaiman Al Habib Medical Services Group.",
"hmgPharmacyText": "Online pharmacy for a complete health shopping experience",
"insuranceRequestSubmittedSuccessfully": "Your insurance update request has been successfully submitted. You will be notified once completed.",
"updatingEmailAddress": "Updating email address, Please wait...",
"verifyInsurance": "Verify Insurance",
"tests": "tests",
"calendarPermissionAlert": "Please grant calendar access permission from app settings to set medication reminder.",
"timeForFirstReminder": "Time for 1st reminder",
"timeForFirstReminder": "Add Reminder",
"reminderRemovalNote": "You can remove it from your calendar later by switching off the reminder",
"sortByLocation": "Sort by location",
"sortByNearestLocation": "Sort by nearest to your location",
@ -1639,22 +1670,22 @@
"smartWatchServiceDescription": "Seamlessly connect your smartwatch to our health app for effortless data tracking and personalized insights.",
"liveChatServiceHeader": "24/7 Expert\nAssistance",
"liveChatServiceDescription": "Need help ? Our live chat connects you with expert support for any questions about features, settings, or troubleshooting.",
"emergencyServiceHeader": "ER Check-in, Faster Than Ever. Call ambulance / Rapid Response Team instantly",
"emergencyServiceDescription": "In a medical emergency? Our ambulances and rapid response teams are on standby 24/7. Plus, quick ER check-in for faster care.",
"homeHealthCareServiceHeader": "Your Health, Elevated. Premium care, delivered to your doorstep.",
"homeHealthCareServiceDescription": "We bring quality healthcare to your doorstep. Experienced nurses providing compassionate care in the comfort of your home.",
"emergencyServiceHeader": "Emergency Care, Faster than Ever.",
"emergencyServiceDescription": "Facing a medical emergency? Our ambulances are on standby 24/7 for your immediate assistance.",
"homeHealthCareServiceHeader": "Premium care, delivered right to your doorstep",
"homeHealthCareServiceDescription": "We provide the highest degree of patient care at home in a value-oriented, efficient, and professional manner.",
"profileOnlyText": "Profile",
"information": "Information",
"noFavouriteDoctors": "You don't have any favourite list yet",
"noFavouriteDoctors": "You don't have any favorite list yet",
"addDoctors": "Add Doctors",
"favouriteList": "Favourite List",
"favouriteList": "Favorite List",
"later": "Later",
"cancelAppointmentConfirmMessage": "Are you sure you want to cancel this appointment?",
"acknowledged": "Acknowledged",
"searchLabResults": "Search lab results",
"callForAssistance": "Call for immediate assistance",
"oneWaySubtitle": "Pickup from location to hospital",
"twoWaySubtitle": "Round trip from location to hospital and back",
"twoWaySubtitle": "Round trip from location to hospital",
"toHospitalSubtitle": "Transfer from your current location to the hospital",
"fromHospitalSubtitle": "Transfer from the hospital back to your home",
"clinicSchedule": "Clinic Schedule",
@ -1682,7 +1713,7 @@
"overWeight": "OverWeight",
"underWeight": "UnderWeight",
"nearestGate": "Nearest Gate",
"RRTDDetails": "The Rapid response team provides a comprehensive medical service for all sorts of urgent and stable cases of all ages, including adult and paediatric patients.",
"RRTDDetails": "Comprehensive medical services for stable and urgent cases, available for all ages.",
"fromHospitalSubtitle": "Transfer from the hospital back to your home",
"normalMapType": "Default",
"satelliteMapType": "Satellite",
@ -1712,5 +1743,48 @@
"aiDisclaimerRad": "Your radiology result data will be securely shared with our AI Analyzer for analysis. This helps provide personalized health insights. Do you want to proceed?",
"sendingAskDrRequest": "Sending Request...",
"askDrRequestSentSuccess": "Request has been sent successfully, The doctor will get back to you soon.",
"onlineCheckIn24HoursOnly": "Online Check-In is only available within 4 hours of the appointment time."
"onlineCheckIn24HoursOnly": "Online Check-In is only available within 4 hours of the appointment time.",
"searchFor": "Search For",
"m": "m",
"inch": "in",
"updateEmailAddressTitle": "Enter the new email address to be updated: ",
"enterNewNewContactNumber": "Enter the new emergency contact number to be updated: ",
"nationalIdFileNumber": "National ID/File Number",
"finda": "Find a",
"calculateCaloriesBasedOnFactors": "Calculates daily calorie intake based on several factors including: height, weight, age, gender and daily physical activity.",
"insurance2": "Insurance",
"conformationCall": "You will receive a call from HMG for confirmation ",
"radiologyLabResults": "Radiology Results",
"disclaimerRadiology": "This radiology result was analyzed by AI, and it is not medical advice. Consult your healthcare provider for diagnosis and treatment.",
"enterIdentificationNumberWithNoStar": "Enter Identification Number",
"enterReferralNumber": "Enter Referral Number",
"referralNumber": "Referral Number",
"phoneHint": "5xxxxxxxx",
"selectSearchCriteria": "Select the Search Criteria",
"selectCriteria": "Select Criteria",
"locationServicesDisabled": "Weather is unavailable because location services are disabled. Please enable location services to see the weather information.",
"selectDateTimeKey": "Select",
"carbsAlert":"Calculate carbohydrate based protein and fat ration in calories and grams according to a pre set ratio.",
"bodyFatAlert": "Estimate the total body fat based on the size.",
"ibwAlert": "Calculates the ideal body weight based on height, weight and body size.",
"glucoseAlert":"Convert blood glucose values between mg/dL and mmol/L. (1 mmol/L ≈ 18.0182 mg/dL)",
"selectBloodGroup": "Select Blood Group",
"routePrescription": "Route",
"noNearestER": "No nearest Er Around you",
"loginToUseServiceLiveCare": "Log In to Use This Service",
"medicalKey": "Medical",
"convertBloodcholesterolInfo": "Convert blood cholesterol values between mg/dL and mmol/L. ",
"mldl": "مليجرام لكل ديسيلتر",
"mmol": "ملي مول لكل لتر",
"triglycerideInfo": "Convert triglyceride values between mg/dL and mmol/L.",
"selectSmartWatch": "Select",
"selectBloodDonationCity": "Select City",
"selectBloodDonationGender": "Select Gender",
"selectBloodType": "Select Blood Type",
"doctors2": "Doctor",
"illurgyInfomation": "Allergy",
"vaccineInfomation": "Vaccine",
"approvals1": "Approvals"
}

@ -172,6 +172,7 @@ class ApiClientImp implements ApiClient {
// body['VersionID'] = ApiConsts.appVersionID.toString();
if (!isExternal) {
body['VersionID'] = ApiConsts.appVersionID.toString();
if(!isRCService)
body['Channel'] = ApiConsts.appChannelId.toString();
body['IPAdress'] = ApiConsts.appIpAddress;
body['generalid'] = ApiConsts.appGeneralId;

@ -189,6 +189,7 @@ class ApiConsts {
static final createEReferral = "Services/Patients.svc/REST/CreateEReferral";
static final getEReferrals = "Services/Patients.svc/REST/GetEReferrals";
//WATER CONSUMPTION
static String h2oGetUserProgress = "Services/H2ORemainder.svc/REST/H2O_GetUserProgress";
static String h2oInsertUserActivity = "Services/H2ORemainder.svc/REST/H2O_InsertUserActivity";
@ -205,6 +206,9 @@ class ApiConsts {
static String updateDiabeticResult = 'Services/Patients.svc/REST/Patient_UpdateDiabtecResult';
static String deactivateDiabeticStatus = 'Services/Patients.svc/REST/Patient_DeactivateDiabeticStatus';
static String sendAverageBloodSugarReport = 'Services/Notifications.svc/REST/SendAverageBloodSugarReport';
static String averageBloodSugarReportDownload = 'Services/Notifications.svc/REST/SendAverageBloodSugarReportDownload';
static String averageWeightReportDownload = 'Services/Notifications.svc/REST/SendAverageBodyWeightReportDownload';
static String averageBloodPressureReportDownload = 'Services/Notifications.svc/REST/SendAverageBloodPressureReportDownload';
// Blood Pressure
static String getBloodPressureResultAverage = 'Services/Patients.svc/REST/Patient_GetBloodPressureResultAverage';

@ -17,6 +17,9 @@ class AppAssets {
static const String email = '$svgBasePath/email.svg';
static const String globe = '$svgBasePath/globe.svg';
static const String globeOther = '$svgBasePath/globe_other.svg';
static const String document = '$svgBasePath/document.svg';
static const String report = '$svgBasePath/report.svg';
static const String sucess = '$svgBasePath/sucess.svg';
// static const String globeOther = '$svgBasePath/globe_other.svg';
static const String cancel = '$svgBasePath/cancel.svg';
@ -119,6 +122,8 @@ class AppAssets {
static const String search_by_region_icon = '$svgBasePath/search_by_region_icon.svg';
static const String location_red = '$svgBasePath/location_red.svg';
static const String location_unavailable = '$svgBasePath/location_unavailable.svg';
static const String location_unavailable_filled = '$svgBasePath/location_unavailable_filled.svg';
static const String location_unavailable_filled_background = '$svgBasePath/location_unavailable_filled_background.svg';
static const String livecare_clinic_icon = '$svgBasePath/livecare_clinic_icon.svg';
static const String immediate_service_icon = '$svgBasePath/immediate_service_icon.svg';
static const String no_visit_icon = '$svgBasePath/no_visit_icon.svg';
@ -363,6 +368,14 @@ class AppAssets {
static const String files = '$svgBasePath/files.svg';
static const String online_checkin_icon = '$svgBasePath/online_checkin_icon.svg';
//Health Calculators
static const String bodyfatcalc = '$svgBasePath/bfc.svg';
static const String duedatecalc = '$svgBasePath/ddc.svg';
static const String idealbodyweightcacl = '$svgBasePath/ibwc.svg';
static const String ovluationperiod = '$svgBasePath/op.svg';
static const String bmicalc = '$svgBasePath/bmi1.svg';
static const String bmrcalc = '$svgBasePath/bmr1.svg';
// PNGS //
static const String hmgLogo = '$pngBasePath/hmg_logo.png';
static const String liveCareService = '$pngBasePath/livecare_service.png';
@ -391,7 +404,7 @@ class AppAssets {
static const String lockIcon = '$pngBasePath/lock-icon.png';
static const String dummyUser = '$pngBasePath/dummy_user.png';
static const String comprehensiveCheckupEn = '$pngBasePath/cc_en.png';
static const String comprehensiveCheckupAr = '$pngBasePath/cc_er.png';
static const String comprehensiveCheckupAr = '$pngBasePath/cc_ar.png';
static const String maleIcon = '$pngBasePath/male_icon.png';
static const String femaleIcon = '$pngBasePath/female_icon.png';
@ -400,7 +413,13 @@ class AppAssets {
static const String fullBodyFrontFemale = '$pngBasePath/full_body_front_female.png';
static const String fullBodyBackFemale = '$pngBasePath/full_body_back_female.png';
static const String bmiFullBody = '$pngBasePath/bmi_image_1.png';
static const String bmiFullBodyFemale = '$pngBasePath/female_bmi.png';
static const String defaultMEP = '$pngBasePath/default_MEP.png';
static const String ambulance_section = '$pngBasePath/ambulance_section.png';
static const String rrt_section = '$pngBasePath/rrt_section.png';
}
class AppAnimations {

@ -94,24 +94,33 @@ class AppState {
String? get getProfileImageData => _profileImageData;
set setProfileImageData(String? value) {
// Clear old data first
if (_profileImageData != null && _profileImageData != value) {
print('🔄 Replacing profile image: old data length=${_profileImageData!.length}, new data length=${value?.length ?? 0}');
}
// Set new value directly WITHOUT caching
_profileImageData = value;
// Persist to cache
if (value != null && value.isNotEmpty) {
cacheService.saveString(key: _profileImageKey, value: value);
print('✅ Profile image set (length: ${value.length}) - NO CACHE');
} else {
cacheService.remove(key: _profileImageKey);
print('🗑️ Profile image cleared - NO CACHE');
}
}
/// Load profile image from cache on app initialization
/// Load profile image from cache on app initialization - DISABLED
void _loadProfileImageFromCache() {
_profileImageData = cacheService.getString(key: _profileImageKey);
// DO NOT LOAD FROM CACHE - always fetch fresh from API
_profileImageData = null;
print('📂 Profile image cache disabled - will fetch from API');
}
/// Clear profile image from cache (e.g., on logout)
/// Clear profile image data (e.g., on logout or user switch)
void clearProfileImageCache() {
print('🧹 Clearing profile image data (was: ${_profileImageData != null ? "${_profileImageData!.length} chars" : "null"})');
_profileImageData = null;
cacheService.remove(key: _profileImageKey);
print('✅ Profile image data cleared - NO CACHE');
}
SelectDeviceByImeiRespModelElement? _selectDeviceByImeiRespModelElement;

@ -78,5 +78,6 @@ class CacheConst {
static const String isMonthlyReportEnabled = 'is-monthly-report-enabled';
static const String zoomRoomID = 'zoom-room-id';
static const String callTypeID = 'call-type-id';
static String isAppOpenedFromCall = "is_app_opened_from_call";
}

@ -64,6 +64,8 @@ import 'package:hmg_patient_app_new/features/todo_section/todo_section_repo.dart
import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart';
import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_repo.dart';
import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart';
import 'package:hmg_patient_app_new/features/weather/weather_repo.dart';
import 'package:hmg_patient_app_new/features/weather/weather_view_model.dart';
import 'package:hmg_patient_app_new/features/health_trackers/health_trackers_view_model.dart';
import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart';
@ -74,6 +76,7 @@ import 'package:hmg_patient_app_new/services/localauth_service.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/services/notification_service.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_calendar_model.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart';
import 'package:local_auth/local_auth.dart';
import 'package:logger/web.dart';
@ -131,7 +134,6 @@ class AppDependencies {
navigationService: getIt(),
));
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
getIt.registerLazySingleton<NotificationService>(() => NotificationServiceImp(
flutterLocalNotificationsPlugin: flutterLocalNotificationsPlugin,
@ -164,13 +166,18 @@ 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<WeatherRepo>(() => WeatherRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<MyInvoicesRepo>(() => MyInvoicesRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<HealthTrackersRepo>(() => HealthTrackersRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<MonthlyReportRepo>(() => MonthlyReportRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<ActivePrescriptionsRepo>(() => ActivePrescriptionsRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<TermsConditionsRepo>(() => TermsConditionsRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerFactory<TermsConditionsViewModel>(() => TermsConditionsViewModel(termsConditionsRepo: getIt<TermsConditionsRepo>(), errorHandlerService: getIt<ErrorHandlerService>(),
),);
getIt.registerFactory<TermsConditionsViewModel>(
() => TermsConditionsViewModel(
termsConditionsRepo: getIt<TermsConditionsRepo>(),
errorHandlerService: getIt<ErrorHandlerService>(),
),
);
getIt.registerLazySingleton<MonthlyReportsRepo>(() => MonthlyReportsRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<QrParkingRepo>(() => QrParkingRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<NotificationsRepo>(() => NotificationsRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
@ -230,10 +237,10 @@ class AppDependencies {
);
getIt.registerLazySingleton<ProfileSettingsViewModel>(() => ProfileSettingsViewModel(
cacheService: getIt<CacheService>(),
profileSettingsRepo: getIt<ProfileSettingsRepo>(),
errorHandlerService: getIt<ErrorHandlerService>(),
));
cacheService: getIt<CacheService>(),
profileSettingsRepo: getIt<ProfileSettingsRepo>(),
errorHandlerService: getIt<ErrorHandlerService>(),
));
getIt.registerLazySingleton<DateRangeSelectorRangeViewModel>(() => DateRangeSelectorRangeViewModel());
@ -299,6 +306,8 @@ class AppDependencies {
getIt.registerLazySingleton<WaterMonitorViewModel>(() => WaterMonitorViewModel(waterMonitorRepo: getIt(), errorHandlerService: getIt()));
getIt.registerLazySingleton<WeatherMonitorViewModel>(() => WeatherMonitorViewModel(weatherRepo: getIt(), errorHandlerService: getIt(), locationUtils: getIt()));
//commenting this because its already define there was on run time error because of this.
getIt.registerLazySingleton<MyInvoicesViewModel>(() => MyInvoicesViewModel(myInvoicesRepo: getIt(), errorHandlerService: getIt(), navServices: getIt()));
@ -307,22 +316,12 @@ class AppDependencies {
getIt.registerLazySingleton<NotificationsViewModel>(() => NotificationsViewModel(notificationsRepo: getIt(), errorHandlerService: getIt()));
getIt.registerLazySingleton<HealthTrackersViewModel>(() => HealthTrackersViewModel(healthTrackersRepo: getIt(), errorHandlerService: getIt()));
getIt.registerLazySingleton<ActivePrescriptionsViewModel>(
() => ActivePrescriptionsViewModel(
errorHandlerService: getIt(),
activePrescriptionsRepo: getIt()
),
);
getIt.registerFactory<QrParkingViewModel>(
() => QrParkingViewModel(
qrParkingRepo: getIt<QrParkingRepo>(),
errorHandlerService: getIt<ErrorHandlerService>(),
cacheService: getIt<CacheService>(),
),
);
getIt.registerLazySingleton<ActivePrescriptionsViewModel>(() => ActivePrescriptionsViewModel(errorHandlerService: getIt(), activePrescriptionsRepo: getIt()));
getIt.registerFactory<QrParkingViewModel>(() => QrParkingViewModel(qrParkingRepo: getIt<QrParkingRepo>(), errorHandlerService: getIt<ErrorHandlerService>(), cacheService: getIt<CacheService>()));
getIt.registerLazySingleton<AskDoctorViewModel>(() => AskDoctorViewModel(askDoctorRepo: getIt(), errorHandlerService: getIt()));
getIt.registerLazySingleton<ServicesPriceListViewModel>(() => ServicesPriceListViewModel(servicesPriceListRepo: getIt(), errorHandlerService: getIt()));
getIt.registerLazySingleton<DateRangCalenderModel>(() => DateRangCalenderModel(appState: getIt(), navigationService: getIt(), dialogService: getIt()));
}
}

@ -5,8 +5,11 @@
// unverified,
// }
import 'package:easy_localization/easy_localization.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/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
enum AuthMethodTypesEnum { sms, whatsApp, fingerPrint, faceID, moreOptions }
@ -20,6 +23,17 @@ enum SelectionTypeEnum { dropdown, calendar, search, time }
enum GenderTypeEnum { male, female }
extension GenderTypeEnumExtension on GenderTypeEnum {
String get displayName {
switch (this) {
case GenderTypeEnum.male:
return LocaleKeys.malE.tr();
case GenderTypeEnum.female:
return LocaleKeys.femaleGender.tr();
}
}
}
enum MaritalStatusTypeEnum { single, married, divorced, widowed }
enum ChipTypeEnum { success, error, alert, info, warning, lightBg, primaryRed }
@ -58,17 +72,17 @@ extension HealthCalculatorExtenshion on HealthCalculatorsTypeEnum {
bool isArabic = appState.getLanguageID() == 1 ? true : false;
switch (this) {
case HealthCalculatorsTypeEnum.bmi:
return isArabic ? "حاسبة مؤشر كتلة الجسم" : "BMI Calculator";
return isArabic ? "مؤشر كتلة الجسم" : "BMI Calculator";
case HealthCalculatorsTypeEnum.calories:
return isArabic ? "حاسبة السعرات الحرارية" : "Calories Calculator";
return isArabic ? "السعرات الحرارية" : "Calories Calculator";
case HealthCalculatorsTypeEnum.bmr:
return isArabic ? "حاسبة معدل الأيض الأساسي" : "BMR Calculator";
return isArabic ? "معدل الايض" : "BMR Calculator";
case HealthCalculatorsTypeEnum.idealBodyWeight:
return isArabic ? "الوزن المثالي للجسم" : "Ideal Body Weight Calculator";
return isArabic ? "الوزن المثالي" : "Ideal Body Weight Calculator";
case HealthCalculatorsTypeEnum.bodyFat:
return isArabic ? "حاسبة الدهون في الجسم" : "Body Fat Calculator";
return isArabic ? "دهون الجسم" : "Body Fat Calculator";
case HealthCalculatorsTypeEnum.crabsProteinFat:
return isArabic ? "حاسبة البروتين والدهون في سرطان البحر" : "Crabs Protein & Fat Calculator";
return isArabic ? "حاسبة المغذيات" : "Crabs Protein & Fat Calculator";
case HealthCalculatorsTypeEnum.ovulation:
return isArabic ? "فترة الإباضة" : "Ovulation Period";
case HealthCalculatorsTypeEnum.deliveryDueDate:
@ -80,6 +94,22 @@ extension HealthCalculatorExtenshion on HealthCalculatorsTypeEnum {
case HealthCalculatorsTypeEnum.triglycerides:
return isArabic ? "الدهون الثلاثية في الدم" : "Triglycerides Fat Blood";
}
}
String get title {
AppState appState = getIt.get<AppState>();
bool isArabic = appState.getLanguageID() == 1 ? true : false;
switch (this) {
case HealthCalculatorsTypeEnum.crabsProteinFat:
return isArabic ? "نتيجة المغذيات" : "Nutrient Results";
case HealthCalculatorsTypeEnum.calories:
return isArabic ? "سعراتك الحرارية" : "Calories";
case HealthCalculatorsTypeEnum.calories:
return isArabic ? "مؤشر كتلة الجسم" : "BMI";
default:
return "Your ${this.name.toCamelCase}";
}
}
static LoginTypeEnum? fromValue(int value) {

@ -89,7 +89,7 @@ class LocationUtils {
}
}
LocationPermission permissionGranted = await Geolocator.checkPermission();
LocationPermission permissionGranted = await Geolocator.checkPermission();
if (permissionGranted == LocationPermission.denied) {
permissionGranted = await Geolocator.requestPermission();
if (permissionGranted != LocationPermission.whileInUse && permissionGranted != LocationPermission.always) {
@ -257,6 +257,30 @@ class LocationUtils {
appState.userLong = locationData.longitude;
}
Future<bool> isLocationPermissionEnabled() async {
if (Platform.isIOS || (await isGMSDevice ?? true)) {
return await _isGMSLocationPermissionEnabled();
} else {
return await _isHMSLocationPermissionEnabled();
}
}
Future<bool> _isGMSLocationPermissionEnabled() async {
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) return false;
LocationPermission permission = await Geolocator.checkPermission();
return permission == LocationPermission.whileInUse || permission == LocationPermission.always;
}
Future<bool> _isHMSLocationPermissionEnabled() async {
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) return false;
LocationPermission permission = await Geolocator.checkPermission();
return permission == LocationPermission.whileInUse || permission == LocationPermission.always;
}
void getHMSLocation({VoidCallback? onFailure, Function(LatLng p1)? onSuccess, VoidCallback? onLocationDeniedForever}) async {
try {
var location = Location();

@ -16,33 +16,64 @@ class CalenderUtilsNew {
final DeviceCalendar calender = DeviceCalendar.instance;
List<Calendar> writableCalender = [];
CalenderUtilsNew._instance() {
getCalenders();
}
/// Completer to ensure getCalenders() only runs once at a time
Completer<void>? _calendarInitCompleter;
CalenderUtilsNew._instance();
static final CalenderUtilsNew instance = CalenderUtilsNew._instance();
Future<void> getCalenders() async {
/// Ensures calendars are loaded exactly once. Safe to call multiple times
/// subsequent calls await the same future.
Future<void> ensureCalendarsLoaded() async {
if (writableCalender.isNotEmpty) return;
if (_calendarInitCompleter != null) {
// Already in progress just wait for it
return _calendarInitCompleter!.future;
}
_calendarInitCompleter = Completer<void>();
try {
await _loadCalendars();
_calendarInitCompleter!.complete();
} catch (e) {
_calendarInitCompleter!.completeError(e);
_calendarInitCompleter = null; // Allow retry on failure
rethrow;
}
}
Future<void> _loadCalendars() async {
print("the get calenders is called");
CalendarPermissionStatus result = await DeviceCalendar.instance.hasPermissions();
if (result != CalendarPermissionStatus.granted) {
// await DeviceCalendar.instance.requestPermissions();
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.notice.tr(context: GetIt.instance<NavigationService>().navigatorKey.currentContext!),
GetIt.instance<NavigationService>().navigatorKey.currentContext!,
child: Utils.getWarningWidget(
loadingText: LocaleKeys.calendarPermissionAlert.tr(),
isShowActionButtons: true,
onCancelTap: () {
GetIt.instance<NavigationService>().pop();
},
onConfirmTap: () async {
GetIt.instance<NavigationService>().pop();
openAppSettings();
}),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
await DeviceCalendar.instance.requestPermissions();
result = await DeviceCalendar.instance.hasPermissions();
// if (result != CalendarPermissionStatus.granted) {
// print("the permission is not granted");
// showCommonBottomSheetWithoutHeight(
// title: LocaleKeys.notice.tr(context: GetIt
// .instance<NavigationService>()
// .navigatorKey
// .currentContext!),
// GetIt
// .instance<NavigationService>()
// .navigatorKey
// .currentContext!,
// child: Utils.getWarningWidget(
// loadingText: LocaleKeys.calendarPermissionAlert.tr(),
// isShowActionButtons: true,
// onCancelTap: () {
// GetIt.instance<NavigationService>().pop();
// },
// onConfirmTap: () async {
// GetIt.instance<NavigationService>().pop();
// openAppSettings();
// }),
// callBackFunc: () {},
// isFullScreen: false,
// isCloseButtonVisible: true,
// );
// }
}
var calenders = await calender.listCalendars();
calenders.forEach((calender) {
@ -52,21 +83,13 @@ class CalenderUtilsNew {
});
}
FutureOr<bool> createOrUpdateEvent(
{required String title,
required String description,
required String location,
DateTime? scheduleDateTime,
String? eventId,
int? reminderMinutes}) async {
if (writableCalender.isEmpty) {
await getCalenders();
}
FutureOr<bool> createOrUpdateEvent({required String title, required String description, required String location, DateTime? scheduleDateTime, String? eventId, int? reminderMinutes}) async {
await ensureCalendarsLoaded();
var writableCalendars = writableCalender.first;
String eventResult = "";
CalendarPermissionStatus result = await DeviceCalendar.instance.hasPermissions();
if (result != CalendarPermissionStatus.granted) await DeviceCalendar.instance.requestPermissions();
print(result);
// CalendarPermissionStatus result = await DeviceCalendar.instance.hasPermissions();
// if (result != CalendarPermissionStatus.granted) await DeviceCalendar.instance.requestPermissions();
// print(result);
// String eventId = await getEventIdIfEventExist(title!.split("#").last);
// if (eventId.isEmpty) {
@ -93,19 +116,19 @@ class CalenderUtilsNew {
// return eventId.isNotEmpty;
}
FutureOr<bool> createMultipleEvents(
{required int reminderMinutes,
int? frequencyNumber,
required int days,
required String orderDate,
required String itemDescriptionN,
required String route,
Function(String)? onFailure,
String? prescriptionNumber,
DateTime? scheduleDateTime,
}) async {
FutureOr<bool> createMultipleEvents({
required int reminderMinutes,
int? frequencyNumber,
required int days,
required String orderDate,
required String itemDescriptionN,
required String route,
Function(String)? onFailure,
String? prescriptionNumber,
DateTime? scheduleDateTime,
}) async {
DateTime currentDay = DateTime.now();
DateTime actualDate = scheduleDateTime??DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day, 8, 0);
DateTime actualDate = scheduleDateTime ?? DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day, 8, 0);
print("the frequency is $frequencyNumber");
frequencyNumber ??= 2; //Some time frequency number is null so by default will be 2
int interval = calculateIntervalAsPerFrequency(frequencyNumber);
@ -152,9 +175,7 @@ class CalenderUtilsNew {
}
FutureOr<bool> checkIfEventExist(String admissionId) async {
if (writableCalender.isEmpty) {
await getCalenders();
}
await ensureCalendarsLoaded();
List<Event> events = await getEvents();
if (events.isEmpty) return false;
for (var event in events) {
@ -167,7 +188,7 @@ class CalenderUtilsNew {
FutureOr<String> getEventIdIfEventExist(String admissionId) async {
if (writableCalender.isEmpty) {
await getCalenders();
await ensureCalendarsLoaded();
}
List<Event> events = await getEvents();
if (events.isEmpty) return "";
@ -180,7 +201,7 @@ class CalenderUtilsNew {
FutureOr<bool> checkAndRemove({required String id}) async {
if (writableCalender.isEmpty) {
await getCalenders();
await ensureCalendarsLoaded();
}
List<Event> events = await getEvents();
if (events.isEmpty) return false;
@ -197,7 +218,7 @@ class CalenderUtilsNew {
FutureOr<bool> checkAndRemoveMultipleItems({required String id}) async {
if (writableCalender.isEmpty) {
await getCalenders();
await ensureCalendarsLoaded();
}
List<Event> events = await getEvents();
if (events.isEmpty) return false;

@ -599,6 +599,17 @@ class DateUtil {
return '${hours} hr ${minutes} min';
}
/// Parses an ISO 8601 date string that may have more than 6 fractional-second digits.
/// e.g. '2026-04-12T12:07:35.4226413' truncates to 6 digits before parsing.
static DateTime? parseISODate(String? date) {
if (date == null || date.isEmpty) return null;
final truncated = date.replaceFirstMapped(
RegExp(r'(\.\d{6})\d+'),
(m) => m.group(1)!,
);
return DateTime.tryParse(truncated);
}
}
extension OnlyDate on DateTime {

@ -9,17 +9,27 @@ import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_callkit_incoming/entities/android_params.dart';
import 'package:flutter_callkit_incoming/entities/call_event.dart';
import 'package:flutter_callkit_incoming/entities/call_kit_params.dart';
import 'package:flutter_callkit_incoming/entities/ios_params.dart';
import 'package:flutter_callkit_incoming/entities/notification_params.dart';
import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart';
import 'package:flutter_ios_voip_kit_karmm/call_state_type.dart';
import 'package:flutter_ios_voip_kit_karmm/flutter_ios_voip_kit.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/cache_consts.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/core/api/api_client.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:uuid/uuid.dart';
// Global tracking variables for call state
bool _isCallInProgress = false;
String? _currentCallId;
Timer? _callTimeoutTimer;
String? _currentSessionId; // Store session ID for API call
// |--> Push Notification Background
@pragma('vm:entry-point')
Future<dynamic> backgroundMessageHandler(dynamic message) async {
@ -39,30 +49,37 @@ callPage(String sessionID, String token) async {}
_incomingCall(Map<String, dynamic> data) async {
print('the value of the _incomingCall remote message is $data');
// LandingPage.incomingCallData = IncomingCallData.fromJson(data);
// var dataItem = await AppSharedPreferences().getObject('call_data');
// if(dataItem != null ) return; // to stop repeated attempt to invoke the call
// if (LandingPage.isOpenCallPage == false) {
// LandingPage.isOpenCallPage = true;
// Check if there's already a call in progress to prevent duplicates
if (_isCallInProgress && _currentCallId != null) {
print('⚠️ Call already in progress (ID: $_currentCallId), ignoring duplicate notification');
return;
}
String roomID = data['session_id'] ?? '';
String callTypeID = data['AppointmentNo'] ?? '';
// GetIt.instance<CacheService>().saveString(key: CacheConst.zoomRoomID, value: roomID);
// GetIt.instance<CacheService>().saveBool(key: CacheConst.isAppOpenedFromCall, value: true);
// Generate unique call ID
var _currentUuid = Uuid().v4();
_currentCallId = _currentUuid;
_isCallInProgress = true;
_currentSessionId = roomID; // Store session ID for decline API call
print('📞 Starting new call with ID: $_currentCallId, Session: $_currentSessionId');
Utils.saveStringFromPrefs(CacheConst.zoomRoomID, roomID);
Utils.saveBoolFromPrefs(CacheConst.isAppOpenedFromCall, true);
await Utils.saveStringFromPrefs(CacheConst.zoomRoomID, roomID);
await Utils.saveStringFromPrefs(CacheConst.callTypeID, callTypeID);
await Utils.saveBoolFromPrefs(CacheConst.isAppOpenedFromCall, true);
WidgetsFlutterBinding.ensureInitialized();
var _currentUuid = Uuid().v4();
int callKitType = callTypeID == '2' ? 0 : 1;
CallKitParams callKitParams = CallKitParams(
id: _currentUuid,
nameCaller: "Dr Al Habib",
appName: 'Dr Al Habib',
avatar: 'https://play-lh.googleusercontent.com/FBNNpxb7m6eM6wtW7MV1Ffp6OXOGLI38q47zcvP29OCYA1yhYH5mZzl5itZi0TgOyZpG',
// handle: LandingPage.incomingCallData.name,
type: 1,
type: callKitType,
textAccept: 'Accept',
textDecline: 'Decline',
missedCallNotification: NotificationParams(
@ -78,7 +95,11 @@ _incomingCall(Map<String, dynamic> data) async {
callbackText: 'Hang Up',
),
duration: 30000,
extra: <String, dynamic>{'userId': '1a2b3c4d'},
extra: <String, dynamic>{
'userId': '1a2b3c4d',
'callTypeID': callTypeID,
'roomID': roomID,
},
headers: <String, dynamic>{'apiKey': 'Abc@123!', 'platform': 'flutter'},
android: const AndroidParams(
isImportant: true,
@ -87,10 +108,10 @@ _incomingCall(Map<String, dynamic> data) async {
isShowLogo: false,
logoUrl: "https://play-lh.googleusercontent.com/FBNNpxb7m6eM6wtW7MV1Ffp6OXOGLI38q47zcvP29OCYA1yhYH5mZzl5itZi0TgOyZpG",
ringtonePath: 'system_ringtone_default',
backgroundColor: '#ED1C2B',
backgroundColor: '#2E3039',
backgroundUrl: 'https://saudipedia.com/en/saudipediaen/uploads/images/2024/08/02/97358.jpg',
actionColor: '#4CAF50',
textColor: '#000000',
textColor: '#FFFFFF',
incomingCallNotificationChannelName: "Incoming Call",
missedCallNotificationChannelName: "Missed Call",
isShowCallID: false),
@ -111,12 +132,89 @@ _incomingCall(Map<String, dynamic> data) async {
ringtonePath: 'system_ringtone_default',
),
);
FlutterCallkitIncoming.unsilenceEvents();
await FlutterCallkitIncoming.showCallkitIncoming(callKitParams);
// Start 60-second timeout timer to auto-dismiss call
_callTimeoutTimer?.cancel(); // Cancel any existing timer
_callTimeoutTimer = Timer(Duration(seconds: 40), () async {
print('⏱️ Call timeout (60 seconds) - auto dismissing call notification');
await _endCallAndCleanup(_currentUuid);
});
await Future.delayed(Duration(milliseconds: 500));
}
// Helper method to end call and cleanup
Future<void> _endCallAndCleanup(String callId, {bool isDeclined = false}) async {
try {
print('🧹 Cleaning up call: $callId');
// If call was declined, update session status via API
if (isDeclined && _currentSessionId != null && _currentSessionId!.isNotEmpty) {
await _updateSessionStatus(_currentSessionId!, 3, 'Patient');
}
// End the specific call
await FlutterCallkitIncoming.endCall(callId);
// Clear all calls to ensure clean state
await FlutterCallkitIncoming.endAllCalls();
// Reset tracking flags
_isCallInProgress = false;
_currentCallId = null;
_currentSessionId = null;
// Cancel timer
_callTimeoutTimer?.cancel();
_callTimeoutTimer = null;
// Clear cache
await Utils.saveBoolFromPrefs(CacheConst.isAppOpenedFromCall, false);
await Utils.saveStringFromPrefs(CacheConst.zoomRoomID, '');
await Utils.saveStringFromPrefs(CacheConst.callTypeID, '');
print('✅ Call and push notification cleanup completed successfully');
} catch (e) {
print('❌ Error during call cleanup: $e');
// Force reset flags even if there's an error
_isCallInProgress = false;
_currentCallId = null;
_currentSessionId = null;
_callTimeoutTimer?.cancel();
_callTimeoutTimer = null;
}
}
// Helper method to update session status via API
Future<void> _updateSessionStatus(String sessionId, int sessionStatus, String sessionEndedBy) async {
try {
final apiClient = getIt.get<ApiClient>();
Map<String, dynamic> requestBody = {
"Open_SessionID": sessionId,
"SessionStatus": sessionStatus,
"SessionEndedBy": sessionEndedBy
};
await apiClient.post(
CHANGE_PATIENT_ER_SESSION,
body: requestBody,
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
print('✅ Session status updated successfully: $response');
},
onFailure: (error, statusCode, {messageStatus, failureType}) {
print('❌ Failed to update session status: $error');
},
);
} catch (e) {
print('❌ Exception updating session status: $e');
}
}
Future<void> openCallPage(BuildContext context) async {
try {
// if (incomingCallData!.background == "0") {
@ -282,6 +380,10 @@ class PushNotificationHandler {
try {
print('🎈 example: onDidRejectIncomingCall $uuid - $callerId');
timeOutTimer.cancel();
// Cleanup on reject with API call
if (_currentCallId != null) {
await _endCallAndCleanup(_currentCallId!, isDeclined: true);
}
} catch (err) {}
};
@ -292,6 +394,12 @@ class PushNotificationHandler {
print('🎈 example: onDidAcceptIncomingCall $uuid - $callerId');
await voIPKit.acceptIncomingCall(callerState: CallStateType.calling);
await voIPKit.callConnected();
// Cleanup on accept - cancel timer and reset flags
_callTimeoutTimer?.cancel();
_callTimeoutTimer = null;
_isCallInProgress = false;
_currentCallId = null;
await Future.delayed(Duration(seconds: 2)).then((val) async {
await voIPKit.endCall();
});

@ -1,7 +1,6 @@
import 'dart:developer';
import 'package:easy_localization/easy_localization.dart';
import 'package:hijri_gregorian_calendar/hijri_gregorian_calendar.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
@ -12,6 +11,7 @@ import 'package:hmg_patient_app_new/features/authentication/models/request_model
import 'package:hmg_patient_app_new/features/authentication/models/request_models/send_activation_request_model.dart';
import 'package:hmg_patient_app_new/features/common/models/commong_authanticated_req_model.dart';
import 'package:hmg_patient_app_new/features/common/models/family_file_request.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_calendar_model.dart';
class RequestUtils {
static dynamic getPatientAuthenticationRequest({

@ -820,6 +820,28 @@ class Utils {
// return Image.asset(icon, width: width, height: height, fit: fit, );
}
static Widget buildImgWithAssetsWithBorderRadius({
required String icon,
Color? iconColor,
bool isDisabled = false,
double? width,
double? height,
BoxFit fit = BoxFit.cover,
double? border,
double? borderRadius,
}) {
final iconH = height ?? 24.h;
final iconW = width ?? 24.w;
return Container(
width: iconW, height: iconH,
decoration: BoxDecoration(
border: border != null ? Border.all(color: AppColors.whiteColor, width: border) : null,
borderRadius: borderRadius != null ? BorderRadius.circular(borderRadius ?? 12.r) : null,
image: DecorationImage(image: AssetImage(icon,), fit: fit)
),
);
}
/// Widget to build an SVG from network
static Widget buildImgWithNetwork({
required String url,

@ -322,8 +322,10 @@ extension EmailValidator on String {
Widget toText32({bool isEnglishOnly = false, FontWeight? weight, Color? color, bool isBold = false, bool isCenter = false}) => Text(
this,
textAlign: isCenter ? TextAlign.center : null,
style: TextStyle(
height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 32.f, letterSpacing: -1, fontFamily: getIt.get<AppState>().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins', fontWeight: isBold ? FontWeight.bold : weight ?? FontWeight.normal),
height: getIt.get<AppState>().getLanguageCode() == "ar" ? 1.2 : 32 / 32, color: color ?? AppColors.blackColor, fontSize: 32.f, letterSpacing: -1, fontFamily: getIt.get<AppState>().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins', fontWeight: isBold ? FontWeight.bold : weight ?? FontWeight.normal,),
);
Widget toText44({Color? color, bool isBold = false, bool isEnglishOnly = false,}) => Text(

@ -6,7 +6,6 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:get_it/get_it.dart';
import 'package:hijri_gregorian_calendar/hijri_gregorian_calendar.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/cache_consts.dart';
@ -42,6 +41,7 @@ import 'package:hmg_patient_app_new/services/dialog_service.dart';
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/date_range_selector/viewmodel/date_range_calendar_model.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';
@ -152,10 +152,12 @@ class AuthenticationViewModel extends ChangeNotifier {
void onDobChange(String? date) {
if (calenderType == CalenderEnum.hijri) {
var hijriDate = HijriGregConverter.gregorianToHijri(DateTime.parse(date!));
// Use DateRangCalenderModel for accurate Umm al-Qura conversion
final calendarModel = DateRangCalenderModel(appState: _appState, navigationService: _errorHandlerService, dialogService: _dialogService);
var hijriDate = calendarModel.gregorianToHijri(DateTime.parse(date!));
DateTime hijriDateTimeForController = DateTime(hijriDate.year, hijriDate.month, hijriDate.day);
dob = Utils.formatDateForApi(date);
dobController.text = Utils.formatHijriDateToDisplay(hijriDateTimeForController.toIso8601String()); // Or directly hijriDate.toString() if that's what your formatter expects
dobController.text = Utils.formatHijriDateToDisplay(hijriDateTimeForController.toIso8601String());
} else {
dobController.text = Utils.formatDateToDisplay(date!);
dob = Utils.formatDateForApi(date);
@ -897,7 +899,13 @@ class AuthenticationViewModel extends ChangeNotifier {
}
final resultEither = await _authenticationRepo.checkPatientForRegistration(commonAuthanticatedRequest: nRequest);
resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async {
resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure, onUnHandledFailure: (failur) {
LoaderBottomSheet.hideLoader();
_dialogService.showExceptionBottomSheet(message: failur.message, onOkPressed: () {
_navigationService.pop();
});
}), (apiResponse) async {
checkUserStatusForRegistration(response: apiResponse.data, request: request);
});
}
@ -907,6 +915,7 @@ class AuthenticationViewModel extends ChangeNotifier {
// LoadingUtils.showFullScreenLoader(loadingText: "Setting up your medical file.\nMay take a moment.");
var request = RequestUtils.getUserSignupCompletionRequest(fullName: nameController.text, emailAddress: emailController.text, gender: genderType, maritalStatus: maritalStatus);
final resultEither = await _authenticationRepo.registerUser(registrationPayloadDataModelRequest: request);
resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async {
if (apiResponse.data is String) {
@ -926,6 +935,8 @@ class AuthenticationViewModel extends ChangeNotifier {
_navigationService.pushAndRemoveUntil(CustomPageRoute(page: LandingNavigation()), (r) => false);
_navigationService.push(CustomPageRoute(page: LoginScreen()));
});
} else {
print("=========");
}
}
});
@ -1109,7 +1120,7 @@ class AuthenticationViewModel extends ChangeNotifier {
_dialogService.showPhoneNumberPickerSheet(
// label: "Where would you like to receive OTP?",
label: LocaleKeys.receiveOtpToast.tr(),
message: "Please select from the below options to receive OTP.",
message: _appState.isArabic() ? "يرجى الاختيار من الخيارات أدناه لاستلام رمز التحقق." "Please select from the below options to receive OTP.",
onSMSPress: () {
checkUserAuthentication(otpTypeEnum: OTPTypeEnum.sms);
},
@ -1240,6 +1251,12 @@ class AuthenticationViewModel extends ChangeNotifier {
try {
log("Fetching profile image for patient ID: $patientID");
// IMPORTANT: Clear old cache BEFORE fetching new image
// This ensures old data is removed immediately
_appState.clearProfileImageCache();
log("🧹 Cleared profile image cache before fetch");
final profileSettingsRepo = GetIt.instance<ProfileSettingsRepo>();
final result = await profileSettingsRepo.getProfileImage(patientID: patientID);
@ -1247,6 +1264,8 @@ class AuthenticationViewModel extends ChangeNotifier {
(failure) {
// Silently fail - profile image is optional
log("Failed to fetch profile image: ${failure.message}");
// Ensure cache is cleared even on failure
_appState.setProfileImageData = null;
},
(apiResponse) {
if (apiResponse.data != null && apiResponse.data['Patient_GetProfileImageDataList'] != null) {
@ -1254,21 +1273,26 @@ class AuthenticationViewModel extends ChangeNotifier {
if (imageList is List && imageList.isNotEmpty) {
String? imageData = imageList[0]['ImageData'];
if (imageData != null && imageData.isNotEmpty) {
// Set new profile image data - this will update cache and notify all listeners
_appState.setProfileImageData = imageData;
log("✅ Profile image loaded and cached successfully");
log("✅ Profile image loaded and cached successfully for patient: $patientID");
} else {
log("⚠️ Profile image data is empty");
log("⚠️ Profile image data is empty for patient: $patientID");
_appState.setProfileImageData = null;
}
} else {
log("⚠️ Profile image list is empty");
log("⚠️ Profile image list is empty for patient: $patientID");
_appState.setProfileImageData = null;
}
} else {
log("⚠️ No profile image data in response");
log("⚠️ No profile image data in response for patient: $patientID");
_appState.setProfileImageData = null;
}
},
);
} catch (e) {
log("❌ Error fetching profile image: $e");
_appState.setProfileImageData = null;
}
}
}

@ -558,9 +558,9 @@ class _OTPVerificationScreenState extends State<OTPVerificationScreen> {
LocaleKeys.via.tr(context: context).toText15(color: AppColors.inputLabelTextColor, letterSpacing: -0.4),
(widget.isFormFamilyFile ? LoginTypeEnum.sms.displayName : authVM.loginTypeEnum.displayName)
.toText15(color: AppColors.inputLabelTextColor, isBold: true, letterSpacing: -0.4),
appState.getUserRegistrationPayload.isRegister != null && appState.getUserRegistrationPayload.isRegister == true
? LocaleKeys.forRegistrationVerification.tr(context: context).toText15(color: AppColors.inputLabelTextColor, letterSpacing: -0.4)
: LocaleKeys.forLoginVerification.tr(context: context).toText15(color: AppColors.inputLabelTextColor, letterSpacing: -0.4),
// appState.getUserRegistrationPayload.isRegister != null && appState.getUserRegistrationPayload.isRegister == true
// ? LocaleKeys.forRegistrationVerification.tr(context: context).toText15(color: AppColors.inputLabelTextColor, letterSpacing: -0.4)
// : LocaleKeys.forLoginVerification.tr(context: context).toText15(color: AppColors.inputLabelTextColor, letterSpacing: -0.4),
],
),

@ -183,7 +183,7 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo {
@override
Future<Either<Failure, GenericApiModel<List<HospitalsModel>>>> getProjectList() async {
Map<String, dynamic> request = {'IsOnlineCheckIn': true, 'IsAdvancePayment': false, 'IsForER': true};
Map<String, dynamic> request = {"IsEROnlineCheckIn":true,'IsOnlineCheckIn': false, 'IsAdvancePayment': false, 'IsForER': true};
try {
GenericApiModel<List<HospitalsModel>>? apiResponse;

@ -54,6 +54,7 @@ import 'package:hmg_patient_app_new/widgets/map/map_utility_screen.dart';
import 'package:hmg_patient_app_new/widgets/order_tracking/order_tracking_state.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:huawei_map/huawei_map.dart' as HMSCameraServices;
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
import '../location/GeocodeResponse.dart';
@ -274,7 +275,6 @@ class EmergencyServicesViewModel extends ChangeNotifier {
void navigateTOAmbulancePage() {
placeValueInController();
getProjects();
flushData();
selectedFacility = FacilitySelection.ALL;
@ -282,6 +282,7 @@ class EmergencyServicesViewModel extends ChangeNotifier {
locationUtils!.getLocation(
isShowConfirmDialog: true,
onSuccess: (position) {
getProjects();
updateBottomSheetState(BottomSheetType.FIXED);
navServices.push(
CustomPageRoute(page: CallAmbulancePage(), direction: AxisDirection.down),
@ -310,17 +311,21 @@ class EmergencyServicesViewModel extends ChangeNotifier {
}
void updateBottomSheetState(BottomSheetType sheetType) {
if (sheetType == BottomSheetType.EXPANDED && selectedHospital == null) {
dialogService.showErrorBottomSheet(message: LocaleKeys.selectHospital.tr());
return;
}
// if (sheetType == BottomSheetType.EXPANDED && selectedHospital == null) {
// dialogService.showErrorBottomSheet(message: LocaleKeys.selectHospital.tr());
// return;
// }
bottomSheetType = sheetType;
if (sheetType == BottomSheetType.EXPANDED) {
clearAppointmentData();
}
// if (sheetType == BottomSheetType.FIXED) {
// clearAppointmentData();
// }
notifyListeners();
}
void showError(String message) {
dialogService.showErrorBottomSheet(message: message);
}
void setIsGMSAvailable(bool value) {
notifyListeners();
}
@ -732,6 +737,10 @@ class EmergencyServicesViewModel extends ChangeNotifier {
}
FutureOr<void> submitAmbulanceRequest(GeocodeResult? result, PlaceDetails? place, PlacePrediction? placePrediction) async {
if (false == haveAnAppointment && null == selectedHospital) {
dialogService.showErrorBottomSheet(message: LocaleKeys.selectHospital.tr());
return;
}
PatientER_RC request = createRequest(result, place, placePrediction);
navServices.push(CustomPageRoute(page: RequestingServicesPage()));
@ -802,19 +811,19 @@ class EmergencyServicesViewModel extends ChangeNotifier {
request.patientERTransportationMethod = selectedTransportOption;
transportationDetails.pickupUrgency = 1;
switch (callingPlace) {
case AmbulanceCallingPlace.FROM_HOSPITAL:
transportationDetails.dropoffLatitude = selectedHospital!.latitude;
transportationDetails.dropoffLongitude = selectedHospital!.longitude;
transportationDetails.dropoffLocationName = selectedHospital!.name;
case AmbulanceCallingPlace.TO_HOSPITAL:
transportationDetails.dropoffLatitude = selectedHospital?.latitude;
transportationDetails.dropoffLongitude = selectedHospital?.longitude;
transportationDetails.dropoffLocationName = selectedHospital?.name;
transportationDetails.pickupLatitude = ((result?.geometry.location.lat) ?? place?.lat)?.toStringAsFixed(6);
transportationDetails.pickupLongitude = ((result?.geometry.location.lng) ?? place?.lng)?.toStringAsFixed(6);
transportationDetails.pickupLocationName = result?.formattedAddress ?? placePrediction?.description;
transportationDetails.direction = 1;
break;
case AmbulanceCallingPlace.TO_HOSPITAL:
transportationDetails.pickupLatitude = selectedHospital!.latitude;
transportationDetails.pickupLongitude = selectedHospital!.longitude;
transportationDetails.pickupLocationName = selectedHospital!.name;
case AmbulanceCallingPlace.FROM_HOSPITAL:
transportationDetails.pickupLatitude = selectedHospital?.latitude;
transportationDetails.pickupLongitude = selectedHospital?.longitude;
transportationDetails.pickupLocationName = selectedHospital?.name;
transportationDetails.dropoffLatitude = ((result?.geometry.location.lat) ?? place?.lat)?.toStringAsFixed(6);
transportationDetails.dropoffLongitude = ((result?.geometry.location.lng) ?? place?.lng)?.toStringAsFixed(6);
transportationDetails.dropoffLocationName = result?.formattedAddress ?? placePrediction?.description;
@ -986,19 +995,47 @@ class EmergencyServicesViewModel extends ChangeNotifier {
this.currentlyDisplayedOrder = currentlyDisplayedOrder;
switch (currentlyDisplayedOrder) {
case OrderDislpay.ALL:
orderDisplayList = allOrders;
orderDisplayList = List.from(allOrders);
break;
case OrderDislpay.RRT:
orderDisplayList = ordersRRT?.completedOrders ?? [];
orderDisplayList = List.from(ordersRRT?.completedOrders ?? []);
break;
case OrderDislpay.AMBULANCE:
orderDisplayList = ambulanceOrders ?? [];
orderDisplayList = List.from(ambulanceOrders ?? []);
break;
}
orderDisplayList = _sortOrdersByDate(orderDisplayList);
notifyListeners();
}
void openRRT() {
List _sortOrdersByDate(List list) {
list.sort((a, b) {
final int? dateA = _parseCreatedDate(a)?.millisecondsSinceEpoch;
final int? dateB = _parseCreatedDate(b)?.millisecondsSinceEpoch;
print("the dateA is $dateA and dateB is $dateB");
if (dateA == null && dateB == null) return 0;
if (dateA == null) return 1;
if (dateB == null) return -1;
return dateB.compareTo(dateA);
});
return list;
}
DateTime? _parseCreatedDate(dynamic order) {
final String? raw = _getCreatedDate(order);
return DateTime.tryParse(raw??"");
}
String? _getCreatedDate(dynamic order) {
if (order is AmbulanceRequestOrdersModel) return order.created;
if (order is GetCMCAllOrdersResponseModel) return order.created;
return null;
}
void openRRT(BuildContext context) {
if (appState.isAuthenticated) {
if (agreedToTermsAndCondition == false) {
dialogService.showErrorBottomSheet(
@ -1032,6 +1069,7 @@ class EmergencyServicesViewModel extends ChangeNotifier {
return;
}
placeValueInController();
context.read<LocationViewModel>().placeValueInController();
locationUtils!.getLocation(
isShowConfirmDialog: true,
onSuccess: (position) async {

@ -15,6 +15,9 @@ abstract class HealthTrackersRepo {
/// Get blood sugar results (week, month, year).
Future<Either<Failure, GenericApiModel<dynamic>>> getDiabeticResults();
Future<Either<Failure, GenericApiModel<String>>> downloadBloodSugarReport();
Future<Either<Failure, GenericApiModel<String>>> downloadWeightReport();
Future<Either<Failure, GenericApiModel<String>>> downloadBloodPressureReport();
/// Add new blood sugar result.
Future<Either<Failure, GenericApiModel<dynamic>>> addDiabeticResult({
@ -211,6 +214,114 @@ class HealthTrackersRepoImp implements HealthTrackersRepo {
}
}
@override
Future<Either<Failure, GenericApiModel<String>>> downloadBloodSugarReport() async {
try {
GenericApiModel<String>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.averageBloodSugarReportDownload,
body: {},
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
dynamic pdf = response['ReportPDFContent'];
apiResponse = GenericApiModel<String>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: pdf,
);
} 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<String>>> downloadWeightReport() async {
try {
GenericApiModel<String>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.averageWeightReportDownload,
body: {},
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
dynamic pdf = response['ReportPDFContent'];
apiResponse = GenericApiModel<String>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: pdf,
);
} 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<String>>> downloadBloodPressureReport() async {
try {
GenericApiModel<String>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.averageBloodPressureReportDownload,
body: {},
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
dynamic pdf = response['ReportPDFContent'];
apiResponse = GenericApiModel<String>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: pdf,
);
} 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<dynamic>>> addDiabeticResult({
required String bloodSugarDateChart,

@ -1,6 +1,10 @@
import 'dart:developer';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/cupertino.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/enums.dart';
import 'package:hmg_patient_app_new/features/health_trackers/health_trackers_repo.dart';
import 'package:hmg_patient_app_new/features/health_trackers/models/blood_pressure/blood_pressure_result.dart';
import 'package:hmg_patient_app_new/features/health_trackers/models/blood_pressure/month_blood_pressure_result_average.dart';
@ -14,7 +18,9 @@ import 'package:hmg_patient_app_new/features/health_trackers/models/weight/month
import 'package:hmg_patient_app_new/features/health_trackers/models/weight/week_weight_measurement_result_average.dart';
import 'package:hmg_patient_app_new/features/health_trackers/models/weight/weight_measurement_result.dart';
import 'package:hmg_patient_app_new/features/health_trackers/models/weight/year_weight_measurement_result_average.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
class HealthTrackersViewModel extends ChangeNotifier {
HealthTrackersRepo healthTrackersRepo;
@ -28,13 +34,25 @@ class HealthTrackersViewModel extends ChangeNotifier {
String? get errorMessage => _errorMessage;
List<String> get durationFilters => ["Week", "Month", "Year"];
final List<String> durationFiltersEn = ["Week", "Month", "Year"];
final List<String> durationFiltersAr = ["أسبوع", "شهر", "سنة"];
List<String> get durationFilters => getIt.get<AppState>().isArabic() ? durationFiltersAr : durationFiltersEn;
String _selectedDuration = "Week";
bool _isGraphView = true;
String get selectedDurationFilter => _selectedDuration;
// Get the localized display value for the selected duration
String get selectedDurationFilterDisplay {
final index = durationFiltersEn.indexOf(_selectedDuration);
if (index >= 0) {
return getIt.get<AppState>().isArabic() ? durationFiltersAr[index] : durationFiltersEn[index];
}
return _selectedDuration;
}
bool get isGraphView => _isGraphView;
final List<String> bloodSugarUnit = ['mg/dlt', 'mol/L'];
@ -72,6 +90,10 @@ class HealthTrackersViewModel extends ChangeNotifier {
"آخر",
];
List<String> getBloodSugarTimeList(){
return getIt.get<AppState>().isArabic()?bloodSugarMeasureTimeArList:bloodSugarMeasureTimeEnList;
}
// Setters with notification
void setBloodSugarMeasureTime(String duration) async {
_selectedBloodSugarMeasureTime = duration;
@ -80,7 +102,15 @@ class HealthTrackersViewModel extends ChangeNotifier {
// Setters with notification
void setFilterDuration(String duration) async {
_selectedDuration = duration;
// Map Arabic values to English keys
if (getIt.get<AppState>().isArabic()) {
final index = durationFiltersAr.indexOf(duration);
if (index >= 0 && index < durationFiltersEn.length) {
_selectedDuration = durationFiltersEn[index];
}
} else {
_selectedDuration = duration;
}
notifyListeners();
}
@ -97,26 +127,73 @@ class HealthTrackersViewModel extends ChangeNotifier {
// ==================== WEIGHT FORM FIELDS ====================
final List<String> weightUnits = ['kg', 'lb'];
final List<String> weightUnitsEn = ['kg', 'lb'];
final List<String> weightUnitsAr = ['كجم', 'رطل'];
String _selectedWeightUnit = 'kg';
String get selectedWeightUnit => _selectedWeightUnit;
List<String> getWeightUnitsList() {
return getIt.get<AppState>().isArabic() ? weightUnitsAr : weightUnitsEn;
}
void setWeightUnit(String unit) {
_selectedWeightUnit = unit;
// Map Arabic values to English keys
if (getIt.get<AppState>().isArabic()) {
final index = weightUnitsAr.indexOf(unit);
if (index >= 0 && index < weightUnitsEn.length) {
_selectedWeightUnit = weightUnitsEn[index];
}
} else {
_selectedWeightUnit = unit;
}
notifyListeners();
}
// Get the localized display value for the selected weight unit
String get selectedWeightUnitDisplay {
final index = weightUnitsEn.indexOf(_selectedWeightUnit);
if (index >= 0) {
return getIt.get<AppState>().isArabic() ? weightUnitsAr[index] : weightUnitsEn[index];
}
return _selectedWeightUnit;
}
// ==================== BLOOD PRESSURE FORM FIELDS ====================
final List<String> measuredArmList = ['Left Arm', 'Right Arm'];
final List<String> measuredArmListEn = ['Left Arm', 'Right Arm'];
final List<String> measuredArmListAr = ['الذراع الأيسر', 'الذراع الأيمن'];
String _selectedMeasuredArm = '';
String get selectedMeasuredArm => _selectedMeasuredArm;
List<String> getMeasuredArmList() {
return getIt.get<AppState>().isArabic() ? measuredArmListAr : measuredArmListEn;
}
void setMeasuredArm(String arm) {
_selectedMeasuredArm = arm;
// Map Arabic values to English keys
if (getIt.get<AppState>().isArabic()) {
final index = measuredArmListAr.indexOf(arm);
if (index >= 0 && index < measuredArmListEn.length) {
_selectedMeasuredArm = measuredArmListEn[index];
}
} else {
_selectedMeasuredArm = arm;
}
notifyListeners();
}
// Get the localized display value for the selected measured arm
String get selectedMeasuredArmDisplay {
final index = measuredArmListEn.indexOf(_selectedMeasuredArm);
if (index >= 0) {
return getIt.get<AppState>().isArabic() ? measuredArmListAr[index] : measuredArmListEn[index];
}
return _selectedMeasuredArm;
}
// Text Controllers
TextEditingController weightController = TextEditingController();
TextEditingController bloodSugarController = TextEditingController();
@ -425,6 +502,91 @@ class HealthTrackersViewModel extends ChangeNotifier {
}
}
Future<bool> downloadBloodSugerReport({
Function(String)? onSuccess,
}) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.generatingReport.tr());
try {
final result = await healthTrackersRepo.downloadBloodSugarReport();
LoaderBottomSheet.hideLoader();
bool success = false;
result.fold(
(failure) {
errorHandlerService.handleError(failure: failure);
},
(apiModel) {
success = true;
if (onSuccess != null) onSuccess(apiModel.data??"");
},
);
notifyListeners();
return success;
} catch (e) {
log('Error in sendWeightReportByEmail: $e');
return false;
}
}
Future<bool> downloadBloodPressureReport({
Function(String)? onSuccess,
}) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.generatingReport.tr());
try {
final result = await healthTrackersRepo.downloadBloodPressureReport();
LoaderBottomSheet.hideLoader();
bool success = false;
result.fold(
(failure) {
errorHandlerService.handleError(failure: failure);
},
(apiModel) {
success = true;
if (onSuccess != null) onSuccess(apiModel.data??"");
},
);
notifyListeners();
return success;
} catch (e) {
log('Error in sendWeightReportByEmail: $e');
return false;
}
}
Future<bool> downloadWeightReport({
Function(String)? onSuccess,
}) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.generatingReport.tr());
try {
final result = await healthTrackersRepo.downloadWeightReport();
LoaderBottomSheet.hideLoader();
bool success = false;
result.fold(
(failure) {
errorHandlerService.handleError(failure: failure);
},
(apiModel) {
success = true;
if (onSuccess != null) onSuccess(apiModel.data??"");
},
);
notifyListeners();
return success;
} catch (e) {
log('Error in sendWeightReportByEmail: $e');
return false;
}
}
// ==================== BLOOD PRESSURE TRACKING METHODS ====================
/// Fetch blood pressure averages and results
@ -857,33 +1019,35 @@ class HealthTrackersViewModel extends ChangeNotifier {
// Validation method
String? _validateBloodSugarEntry(String dateTime) {
final isArabic = getIt.get<AppState>().isArabic();
// Validate blood sugar value
if (bloodSugarController.text.trim().isEmpty) {
return "Please enter blood sugar value";
return isArabic ? "الرجاء إدخال مستوى سكر الدم" : "Please enter blood sugar value";
}
final bloodSugarValue = double.tryParse(bloodSugarController.text.trim());
if (bloodSugarValue == null) {
return "Please enter a valid number";
return isArabic ? "الرجاء إدخال رقم صحيح" : "Please enter a valid number";
}
if (bloodSugarValue <= 0) {
return "Blood sugar value must be greater than 0";
return isArabic ? "يجب أن يكون مستوى سكر الدم أكبر من 0" : "Blood sugar value must be greater than 0";
}
// Validate reasonable range (typical ranges)
if (bloodSugarValue > 1000) {
return "Blood sugar value seems too high. Please check and enter again";
return isArabic ? "مستوى سكر الدم يبدو مرتفع جداً. يرجى التحقق وإدخالها مرة أخرى" : "Blood sugar value seems too high. Please check and enter again";
}
// Validate date time
if (dateTime.trim().isEmpty) {
return "Please select date and time";
return isArabic ? "الرجاء اختيار التاريخ والوقت" : "Please select date and time";
}
// Validate measure time
if (_selectedBloodSugarMeasureTime.isEmpty) {
return "Please select when the measurement was taken";
return isArabic ? "الرجاء اختيار وقت القياس" : "Please select when the measurement was taken";
}
return null; // No errors
@ -896,6 +1060,8 @@ class HealthTrackersViewModel extends ChangeNotifier {
Function()? onSuccess,
Function(String error)? onFailure,
}) async {
final isArabic = getIt.get<AppState>().isArabic();
// Validate
final validationError = _validateBloodSugarEntry(dateTime);
if (validationError != null) {
@ -930,13 +1096,13 @@ class HealthTrackersViewModel extends ChangeNotifier {
notifyListeners();
if (onSuccess != null) onSuccess();
} else {
_errorMessage = "Failed to save blood sugar entry. Please try again";
_errorMessage = isArabic ? "فشل حفظ قراءة سكر الدم. يرجى المحاولة مرة أخرى" : "Failed to save blood sugar entry. Please try again";
notifyListeners();
if (onFailure != null) onFailure(_errorMessage!);
}
} catch (e) {
log('Error in saveBloodSugarEntry: $e');
_errorMessage = "An error occurred. Please try again";
_errorMessage = isArabic ? "حدث خطأ. يرجى المحاولة مرة أخرى" : "An error occurred. Please try again";
isLoading = false;
notifyListeners();
if (onFailure != null) onFailure(_errorMessage!);
@ -947,21 +1113,23 @@ class HealthTrackersViewModel extends ChangeNotifier {
// Validate weight entry before saving
String? _validateWeightEntry(String dateTime) {
final isArabic = getIt.get<AppState>().isArabic();
// Validate weight value
final weightValue = weightController.text.trim();
if (weightValue.isEmpty) {
return "Please enter weight value";
return isArabic ? "الرجاء إدخال قيمة الوزن" : "Please enter weight value";
}
// Check if it's a valid number
final parsedValue = double.tryParse(weightValue);
if (parsedValue == null || parsedValue <= 0) {
return "Please enter a valid weight value";
return isArabic ? "الرجاء إدخال قيمة وزن صحيحة" : "Please enter a valid weight value";
}
// Validate date time
if (dateTime.trim().isEmpty) {
return "Please select date and time";
return isArabic ? "الرجاء تحديد التاريخ والوقت" : "Please select date and time";
}
return null; // No errors
@ -973,6 +1141,8 @@ class HealthTrackersViewModel extends ChangeNotifier {
Function()? onSuccess,
Function(String error)? onFailure,
}) async {
final isArabic = getIt.get<AppState>().isArabic();
// Validate
final validationError = _validateWeightEntry(dateTime);
if (validationError != null) {
@ -1005,13 +1175,13 @@ class HealthTrackersViewModel extends ChangeNotifier {
notifyListeners();
if (onSuccess != null) onSuccess();
} else {
_errorMessage = "Failed to save weight entry. Please try again";
_errorMessage = isArabic ? "فشل حفظ قراءة الوزن. يرجى المحاولة مرة أخرى" : "Failed to save weight entry. Please try again";
notifyListeners();
if (onFailure != null) onFailure(_errorMessage!);
}
} catch (e) {
log('Error in saveWeightEntry: $e');
_errorMessage = "An error occurred. Please try again";
_errorMessage = isArabic ? "حدث خطأ. يرجى المحاولة مرة أخرى" : "An error occurred. Please try again";
isLoading = false;
notifyListeners();
if (onFailure != null) onFailure(_errorMessage!);
@ -1022,34 +1192,36 @@ class HealthTrackersViewModel extends ChangeNotifier {
// Validate blood pressure entry before saving
String? _validateBloodPressureEntry(String dateTime) {
final isArabic = getIt.get<AppState>().isArabic();
// Validate systolic value
final systolicValue = systolicController.text.trim();
if (systolicValue.isEmpty) {
return "Please enter systolic value";
return isArabic ? "الرجاء إدخال القيمة الانقباضية" : "Please enter systolic value";
}
final parsedSystolic = int.tryParse(systolicValue);
if (parsedSystolic == null || parsedSystolic <= 0) {
return "Please enter a valid systolic value";
return isArabic ? "الرجاء إدخال قيمة صحيحة للضغط الانقباضي" : "Please enter a valid systolic value";
}
// Validate diastolic value
final diastolicValue = diastolicController.text.trim();
if (diastolicValue.isEmpty) {
return "Please enter diastolic value";
return isArabic ? "الرجاء إدخال قيمة الضغط الانبساطي" : "Please enter diastolic value";
}
final parsedDiastolic = int.tryParse(diastolicValue);
if (parsedDiastolic == null || parsedDiastolic <= 0) {
return "Please enter a valid diastolic value";
return isArabic ? "الرجاء إدخال قيمة صحيحة للضغط الانبساطي" : "Please enter a valid diastolic value";
}
// Validate arm selection
if (_selectedMeasuredArm.isEmpty) {
return "Please select measured arm";
return isArabic ? "الرجاء اختيار الذراع المقاسة" : "Please select measured arm";
}
// Validate date time
if (dateTime.trim().isEmpty) {
return "Please select date and time";
return isArabic ? "الرجاء تحديد التاريخ والوقت" : "Please select date and time";
}
return null; // No errors
@ -1061,6 +1233,8 @@ class HealthTrackersViewModel extends ChangeNotifier {
Function()? onSuccess,
Function(String error)? onFailure,
}) async {
final isArabic = getIt.get<AppState>().isArabic();
// Validate
final validationError = _validateBloodPressureEntry(dateTime);
if (validationError != null) {
@ -1076,7 +1250,7 @@ class HealthTrackersViewModel extends ChangeNotifier {
try {
// Get measured arm index (0 = Left Arm, 1 = Right Arm)
final measuredArmIndex = measuredArmList.indexOf(_selectedMeasuredArm);
final measuredArmIndex = measuredArmListEn.indexOf(_selectedMeasuredArm);
// Call API
final success = await addOrUpdateBloodPressureResult(
@ -1097,13 +1271,13 @@ class HealthTrackersViewModel extends ChangeNotifier {
notifyListeners();
if (onSuccess != null) onSuccess();
} else {
_errorMessage = "Failed to save blood pressure entry. Please try again";
_errorMessage = isArabic ? "فشل حفظ قراءة ضغط الدم. يرجى المحاولة مرة أخرى" : "Failed to save blood pressure entry. Please try again";
notifyListeners();
if (onFailure != null) onFailure(_errorMessage!);
}
} catch (e) {
log('Error in saveBloodPressureEntry: $e');
_errorMessage = "An error occurred. Please try again";
_errorMessage = isArabic ? "حدث خطأ. يرجى المحاولة مرة أخرى" : "An error occurred. Please try again";
isLoading = false;
notifyListeners();
if (onFailure != null) onFailure(_errorMessage!);
@ -1118,4 +1292,30 @@ class HealthTrackersViewModel extends ChangeNotifier {
diastolicController.dispose();
super.dispose();
}
Future<void> downloadReport(HealthTrackerTypeEnum trackerType, {required Function(dynamic report) onSuccess}) async {
switch (trackerType) {
case HealthTrackerTypeEnum.bloodSugar:
await downloadBloodSugerReport(
onSuccess : (value){
onSuccess(value);
}
);
break;
case HealthTrackerTypeEnum.bloodPressure:
await downloadBloodPressureReport(
onSuccess : (value){
onSuccess(value);
}
);
break;
case HealthTrackerTypeEnum.weightTracker:
await downloadWeightReport(
onSuccess : (value){
onSuccess(value);
}
);
break;
}
}
}

@ -11,6 +11,7 @@ enum VitalSignStatus {
obese,
overweight,
underweight,
skinny,
}
/// UI-only helper model for Vital Sign cards.
@ -34,8 +35,9 @@ class VitalSignUiModel {
///
/// Rules:
/// - Height is always blue.
/// - High, Obese, Overweight => red scheme.
/// - Low, Underweight => yellow scheme.
/// - High, Obese => red scheme.
/// - Overweight => orange scheme.
/// - Low, Underweight, Skinny => yellow scheme.
/// - Otherwise => green scheme (Normal).
static VitalSignUiModel scheme({required VitalSignStatus? status, required String label}) {
final l = label.toLowerCase();
@ -50,10 +52,8 @@ class VitalSignUiModel {
);
}
// High, Obese, Overweight => red scheme (health concerns)
if (status == VitalSignStatus.high ||
status == VitalSignStatus.obese ||
status == VitalSignStatus.overweight) {
// High, Obese => red scheme (health concerns)
if (status == VitalSignStatus.high || status == VitalSignStatus.obese) {
return VitalSignUiModel(
iconBg: AppColors.chipSecondaryLightRedColor,
iconFg: AppColors.primaryRedColor,
@ -62,8 +62,20 @@ class VitalSignUiModel {
);
}
// Low, Underweight => yellow scheme (warning)
if (status == VitalSignStatus.low || status == VitalSignStatus.underweight) {
// Overweight => orange scheme (warning)
if (status == VitalSignStatus.overweight) {
return VitalSignUiModel(
iconBg: AppColors.warningColorYellow.withValues(alpha: 0.12),
iconFg: AppColors.warningColorYellow,
chipBg: AppColors.warningColorYellow.withValues(alpha: 0.12),
chipFg: AppColors.warningColorYellow,
);
}
// Low, Underweight, Skinny => yellow scheme (warning)
if (status == VitalSignStatus.low ||
status == VitalSignStatus.underweight ||
status == VitalSignStatus.skinny) {
final Color yellowBg = AppColors.highAndLow.withValues(alpha: 0.12);
return VitalSignUiModel(
iconBg: yellowBg,
@ -98,6 +110,8 @@ class VitalSignUiModel {
return LocaleKeys.vitalSignOverweight.tr();
case VitalSignStatus.underweight:
return LocaleKeys.vitalSignUnderweight.tr();
case VitalSignStatus.skinny:
return LocaleKeys.vitalSignSkinny.tr();
}
}
@ -131,22 +145,30 @@ class VitalSignUiModel {
return null;
}
// BMI >= 25 (Overweight or Obese) => High
if (bmiResult >= 25) {
return VitalSignStatus.high;
// BMI >= 30 (Obese) => Red
if (bmiResult >= 30) {
return VitalSignStatus.obese;
}
// BMI >= 25 (Overweight) => Orange
else if (bmiResult >= 25) {
return VitalSignStatus.overweight;
}
// BMI >= 18.5 and < 25 => Normal
// BMI >= 18.5 (Normal) => Green
else if (bmiResult >= 18.5) {
return VitalSignStatus.normal;
}
// BMI < 18.5 (Underweight) => Low
// BMI >= 16 (Underweight) => Yellow
else if (bmiResult >= 16) {
return VitalSignStatus.underweight;
}
// BMI < 16 (Skinny/Severely underweight) => Yellow
else {
return VitalSignStatus.low;
return VitalSignStatus.skinny;
}
}
/// Weight status based on BMI with detailed classification
/// Returns: Obese, Overweight, Normal, Underweight
/// Returns: Obese, Overweight, Normal, Underweight, Skinny
static VitalSignStatus? weightStatus(dynamic bmi) {
// Return null if BMI is not available or is 0
final double bmiResult = double.tryParse(bmi.toString()) ?? 0;
@ -155,15 +177,26 @@ class VitalSignUiModel {
return null;
}
// BMI >= 30 (Obese) => Red
if (bmiResult >= 30) {
return VitalSignStatus.obese;
} else if (bmiResult >= 25) {
}
// BMI >= 25 (Overweight) => Orange
else if (bmiResult >= 25) {
return VitalSignStatus.overweight;
} else if (bmiResult >= 18.5) {
}
// BMI >= 18.5 (Normal) => Green
else if (bmiResult >= 18.5) {
return VitalSignStatus.normal;
} else {
}
// BMI >= 16 (Underweight) => Yellow
else if (bmiResult >= 16) {
return VitalSignStatus.underweight;
}
// BMI < 16 (Skinny/Severely underweight) => Yellow
else {
return VitalSignStatus.skinny;
}
}
}

@ -39,6 +39,7 @@ class ImmediateLiveCareViewModel extends ChangeNotifier {
MyAppointmentsViewModel myAppointmentsViewModel;
List<GetLiveCareClinicListResponseModel> immediateLiveCareClinicsList = [];
List<GetLiveCareClinicListResponseModel> mainLiveCareClinicList = [];
bool isImmediateLiveCareClinicsLoading = false;
int liveCareSelectedCallType = 0; // 1- Video, 2- Audio, 3- Phone
late GetLiveCareClinicListResponseModel immediateLiveCareSelectedClinic;
@ -61,6 +62,7 @@ class ImmediateLiveCareViewModel extends ChangeNotifier {
initImmediateLiveCare() {
_appState = getIt<AppState>();
immediateLiveCareClinicsList = [];
mainLiveCareClinicList = [];
isImmediateLiveCareClinicsLoading = true;
liveCareSelectedCallType = 0; // 1- Video, 2- Audio, 3- Phone
immediateLiveCareSelectedClinic = GetLiveCareClinicListResponseModel();
@ -90,9 +92,11 @@ class ImmediateLiveCareViewModel extends ChangeNotifier {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
immediateLiveCareClinicsList = apiResponse.data!;
mainLiveCareClinicList = apiResponse.data!;
immediateLiveCareClinicsList.sort((a, b) => b.isOnline!.compareTo(a.isOnline!));
mainLiveCareClinicList.sort((a, b) => b.isOnline!.compareTo(a.isOnline!));
immediateLiveCareClinicsList.clear();
immediateLiveCareClinicsList.addAll(mainLiveCareClinicList);
isImmediateLiveCareClinicsLoading = false;
notifyListeners();
@ -103,6 +107,18 @@ class ImmediateLiveCareViewModel extends ChangeNotifier {
},
);
}
void filterClinic(String query){
if(query.isEmpty){
immediateLiveCareClinicsList.clear();
immediateLiveCareClinicsList.addAll(mainLiveCareClinicList);
}else{
// bool isArabic = getIt.get<AppState>().isArabic();
immediateLiveCareClinicsList = mainLiveCareClinicList.where((clinic) => clinic.serviceName!.toLowerCase().contains(query.toLowerCase()) || clinic.serviceNameN!.toLowerCase().contains(query.toLowerCase()
)).toList();
}
notifyListeners();
}
Future<void> getLiveCareImmediateAppointmentFees({Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result =

@ -191,6 +191,7 @@ class InsuranceViewModel extends ChangeNotifier {
notifyListeners();
},
(apiResponse) {
patientInsuranceApprovalsList =[];
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {

@ -18,7 +18,6 @@ class LocationViewModel extends ChangeNotifier {
final ErrorHandlerService errorHandlerService;
LocationViewModel({required this.locationRepo, required this.errorHandlerService}) {
placeValueInController();
}
List<PlacePrediction> predictions = [];
@ -31,7 +30,9 @@ class LocationViewModel extends ChangeNotifier {
Completer<GMSMapServices.GoogleMapController>? gmsController;
Completer<HMSCameraServices.HuaweiMapController>? hmsController;
get isGMSAvailable {
return getIt.get<AppState>().isGMSAvailable;
}
HMSCameraServices.CameraPosition getHMSLocation() {
return HMSCameraServices.CameraPosition(target: HMSCameraServices.LatLng(getIt<AppState>().userLat, getIt<AppState>().userLong), zoom: 18);
}
@ -43,7 +44,7 @@ class LocationViewModel extends ChangeNotifier {
}
void placeValueInController() async {
if (await getIt<AppState>().isGMSAvailable) {
if (isGMSAvailable) {
gmsController = Completer<GMSMapServices.GoogleMapController>();
} else {
hmsController = Completer<HMSCameraServices.HuaweiMapController>();

@ -41,6 +41,7 @@ class MedicalFileViewModel extends ChangeNotifier {
List<SickLeaveList> patientSickLeavesViewList = [];
bool isSickLeavesSortByClinic = true;
// bool isSickLeavesDataNeedsReloading = true;
List<GetAllergiesResponseModel> patientAllergiesList = [];
@ -165,13 +166,13 @@ class MedicalFileViewModel extends ChangeNotifier {
setIsPatientSickLeaveListLoading(bool val) {
// if (val && isSickLeavesDataNeedsReloading) {
patientSickLeaveList.clear();
patientSickLeavesByClinic.clear();
patientSickLeavesByHospital.clear();
patientSickLeavesViewList.clear();
patientSickLeavePDFBase64 = "";
isSickLeavesSortByClinic = true;
isPatientSickLeaveListLoading = val;
patientSickLeaveList.clear();
patientSickLeavesByClinic.clear();
patientSickLeavesByHospital.clear();
patientSickLeavesViewList.clear();
patientSickLeavePDFBase64 = "";
isSickLeavesSortByClinic = true;
isPatientSickLeaveListLoading = val;
// }
notifyListeners();
}
@ -456,6 +457,7 @@ class MedicalFileViewModel extends ChangeNotifier {
patientIdenficationNumber: currentUser.patientIdentificationNo,
emaiLAddress: currentUser.emailAddress,
genderDescription: currentUser.genderDescription,
patientImageData: _appState.getProfileImageData, // Set main user's profile image
);
// Clear and start fresh with current user
@ -499,6 +501,7 @@ class MedicalFileViewModel extends ChangeNotifier {
genderDescription: element.genderDescription,
genderImage: element.genderImage,
emaiLAddress: element.emaiLAddress,
patientImageData: element.patientImageData, // Include profile image data
);
if (isPending) {
@ -774,4 +777,41 @@ class MedicalFileViewModel extends ChangeNotifier {
},
);
}
/// Update family member's profile image in cached family files
void updateFamilyMemberProfileImage(int patientID, String imageData) {
try {
bool updated = false;
// Update in patientFamilyFiles list
for (var i = 0; i < patientFamilyFiles.length; i++) {
if (patientFamilyFiles[i].patientId == patientID ||
patientFamilyFiles[i].responseId == patientID) {
patientFamilyFiles[i].patientImageData = imageData;
updated = true;
print("✅ Updated profile image for family member in patientFamilyFiles (index: $i, patientID: $patientID)");
break;
}
}
// Update in pendingFamilyFiles list if exists
for (var i = 0; i < pendingFamilyFiles.length; i++) {
if (pendingFamilyFiles[i].patientId == patientID ||
pendingFamilyFiles[i].responseId == patientID) {
pendingFamilyFiles[i].patientImageData = imageData;
print("✅ Updated profile image for pending family member (index: $i, patientID: $patientID)");
break;
}
}
if (updated) {
// Notify listeners to update UI
notifyListeners();
} else {
print("⚠️ Family member not found in cache for patientID: $patientID");
}
} catch (e) {
print("❌ Error updating family member profile image in cache: $e");
}
}
}

@ -25,6 +25,7 @@ class FamilyFileResponseModelLists {
String? statusDescription;
bool? isSuperUser = false;
bool? isRequestFromMySide;
String? patientImageData;
FamilyFileResponseModelLists(
{this.id,
@ -50,7 +51,8 @@ class FamilyFileResponseModelLists {
this.patientName,
this.statusDescription,
this.isSuperUser,
this.isRequestFromMySide});
this.isRequestFromMySide,
this.patientImageData});
factory FamilyFileResponseModelLists.fromRawJson(String str) => FamilyFileResponseModelLists.fromJson(json.decode(str));
@ -81,6 +83,7 @@ class FamilyFileResponseModelLists {
statusDescription: json["StatusDescription"],
isSuperUser: json["isSuperUser"] ?? false,
isRequestFromMySide: json["isRequestFromMySide"] ?? false,
patientImageData: json["PatientImageData"],
);
Map<String, dynamic> toJson() => {
@ -108,5 +111,6 @@ class FamilyFileResponseModelLists {
"StatusDescription": statusDescription,
"isSuperUser": isSuperUser,
"isRequestFromMySide": isRequestFromMySide,
"PatientImageData": patientImageData,
};
}

@ -0,0 +1,50 @@
/// Enum to define different types of reminders
enum ReminderType {
/// Reminder for appointment schedule
appointment,
/// Reminder for payment due
payment,
/// Reminder for check-in
checkIn,
}
extension ReminderTypeExtension on ReminderType {
/// Get display name for the reminder type
String get displayName {
switch (this) {
case ReminderType.appointment:
return 'Appointment Reminder';
case ReminderType.payment:
return 'Payment Reminder';
case ReminderType.checkIn:
return 'Check-In Reminder';
}
}
/// Get description for the reminder type
String get description {
switch (this) {
case ReminderType.appointment:
return 'Appointment scheduled reminder';
case ReminderType.payment:
return 'Payment due reminder';
case ReminderType.checkIn:
return 'Check-in reminder';
}
}
/// Get event ID prefix for calendar events
String get eventIdPrefix {
switch (this) {
case ReminderType.appointment:
return 'APPT';
case ReminderType.payment:
return 'PAY';
case ReminderType.checkIn:
return 'CHKIN';
}
}
}

@ -75,6 +75,7 @@ class PatientAppointmentHistoryResponseModel {
num? patientShareWithTax;
num? patientTaxAmount;
String? doctorNationalityFlagURL;
bool? isClinicReBookingAllowed;
PatientAppointmentHistoryResponseModel({
this.setupID,
@ -152,6 +153,7 @@ class PatientAppointmentHistoryResponseModel {
this.patientShareWithTax,
this.patientTaxAmount,
this.doctorNationalityFlagURL,
this.isClinicReBookingAllowed,
});
PatientAppointmentHistoryResponseModel.fromJson(Map<String, dynamic> json) {
@ -241,6 +243,7 @@ class PatientAppointmentHistoryResponseModel {
patientShareWithTax = json['PatientShareWithTax'];
patientTaxAmount = json['PatientTaxAmount'];
doctorNationalityFlagURL = json['DoctorNationalityFlagURL'];
isClinicReBookingAllowed = json['IsClinicReBookingAllowed'];
}
Map<String, dynamic> toJson() {
@ -313,6 +316,7 @@ class PatientAppointmentHistoryResponseModel {
data['RemaniningHoursTocanPay'] = this.remaniningHoursTocanPay;
data['SMSButtonVisable'] = this.sMSButtonVisable;
data['ServiceID'] = this.serviceID;
data['IsClinicReBookingAllowed'] = this.isClinicReBookingAllowed;
return data;
}
}

@ -847,7 +847,7 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo {
// final queueList = list.map((item) => PatientQueueDetails.fromJson(item as Map<String, dynamic>)).toList().cast<PatientQueueDetails>();
final queueList = PatientQueueDetails.fromJson(queueData);
queueList.patientQueueData!.isClinicConfigured = 1;
// queueList.patientQueueData!.isClinicConfigured = 1;
apiResponse = GenericApiModel<PatientQueueDetails>(
messageStatus: messageStatus,

@ -2,11 +2,13 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/utils/calender_utils_new.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_favorite_doctors_list.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/appointemnet_filters.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/reminder_type.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/appointment_rated_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ask_doctor_request_type_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart';
@ -19,6 +21,7 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
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';
@ -1109,4 +1112,409 @@ class MyAppointmentsViewModel extends ChangeNotifier {
},
);
}
/// Creates a reminder for the appointment
/// Shows a bottom sheet to select reminder time and creates calendar event
/// [reminderType] specifies the type of reminder (appointment, payment, or check-in)
/// For payment and check-in reminders, shows only 1, 2, 3 hours before appointment
/// Automatically removes previous reminders when creating a new one
Future<void> createReminder({
required BuildContext context,
required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel,
ReminderType reminderType = ReminderType.appointment,
Function(bool)? onSuccess,
Function(String)? onError,
}) async {
try {
CalenderUtilsNew calender = CalenderUtilsNew.instance;
DateTime endDate = DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate);
// Generate title and description based on reminder type
String title;
String description;
String eventIdPrefix = reminderType.eventIdPrefix;
switch (reminderType) {
case ReminderType.appointment:
title = "Appointment with ${patientAppointmentHistoryResponseModel.doctorNameObj}";
description = "${patientAppointmentHistoryResponseModel.doctorNameObj} will be having an appointment on ${patientAppointmentHistoryResponseModel.appointmentDate}";
break;
case ReminderType.payment:
title = "Payment Due for Appointment #${patientAppointmentHistoryResponseModel.appointmentNo}";
description = "Payment reminder for appointment with ${patientAppointmentHistoryResponseModel.doctorNameObj} at ${patientAppointmentHistoryResponseModel.projectName}";
break;
case ReminderType.checkIn:
title = "Check-In Reminder for Appointment #${patientAppointmentHistoryResponseModel.appointmentNo}";
description = "Don't forget to check in for your appointment with ${patientAppointmentHistoryResponseModel.doctorNameObj}";
break;
}
// For payment and check-in, remove existing reminders first
if (reminderType == ReminderType.payment || reminderType == ReminderType.checkIn) {
// Remove all existing reminder types
await _removeAllRemindersForAppointment(patientAppointmentHistoryResponseModel);
}
// For payment and check-in reminders, use custom hour-based options
if (reminderType == ReminderType.payment || reminderType == ReminderType.checkIn) {
_showHourBasedReminderSheet(
context: context,
patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel,
reminderType: reminderType,
title: title,
description: description,
eventIdPrefix: eventIdPrefix,
endDate: endDate,
onSuccess: onSuccess,
onError: onError,
);
} else {
// For appointment reminders, use the standard reminder sheet with all options
BottomSheetUtils().showReminderBottomSheet(
context,
endDate,
patientAppointmentHistoryResponseModel.doctorNameObj ?? "",
"${patientAppointmentHistoryResponseModel.appointmentNo}",
"",
"",
title: title,
description: description,
onSuccess: () {
setAppointmentReminder(true, patientAppointmentHistoryResponseModel);
if (onSuccess != null) {
onSuccess(true);
}
},
isMultiAllowed: true,
onMultiDateSuccess: (int selectedIndex) async {
String eventTitle;
String eventDescription;
switch (reminderType) {
case ReminderType.appointment:
eventTitle = "Appointment Reminder with ${patientAppointmentHistoryResponseModel.doctorNameObj} on ${DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)}, Appointment #${patientAppointmentHistoryResponseModel.appointmentNo}";
eventDescription = "Appointment Reminder with ${patientAppointmentHistoryResponseModel.doctorNameObj} in ${patientAppointmentHistoryResponseModel.projectName}";
break;
case ReminderType.payment:
eventTitle = "Payment Due Reminder for Appointment #${patientAppointmentHistoryResponseModel.appointmentNo} with ${patientAppointmentHistoryResponseModel.doctorNameObj}";
eventDescription = "Payment reminder for ${patientAppointmentHistoryResponseModel.doctorNameObj} at ${patientAppointmentHistoryResponseModel.projectName}";
break;
case ReminderType.checkIn:
eventTitle = "Check-In Reminder for Appointment #${patientAppointmentHistoryResponseModel.appointmentNo} with ${patientAppointmentHistoryResponseModel.doctorNameObj}";
eventDescription = "Check-in reminder for appointment with ${patientAppointmentHistoryResponseModel.doctorNameObj} at ${patientAppointmentHistoryResponseModel.projectName}";
break;
}
bool isEventAddedOrRemoved = await calender.createOrUpdateEvent(
title: eventTitle,
description: eventDescription,
scheduleDateTime: DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate),
eventId: "${eventIdPrefix}_${patientAppointmentHistoryResponseModel.appointmentNo}",
location: patientAppointmentHistoryResponseModel.projectName ?? '',
reminderMinutes: selectedIndex,
);
setAppointmentReminder(isEventAddedOrRemoved, patientAppointmentHistoryResponseModel);
if (onSuccess != null) {
onSuccess(isEventAddedOrRemoved);
}
},
);
}
} catch (e) {
if (onError != null) {
onError(e.toString());
}
}
}
/// Removes all reminder types for an appointment (internal helper)
Future<void> _removeAllRemindersForAppointment(PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel) async {
CalenderUtilsNew calender = CalenderUtilsNew.instance;
// Remove all possible reminder types
for (var type in ReminderType.values) {
try {
await calender.checkAndRemove(
id: "${type.eventIdPrefix}_${patientAppointmentHistoryResponseModel.appointmentNo}",
);
} catch (e) {
// Ignore errors if reminder doesn't exist
debugPrint("No existing ${type.displayName} to remove");
}
}
}
/// Shows hour-based reminder selection for payment and check-in reminders
void _showHourBasedReminderSheet({
required BuildContext context,
required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel,
required ReminderType reminderType,
required String title,
required String description,
required String eventIdPrefix,
required DateTime endDate,
Function(bool)? onSuccess,
Function(String)? onError,
}) {
showCommonBottomSheetWithoutHeight(
context,
title: "Select Reminder Time",
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: Text(
"When would you like to be reminded?",
style: TextStyle(
fontSize: 14.f,
color: AppColors.greyTextColor,
),
textAlign: TextAlign.center,
),
),
SizedBox(height: 24.h),
_buildHourOption(
context: context,
hours: 1,
icon: Icons.alarm,
patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel,
reminderType: reminderType,
title: title,
description: description,
eventIdPrefix: eventIdPrefix,
endDate: endDate,
onSuccess: onSuccess,
onError: onError,
),
SizedBox(height: 12.h),
_buildHourOption(
context: context,
hours: 2,
icon: Icons.alarm,
patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel,
reminderType: reminderType,
title: title,
description: description,
eventIdPrefix: eventIdPrefix,
endDate: endDate,
onSuccess: onSuccess,
onError: onError,
),
SizedBox(height: 12.h),
_buildHourOption(
context: context,
hours: 3,
icon: Icons.alarm,
patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel,
reminderType: reminderType,
title: title,
description: description,
eventIdPrefix: eventIdPrefix,
endDate: endDate,
onSuccess: onSuccess,
onError: onError,
),
],
),
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
/// Builds an hour option button for payment/check-in reminders
Widget _buildHourOption({
required BuildContext context,
required int hours,
required IconData icon,
required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel,
required ReminderType reminderType,
required String title,
required String description,
required String eventIdPrefix,
required DateTime endDate,
Function(bool)? onSuccess,
Function(String)? onError,
}) {
return GestureDetector(
onTap: () async {
Navigator.of(context).pop(); // Close the selection sheet
// Create reminder with selected hours (converted to minutes)
await _createReminderWithHours(
hours: hours,
patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel,
reminderType: reminderType,
title: title,
description: description,
eventIdPrefix: eventIdPrefix,
endDate: endDate,
onSuccess: onSuccess,
onError: onError,
);
},
child: Container(
padding: EdgeInsets.all(16.h),
decoration: BoxDecoration(
color: AppColors.whiteColor,
border: Border.all(color: AppColors.borderOnlyColor),
borderRadius: BorderRadius.circular(12.r),
),
child: Row(
children: [
Container(
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: AppColors.primaryRedColor.withAlpha(26),
borderRadius: BorderRadius.circular(8.r),
),
child: Icon(
icon,
color: AppColors.primaryRedColor,
size: 24.h,
),
),
SizedBox(width: 12.w),
Expanded(
child: Text(
"$hours ${hours == 1 ? 'Hour' : 'Hours'} Before Appointment",
style: TextStyle(
fontSize: 16.f,
fontWeight: FontWeight.w600,
color: AppColors.textColor,
),
),
),
Icon(
Icons.arrow_forward_ios,
size: 16.h,
color: AppColors.greyTextColor,
),
],
),
),
);
}
/// Creates reminder with specific hours before appointment
Future<void> _createReminderWithHours({
required int hours,
required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel,
required ReminderType reminderType,
required String title,
required String description,
required String eventIdPrefix,
required DateTime endDate,
Function(bool)? onSuccess,
Function(String)? onError,
}) async {
try {
CalenderUtilsNew calender = CalenderUtilsNew.instance;
// Convert hours to minutes
int reminderMinutes = hours * 60;
String eventTitle;
String eventDescription;
switch (reminderType) {
case ReminderType.appointment:
eventTitle = "Appointment Reminder with ${patientAppointmentHistoryResponseModel.doctorNameObj} on ${DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)}, Appointment #${patientAppointmentHistoryResponseModel.appointmentNo}";
eventDescription = "Appointment Reminder with ${patientAppointmentHistoryResponseModel.doctorNameObj} in ${patientAppointmentHistoryResponseModel.projectName}";
break;
case ReminderType.payment:
eventTitle = "Payment Due - $hours ${hours == 1 ? 'hour' : 'hours'} before Appointment #${patientAppointmentHistoryResponseModel.appointmentNo}";
eventDescription = "Payment reminder for ${patientAppointmentHistoryResponseModel.doctorNameObj} at ${patientAppointmentHistoryResponseModel.projectName}";
break;
case ReminderType.checkIn:
eventTitle = "Check-In Now - $hours ${hours == 1 ? 'hour' : 'hours'} before Appointment #${patientAppointmentHistoryResponseModel.appointmentNo}";
eventDescription = "Check-in reminder for appointment with ${patientAppointmentHistoryResponseModel.doctorNameObj} at ${patientAppointmentHistoryResponseModel.projectName}";
break;
}
bool isEventAddedOrRemoved = await calender.createOrUpdateEvent(
title: eventTitle,
description: eventDescription,
scheduleDateTime: DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate),
eventId: "${eventIdPrefix}_${patientAppointmentHistoryResponseModel.appointmentNo}",
location: patientAppointmentHistoryResponseModel.projectName ?? '',
reminderMinutes: reminderMinutes,
);
setAppointmentReminder(isEventAddedOrRemoved, patientAppointmentHistoryResponseModel);
if (onSuccess != null) {
onSuccess(isEventAddedOrRemoved);
}
} catch (e) {
if (onError != null) {
onError(e.toString());
}
}
}
/// Deletes the reminder for the appointment
/// Removes the calendar event associated with the appointment
/// [reminderType] specifies the type of reminder to delete (appointment, payment, or check-in)
Future<void> deleteReminder({
required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel,
ReminderType reminderType = ReminderType.appointment,
Function(bool)? onSuccess,
Function(String)? onError,
}) async {
try {
CalenderUtilsNew calender = CalenderUtilsNew.instance;
String eventIdPrefix = reminderType.eventIdPrefix;
bool isEventRemoved = await calender.checkAndRemove(
id: "${eventIdPrefix}_${patientAppointmentHistoryResponseModel.appointmentNo}",
);
// Update the reminder status (inverted because we're removing)
setAppointmentReminder(!isEventRemoved, patientAppointmentHistoryResponseModel);
if (onSuccess != null) {
onSuccess(isEventRemoved);
}
} catch (e) {
if (onError != null) {
onError(e.toString());
}
}
}
/// Toggle reminder for an appointment
/// Creates or deletes reminder based on the current state
/// [reminderType] specifies the type of reminder (appointment, payment, or check-in)
Future<void> toggleReminder({
required BuildContext context,
required bool shouldCreateReminder,
required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel,
required ReminderType reminderType ,
Function(bool)? onSuccess,
Function(String)? onError,
}) async {
if (shouldCreateReminder) {
await createReminder(
context: context,
patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel,
reminderType: reminderType,
onSuccess: onSuccess,
onError: onError,
);
} else {
await deleteReminder(
patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel,
reminderType: reminderType,
onSuccess: onSuccess,
onError: onError,
);
}
}
}

@ -52,6 +52,8 @@ class MyInvoicesViewModel extends ChangeNotifier {
notifyListeners();
},
(apiResponse) {
_originalInvoicesList =[];
allInvoicesList =[];
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {

@ -20,6 +20,7 @@ import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/widgets/map/map_utility_screen.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart';
class PrescriptionsViewModel extends ChangeNotifier {
bool isPrescriptionsOrdersLoading = false;
@ -255,7 +256,9 @@ class PrescriptionsViewModel extends ChangeNotifier {
);
}
void initiatePrescriptionDelivery() async {
void initiatePrescriptionDelivery(BuildContext context) async {
context.read<LocationViewModel>().placeValueInController();
getIt.get<LocationUtils>().getLocation(
isShowConfirmDialog: true,
onSuccess: (position) async {
@ -303,6 +306,23 @@ class PrescriptionsViewModel extends ChangeNotifier {
} else if (apiResponse.messageStatus == 1) {
isPrescriptionsDeliveryOrdersLoading = false;
prescriptionsOrderList = apiResponse.data!;
// Sort by created date (newest first)
prescriptionsOrderList.sort((a, b) {
try {
if (a.created == null && b.created == null) return 0;
if (a.created == null) return 1;
if (b.created == null) return -1;
DateTime dateA = DateTime.parse(a.created!);
DateTime dateB = DateTime.parse(b.created!);
return dateB.compareTo(dateA); // Descending order (newest first)
} catch (e) {
print('Error parsing date for sorting: $e');
return 0;
}
});
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);

@ -151,7 +151,7 @@ class ProfileSettingsRepoImp implements ProfileSettingsRepo {
}
@override
Future<Either<Failure, GenericApiModel<dynamic>>> getProfileImage({
Future<Either<Failure, GenericApiModel<dynamic>>> getProfileImage({
required int patientID,
}) async {
final Map<String, dynamic> body = {

@ -3,6 +3,7 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:get_it/get_it.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart';
import 'package:hmg_patient_app_new/features/profile_settings/models/get_patient_info_response_model.dart';
import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_repo.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
@ -188,19 +189,15 @@ class ProfileSettingsViewModel extends ChangeNotifier {
Function(String)? onError,
bool forceRefresh = false, // Add flag to force refresh
}) async {
// Skip API call if image is already loaded and not forcing refresh
if (!forceRefresh && GetIt.instance<AppState>().getProfileImageData != null) {
// Use cached data
profileImageData = GetIt.instance<AppState>().getProfileImageData;
if (onSuccess != null) {
onSuccess(profileImageData);
}
return;
}
print('🌐 Fetching profile image from API for patient: $patientID (forceRefresh: $forceRefresh)');
// IMPORTANT: Always clear old data BEFORE fetching
// Do NOT use cached data - always fetch fresh from API
profileImageData = null;
isProfileImageLoading = true;
profileImageError = null;
notifyListeners();
notifyListeners(); // Notify to show loading state
final result = await profileSettingsRepo.getProfileImage(patientID: patientID);
@ -208,6 +205,11 @@ class ProfileSettingsViewModel extends ChangeNotifier {
(failure) {
isProfileImageLoading = false;
profileImageError = failure.message;
print('❌ Failed to fetch profile image: ${failure.message}');
// Ensure data is cleared on failure
profileImageData = null;
notifyListeners();
if (onError != null) {
onError(failure.message);
@ -215,43 +217,48 @@ class ProfileSettingsViewModel extends ChangeNotifier {
},
(response) {
isProfileImageLoading = false;
// Extract image data from response
if (response.data != null && response.data['Patient_GetProfileImageDataList'] != null) {
var imageList = response.data['Patient_GetProfileImageDataList'];
if (imageList is List && imageList.isNotEmpty) {
profileImageData = imageList[0]['PatientsProfileImageData'];
// Store in AppState for global access
GetIt.instance<AppState>().setProfileImageData = profileImageData;
profileImageData = imageList[0]['ImageData'];
if (profileImageData != null && profileImageData!.isNotEmpty) {
// ONLY store in ViewModel - AppState will be updated separately by auth flow
print('✅ Profile image loaded from API (length: ${profileImageData!.length})');
} else {
profileImageData = null;
print('⚠️ Profile image data is empty in API response');
}
} else {
profileImageData = null;
GetIt.instance<AppState>().setProfileImageData = null;
print('⚠️ Profile image list is empty in API response');
}
} else {
profileImageData = null;
GetIt.instance<AppState>().setProfileImageData = null;
print('⚠️ No profile image data in API response');
}
notifyListeners();
notifyListeners(); // Notify to update UI with new/null data
onSuccess?.call(response.data);
},
);
}
/// Clear cached profile image data
/// Clear profile image data from ViewModel
void clearProfileImageCache() {
print('🧹 Clearing profile image from ViewModel');
profileImageData = null;
GetIt.instance<AppState>().setProfileImageData = null;
notifyListeners();
}
/// Sync profile image data from AppState
/// Sync profile image data from AppState - DISABLED
/// Profile Settings screen uses only ViewModel data from API
void syncProfileImageFromAppState() {
final appStateImageData = GetIt.instance<AppState>().getProfileImageData;
if (appStateImageData != null && appStateImageData.isNotEmpty) {
if (profileImageData != appStateImageData) {
profileImageData = appStateImageData;
notifyListeners();
}
}
// DO NOTHING - Profile Settings uses only direct API data
// No syncing from AppState to avoid stale cache issues
print('⚠️ syncProfileImageFromAppState called but disabled - use API data only');
}
Future<void> uploadProfileImage({
@ -286,6 +293,16 @@ class ProfileSettingsViewModel extends ChangeNotifier {
profileImageData = imageData;
// Store in AppState for global access
GetIt.instance<AppState>().setProfileImageData = imageData;
// Update the family files cache with the new profile image
try {
final medicalFileViewModel = GetIt.instance.get<MedicalFileViewModel>();
medicalFileViewModel.updateFamilyMemberProfileImage(patientID, imageData);
print("✅ Updated profile image in family files cache for patient: $patientID");
} catch (e) {
print("⚠️ Could not update family files cache: $e");
}
notifyListeners();
onSuccess?.call(response.data);
},

@ -2,9 +2,19 @@ import 'dart:convert';
class RadiologyAiAnalysisResponse {
String? summary;
String? labResults;
String? aiPredictions;
String? riskFactors;
String? prevention;
int? languageId;
RadiologyAiAnalysisResponse({
this.summary,
this.labResults,
this.aiPredictions,
this.riskFactors,
this.prevention,
this.languageId,
});
factory RadiologyAiAnalysisResponse.fromRawJson(String str) => RadiologyAiAnalysisResponse.fromJson(json.decode(str));
@ -12,11 +22,20 @@ class RadiologyAiAnalysisResponse {
String toRawJson() => json.encode(toJson());
factory RadiologyAiAnalysisResponse.fromJson(Map<String, dynamic> json) => RadiologyAiAnalysisResponse(
summary: json["summary"],
);
summary: json["summary"],
labResults: json["lab_results"],
aiPredictions: json["ai_predictions"],
riskFactors: json["riskFactors"],
prevention: json["prevention"],
languageId: json["language_id"],
);
Map<String, dynamic> toJson() => {
"summary": summary,
};
"summary": summary,
"lab_results": labResults,
"ai_predictions": aiPredictions,
"riskFactors": riskFactors,
"prevention": prevention,
"language_id": languageId,
};
}

@ -42,7 +42,7 @@ class RadiologyRepoImp implements RadiologyRepo {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
final radOrders;
List<PatientRadiologyResponseModel> radOrders = [];
try {
if (response['FinalRadiologyList'] != null && response['FinalRadiologyList'].length != 0) {
final list = response['FinalRadiologyList'];
@ -52,10 +52,18 @@ class RadiologyRepoImp implements RadiologyRepo {
.cast<PatientRadiologyResponseModel>();
} else {
final list = response['FinalRadiologyListAPI'];
radOrders = list
.map((item) => PatientRadiologyResponseModel.fromJson(item as Map<String, dynamic>))
.toList()
.cast<PatientRadiologyResponseModel>();
if(list != null && list.length != 0) {
radOrders = list
.map((item) => PatientRadiologyResponseModel.fromJson(item as Map<String, dynamic>))
.toList()
.cast<PatientRadiologyResponseModel>();
}
}
// Check if radOrders is empty and set failure
if (radOrders.isEmpty) {
failure = ServerFailure(errorMessage ?? "No radiology orders found");
return;
}
apiResponse = GenericApiModel<List<PatientRadiologyResponseModel>>(
@ -203,6 +211,12 @@ class RadiologyRepoImp implements RadiologyRepo {
}
}
// Check if radOrders is empty and set failure
if (radOrders.isEmpty) {
failure = ServerFailure(errorMessage ?? "No radiology orders found for this appointment");
return;
}
apiResponse = GenericApiModel<List<PatientRadiologyResponseModel>>(
messageStatus: messageStatus,
statusCode: statusCode,

@ -1,12 +1,10 @@
import 'package:easy_localization/easy_localization.dart';
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/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/features/authentication/models/resp_models/authenticated_user_resp_model.dart';
import 'package:hmg_patient_app_new/features/radiology/models/resp_models/radiology_ai_analysis_response_model.dart';
import 'package:hmg_patient_app_new/features/radiology/radiology_repo.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/radiology/radiology_ai_analysis_detailed_page.dart';
import 'package:hmg_patient_app_new/presentation/radiology/radiology_result_page.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
@ -216,12 +214,11 @@ class RadiologyViewModel extends ChangeNotifier {
void setIsRadiologyAIAnalysisNeedsToBeShown(bool value) {
isRadiologyAIAnalysisNeedsToBeShown = value;
notifyListeners();
}
void closeRadiologyAIAnalysis() {
radiologyAiAnalysisResponse = null;
notifyListeners();
}
Future<void> getRadiologyAiAnalysis({required String reportData, required String loadingText, required int langId}) async {
@ -229,7 +226,6 @@ class RadiologyViewModel extends ChangeNotifier {
loadingText: loadingText,
showCloseButton: true,
onCloseTap: () {
setIsRadiologyAIAnalysisNeedsToBeShown(false);
},
);
@ -248,6 +244,10 @@ class RadiologyViewModel extends ChangeNotifier {
if (isRadiologyAIAnalysisNeedsToBeShown) {
radiologyAiAnalysisResponse = apiResponse.data;
notifyListeners();
// Navigate to detailed page
navigationService.push(
CustomPageRoute(direction: AxisDirection.down, page: RadiologyAiAnalysisDetailedPage()),
);
}
}
},

@ -40,7 +40,20 @@ class WaterMonitorViewModel extends ChangeNotifier {
// Units
final List<String> heightUnits = ['cm', 'm', 'ft', 'in'];
final List<String> heightUnitsEn = ['cm', 'm', 'ft', 'in'];
final List<String> heightUnitsAr = ['سم', 'م', 'قدم', 'بوصة'];
final List<String> weightUnits = ['kg', 'lb'];
final List<String> weightUnitsEn = ['kg', 'lb'];
final List<String> weightUnitsAr = ['كجم', 'رطل'];
List<String> getHeightUnitsList() {
return getIt.get<AppState>().isArabic() ? heightUnitsAr : heightUnitsEn;
}
List<String> getWeightUnitsList() {
return getIt.get<AppState>().isArabic() ? weightUnitsAr : weightUnitsEn;
}
// Selected values
String _selectedHeightUnit = 'cm';
@ -62,8 +75,26 @@ class WaterMonitorViewModel extends ChangeNotifier {
String get selectedHeightUnit => _selectedHeightUnit;
// Get the localized display value for the selected height unit
String get selectedHeightUnitDisplay {
final index = heightUnitsEn.indexOf(_selectedHeightUnit);
if (index >= 0) {
return getIt.get<AppState>().isArabic() ? heightUnitsAr[index] : heightUnitsEn[index];
}
return _selectedHeightUnit;
}
String get selectedWeightUnit => _selectedWeightUnit;
// Get the localized display value for the selected weight unit
String get selectedWeightUnitDisplay {
final index = weightUnitsEn.indexOf(_selectedWeightUnit);
if (index >= 0) {
return getIt.get<AppState>().isArabic() ? weightUnitsAr[index] : weightUnitsEn[index];
}
return _selectedWeightUnit;
}
String get selectedActivityLevel => _selectedActivityLevel;
String get selectedNumberOfReminders => _selectedNumberOfReminders;
@ -75,17 +106,75 @@ class WaterMonitorViewModel extends ChangeNotifier {
bool get isGraphView => _isGraphView;
// Activity level options
List<String> get activityLevels =>
["Almost Inactive (no exercise)", "Lightly active", "Lightly active (1-3) days per week", "Super active (very hard exercise)"];
final List<String> activityLevelsEn = [
"Almost Inactive (no exercise)",
"Lightly active",
"Lightly active (1-3) days per week",
"Super active (very hard exercise)"
];
final List<String> activityLevelsAr = [
"غير نشط تقريباً (بدون تمرين)",
"نشط قليلاً",
"نشط قليلاً (1-3) أيام في الأسبوع",
"نشط جداً (تمرين شاق جداً)"
];
List<String> get activityLevels => getIt.get<AppState>().isArabic() ? activityLevelsAr : activityLevelsEn;
// Get the localized display value for the selected activity level
String get selectedActivityLevelDisplay {
final index = activityLevelsEn.indexOf(_selectedActivityLevel);
if (index >= 0) {
return getIt.get<AppState>().isArabic() ? activityLevelsAr[index] : activityLevelsEn[index];
}
return _selectedActivityLevel;
}
// Reminder options
List<String> get reminderOptions => ["1 Time", "2 Time", "3 Time", "4 Time", "5 Time", "6 Time"];
final List<String> reminderOptionsEn = ["1 Time", "2 Time", "3 Time", "4 Time", "5 Time", "6 Time"];
final List<String> reminderOptionsAr = ["مرة واحدة", "مرتين", "3 مرات", "4 مرات", "5 مرات", "6 مرات"];
List<String> get reminderOptions => getIt.get<AppState>().isArabic() ? reminderOptionsAr : reminderOptionsEn;
// Get the localized display value for the selected number of reminders
String get selectedNumberOfRemindersDisplay {
final index = reminderOptionsEn.indexOf(_selectedNumberOfReminders);
if (index >= 0) {
return getIt.get<AppState>().isArabic() ? reminderOptionsAr[index] : reminderOptionsEn[index];
}
return _selectedNumberOfReminders;
}
// Gender options
List<String> get genderOptions => ["Male", "Female"];
final List<String> genderOptionsEn = ["Male", "Female"];
final List<String> genderOptionsAr = ["ذكر", "أنثى"];
List<String> get genderOptions => getIt.get<AppState>().isArabic() ? genderOptionsAr : genderOptionsEn;
// Get the localized display value for the selected gender
String get selectedGenderDisplay {
final index = genderOptionsEn.indexOf(_selectedGender);
if (index >= 0) {
return getIt.get<AppState>().isArabic() ? genderOptionsAr[index] : genderOptionsEn[index];
}
return _selectedGender;
}
// Duration Options
final List<String> durationFiltersEn = ["Daily", "Weekly", "Monthly"];
final List<String> durationFiltersAr = ["يومي", "أسبوعي", "شهري"];
//Duration Options
List<String> get durationFilters => ["Daily", "Weekly", "Monthly"];
List<String> get durationFilters => getIt.get<AppState>().isArabic() ? durationFiltersAr : durationFiltersEn;
// Get the localized display value for the selected duration
String get selectedDurationFilterDisplay {
final index = durationFiltersEn.indexOf(_selectedDuration);
if (index >= 0) {
return getIt.get<AppState>().isArabic() ? durationFiltersAr[index] : durationFiltersEn[index];
}
return _selectedDuration;
}
// Network/data
final AppState _appState = GetIt.instance<AppState>();
@ -503,7 +592,15 @@ class WaterMonitorViewModel extends ChangeNotifier {
// Setters with notification
void setFilterDuration(String duration) async {
_selectedDuration = duration;
// Map Arabic values to English keys
if (getIt.get<AppState>().isArabic()) {
final index = durationFiltersAr.indexOf(duration);
if (index >= 0 && index < durationFiltersEn.length) {
_selectedDuration = durationFiltersEn[index];
}
} else {
_selectedDuration = duration;
}
notifyListeners();
// Fetch new progress data when duration filter changes
await fetchUserProgressForMonitoring();
@ -517,27 +614,67 @@ class WaterMonitorViewModel extends ChangeNotifier {
// Setters with notification
void setGender(String gender) {
_selectedGender = gender;
// Map Arabic values to English keys
if (getIt.get<AppState>().isArabic()) {
final index = genderOptionsAr.indexOf(gender);
if (index >= 0 && index < genderOptionsEn.length) {
_selectedGender = genderOptionsEn[index];
}
} else {
_selectedGender = gender;
}
notifyListeners();
}
void setHeightUnit(String unit) {
_selectedHeightUnit = unit;
// Map Arabic values to English keys
if (getIt.get<AppState>().isArabic()) {
final index = heightUnitsAr.indexOf(unit);
if (index >= 0 && index < heightUnitsEn.length) {
_selectedHeightUnit = heightUnitsEn[index];
}
} else {
_selectedHeightUnit = unit;
}
notifyListeners();
}
void setWeightUnit(String unit) {
_selectedWeightUnit = unit;
// Map Arabic values to English keys
if (getIt.get<AppState>().isArabic()) {
final index = weightUnitsAr.indexOf(unit);
if (index >= 0 && index < weightUnitsEn.length) {
_selectedWeightUnit = weightUnitsEn[index];
}
} else {
_selectedWeightUnit = unit;
}
notifyListeners();
}
void setActivityLevel(String level) {
_selectedActivityLevel = level;
// Map Arabic values to English keys
if (getIt.get<AppState>().isArabic()) {
final index = activityLevelsAr.indexOf(level);
if (index >= 0 && index < activityLevelsEn.length) {
_selectedActivityLevel = activityLevelsEn[index];
}
} else {
_selectedActivityLevel = level;
}
notifyListeners();
}
void setNumberOfReminders(String number) {
_selectedNumberOfReminders = number;
// Map Arabic values to English keys
if (getIt.get<AppState>().isArabic()) {
final index = reminderOptionsAr.indexOf(number);
if (index >= 0 && index < reminderOptionsEn.length) {
_selectedNumberOfReminders = reminderOptionsEn[index];
}
} else {
_selectedNumberOfReminders = number;
}
notifyListeners();
}

@ -0,0 +1,257 @@
import 'dart:convert';
class GetCityInfoList {
CategoriesNames? categoriesNames;
String? category;
String? categoryValue;
int? cityId;
String? cityName;
String? cityNameN;
ColorName? colorName;
String? createdOn;
String? id;
int? iniceId;
bool? isOrderEmpty;
bool? isValuesReversed;
bool? language;
double? latitude;
double? longitude;
String? name;
int? orderNum;
double? temperature;
String? value;
GetCityInfoList({
this.categoriesNames,
this.category,
this.categoryValue,
this.cityId,
this.cityName,
this.cityNameN,
this.colorName,
this.createdOn,
this.id,
this.iniceId,
this.isOrderEmpty,
this.isValuesReversed,
this.language,
this.latitude,
this.longitude,
this.name,
this.orderNum,
this.temperature,
this.value,
});
factory GetCityInfoList.fromRawJson(String str) => GetCityInfoList.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory GetCityInfoList.fromJson(Map<String, dynamic> json) {
ColorName? parseColorName(dynamic value) {
if (value == null) return null;
try {
return colorNameValues.map[value.toString()];
} catch (e) {
print("Unknown color name: $value");
return null;
}
}
CategoriesNames? parseCategoriesNames(dynamic value) {
if (value == null) return null;
try {
return CategoriesNames.fromJson(value as Map<String, dynamic>);
} catch (e) {
print("Error parsing CategoriesNames: $e");
return null;
}
}
return GetCityInfoList(
categoriesNames: parseCategoriesNames(json["CategoriesNames"]),
category: json["Category"],
categoryValue: json["CategoryValue"],
cityId: json["CityID"],
cityName: json["CityName"],
cityNameN: json["CityNameN"],
colorName: parseColorName(json["ColorName"]),
createdOn: json["CreatedOn"],
id: json["ID"],
iniceId: json["IniceID"],
isOrderEmpty: json["IsOrderEmpty"],
isValuesReversed: json["IsValuesReversed"],
language: json["Language"],
latitude: json["Latitude"]?.toDouble(),
longitude: json["Longitude"]?.toDouble(),
name: json["Name"],
orderNum: json["OrderNum"],
temperature: json["Temperature"]?.toDouble(),
value: json["Value"],
);
}
Map<String, dynamic> toJson() => {
"CategoriesNames": categoriesNames?.toJson(),
"Category": category,
"CategoryValue": categoryValue,
"CityID": cityId,
"CityName": cityName,
"CityNameN": cityNameN,
"ColorName": colorNameValues.reverse[colorName],
"CreatedOn": createdOn,
"ID": id,
"IniceID": iniceId,
"IsOrderEmpty": isOrderEmpty,
"IsValuesReversed": isValuesReversed,
"Language": language,
"Latitude": latitude,
"Longitude": longitude,
"Name": name,
"OrderNum": orderNum,
"Temperature": temperature,
"Value": value,
};
}
class CategoriesNames {
Category1? category1;
Category2? category2;
Category3? category3;
Category4? category4;
Category5? category5;
CategoriesNames({
this.category1,
this.category2,
this.category3,
this.category4,
this.category5,
});
factory CategoriesNames.fromRawJson(String str) => CategoriesNames.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory CategoriesNames.fromJson(Map<String, dynamic> json) {
T? parseEnum<T>(dynamic value, EnumValues<T> enumValues, String enumName) {
if (value == null) return null;
try {
return enumValues.map[value.toString()];
} catch (e) {
print("Unknown $enumName value: $value");
return null;
}
}
return CategoriesNames(
category1: parseEnum(json["Category1"], category1Values, "Category1"),
category2: parseEnum(json["Category2"], category2Values, "Category2"),
category3: parseEnum(json["Category3"], category3Values, "Category3"),
category4: parseEnum(json["Category4"], category4Values, "Category4"),
category5: parseEnum(json["Category5"], category5Values, "Category5"),
);
}
Map<String, dynamic> toJson() => {
"Category1": category1Values.reverse[category1],
"Category2": category2Values.reverse[category2],
"Category3": category3Values.reverse[category3],
"Category4": category4Values.reverse[category4],
"Category5": category5Values.reverse[category5],
};
}
enum Category1 {
AT_EXTREME_RISK,
CATEGORY1_EXTREME,
EXTREME,
POOR
}
final category1Values = EnumValues({
"At Extreme Risk": Category1.AT_EXTREME_RISK,
"Extreme\u000d\n": Category1.CATEGORY1_EXTREME,
"Extreme": Category1.EXTREME,
"Poor": Category1.POOR
});
enum Category2 {
AT_HIGH_RISK,
CATEGORY2_VERY_HIGH,
FAIR,
VERY_HIGH
}
final category2Values = EnumValues({
"At High Risk": Category2.AT_HIGH_RISK,
"Very High\u000d\n": Category2.CATEGORY2_VERY_HIGH,
"Fair": Category2.FAIR,
"Very High": Category2.VERY_HIGH
});
enum Category3 {
AT_RISK,
CATEGORY3_HIGH,
GOOD,
HIGH
}
final category3Values = EnumValues({
"At Risk": Category3.AT_RISK,
"High\u000d\n": Category3.CATEGORY3_HIGH,
"Good": Category3.GOOD,
"High": Category3.HIGH
});
enum Category4 {
CATEGORY4_MODERATE,
MODERATE,
NEUTRAL,
VERY_GOOD
}
final category4Values = EnumValues({
"Moderate\u000d\n": Category4.CATEGORY4_MODERATE,
"Moderate": Category4.MODERATE,
"Neutral": Category4.NEUTRAL,
"Very Good": Category4.VERY_GOOD
});
enum Category5 {
BENEFICIAL,
CATEGORY5_LOW,
EXCELLENT,
LOW
}
final category5Values = EnumValues({
"Beneficial": Category5.BENEFICIAL,
"Low\u000d\n": Category5.CATEGORY5_LOW,
"Excellent": Category5.EXCELLENT,
"Low": Category5.LOW
});
enum ColorName {
GREEN,
ORANGE,
RED
}
final colorNameValues = EnumValues({
"green": ColorName.GREEN,
"orange": ColorName.ORANGE,
"red": ColorName.RED
});
class EnumValues<T> {
Map<String, T> map;
late Map<T, String> reverseMap;
EnumValues(this.map);
Map<T, String> get reverse {
reverseMap = map.map((k, v) => MapEntry(v, k));
return reverseMap;
}
}

@ -0,0 +1,72 @@
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/core/location_util.dart';
import 'package:hmg_patient_app_new/features/weather/models/waether_cities_model.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
abstract class WeatherRepo {
Future<Either<Failure, GenericApiModel<List<GetCityInfoList>>>> getCityInfo();
}
class WeatherRepoImp implements WeatherRepo {
final ApiClient apiClient;
final LoggerService loggerService;
WeatherRepoImp({required this.loggerService, required this.apiClient});
@override
Future<Either<Failure, GenericApiModel<List<GetCityInfoList>>>> getCityInfo() async {
Map<String, dynamic> request = {};
try {
GenericApiModel<List<GetCityInfoList>>? apiResponse;
Failure? failure;
await apiClient.post(
WEATHER_INDICATOR,
body: request,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
loggerService.logInfo("Weather API Response: $response");
final list = response['GetCityInfo_List'];
if (list == null || list.isEmpty) {
loggerService.logInfo("Weather API returned empty or null list");
// Return empty list if no city info
apiResponse = GenericApiModel<List<GetCityInfoList>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: [],
);
return;
}
loggerService.logInfo("Parsing ${list.length} city info items");
final cityInfoList = list.map((item) => GetCityInfoList.fromJson(item as Map<String, dynamic>)).toList().cast<GetCityInfoList>();
apiResponse = GenericApiModel<List<GetCityInfoList>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: cityInfoList,
);
} catch (e) {
loggerService.logError("Error parsing weather data: $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,102 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/cupertino.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/location_util.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/features/weather/models/waether_cities_model.dart';
import 'package:hmg_patient_app_new/features/weather/weather_repo.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:permission_handler/permission_handler.dart' show openAppSettings;
import '../../core/app_state.dart' show AppState;
class WeatherMonitorViewModel extends ChangeNotifier {
WeatherRepo weatherRepo;
ErrorHandlerService errorHandlerService;
LocationUtils locationUtils;
bool isLocationAvailable = false;
WeatherMonitorViewModel({required this.weatherRepo, required this.errorHandlerService, required this.locationUtils});
final List<GetCityInfoList> _cityInfoList = [];
bool isLoading = false;
List<GetCityInfoList> get cityInfoList => _cityInfoList;
Future<void> fetchCityInfoList({Function(dynamic)? onSuccess, Function(String)? onError}) async {
isLoading = true;
notifyListeners();
_cityInfoList.clear();
locationUtils.isShowConfirmDialog = true;
locationUtils.getLocation(
isShowConfirmDialog: true,
onFailure: (){
isLoading = false;
notifyListeners();
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.notice.tr(),
getIt.get<NavigationService>().navigatorKey.currentContext!,
child: Utils.getWarningWidget(
loadingText: LocaleKeys.grantLocationPermission.tr(),
isShowActionButtons: true,
onCancelTap: () {
getIt.get<NavigationService>().pop();
},
onConfirmTap: () async {
getIt.get<NavigationService>().pop();
openAppSettings();
}),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
},
onSuccess: (position) async {
isLocationAvailable = true;
final result = await weatherRepo.getCityInfo();
result.fold(
(failure) async {
isLoading = false;
notifyListeners();
await errorHandlerService.handleError(failure: failure);
if (onError != null) {
onError(failure.toString());
}
},
(apiResponse) {
isLoading = false;
if (apiResponse.messageStatus == 2) {
notifyListeners();
if (onError != null) {
onError(apiResponse.errorMessage ?? "Unknown error");
}
} else if (apiResponse.messageStatus == 1) {
final cities = apiResponse.data ?? [];
_cityInfoList.addAll(cities);
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
});
}
Future<bool> checkIfTheLocationIsEnabledOrNot() async {
bool value = await locationUtils.isLocationPermissionEnabled();
isLocationAvailable = value;
notifyListeners();
return value;
}
void initiateFetchWeather() async {
if(await checkIfTheLocationIsEnabledOrNot()){
fetchCityInfoList();
}
}
}

File diff suppressed because it is too large Load Diff

@ -87,6 +87,8 @@ abstract class LocaleKeys {
static const bookSuccessConfirmMore2412 = 'bookSuccessConfirmMore2412';
static const upcomingPaymentPending = 'upcomingPaymentPending';
static const upcomingPaymentNow = 'upcomingPaymentNow';
static const timeUntilAppointment = 'timeUntilAppointment';
static const appointmentTimePassed = 'appointmentTimePassed';
static const upcomingQR = 'upcomingQR';
static const upcomingVirtual = 'upcomingVirtual';
static const upcomingLivecare = 'upcomingLivecare';
@ -198,6 +200,7 @@ abstract class LocaleKeys {
static const enterEmail = 'enterEmail';
static const family = 'family';
static const familyTitle = 'familyTitle';
static const familyTitle2 = 'familyTitle2';
static const myFamily = 'myFamily';
static const addNewMember = 'addNewMember';
static const sentRequests = 'sentRequests';
@ -270,6 +273,7 @@ abstract class LocaleKeys {
static const userViewRequester = 'userViewRequester';
static const userView = 'userView';
static const parking = 'parking';
static const parking2 = 'parking2';
static const alhabiServices = 'alhabiServices';
static const parkingTitle = 'parkingTitle';
static const parkingDescription = 'parkingDescription';
@ -308,13 +312,16 @@ abstract class LocaleKeys {
static const vitalSignObese = 'vitalSignObese';
static const vitalSignOverweight = 'vitalSignOverweight';
static const vitalSignUnderweight = 'vitalSignUnderweight';
static const vitalSignSkinny = 'vitalSignSkinny';
static const myMedical = 'myMedical';
static const myMedicalSubtitle = 'myMedicalSubtitle';
static const myDoctor = 'myDoctor';
static const myDoctorSubtitle = 'myDoctorSubtitle';
static const myDoctorSubtitle2 = 'myDoctorSubtitle2';
static const eye = 'eye';
static const eyeSubtitle = 'eyeSubtitle';
static const insurance = 'insurance';
static const insurance2 = 'insurance2';
static const insuranceSubtitle = 'insuranceSubtitle';
static const updateInsurance = 'updateInsurance';
static const updateInsuranceSubtitle = 'updateInsuranceSubtitle';
@ -329,6 +336,7 @@ abstract class LocaleKeys {
static const monthly = 'monthly';
static const monthlySubtitle = 'monthlySubtitle';
static const sick = 'sick';
static const sick2 = 'sick2';
static const sickSubtitle = 'sickSubtitle';
static const myBalance = 'myBalance';
static const myBalanceSubtitle = 'myBalanceSubtitle';
@ -492,8 +500,10 @@ abstract class LocaleKeys {
static const refferalSubTitle = 'refferalSubTitle';
static const healthCare = 'healthCare';
static const emergency = 'emergency';
static const emergency2 = 'emergency2';
static const erservices = 'erservices';
static const services2 = 'services2';
static const services3 = 'services3';
static const cantSeeProfile = 'cantSeeProfile';
static const loginRegisterNow = 'loginRegisterNow';
static const hmgPharmacy = 'hmgPharmacy';
@ -764,6 +774,7 @@ abstract class LocaleKeys {
static const erConsultation = 'erConsultation';
static const myInvoice = 'myInvoice';
static const invoiceList = 'invoiceList';
static const invoiceList2 = 'invoiceList2';
static const allInvoices = 'allInvoices';
static const hospitals = 'hospitals';
static const clinics = 'clinics';
@ -821,7 +832,9 @@ abstract class LocaleKeys {
static const switchAccount = 'switchAccount';
static const lastLoginBy = 'lastLoginBy';
static const allSet = 'allSet';
static const quickLogin = 'quickLogin';
static const enableQuickLogin = 'enableQuickLogin';
static const enableQuickLoginBtn = 'enableQuickLoginBtn';
static const enableMsg = 'enableMsg';
static const notNow = 'notNow';
static const pendingActivation = 'pendingActivation';
@ -883,6 +896,7 @@ abstract class LocaleKeys {
static const monitor = 'monitor';
static const noSpecialResult = 'noSpecialResult';
static const setTheDateRange = 'setTheDateRange';
static const selectDate = 'selectDate';
static const historyFlowchart = 'historyFlowchart';
static const to = 'to';
static const startDate = 'startDate';
@ -1144,11 +1158,18 @@ abstract class LocaleKeys {
static const recharge = 'recharge';
static const activityLevel = 'activityLevel';
static const selectActivityLevel = 'selectActivityLevel';
static const almostInactive = 'almostInactive';
static const lightlyActive = 'lightlyActive';
static const lightlyActive13Days = 'lightlyActive13Days';
static const superActive = 'superActive';
static const caloriesPerDay = 'caloriesPerDay';
static const dietType = 'dietType';
static const selectDietType = 'selectDietType';
static const bodyFrameSize = 'bodyFrameSize';
static const selectBodyFrameSize = 'selectBodyFrameSize';
static const bodyFrameSmall = 'bodyFrameSmall';
static const bodyFrameMedium = 'bodyFrameMedium';
static const bodyFrameLarge = 'bodyFrameLarge';
static const averageCycleLength = 'averageCycleLength';
static const averageLutealPhase = 'averageLutealPhase';
static const convert = 'convert';
@ -1174,6 +1195,14 @@ abstract class LocaleKeys {
static const carbsProteinFat = 'carbsProteinFat';
static const ovulationPeriod = 'ovulationPeriod';
static const deliveryDueDate = 'deliveryDueDate';
static const bmiCalculatorDesc = 'bmiCalculatorDesc';
static const caloriesCalculatorDesc = 'caloriesCalculatorDesc';
static const bmrCalculatorDesc = 'bmrCalculatorDesc';
static const idealBodyWeightDesc = 'idealBodyWeightDesc';
static const bodyFatCalculatorDesc = 'bodyFatCalculatorDesc';
static const carbsProteinFatDesc = 'carbsProteinFatDesc';
static const ovulationPeriodDesc = 'ovulationPeriodDesc';
static const deliveryDueDateDesc = 'deliveryDueDateDesc';
static const low = 'low';
static const preDiabetic = 'preDiabetic';
static const high = 'high';
@ -1218,6 +1247,7 @@ abstract class LocaleKeys {
static const hmgServices = 'hmgServices';
static const personalServices = 'personalServices';
static const habibWallet = 'habibWallet';
static const habibWallet2 = 'habibWallet2';
static const loginToViewWalletBalance = 'loginToViewWalletBalance';
static const loginToViewMedicalFile = 'loginToViewMedicalFile';
static const addMember = 'addMember';
@ -1307,6 +1337,7 @@ abstract class LocaleKeys {
static const askYourDoctor = 'askYourDoctor';
static const internetPairing = 'internetPairing';
static const requested = 'requested';
static const readyToDownload = 'readyToDownload';
static const youDontHaveAnyMedicalReportsYet = 'youDontHaveAnyMedicalReportsYet';
static const requestMedicalReport = 'requestMedicalReport';
static const youDoNotHaveAnyAppointmentsToRequestMedicalReport = 'youDoNotHaveAnyAppointmentsToRequestMedicalReport';
@ -1451,6 +1482,8 @@ abstract class LocaleKeys {
static const setTimerOfReminder = 'setTimerOfReminder';
static const youHaveAppointmentWithDr = 'youHaveAppointmentWithDr';
static const hours = 'hours';
static const minutes = 'minutes';
static const seconds = 'seconds';
static const secs = 'secs';
static const noAllergiesDataFound = 'noAllergiesDataFound';
static const heartRateDescription = 'heartRateDescription';
@ -1460,6 +1493,7 @@ abstract class LocaleKeys {
static const distanceDescription = 'distanceDescription';
static const overview = 'overview';
static const details = 'details';
static const details2 = 'details2';
static const healthy = 'healthy';
static const warning = 'warning';
static const vitalSignDetails = 'vitalSignDetails';
@ -1522,7 +1556,7 @@ abstract class LocaleKeys {
static const pleaseVisitRoomToTheDoctor = 'pleaseVisitRoomToTheDoctor';
static const erOnlineCheckInRequest = 'erOnlineCheckInRequest';
static const indoor = 'indoor';
static const navigation = 'navigation';
static const thehospital = 'thehospital';
static const health = 'health';
static const calculators = 'calculators';
static const converters = 'converters';
@ -1711,5 +1745,47 @@ abstract class LocaleKeys {
static const sendingAskDrRequest = 'sendingAskDrRequest';
static const askDrRequestSentSuccess = 'askDrRequestSentSuccess';
static const onlineCheckIn24HoursOnly = 'onlineCheckIn24HoursOnly';
static const searchFor = 'searchFor';
static const m = 'm';
static const inch = 'inch';
static const updateEmailAddressTitle = 'updateEmailAddressTitle';
static const enterNewNewContactNumber = 'enterNewNewContactNumber';
static const nationalIdFileNumber = 'nationalIdFileNumber';
static const finda = 'finda';
static const errorFetchingData = 'errorFetchingData';
static const negative = 'negative';
static const calculateCaloriesBasedOnFactors = 'calculateCaloriesBasedOnFactors';
static const conformationCall = 'conformationCall';
static const radiologyLabResults = 'radiologyLabResults';
static const disclaimerRadiology = 'disclaimerRadiology';
static const enterIdentificationNumberWithNoStar = 'enterIdentificationNumberWithNoStar';
static const enterReferralNumber = 'enterReferralNumber';
static const referralNumber = 'referralNumber';
static const phoneHint = 'phoneHint';
static const selectSearchCriteria = 'selectSearchCriteria';
static const selectCriteria = 'selectCriteria';
static const locationServicesDisabled = 'locationServicesDisabled';
static const selectDateTimeKey = 'selectDateTimeKey';
static const medicalKey = 'medicalKey';
static const convertBloodcholesterolInfo = 'convertBloodcholesterolInfo';
static const carbsAlert = 'carbsAlert';
static const bodyFatAlert = 'bodyFatAlert';
static const ibwAlert = 'ibwAlert';
static const glucoseAlert = 'glucoseAlert';
static const selectBloodGroup = 'selectBloodGroup';
static const routePrescription = 'routePrescription';
static const noNearestER = 'noNearestER';
static const loginToUseServiceLiveCare = 'loginToUseServiceLiveCare';
static const mldl = 'mldl';
static const mmol = 'mmol';
static const triglycerideInfo = 'triglycerideInfo';
static const selectSmartWatch = 'selectSmartWatch';
static const selectBloodDonationCity = 'selectBloodDonationCity';
static const selectBloodDonationGender = 'selectBloodDonationGender';
static const selectBloodType = 'selectBloodType';
static const doctors2 = 'doctors2';
static const illurgyInfomation = 'illurgyInfomation';
static const vaccineInfomation = 'vaccineInfomation';
static const approvals1 = 'approvals1';
}

@ -10,6 +10,7 @@ import 'package:flutter/services.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/features/weather/weather_view_model.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/features/ask_doctor/ask_doctor_view_model.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart';
@ -52,6 +53,7 @@ import 'package:hmg_patient_app_new/services/logger_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/theme/app_theme.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_calendar_model.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart' show DateRangeSelectorRangeViewModel;
import 'package:provider/provider.dart';
import 'package:provider/single_child_widget.dart';
@ -242,6 +244,11 @@ void main() async {
),
ChangeNotifierProvider<ServicesPriceListViewModel>(
create: (_) => getIt.get<ServicesPriceListViewModel>(),
) ,
ChangeNotifierProvider<WeatherMonitorViewModel>(
create: (_) => getIt.get<WeatherMonitorViewModel>(),
), ChangeNotifierProvider<DateRangCalenderModel>(
create: (_) => getIt.get<DateRangCalenderModel>(),
)
], child: MyApp()),
), // Wrap your app

File diff suppressed because it is too large Load Diff

@ -222,7 +222,7 @@ class _MyDoctorsPageState extends State<MyDoctorsPage> {
richText: Row(
children: [
"${group.length} ".toText10(isEnglishOnly: true),
"${LocaleKeys.doctors.tr(context: context)} ".toText10()
"${LocaleKeys.doctors2.tr(context: context)} ".toText10()
],
),
// labelText: "${group.length} ${'doctors'}",

@ -1,3 +1,4 @@
import 'dart:async';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
@ -17,7 +18,6 @@ import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_vie
import 'package:hmg_patient_app_new/features/my_appointments/utils/appointment_type.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/appointments/appointment_details_page.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/ask_doctor_request_type_select.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/eye_measurement_details_page.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
@ -27,8 +27,10 @@ import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'dart:ui' as ui;
import 'package:hmg_patient_app_new/presentation/appointments/appointment_payment_page.dart';
import 'package:lottie/lottie.dart';
class AppointmentCard extends StatelessWidget {
class AppointmentCard extends StatefulWidget {
final PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel;
final MyAppointmentsViewModel myAppointmentsViewModel;
final bool isLoading;
@ -56,6 +58,97 @@ class AppointmentCard extends StatelessWidget {
this.contactUsViewModel,
this.isForRate = false});
@override
State<AppointmentCard> createState() => _AppointmentCardState();
}
class _AppointmentCardState extends State<AppointmentCard> {
Timer? _countdownTimer;
Duration? _timeRemaining;
Function(void Function())? _modalSetState;
@override
void initState() {
super.initState();
// Start countdown timer if appointment requires payment
if (widget.patientAppointmentHistoryResponseModel.nextAction == 15) {
_startCountdownTimer();
}
}
@override
void dispose() {
_countdownTimer?.cancel();
super.dispose();
}
void _startCountdownTimer() {
final appointmentDate = DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate!);
final expiryDate = appointmentDate.subtract(const Duration(hours: 4));
setState(() {
_timeRemaining = expiryDate.difference(DateTime.now());
});
_countdownTimer?.cancel();
_countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
final newTimeRemaining = expiryDate.difference(DateTime.now());
if (newTimeRemaining.isNegative) {
timer.cancel();
setState(() {
_timeRemaining = Duration.zero;
});
_modalSetState?.call(() {
_timeRemaining = Duration.zero;
});
} else {
setState(() {
_timeRemaining = newTimeRemaining;
});
_modalSetState?.call(() {
_timeRemaining = newTimeRemaining;
});
}
});
}
// Helper method to build each time unit (number + label)
Widget _buildTimeUnit(String value, String label) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
alignment: Alignment.center,
child: value.toText32(
isBold: true,
color: AppColors.blackColor,
isEnglishOnly: true,
),
),
SizedBox(height: 4.h),
Container(
alignment: Alignment.center,
child: label.toText12(
color: AppColors.greyTextColor,
),
),
],
);
}
// Helper method to build time separator (:)
Widget _buildTimeSeparator() {
return Padding(
padding: EdgeInsets.only(bottom: 16.h, left: 6.w, right: 6.w),
child: ':'.toText32(
isBold: true,
color: AppColors.blackColor,
),
);
}
@override
Widget build(BuildContext context) {
final appState = getIt.get<AppState>();
@ -66,11 +159,11 @@ class AppointmentCard extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
isForRate ? SizedBox() : _buildHeader(context, appState),
widget.isForRate ? SizedBox() : _buildHeader(context, appState),
SizedBox(height: 16.h),
_buildDoctorRow(context),
SizedBox(height: 16.h),
isForRate ? SizedBox() : _buildActionArea(context, appState),
widget.isForRate ? SizedBox() : _buildActionArea(context, appState),
],
),
),
@ -87,7 +180,7 @@ class AppointmentCard extends StatelessWidget {
}
Widget _buildChips(BuildContext context, AppState appState) {
final isLiveCare = !isLoading && patientAppointmentHistoryResponseModel.isLiveCareAppointment!;
final isLiveCare = !widget.isLoading && widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment!;
return Wrap(
alignment: WrapAlignment.start,
@ -96,25 +189,25 @@ class AppointmentCard extends StatelessWidget {
runSpacing: 6.h,
children: [
AppCustomChipWidget(
icon: isLoading ? AppAssets.walkin_appointment_icon : (isLiveCare ? AppAssets.small_livecare_icon : AppAssets.walkin_appointment_icon),
iconColor: isLoading ? AppColors.textColor : (isLiveCare ? Colors.white : AppColors.textColor),
labelText: isLoading ? LocaleKeys.walkin.tr(context: context) : (isLiveCare ? LocaleKeys.livecare.tr(context: context) : LocaleKeys.walkin.tr(context: context)),
backgroundColor: isLoading ? AppColors.greyColor : (isLiveCare ? AppColors.successColor : AppColors.greyColor),
textColor: isLoading ? AppColors.textColor : (isLiveCare ? Colors.white : AppColors.textColor),
).toShimmer2(isShow: isLoading),
icon: widget.isLoading ? AppAssets.walkin_appointment_icon : (isLiveCare ? AppAssets.small_livecare_icon : AppAssets.walkin_appointment_icon),
iconColor: widget.isLoading ? AppColors.textColor : (isLiveCare ? Colors.white : AppColors.textColor),
labelText: widget.isLoading ? LocaleKeys.walkin.tr(context: context) : (isLiveCare ? LocaleKeys.livecare.tr(context: context) : LocaleKeys.walkin.tr(context: context)),
backgroundColor: widget.isLoading ? AppColors.greyColor : (isLiveCare ? AppColors.successColor : AppColors.greyColor),
textColor: widget.isLoading ? AppColors.textColor : (isLiveCare ? Colors.white : AppColors.textColor),
).toShimmer2(isShow: widget.isLoading),
AppCustomChipWidget(
labelText:
isLoading ? 'OutPatient' : (appState.isArabic() ? patientAppointmentHistoryResponseModel.isInOutPatientDescriptionN! : patientAppointmentHistoryResponseModel.isInOutPatientDescription!),
widget.isLoading ? 'OutPatient' : (appState.isArabic() ? widget.patientAppointmentHistoryResponseModel.isInOutPatientDescriptionN! : widget.patientAppointmentHistoryResponseModel.isInOutPatientDescription!),
backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.1),
textColor: AppColors.warningColorYellow,
).toShimmer2(isShow: isLoading),
).toShimmer2(isShow: widget.isLoading),
AppCustomChipWidget(
labelText: isLoading ? 'Booked' : AppointmentType.getAppointmentStatusType(patientAppointmentHistoryResponseModel.patientStatusType!),
labelText: widget.isLoading ? 'Booked' : AppointmentType.getAppointmentStatusType(widget.patientAppointmentHistoryResponseModel.patientStatusType!),
backgroundColor: AppColors.successColor.withValues(alpha: 0.1),
textColor: AppColors.successColor,
).toShimmer2(isShow: isLoading),
).toShimmer2(isShow: widget.isLoading),
],
).toShimmer2(isShow: isLoading);
).toShimmer2(isShow: widget.isLoading);
}
Widget _buildDoctorRow(BuildContext context) {
@ -125,11 +218,11 @@ class AppointmentCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.network(
isLoading ? 'https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png' : patientAppointmentHistoryResponseModel.doctorImageURL!,
widget.isLoading ? 'https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png' : widget.patientAppointmentHistoryResponseModel.doctorImageURL!,
width: 63.h,
height: 63.h,
fit: BoxFit.cover,
).circle(100.r).toShimmer2(isShow: isLoading),
).circle(100.r).toShimmer2(isShow: widget.isLoading),
Transform.translate(
offset: Offset(0.0, -20.h),
child: Container(
@ -149,11 +242,11 @@ class AppointmentCard extends StatelessWidget {
Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 15.w, height: 15.h, iconColor: AppColors.ratingColorYellow),
SizedBox(height: 2.h),
(isFoldable || isTablet)
? "${patientAppointmentHistoryResponseModel.decimalDoctorRate}".toText9(isBold: true, color: AppColors.textColor, isEnglishOnly: true)
: "${patientAppointmentHistoryResponseModel.decimalDoctorRate ?? "0.0"}".toText11(isBold: true, color: AppColors.textColor, isEnglishOnly: true),
? "${widget.patientAppointmentHistoryResponseModel.decimalDoctorRate}".toText9(isBold: true, color: AppColors.textColor, isEnglishOnly: true)
: "${widget.patientAppointmentHistoryResponseModel.decimalDoctorRate ?? "0.0"}".toText11(isBold: true, color: AppColors.textColor, isEnglishOnly: true),
],
),
).circle(100).toShimmer2(isShow: isLoading),
).circle(100).toShimmer2(isShow: widget.isLoading),
),
],
),
@ -164,20 +257,20 @@ class AppointmentCard extends StatelessWidget {
children: [
Row(
children: [
(isLoading ? 'Dr' : "${patientAppointmentHistoryResponseModel.doctorTitle}").toText16(isBold: true, maxlines: 1),
(isLoading ? 'John Doe' : " ${patientAppointmentHistoryResponseModel.doctorNameObj!.truncate(20)}")
.toText16(isBold: true, maxlines: 1, isEnglishOnly: !Utils.isArabicText(patientAppointmentHistoryResponseModel.doctorNameObj ?? "John Doe")),
(widget.isLoading ? 'Dr' : "${widget.patientAppointmentHistoryResponseModel.doctorTitle}").toText16(isBold: true, maxlines: 1),
(widget.isLoading ? 'John Doe' : " ${widget.patientAppointmentHistoryResponseModel.doctorNameObj!.truncate(20)}")
.toText16(isBold: true, maxlines: 1, isEnglishOnly: !Utils.isArabicText(widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "John Doe")),
SizedBox(width: 12.w),
(patientAppointmentHistoryResponseModel.doctorNationalityFlagURL != null && patientAppointmentHistoryResponseModel.doctorNationalityFlagURL!.isNotEmpty)
(widget.patientAppointmentHistoryResponseModel.doctorNationalityFlagURL != null && widget.patientAppointmentHistoryResponseModel.doctorNationalityFlagURL!.isNotEmpty)
? Image.network(
patientAppointmentHistoryResponseModel.doctorNationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SAU.png",
widget.patientAppointmentHistoryResponseModel.doctorNationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SAU.png",
width: 20.h,
height: 15.h,
fit: BoxFit.cover,
)
: SizedBox.shrink(),
],
).toShimmer2(isShow: isLoading),
).toShimmer2(isShow: widget.isLoading),
SizedBox(height: 8.h),
Wrap(
direction: Axis.horizontal,
@ -185,29 +278,29 @@ class AppointmentCard extends StatelessWidget {
runSpacing: 4.h,
children: [
AppCustomChipWidget(
labelText: isLoading
labelText: widget.isLoading
? 'Cardiology'
: (patientAppointmentHistoryResponseModel.clinicName!.length > 20
? '${patientAppointmentHistoryResponseModel.clinicName!.substring(0, 20)}...'
: patientAppointmentHistoryResponseModel.clinicName!),
).toShimmer2(isShow: isLoading),
: (widget.patientAppointmentHistoryResponseModel.clinicName!.length > 20
? '${widget.patientAppointmentHistoryResponseModel.clinicName!.substring(0, 20)}...'
: widget.patientAppointmentHistoryResponseModel.clinicName!),
).toShimmer2(isShow: widget.isLoading),
AppCustomChipWidget(
labelText: isLoading
labelText: widget.isLoading
? 'Olaya'
: (patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital").length > 15
? '${(patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital").substring(0, 15)}...'
: patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital")
.toShimmer2(isShow: isLoading),
: (widget.patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital").length > 15
? '${(widget.patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital").substring(0, 15)}...'
: widget.patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital")
.toShimmer2(isShow: widget.isLoading),
Directionality(
textDirection: ui.TextDirection.ltr,
child: AppCustomChipWidget(
labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w),
icon: AppAssets.appointment_calendar_icon,
richText: isLoading
? 'Cardiology'.toText10().toShimmer2(isShow: isLoading)
: "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)}"
richText: widget.isLoading
? 'Cardiology'.toText10().toShimmer2(isShow: widget.isLoading)
: "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang(DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate), false)}"
.toText10(isEnglishOnly: true, isBold: true),
).toShimmer2(isShow: isLoading),
).toShimmer2(isShow: widget.isLoading),
),
// AppCustomChipWidget(
@ -240,10 +333,10 @@ class AppointmentCard extends StatelessWidget {
}
Widget _buildActionArea(BuildContext context, AppState appState) {
if ((((patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) ||
(patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) ||
!Utils.isClinicAllowedForRebook(patientAppointmentHistoryResponseModel.clinicID ?? 0))) &&
AppointmentType.isArrived(patientAppointmentHistoryResponseModel)) {
if ((((widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) ||
(widget.patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) ||
!Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID ?? 0))) &&
AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) {
// if (((patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) &&
// DateTime.now().difference(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15)) {
// return Row(
@ -325,25 +418,25 @@ class AppointmentCard extends StatelessWidget {
// );
// } else {
return CustomButton(
text: isFromMedicalReport ? LocaleKeys.selectAppointment.tr(context: context) : LocaleKeys.viewDetails.tr(context: context),
text: widget.isFromMedicalReport ? LocaleKeys.selectAppointment.tr(context: context) : LocaleKeys.viewDetails.tr(context: context),
onPressed: () {
if (isFromMedicalReport) {
if (isForFeedback) {
contactUsViewModel!.setPatientFeedbackSelectedAppointment(patientAppointmentHistoryResponseModel);
if (widget.isFromMedicalReport) {
if (widget.isForFeedback) {
widget.contactUsViewModel!.setPatientFeedbackSelectedAppointment(widget.patientAppointmentHistoryResponseModel);
} else {
medicalFileViewModel!.setSelectedMedicalReportAppointment(patientAppointmentHistoryResponseModel);
widget.medicalFileViewModel!.setSelectedMedicalReportAppointment(widget.patientAppointmentHistoryResponseModel);
}
Navigator.pop(context, false);
} else {
Navigator.of(context)
.push(
CustomPageRoute(
page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel),
page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel),
),
)
.then((_) {
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
widget.myAppointmentsViewModel.initAppointmentsViewModel();
widget.myAppointmentsViewModel.getPatientAppointments(true, false);
});
}
},
@ -356,23 +449,23 @@ class AppointmentCard extends StatelessWidget {
padding: EdgeInsets.symmetric(horizontal: 10.w),
// height: isTablet || isFoldable ? 46.h : 40.h,
height: 40.h,
icon: isFromMedicalReport ? AppAssets.checkmark_icon : null,
icon: widget.isFromMedicalReport ? AppAssets.checkmark_icon : null,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
);
// }
} else {
if (isFromMedicalReport) {
if (isForEyeMeasurements) {
if (widget.isFromMedicalReport) {
if (widget.isForEyeMeasurements) {
return SizedBox.shrink();
} else {
return CustomButton(
text: LocaleKeys.selectAppointment.tr(context: context),
onPressed: () {
if (isForFeedback) {
contactUsViewModel!.setPatientFeedbackSelectedAppointment(patientAppointmentHistoryResponseModel);
if (widget.isForFeedback) {
widget.contactUsViewModel!.setPatientFeedbackSelectedAppointment(widget.patientAppointmentHistoryResponseModel);
} else {
medicalFileViewModel!.setSelectedMedicalReportAppointment(patientAppointmentHistoryResponseModel);
widget.medicalFileViewModel!.setSelectedMedicalReportAppointment(widget.patientAppointmentHistoryResponseModel);
}
Navigator.pop(context, false);
},
@ -391,71 +484,75 @@ class AppointmentCard extends StatelessWidget {
);
}
}
return (patientAppointmentHistoryResponseModel.isActiveDoctor ?? true)
? Row(
children: [
Expanded(
flex: 6,
child: (AppointmentType.isArrived(patientAppointmentHistoryResponseModel)
? _getArrivedButton(context)
: CustomButton(
text: AppointmentType.getNextActionText(patientAppointmentHistoryResponseModel.nextAction),
onPressed: () => _goToDetails(context),
backgroundColor: AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.15),
borderColor: AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.01),
textColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction),
fontSize: (isFoldable || isTablet) ? 12.f : 14.f,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
height: 40.h,
icon: AppointmentType.getNextActionIcon(patientAppointmentHistoryResponseModel.nextAction),
iconColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction),
iconSize: 15.h,
))
.toShimmer2(isShow: isLoading),
),
SizedBox(width: 8.h),
Expanded(
flex: 1,
child: Container(
height: (isFoldable || isTablet) ? 50.h : 40.h,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.transparent,
borderRadius: 10.h,
side: BorderSide(
color: AppColors.textColor,
width: 1.2,
),
),
child: Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon,
iconColor: AppColors.textColor,
width: 24.w,
height: 24.h,
fit: BoxFit.contain,
),
),
).toShimmer2(isShow: isLoading).onPress(() {
_goToDetails(context);
}),
),
],
)
return (widget.patientAppointmentHistoryResponseModel.isActiveDoctor ?? true)
? (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) &&
widget.patientAppointmentHistoryResponseModel.isClinicReBookingAllowed == false)
? // Show only View Details button without arrow when rebooking not allowed
_getArrivedButton(context)
: Row(
children: [
Expanded(
flex: 6,
child: (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)
? _getArrivedButton(context)
: CustomButton(
text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction),
onPressed: () => handleAppointmentNextAction(widget.patientAppointmentHistoryResponseModel.nextAction, context),
backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.15),
borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.01),
textColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction),
fontSize: (isFoldable || isTablet) ? 12.f : 14.f,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
height: 40.h,
icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction),
iconColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction),
iconSize: 15.h,
))
.toShimmer2(isShow: widget.isLoading),
),
SizedBox(width: 8.h),
Expanded(
flex: 1,
child: Container(
height: (isFoldable || isTablet) ? 50.h : 40.h,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.transparent,
borderRadius: 10.h,
side: BorderSide(
color: AppColors.textColor,
width: 1.2,
),
),
child: Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon,
iconColor: AppColors.textColor,
width: 24.w,
height: 24.h,
fit: BoxFit.contain,
),
),
).toShimmer2(isShow: widget.isLoading).onPress(() {
_goToDetails(context);
}),
),
],
)
: CustomButton(
text: LocaleKeys.viewDetails.tr(context: context),
onPressed: () {
Navigator.of(context)
.push(
CustomPageRoute(
page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel),
page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel),
),
)
.then((_) {
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
widget.myAppointmentsViewModel.initAppointmentsViewModel();
widget.myAppointmentsViewModel.getPatientAppointments(true, false);
});
},
backgroundColor: AppColors.secondaryLightRedColor,
@ -467,7 +564,7 @@ class AppointmentCard extends StatelessWidget {
padding: EdgeInsets.symmetric(horizontal: 10.w),
// height: isTablet || isFoldable ? 46.h : 40.h,
height: 40.h,
icon: isFromMedicalReport ? AppAssets.checkmark_icon : null,
icon: widget.isFromMedicalReport ? AppAssets.checkmark_icon : null,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
);
@ -475,54 +572,22 @@ class AppointmentCard extends StatelessWidget {
}
Widget _getArrivedButton(BuildContext context) {
// final isRecent = DateTime.now().difference(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15;
//
// if (isRecent) {
// return CustomButton(
// text: LocaleKeys.askDoctor.tr(context: context),
// onPressed: () async {
// LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingDoctorAvailability.tr(context: context));
// await myAppointmentsViewModel.isDoctorAvailable(
// projectID: patientAppointmentHistoryResponseModel.projectID,
// doctorId: patientAppointmentHistoryResponseModel.doctorID,
// clinicId: patientAppointmentHistoryResponseModel.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: patientAppointmentHistoryResponseModel,
// ),
// callBackFunc: () {},
// isFullScreen: false,
// isCloseButtonVisible: true,
// );
// });
// } else {
// print("Doctor is not available");
// }
// });
// },
// backgroundColor: AppColors.secondaryLightRedColor,
// borderColor: AppColors.secondaryLightRedColor,
// textColor: AppColors.primaryRedColor,
// fontSize: (isFoldable || isTablet) ? 12.f : 14.f,
// fontWeight: FontWeight.w600,
// borderRadius: 12.r,
// padding: EdgeInsets.symmetric(horizontal: 10.w),
// // height: isTablet || isFoldable ? 46.h : 40.h,
// height: 40.h,
// icon: AppAssets.ask_doctor_icon,
// iconColor: AppColors.primaryRedColor,
// iconSize: 16.h,
// );
// }
if (widget.patientAppointmentHistoryResponseModel.isClinicReBookingAllowed == false) {
return CustomButton(
text: LocaleKeys.viewDetails.tr(context: context),
onPressed: () => _goToDetails(context),
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: (isFoldable || isTablet) ? 12.f : 14.f,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
height: 40.h,
);
}
// Show Rebook button
return CustomButton(
borderSide: BorderSide(
color: AppColors.textColor,
@ -546,21 +611,21 @@ class AppointmentCard extends StatelessWidget {
}
void _goToDetails(BuildContext context) {
if (isFromMedicalReport) return;
if (isForEyeMeasurements) {
if (widget.isFromMedicalReport) return;
if (widget.isForEyeMeasurements) {
Navigator.of(context).push(
CustomPageRoute(
page: EyeMeasurementDetailsPage(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel),
page: EyeMeasurementDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel),
),
);
} else {
if (!AppointmentType.isArrived(patientAppointmentHistoryResponseModel)) {
bookAppointmentsViewModel.getAppointmentNearestGate(projectID: patientAppointmentHistoryResponseModel.projectID, clinicID: patientAppointmentHistoryResponseModel.clinicID);
if (!AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) {
widget.bookAppointmentsViewModel.getAppointmentNearestGate(projectID: widget.patientAppointmentHistoryResponseModel.projectID, clinicID: widget.patientAppointmentHistoryResponseModel.clinicID);
}
Navigator.of(context)
.push(
CustomPageRoute(
page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel),
page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel),
),
)
.then((_) {
@ -572,22 +637,22 @@ class AppointmentCard extends StatelessWidget {
void openDoctorScheduleCalendar(BuildContext context) async {
final doctor = DoctorsListResponseModel(
clinicID: patientAppointmentHistoryResponseModel.clinicID,
projectID: patientAppointmentHistoryResponseModel.projectID,
doctorID: patientAppointmentHistoryResponseModel.doctorID,
doctorImageURL: patientAppointmentHistoryResponseModel.doctorImageURL,
doctorTitle: patientAppointmentHistoryResponseModel.doctorTitle,
name: patientAppointmentHistoryResponseModel.doctorNameObj,
clinicID: widget.patientAppointmentHistoryResponseModel.clinicID,
projectID: widget.patientAppointmentHistoryResponseModel.projectID,
doctorID: widget.patientAppointmentHistoryResponseModel.doctorID,
doctorImageURL: widget.patientAppointmentHistoryResponseModel.doctorImageURL,
doctorTitle: widget.patientAppointmentHistoryResponseModel.doctorTitle,
name: widget.patientAppointmentHistoryResponseModel.doctorNameObj,
nationalityFlagURL: '',
speciality: [],
clinicName: patientAppointmentHistoryResponseModel.clinicName,
projectName: patientAppointmentHistoryResponseModel.projectName,
clinicName: widget.patientAppointmentHistoryResponseModel.clinicName,
projectName: widget.patientAppointmentHistoryResponseModel.projectName,
);
bookAppointmentsViewModel.setSelectedDoctor(doctor);
widget.bookAppointmentsViewModel.setSelectedDoctor(doctor);
LoaderBottomSheet.showLoader();
await bookAppointmentsViewModel.getDoctorFreeSlots(
await widget.bookAppointmentsViewModel.getDoctorFreeSlots(
isBookingForLiveCare: false,
onSuccess: (respData) async {
LoaderBottomSheet.hideLoader();
@ -612,4 +677,127 @@ class AppointmentCard extends StatelessWidget {
},
);
}
Future<void> handleAppointmentNextAction(nextAction, BuildContext context) async {
switch (nextAction) {
case 0:
// No action needed
_goToDetails(context);
break;
case 10:
// Confirm appointment - go to details
_goToDetails(context);
break;
case 15:
// Pending payment - show waiting modal
// Ensure timer is running before showing modal
if (_countdownTimer == null || !_countdownTimer!.isActive) {
_startCountdownTimer();
}
showCommonBottomSheetWithoutHeight(
context,
title: LocaleKeys.notice.tr(),
child: StatefulBuilder(
builder: (context, setModalState) {
// Store the modal setState callback
_modalSetState = setModalState;
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Lottie.asset(AppAnimations.warningAnimation,
repeat: false, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill),
SizedBox(height: 12,),
LocaleKeys.upcomingPaymentPending.tr(context: context).toText14(
color: AppColors.textColor,
isCenter: true,
),
SizedBox(height: 24.h),
// Countdown Timer - DD : HH : MM : SS format with labels
Directionality(
textDirection: ui.TextDirection.ltr,
child:Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Days
_buildTimeUnit(
_timeRemaining != null ? _timeRemaining!.inDays.toString().padLeft(2, '0') : '00',
LocaleKeys.days.tr(context: context),
),
_buildTimeSeparator(),
// Hours
_buildTimeUnit(
_timeRemaining != null ? _timeRemaining!.inHours.remainder(24).toString().padLeft(2, '0') : '00',
LocaleKeys.hours.tr(context: context),
),
_buildTimeSeparator(),
// Minutes
_buildTimeUnit(
_timeRemaining != null ? _timeRemaining!.inMinutes.remainder(60).toString().padLeft(2, '0') : '00',
LocaleKeys.minutes.tr(context: context),
),
_buildTimeSeparator(),
// Seconds
_buildTimeUnit(
_timeRemaining != null ? _timeRemaining!.inSeconds.remainder(60).toString().padLeft(2, '0') : '00',
LocaleKeys.seconds.tr(context: context),
),
],
)),
SizedBox(height: 24.h),
// Green Acknowledge button with checkmark icon
CustomButton(
text: LocaleKeys.acknowledged.tr(context: context),
onPressed: () {
_modalSetState = null; // Clear callback when closing
Navigator.of(context).pop();
},
backgroundColor: AppColors.successColor,
borderColor: AppColors.successColor,
textColor: Colors.white,
fontSize: 16.f,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
height: 50.h,
icon: AppAssets.checkmark_icon,
iconColor: Colors.white,
),
],
);
},
),
callBackFunc: () {
_modalSetState = null; // Clear callback when closing
},
isFullScreen: false,
isCloseButtonVisible: true,
);
break;
case 20:
// Pay now - navigate to payment page
widget.myAppointmentsViewModel.setIsPatientAppointmentShareLoading(true);
Navigator.of(context).push(
CustomPageRoute(
page: AppointmentPaymentPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel),
),
);
break;
case 50:
// Confirm livecare - go to details
_goToDetails(context);
break;
case 90:
// Check-in - go to details
_goToDetails(context);
break;
default:
// Default - go to details
_goToDetails(context);
}
}
}

@ -24,7 +24,13 @@ import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
class AppointmentDoctorCard extends StatelessWidget {
const AppointmentDoctorCard(
{super.key, required this.patientAppointmentHistoryResponseModel, required this.onRescheduleTap, required this.onCancelTap, required this.onAskDoctorTap, this.renderWidgetForERDisplay = false});
{super.key,
required this.patientAppointmentHistoryResponseModel,
required this.onRescheduleTap,
required this.onCancelTap,
required this.onAskDoctorTap,
this.renderWidgetForERDisplay = false,
this.timerWidget});
final PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel;
final VoidCallback onRescheduleTap;
@ -32,6 +38,8 @@ class AppointmentDoctorCard extends StatelessWidget {
final VoidCallback onAskDoctorTap;
final bool renderWidgetForERDisplay;
final Widget? timerWidget;
@override
Widget build(BuildContext context) {
return Container(
@ -142,43 +150,44 @@ class AppointmentDoctorCard extends StatelessWidget {
],
),
),
patientAppointmentHistoryResponseModel.isLiveCareAppointment!
patientAppointmentHistoryResponseModel.isLiveCareAppointment! || patientAppointmentHistoryResponseModel.isClinicReBookingAllowed! ==false || patientAppointmentHistoryResponseModel.isActiveDoctor! == false
? SizedBox.shrink()
: Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown).onPress(() async {
DoctorsListResponseModel selectedDoctor = DoctorsListResponseModel();
selectedDoctor.doctorID = patientAppointmentHistoryResponseModel.doctorID;
selectedDoctor.doctorImageURL = patientAppointmentHistoryResponseModel.doctorImageURL;
selectedDoctor.name = patientAppointmentHistoryResponseModel.doctorNameObj;
selectedDoctor.doctorTitle = patientAppointmentHistoryResponseModel.doctorTitle;
selectedDoctor.nationalityFlagURL = "";
selectedDoctor.speciality = patientAppointmentHistoryResponseModel.doctorSpeciality;
selectedDoctor.clinicName = patientAppointmentHistoryResponseModel.clinicName;
selectedDoctor.projectName = patientAppointmentHistoryResponseModel.projectName;
selectedDoctor.clinicID = patientAppointmentHistoryResponseModel.clinicID;
selectedDoctor.projectID = patientAppointmentHistoryResponseModel.projectID;
selectedDoctor.doctorID = patientAppointmentHistoryResponseModel.doctorID;
selectedDoctor.doctorImageURL = patientAppointmentHistoryResponseModel.doctorImageURL;
selectedDoctor.name = patientAppointmentHistoryResponseModel.doctorNameObj;
selectedDoctor.doctorTitle = patientAppointmentHistoryResponseModel.doctorTitle;
selectedDoctor.nationalityFlagURL = "";
selectedDoctor.speciality = patientAppointmentHistoryResponseModel.doctorSpeciality;
selectedDoctor.clinicName = patientAppointmentHistoryResponseModel.clinicName;
selectedDoctor.projectName = patientAppointmentHistoryResponseModel.projectName;
selectedDoctor.clinicID = patientAppointmentHistoryResponseModel.clinicID;
selectedDoctor.projectID = patientAppointmentHistoryResponseModel.projectID;
getIt.get<BookAppointmentsViewModel>().setSelectedDoctor(selectedDoctor);
LoaderBottomSheet.showLoader();
await getIt.get<BookAppointmentsViewModel>().getDoctorProfile(onSuccess: (dynamic respData) {
LoaderBottomSheet.hideLoader();
Navigator.of(context).push(
CustomPageRoute(
page: DoctorProfilePage(
isDoctorAllowedToBook: !(patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false),
),
),
);
}, onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
})
getIt.get<BookAppointmentsViewModel>().setSelectedDoctor(selectedDoctor);
LoaderBottomSheet.showLoader();
await getIt.get<BookAppointmentsViewModel>().getDoctorProfile(onSuccess: (dynamic respData) {
LoaderBottomSheet.hideLoader();
Navigator.of(context).push(
CustomPageRoute(
page: DoctorProfilePage(
isDoctorAllowedToBook: !(patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false),
),
),
);
}, onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
})
],
),
SizedBox(height: 8.h),
@ -188,6 +197,8 @@ class AppointmentDoctorCard extends StatelessWidget {
AppointmentType.isArrived(patientAppointmentHistoryResponseModel),
),
),
if (timerWidget != null)
timerWidget ?? SizedBox()
],
),
),
@ -215,26 +226,26 @@ class AppointmentDoctorCard extends StatelessWidget {
iconColor: AppColors.primaryRedColor,
)
:
// !patientAppointmentHistoryResponseModel.isLiveCareAppointment!
// ? CustomButton(
// text: LocaleKeys.rebookSameDoctor.tr(),
// onPressed: () {
// onRescheduleTap();
// },
// backgroundColor: AppColors.greyColor,
// borderColor: AppColors.greyColor,
// textColor: AppColors.blackColor,
// fontSize: 12.f,
// isBold: true,
// borderRadius: 12.r,
// padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0),
// height: 40.h,
// icon: AppAssets.rebook_appointment_icon,
// iconColor: AppColors.blackColor,
// iconSize: 14.h,
// )
// :
SizedBox.shrink();
// !patientAppointmentHistoryResponseModel.isLiveCareAppointment!
// ? CustomButton(
// text: LocaleKeys.rebookSameDoctor.tr(),
// onPressed: () {
// onRescheduleTap();
// },
// backgroundColor: AppColors.greyColor,
// borderColor: AppColors.greyColor,
// textColor: AppColors.blackColor,
// fontSize: 12.f,
// isBold: true,
// borderRadius: 12.r,
// padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0),
// height: 40.h,
// icon: AppAssets.rebook_appointment_icon,
// iconColor: AppColors.blackColor,
// iconSize: 14.h,
// )
// :
SizedBox.shrink();
} else {
return patientAppointmentHistoryResponseModel.isLiveCareAppointment!
? CustomButton(

@ -70,12 +70,13 @@ class LoginScreenState extends State<LoginScreen> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Utils.showLottie(context: context, assetPath: AppAnimations.login, width: 200.h, height: 200.h, repeat: true, fit: BoxFit.cover),
SizedBox(height: 130.h), // Adjusted to sizer unit
LocaleKeys.welcomeToDrSulaiman.tr(context: context).toText32(isBold: true, color: AppColors.textColor),
// SizedBox(height: 130.h),
SizedBox(height: MediaQuery.of(context).size.height * 0.12), // based on the screen height Adjusted to sizer unit
LocaleKeys.welcomeToDrSulaiman.tr(context: context).toText32(isBold: true, color: AppColors.textColor ),
SizedBox(height: 32.h),
Localizations.override(context: context, locale: Locale('en', 'US'), child: Container()), // Force English locale for this widget
TextInputWidget(
labelText: "${LocaleKeys.nationalId.tr(context: context)} / ${LocaleKeys.fileNo.tr(context: context)}",
labelText: "${LocaleKeys.nationalIdFileNumber.tr(context: context)}",
hintText: "xxxxxxxxx",
controller: authVm.nationalIdController,
focusNode: _nationalIdFocusNode,

@ -68,7 +68,7 @@ class QuickLoginState extends State<QuickLogin> {
// Image.asset(AppAssets.lockIcon, height: 100),
Utils.buildSvgWithAssets(icon: AppAssets.biometricLockIcon, iconColor: AppColors.textColor, width: 100.h, height: 100.h),
SizedBox(height: 10.h),
LocaleKeys.enableQuickLogin.tr(context: context).toText26(isBold: true),
LocaleKeys.quickLogin.tr(context: context).toText26(isBold: true),
// Text(
// ' TranslationBase.of(context).enableQuickLogin',
// style: context.dynamicTextStyle(
@ -77,8 +77,8 @@ class QuickLoginState extends State<QuickLogin> {
// color: Colors.black,
// ),
// ),
SizedBox(height: 5.h),
LocaleKeys.enableQuickLogin.tr(context: context).toText16(color: AppColors.quickLoginColor),
// SizedBox(height: 5.h),
// LocaleKeys.enableQuickLogin.tr(context: context).toText16(color: AppColors.quickLoginColor),
// Description
// Text(
// 'TranslationBase.of(context).enableMsg',
@ -95,7 +95,7 @@ class QuickLoginState extends State<QuickLogin> {
children: [
Expanded(
child: CustomButton(
text: LocaleKeys.enableQuickLogin.tr(context: context),
text: LocaleKeys.enableQuickLoginBtn.tr(context: context),
onPressed: () {
widget.onPressed();
},

@ -73,7 +73,7 @@ class _RegisterNew extends State<RegisterNewStep2> {
onLanguageChanged: (lang) {},
hideLogoAndLang: true,
),
bottomSheet: Container(
bottomSheet: Container(
// height: 200.h,
width: MediaQuery.of(context).size.width,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
@ -81,51 +81,52 @@ class _RegisterNew extends State<RegisterNewStep2> {
customBorder: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)),
hasShadow: true,
),
child: Row(
children: [
Expanded(
child: CustomButton(
text: LocaleKeys.cancel.tr(),
icon: AppAssets.cancel,
onPressed: () {
Navigator.of(context).pop();
// authVM!.clearDefaultInputValues();
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
iconColor: AppColors.primaryRedColor,
child: Row(
children: [
Expanded(
child: CustomButton(
text: LocaleKeys.cancel.tr(),
icon: AppAssets.cancel,
onPressed: () {
Navigator.of(context).pop();
// authVM!.clearDefaultInputValues();
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
iconColor: AppColors.primaryRedColor,
),
),
),
SizedBox(width: 16.w),
Expanded(
child: CustomButton(
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
text: LocaleKeys.confirm.tr(),
icon: AppAssets.confirm,
iconColor: AppColors.whiteColor,
onPressed: () {
if (appState.getUserRegistrationPayload.zipCode != CountryEnum.saudiArabia.countryCode) {
if (ValidationUtils.validateUaeRegistration(
name: authVM!.nameController.text,
gender: authVM!.genderType,
country: authVM!.pickedCountryByUAEUser,
maritalStatus: authVM!.maritalStatus,
onOkPress: () {
Navigator.of(context).pop();
})) {
SizedBox(width: 16.w),
Expanded(
child: CustomButton(
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
text: LocaleKeys.confirm.tr(),
icon: AppAssets.confirm,
iconColor: AppColors.whiteColor,
onPressed: () {
if (appState.getUserRegistrationPayload.zipCode != CountryEnum.saudiArabia.countryCode) {
if (ValidationUtils.validateUaeRegistration(
name: authVM!.nameController.text,
gender: authVM!.genderType,
country: authVM!.pickedCountryByUAEUser,
maritalStatus: authVM!.maritalStatus,
onOkPress: () {
Navigator.of(context).pop();
})) {
showModel(context: context);
}
} else {
showModel(context: context);
}
} else {
showModel(context: context);
}
},
),
)
],
).paddingAll(16.h),),
},
),
)
],
).paddingAll(16.h),
),
body: GestureDetector(
onTap: () {
FocusScope.of(context).unfocus();

@ -148,8 +148,8 @@ class _BloodDonationPageState extends State<BloodDonationPage> {
children: [
LocaleKeys.city.tr().toText16(color: AppColors.textColor, isBold: true),
(appState.isArabic()
? (bloodDonationVM.selectedCity?.descriptionN ?? LocaleKeys.select.tr())
: bloodDonationVM.selectedCity?.description ?? LocaleKeys.select.tr(context: context))
? (bloodDonationVM.selectedCity?.descriptionN ?? LocaleKeys.selectBloodDonationCity.tr())
: bloodDonationVM.selectedCity?.description ?? LocaleKeys.selectBloodDonationCity.tr(context: context))
.toText14(color: AppColors.greyTextColor, isBold: true),
],
),
@ -176,8 +176,8 @@ class _BloodDonationPageState extends State<BloodDonationPage> {
children: [
LocaleKeys.gender.tr().toText16(color: AppColors.textColor, isBold: true),
(appState.isArabic()
? (bloodDonationVM.selectedGender?.typeAr ?? LocaleKeys.select.tr())
: bloodDonationVM.selectedGender?.type ?? LocaleKeys.select.tr(context: context))
? (bloodDonationVM.selectedGender?.typeAr ?? LocaleKeys.selectBloodDonationGender.tr())
: bloodDonationVM.selectedGender?.type ?? LocaleKeys.selectBloodDonationGender.tr(context: context))
.toText14(color: AppColors.greyTextColor, isBold: true),
],
),
@ -208,8 +208,8 @@ class _BloodDonationPageState extends State<BloodDonationPage> {
LocaleKeys.bloodType.tr().toText16(color: AppColors.textColor, isBold: true),
// bloodDonationVM.selectedBloodType?.toText14(color: AppColors.greyTextColor, isBold: true),
(appState.isArabic()
? (bloodDonationVM.selectedBloodType ?? LocaleKeys.select.tr())
: bloodDonationVM.selectedBloodType ?? LocaleKeys.select.tr(context: context))
? (bloodDonationVM.selectedBloodType ?? LocaleKeys.selectBloodType.tr())
: bloodDonationVM.selectedBloodType ?? LocaleKeys.selectBloodType.tr(context: context))
.toText14(color: AppColors.greyTextColor, isBold: true)
],
),
@ -219,7 +219,7 @@ class _BloodDonationPageState extends State<BloodDonationPage> {
],
).onPress(() {
showCommonBottomSheetWithoutHeight(context,
title: LocaleKeys.select.tr(context: context),
title: LocaleKeys.selectBloodGroup.tr(context: context),
isDismissible: true,
child: SelectBloodGroupWidget(isArabic: appState.isArabic(), bloodDonationViewModel: bloodDonationVM),
callBackFunc: () {});

@ -37,7 +37,12 @@ class SelectGenderWidget extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8.h,
children: [GenderTypeEnum.values[index].name.toCamelCase.toText16(color: AppColors.textColor, isBold: true)],
children: [
(isArabic
? GenderTypeEnum.values[index].typeAr
: GenderTypeEnum.values[index].type)
.toText16(color: AppColors.textColor, isBold: true)
],
),
),
Transform.flip(
@ -52,7 +57,10 @@ class SelectGenderWidget extends StatelessWidget {
),
],
).paddingSymmetrical(16.h, 16.h).onPress(() {
bloodDonationViewModel.onGenderChange(GenderTypeEnum.values[index].name.toCamelCase);
final genderValue = isArabic
? GenderTypeEnum.values[index].typeAr
: GenderTypeEnum.values[index].type;
bloodDonationViewModel.onGenderChange(genderValue);
Navigator.of(context).pop();
}));
},

@ -472,7 +472,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.searchByClinic.tr(context: context).toText14(color: AppColors.textColor, isBold: true),
LocaleKeys.tapToSelectClinic.tr(context: context).toText12(color: AppColors.primaryRedColor, isBold: true),
LocaleKeys.tapToSelect.tr(context: context).toText12(color: AppColors.primaryRedColor, isBold: true),
],
),
],
@ -534,7 +534,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.searchByRegion.tr(context: context).toText14(color: AppColors.textColor, isBold: true),
LocaleKeys.centralRegion.tr(context: context).toText12(color: AppColors.primaryRedColor, isBold: true),
LocaleKeys.tapToSelect.tr(context: context).toText12(color: AppColors.primaryRedColor, isBold: true),
],
),
],
@ -907,7 +907,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
),
SizedBox(height: 36.h),
CustomButton(
text: "Login to use this service",
text: LocaleKeys.loginToUseServiceLiveCare.tr(),
onPressed: () async {
await authVM.onLoginPressed();
},

@ -1,6 +1,8 @@
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';
@ -59,7 +61,7 @@ class DoctorsFilters extends StatelessWidget{
Text(
LocaleKeys.filters.tr(),
style:TextStyle(
fontFamily: 'Poppins',
fontFamily: getIt.get<AppState>().isArabic() ? 'CairoArabic' : 'Poppins',
fontWeight: FontWeight.w600,
fontSize: 27.f,
color: AppColors.textColor,
@ -69,7 +71,7 @@ class DoctorsFilters extends StatelessWidget{
Text(
LocaleKeys.clearAllFilters.tr(),
style:TextStyle(
fontFamily: 'Poppins',
fontFamily: getIt.get<AppState>().isArabic() ? 'CairoArabic' : 'Poppins',
fontWeight: FontWeight.w600,
fontSize: 14.f,
color: AppColors.errorColor
@ -199,7 +201,7 @@ class DoctorsFilters extends StatelessWidget{
Text(
title,
style:TextStyle(
fontFamily: 'Poppins',
fontFamily: getIt.get<AppState>().isArabic() ? 'CairoArabic' : 'Poppins',
fontWeight: FontWeight.w600,
fontSize: 16.f,
color: AppColors.textColor,

@ -236,7 +236,7 @@ class DoctorProfilePage extends StatelessWidget {
onSuccess: (dynamic respData) async {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.pickADate.tr(),
title: LocaleKeys.pickADate.tr() ,
context,
child: AppointmentCalendar(),
isFullScreen: false,

@ -240,6 +240,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
],
).paddingSymmetrical(24.h, 0.h),
(immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0" || immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0.0")
// (true)
? CustomButton(
text: LocaleKeys.confirmLiveCare.tr(context: context),
onPressed: () async {

@ -3,6 +3,7 @@ import 'dart:async';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:hmg_patient_app_new/core/app_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';
@ -22,6 +23,7 @@ import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/widge
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/input_widget.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';
@ -68,6 +70,34 @@ class _SelectImmediateLiveCareClinicPageState extends State<SelectImmediateLiveC
child: Consumer<ImmediateLiveCareViewModel>(builder: (context, immediateLiveCareVM, child) {
return Column(
children: [
TextInputWidget(
labelText: LocaleKeys.search.tr(context: context),
hintText: LocaleKeys.clinicName.tr(context: context),
controller: searchEditingController,
isEnable: true,
prefix: null,
autoFocus: false,
isBorderAllowed: false,
keyboardType: TextInputType.text,
focusNode: textFocusNode,
suffix: searchEditingController.text.isNotEmpty
? GestureDetector(
onTap: () {
searchEditingController.clear();
immediateLiveCareVM.filterClinic("");
textFocusNode.unfocus();
},
child: Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown),
)
: null,
onChange: (value) {
immediateLiveCareVM.filterClinic(value!);
},
padding: EdgeInsets.symmetric(
vertical: ResponsiveExtension(10).h,
horizontal: ResponsiveExtension(15).h,
),
),
ListView.separated(
padding: EdgeInsets.only(top: 16.h),
shrinkWrap: true,

@ -90,12 +90,14 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
.toString()
.toText16(isBold: true, maxlines: 1),
SizedBox(width: 12.w),
(bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL != null && bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL!.isNotEmpty )? Image.network(
bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SAU.png",
width: 20.h,
height: 15.h,
fit: BoxFit.cover,
) : SizedBox.shrink(),
(bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL != null && bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL!.isNotEmpty)
? Image.network(
bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SAU.png",
width: 20.h,
height: 15.h,
fit: BoxFit.cover,
)
: SizedBox.shrink(),
],
),
SizedBox(height: 2.h),
@ -162,7 +164,9 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
runSpacing: 4.h,
children: [
AppCustomChipWidget(labelText: "${appState.getAuthenticatedUser()!.age} ${LocaleKeys.yearsOld.tr(context: context)}"),
AppCustomChipWidget(labelText: "${LocaleKeys.gender.tr(context: context)}: ${appState.getAuthenticatedUser()?.gender == 1 ? LocaleKeys.malE.tr(context: context) : LocaleKeys.femaleGender.tr(context: context)}"),
AppCustomChipWidget(
labelText:
"${LocaleKeys.gender.tr(context: context)}: ${appState.getAuthenticatedUser()?.gender == 1 ? LocaleKeys.malE.tr(context: context) : LocaleKeys.femaleGender.tr(context: context)}"),
],
),
],
@ -191,19 +195,19 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
bookAppointmentsViewModel.appointmentNearestGateResponseModel != null
? Wrap(
direction: Axis.horizontal,
spacing: 8.w,
runSpacing: 8.h,
children: [
AppCustomChipWidget(
labelText: bookAppointmentsVM.isAppointmentNearestGateLoading
? "Floor: Ground Floor"
spacing: 8.w,
runSpacing: 8.h,
children: [
AppCustomChipWidget(
labelText: bookAppointmentsVM.isAppointmentNearestGateLoading
? "Floor: Ground Floor"
: "${LocaleKeys.floor.tr(context: context)} ${getIt.get<AppState>().isArabic() ? bookAppointmentsViewModel.appointmentNearestGateResponseModel!.clinicLocationN : bookAppointmentsViewModel.appointmentNearestGateResponseModel!.clinicLocation}",
).toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading),
AppCustomChipWidget(
labelText:
AppCustomChipWidget(
labelText:
"${LocaleKeys.nearestGate.tr(context: context)} ${getIt.get<AppState>().isArabic() ? bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumberN : bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumber}")
.toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading),
],
],
)
: SizedBox.shrink(),
],
@ -317,8 +321,8 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
void initiateBookAppointment() async {
// LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: bookAppointmentsViewModel.isPatientRescheduleAppointment ? LocaleKeys.reschedulingAppo.tr(context: context) : LocaleKeys.bookingYourAppointment.tr(context: context));
// LoaderBottomSheet.showLoader(loadingText: bookAppointmentsViewModel.isPatientRescheduleAppointment ? LocaleKeys.reschedulingAppo.tr(context: context) : LocaleKeys.bookingYourAppointment.tr(context: context));
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr(context: context));
LoaderBottomSheet.showLoader(
loadingText: bookAppointmentsViewModel.isPatientRescheduleAppointment ? LocaleKeys.reschedulingAppo.tr(context: context) : LocaleKeys.bookingYourAppointment.tr(context: context));
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
if (bookAppointmentsViewModel.isLiveCareSchedule) {
@ -329,23 +333,16 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
LoaderBottomSheet.hideLoader();
await Future.delayed(Duration(milliseconds: 50)).then((value) async {
// LoaderBottomSheet.showLoader(loadingText: LocaleKeys.appointmentSuccess.tr());
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()),
callBackFunc: () {
bookAppointmentsViewModel.setIsPatientRescheduleAppointment(false);
bookAppointmentsViewModel.setIsLiveCareSchedule(false);
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
},
isFullScreen: false,
isCloseButtonVisible: false,
isAutoDismiss: true
);
showCommonBottomSheetWithoutHeight(context, child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()), callBackFunc: () {
bookAppointmentsViewModel.setIsPatientRescheduleAppointment(false);
bookAppointmentsViewModel.setIsLiveCareSchedule(false);
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
}, isFullScreen: false, isCloseButtonVisible: false, isAutoDismiss: true);
});
});
} else {
@ -368,23 +365,11 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
}, onSuccess: (apiResp) async {
LoaderBottomSheet.hideLoader();
await Future.delayed(Duration(milliseconds: 50)).then((value) async {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()).paddingSymmetrical(0.h, 24.h),
callBackFunc: () {
bookAppointmentsViewModel.setIsLiveCareSchedule(false);
bookAppointmentsViewModel.setIsPatientRescheduleAppointment(false);
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
},
isFullScreen: false,
isCloseButtonVisible: false,
isAutoDismiss: true
);
showCommonBottomSheetWithoutHeight(context, child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()).paddingSymmetrical(0.h, 24.h), callBackFunc: () {
bookAppointmentsViewModel.setIsLiveCareSchedule(false);
bookAppointmentsViewModel.setIsPatientRescheduleAppointment(false);
Navigator.pushAndRemoveUntil(context, CustomPageRoute(page: LandingNavigation()), (r) => false);
}, isFullScreen: false, isCloseButtonVisible: false, isAutoDismiss: true);
});
});
}

@ -55,12 +55,15 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
late AppointmentViaRegionViewmodel regionalViewModel;
late AppState appState;
late BookAppointmentsViewModel bookAppointmentsViewModel;
late String name;
@override
void initState() {
scheduleMicrotask(() {
bookAppointmentsViewModel.getClinics();
});
name = (widget.calculatorType?.name.toLowerCase() == "bmi" ? widget.calculatorType?.name.toUpperCase() : widget.calculatorType?.name.toCamelCase) ?? "";
super.initState();
}
@ -77,28 +80,29 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
appState = getIt.get<AppState>();
if (widget.calculatorType != null) {
return CollapsingListView(
title: "Your ${widget.calculatorType!.name.toCamelCase}",
title: appState.isArabic() ? widget.calculatorType!.displayName.toCamelCase : "Your ${widget.calculatorType!.displayName.toCamelCase}",
bottomChild: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.only(topLeft: Radius.circular(24.r), topRight: Radius.circular(24.r))),
padding: EdgeInsets.symmetric(vertical: 20.h, horizontal: 20.h),
child: CustomButton(
text: LocaleKeys.bookAppo.tr(context: context),
onPressed: () {
if (widget.calculatedResult["clinicId"] == null) {
bookAppointmentsViewModel.setIsDoctorsListLoading(true);
bookAppointmentsViewModel.isGetDocForHealthCal = true;
bookAppointmentsViewModel.calculationID = widget.calculatedResult["calculationID"];
Navigator.push(context, CustomPageRoute(page: SelectDoctorPage()));
} else {
onClinicSelected(GetClinicsListResponseModel(clinicID: widget.calculatedResult["clinicId"], clinicDescription: "asdfds", clinicDescriptionN: "asdfds"));
}
},
icon: null,
fontSize: 16.f,
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
borderRadius: 12.r,
fontWeight: FontWeight.w600,),
text: LocaleKeys.bookAppo.tr(context: context),
onPressed: () {
if (widget.calculatedResult["clinicId"] == null) {
bookAppointmentsViewModel.setIsDoctorsListLoading(true);
bookAppointmentsViewModel.isGetDocForHealthCal = true;
bookAppointmentsViewModel.calculationID = widget.calculatedResult["calculationID"];
Navigator.push(context, CustomPageRoute(page: SelectDoctorPage()));
} else {
onClinicSelected(GetClinicsListResponseModel(clinicID: widget.calculatedResult["clinicId"], clinicDescription: "asdfds", clinicDescriptionN: "asdfds"));
}
},
icon: null,
fontSize: 16.f,
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
borderRadius: 12.r,
fontWeight: FontWeight.w600,
),
),
child: getCalculatorResultWidget(type: widget.calculatorType!, calculatedResult: widget.calculatedResult).paddingSymmetrical(18.w, 24.h),
);
@ -266,6 +270,7 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
}
Widget resultWidget({required HealthCalculatorsTypeEnum type, dynamic calculatedResult}) {
AppState appState = getIt<AppState>();
return Column(
children: [
if (type == HealthCalculatorsTypeEnum.bmi) ...[
@ -655,7 +660,7 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
borderRadius: 8.h,
textColor: AppColors.textColor),
SizedBox(height: 16.h),
"Fertility Window".toText16(color: AppColors.textColor, isBold: true),
appState.isArabic() ? "فترة الخصوبة".toText16(color: AppColors.textColor, isBold: true) : "Fertility Window".toText16(color: AppColors.textColor, isBold: true),
SizedBox(height: 8.h),
Row(
children: [
@ -675,8 +680,11 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
],
),
SizedBox(height: 8.h),
"This is your fertile window. Maintaining a healthy calorie range supports your body during this period, considering your Basal Metabolic Rate and activity. Your Body Mass Index is within the healthy range."
.toText12(height: 1.5, color: AppColors.textColorLight, isBold: true)
appState.isArabic()
? "هذه هي فترة خصوبتك. يساعد الحفاظ على نطاق صحي من السعرات الحرارية في دعم جسمك خلال هذه الفترة، مع مراعاة معدل الأيض الأساسي ومستوى نشاطك. مؤشر كتلة جسمك ضمن النطاق الصحي."
.toText12(height: 1.5, color: AppColors.textColorLight, isBold: true)
: "This is your fertile window. Maintaining a healthy calorie range supports your body during this period, considering your Basal Metabolic Rate and activity. Your Body Mass Index is within the healthy range."
.toText12(height: 1.5, color: AppColors.textColorLight, isBold: true)
],
),
),
@ -697,7 +705,7 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
),
Row(
children: [
"Tips to keep in mind".toText16(color: AppColors.textColor, isBold: true),
appState.isArabic() ? "نصائح هامة".toText16(color: AppColors.textColor, isBold: true) : "Tips to keep in mind".toText16(color: AppColors.textColor, isBold: true),
],
),
],
@ -705,13 +713,13 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
SizedBox(height: 8.h),
Column(
children: [
_buildTipRow("Drink before you feel thirsty."),
_buildTipRow(appState.isArabic() ? "اشربي الماء قبل الشعور بالعطش" : "Drink before you feel thirsty."),
SizedBox(height: 4.h),
_buildTipRow("Keep a refillable bottle next to you."),
_buildTipRow(appState.isArabic() ? "احتفظي بزجاجة ماء قابلة لإعادة التعبئة بجانبك" : "Keep a refillable bottle next to you."),
SizedBox(height: 4.h),
_buildTipRow("Track your daily intake to stay motivated."),
_buildTipRow(appState.isArabic() ? "تابعي استهلاكك اليومي للحفاظ على حماسك" : "Track your daily intake to stay motivated."),
SizedBox(height: 4.h),
_buildTipRow("Choose sparkling water instead of soda."),
_buildTipRow(appState.isArabic() ? "اختاري المياه الفوارة بدلاً من المشروبات الغازية" : "Choose sparkling water instead of soda."),
],
).paddingOnly(left: 4.w, right: 4.w)
],
@ -1375,6 +1383,7 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
}
}
}
bookAppointmentsViewModel.addListener(listener);
if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_REGION || regionalViewModel.regionBottomSheetType == RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER) {

@ -189,7 +189,7 @@ class _AppointmentCalendarState extends State<AppointmentCalendar> {
SizedBox(height: 16.h),
CustomButton(
text: LocaleKeys.select.tr(context: context),
text: LocaleKeys.selectDateTimeKey.tr(context: context),
isDisabled: dayEvents.isEmpty,
onPressed: () async {
if (appState.isAuthenticated) {

@ -0,0 +1,441 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.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/features/my_appointments/models/reminder_type.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/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';
enum ReminderBottomSheetStep {
success,
reminderConfirmation,
reminderTypeSelection,
creatingReminder,
reminderSuccess,
reminderError,
}
class AppointmentSuccessReminderBottomSheet extends StatefulWidget {
final PatientAppointmentHistoryResponseModel appointmentModel;
final MyAppointmentsViewModel myAppointmentsViewModel;
final VoidCallback onComplete;
const AppointmentSuccessReminderBottomSheet({
Key? key,
required this.appointmentModel,
required this.myAppointmentsViewModel,
required this.onComplete,
}) : super(key: key);
@override
State<AppointmentSuccessReminderBottomSheet> createState() => _AppointmentSuccessReminderBottomSheetState();
}
class _AppointmentSuccessReminderBottomSheetState extends State<AppointmentSuccessReminderBottomSheet> {
ReminderBottomSheetStep currentStep = ReminderBottomSheetStep.success;
ReminderType? selectedReminderType;
String errorMessage = '';
bool isProcessing = false;
@override
void initState() {
super.initState();
// Auto-progress to reminder confirmation after showing success
Future.delayed(Duration(milliseconds: 2000), () {
if (mounted) {
setState(() {
currentStep = ReminderBottomSheetStep.reminderConfirmation;
});
}
});
}
void _handleNoReminder() {
Navigator.of(context).pop();
widget.onComplete();
}
void _handleYesReminder() {
setState(() {
currentStep = ReminderBottomSheetStep.reminderTypeSelection;
});
}
void _selectReminderType(ReminderType type) {
setState(() {
selectedReminderType = type;
});
}
Future<void> _createReminder() async {
if (selectedReminderType == null) return;
setState(() {
currentStep = ReminderBottomSheetStep.creatingReminder;
isProcessing = true;
});
await widget.myAppointmentsViewModel.toggleReminder(
context: context,
shouldCreateReminder: true,
reminderType: selectedReminderType!,
patientAppointmentHistoryResponseModel: widget.appointmentModel,
onSuccess: (result) {
if (mounted) {
setState(() {
currentStep = ReminderBottomSheetStep.reminderSuccess;
isProcessing = false;
});
// Auto-close after showing success
Future.delayed(Duration(milliseconds: 2000), () {
if (mounted) {
Navigator.of(context).pop();
widget.onComplete();
}
});
}
},
onError: (error) {
if (mounted) {
setState(() {
currentStep = ReminderBottomSheetStep.reminderError;
errorMessage = error;
isProcessing = false;
});
}
},
);
}
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.vertical(top: Radius.circular(24.r)),
),
padding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 24.h),
child: AnimatedSwitcher(
duration: Duration(milliseconds: 300),
child: _buildStepContent(),
),
);
}
Widget _buildStepContent() {
switch (currentStep) {
case ReminderBottomSheetStep.success:
return _buildSuccessStep();
case ReminderBottomSheetStep.reminderConfirmation:
return _buildReminderConfirmationStep();
case ReminderBottomSheetStep.reminderTypeSelection:
return _buildReminderTypeSelectionStep();
case ReminderBottomSheetStep.creatingReminder:
return _buildCreatingReminderStep();
case ReminderBottomSheetStep.reminderSuccess:
return _buildReminderSuccessStep();
case ReminderBottomSheetStep.reminderError:
return _buildReminderErrorStep();
}
}
Widget _buildSuccessStep() {
return Column(
key: ValueKey('success'),
mainAxisSize: MainAxisSize.min,
children: [
Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr(context: context)),
SizedBox(height: 16.h),
Center(
child: "Your appointment has been booked successfully!".toText14(
color: AppColors.greyTextColor,
),
),
],
);
}
Widget _buildReminderConfirmationStep() {
return Column(
key: ValueKey('confirmation'),
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.notifications_active_outlined,
size: 56.h,
color: AppColors.primaryRedColor,
),
SizedBox(height: 16.h),
Center(child: "Add Reminder?".toText20(isBold: true)),
SizedBox(height: 12.h),
Center(
child: "Would you like to add a reminder for this appointment?".toText14(
color: AppColors.greyTextColor,
),
),
SizedBox(height: 24.h),
Row(
children: [
Expanded(
child: CustomButton(
text: LocaleKeys.cancel.tr(context: context),
onPressed: _handleNoReminder,
backgroundColor: AppColors.whiteColor,
borderColor: AppColors.greyTextColor,
textColor: AppColors.greyTextColor,
fontSize: 14.f,
fontWeight: FontWeight.w600,
borderRadius: 12,
height: 48.h,
),
),
SizedBox(width: 12.w),
Expanded(
child: CustomButton(
text: LocaleKeys.confirm.tr(context: context),
onPressed: _handleYesReminder,
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: Colors.white,
fontSize: 14.f,
fontWeight: FontWeight.w600,
borderRadius: 12,
height: 48.h,
),
),
],
),
],
);
}
Widget _buildReminderTypeSelectionStep() {
return Column(
key: ValueKey('type_selection'),
mainAxisSize: MainAxisSize.min,
children: [
Center(child: "Select Reminder Type".toText20(isBold: true)),
SizedBox(height: 8.h),
Center(
child: "Choose what type of reminder you want to set".toText14(
color: AppColors.greyTextColor,
),
),
SizedBox(height: 24.h),
_buildReminderTypeCard(
type: ReminderType.appointment,
icon: Icons.event_outlined,
title: "Appointment Reminder",
description: "Get reminded about your upcoming appointment",
),
SizedBox(height: 12.h),
_buildReminderTypeCard(
type: ReminderType.payment,
icon: Icons.payment_outlined,
title: "Payment Reminder",
description: "Get reminded about payment for this appointment",
),
SizedBox(height: 12.h),
_buildReminderTypeCard(
type: ReminderType.checkIn,
icon: Icons.check_circle_outline,
title: "Check-In Reminder",
description: "Get reminded to check in for your appointment",
),
SizedBox(height: 24.h),
Row(
children: [
Expanded(
child: CustomButton(
text: LocaleKeys.cancel.tr(context: context),
onPressed: () {
setState(() {
currentStep = ReminderBottomSheetStep.reminderConfirmation;
selectedReminderType = null;
});
},
backgroundColor: AppColors.whiteColor,
borderColor: AppColors.greyTextColor,
textColor: AppColors.greyTextColor,
fontSize: 14.f,
fontWeight: FontWeight.w600,
borderRadius: 12,
height: 48.h,
),
),
SizedBox(width: 12.w),
Expanded(
child: CustomButton(
text: "Continue",
onPressed: selectedReminderType != null ? _createReminder : null,
backgroundColor: selectedReminderType != null ? AppColors.primaryRedColor : AppColors.greyTextColor.withAlpha(76),
borderColor: selectedReminderType != null ? AppColors.primaryRedColor : AppColors.greyTextColor.withAlpha(76),
textColor: Colors.white,
fontSize: 14.f,
fontWeight: FontWeight.w600,
borderRadius: 12,
height: 48.h,
),
),
],
),
],
);
}
Widget _buildReminderTypeCard({
required ReminderType type,
required IconData icon,
required String title,
required String description,
}) {
final isSelected = selectedReminderType == type;
return GestureDetector(
onTap: () => _selectReminderType(type),
child: Container(
padding: EdgeInsets.all(16.h),
decoration: BoxDecoration(
color: isSelected ? AppColors.primaryRedColor.withAlpha(26) : AppColors.scaffoldBgColor,
border: Border.all(
color: isSelected ? AppColors.primaryRedColor : AppColors.borderOnlyColor,
width: isSelected ? 2 : 1,
),
borderRadius: BorderRadius.circular(12.r),
),
child: Row(
children: [
Container(
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: isSelected ? AppColors.primaryRedColor : AppColors.greyTextColor.withAlpha(26),
borderRadius: BorderRadius.circular(8.r),
),
child: Icon(
icon,
color: isSelected ? Colors.white : AppColors.greyTextColor,
size: 24.h,
),
),
SizedBox(width: 12.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
title.toText16(
isBold: true,
color: isSelected ? AppColors.primaryRedColor : AppColors.textColor,
),
SizedBox(height: 4.h),
description.toText12(
color: AppColors.greyTextColor,
),
],
),
),
if (isSelected)
Icon(
Icons.check_circle,
color: AppColors.primaryRedColor,
size: 24.h,
),
],
),
),
);
}
Widget _buildCreatingReminderStep() {
return Column(
key: ValueKey('creating'),
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(color: AppColors.primaryRedColor),
SizedBox(height: 24.h),
Center(
child: "Creating your reminder...".toText16(
isBold: true,
),
),
SizedBox(height: 8.h),
Center(
child: "Please wait while we set up your reminder".toText14(
color: AppColors.greyTextColor,
),
),
],
);
}
Widget _buildReminderSuccessStep() {
return Column(
key: ValueKey('reminder_success'),
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.check_circle,
size: 64.h,
color: AppColors.successColor,
),
SizedBox(height: 16.h),
Center(
child: LocaleKeys.reminderSuccess.tr(context: context).toText20(
isBold: true,
),
),
SizedBox(height: 8.h),
Center(
child: "Your ${selectedReminderType?.displayName ?? 'reminder'} has been set successfully!".toText14(
color: AppColors.greyTextColor,
),
),
],
);
}
Widget _buildReminderErrorStep() {
return Column(
key: ValueKey('error'),
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.error_outline,
size: 64.h,
color: AppColors.primaryRedColor,
),
SizedBox(height: 16.h),
Center(
child: "Error".toText20(
isBold: true,
color: AppColors.primaryRedColor,
),
),
SizedBox(height: 8.h),
Center(
child: errorMessage.toText14(
color: AppColors.greyTextColor,
),
),
SizedBox(height: 24.h),
CustomButton(
text: "Close",
onPressed: () {
Navigator.of(context).pop();
widget.onComplete();
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: Colors.white,
fontSize: 14.f,
fontWeight: FontWeight.w600,
borderRadius: 12,
height: 48.h,
),
],
);
}
}

@ -0,0 +1,192 @@
import 'package:flutter/material.dart';
class BellAnimatedSwitch extends StatefulWidget {
final bool initialValue;
final ValueChanged<bool>? onChanged;
final Color? activeColor;
final Color? inactiveColor;
final Color? activeIconColor;
final Color? inactiveIconColor;
final Color? activeCircleColor;
final Color? inactiveCircleColor;
final Widget? activeIcon;
final Widget? inactiveIcon;
final double width;
final double height;
final Duration animationDuration;
const BellAnimatedSwitch({
super.key,
this.initialValue = false,
this.onChanged,
this.activeColor,
this.inactiveColor,
this.activeIconColor,
this.inactiveIconColor,
this.activeCircleColor,
this.inactiveCircleColor,
this.activeIcon,
this.inactiveIcon,
this.width = 48,
this.height = 30,
this.animationDuration = const Duration(milliseconds: 300),
});
@override
State<BellAnimatedSwitch> createState() => BellAnimatedSwitchState();
}
class BellAnimatedSwitchState extends State<BellAnimatedSwitch> with SingleTickerProviderStateMixin {
late bool _isActive;
late AnimationController _animationController;
late Animation<Alignment> _circleAlignment;
late Animation<double> _scaleAnimation;
@override
void initState() {
super.initState();
_isActive = widget.initialValue;
_animationController = AnimationController(
duration: widget.animationDuration,
vsync: this,
);
_circleAlignment = AlignmentTween(begin: Alignment.centerLeft, end: Alignment.centerRight).animate(
CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
);
// Scale animation for tap feedback
_scaleAnimation = Tween<double>(begin: 1.0, end: 0.9).animate(
CurvedAnimation(
parent: _animationController,
curve: const Interval(0.0, 0.5, curve: Curves.easeOut),
),
);
if (_isActive) {
_animationController.value = 1.0;
}
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
/// Public method to set switch value programmatically without triggering onChanged callback
void setSwitchValue(bool value, {bool animate = true}) {
if (_isActive == value) return; // No change needed
setState(() {
_isActive = value;
if (animate) {
if (_isActive) {
_animationController.forward();
} else {
_animationController.reverse();
}
} else {
_animationController.value = _isActive ? 1.0 : 0.0;
}
});
}
void _toggle() {
setState(() {
_isActive = !_isActive;
if (_isActive) {
_animationController.forward();
} else {
_animationController.reverse();
}
widget.onChanged?.call(_isActive);
});
}
Color _getBackgroundColor() {
if (_isActive) {
return widget.activeColor ?? Colors.green.shade400;
}
return widget.inactiveColor ?? Colors.grey.shade300;
}
Color _getCircleColor() {
if (_isActive) {
return widget.activeCircleColor ?? Colors.white;
}
return widget.inactiveCircleColor ?? Colors.white;
}
Widget _getIcon() {
if (_isActive) {
return widget.activeIcon ??
Icon(
Icons.check,
size: 18,
color: widget.activeIconColor ?? Colors.green.shade400,
);
}
return widget.inactiveIcon ??
Icon(
Icons.close,
size: 18,
color: widget.inactiveIconColor ?? Colors.grey.shade600,
);
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _toggle,
child: AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value,
child: Container(
width: widget.width,
height: widget.height,
decoration: BoxDecoration(
color: _getBackgroundColor(),
borderRadius: BorderRadius.circular(widget.height / 2),
// boxShadow: [
// BoxShadow(
// color: Colors.black.withAlpha(26),
// blurRadius: 4,
// offset: const Offset(0, 2),
// ),
// ],
),
child: Stack(
children: [
// Animated circle with icon inside
Align(
alignment: _circleAlignment.value,
child: Container(
margin: const EdgeInsets.all(4),
width: widget.height - 8,
height: widget.height - 8,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: _getCircleColor(),
boxShadow: [
BoxShadow(
color: Colors.black.withAlpha(51),
blurRadius: 2,
offset: const Offset(0, 1),
),
],
),
child: Center(child: _getIcon()),
),
),
],
),
),
);
},
),
);
}
}

@ -250,7 +250,10 @@ class _ComprehensiveCheckupPageState extends State<ComprehensiveCheckupPage> {
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
final service = services[index];
final serviceName = service.text ?? service.textN ?? '';
final appStateLocal = getIt.get<AppState>();
final serviceName = appStateLocal.isArabic()
? (service.textN ?? service.text ?? '')
: (service.text ?? service.textN ?? '');
final price = service.priceTotal ?? 0.0;
return RadioListTileWidget<int>(
value: service.iD ?? 0,

@ -475,12 +475,12 @@ class FeedbackPage extends StatelessWidget {
),
SizedBox(height: 8.h),
"${contactUsViewModel.cocItemsList[index].cOCTitle}"
.toText16(weight: FontWeight.bold, fontFamily: Utils.isArabicText(contactUsViewModel.cocItemsList[index].cOCTitle ?? "") ? "GESSTwo" : "Poppins"),
.toText16(weight: FontWeight.bold, fontFamily: Utils.isArabicText(contactUsViewModel.cocItemsList[index].cOCTitle ?? "") ? "CairoArabic" : "Poppins"),
SizedBox(height: 8.h),
"${contactUsViewModel.cocItemsList[index].detail}".toText14(
weight: FontWeight.w600,
color: AppColors.textColorLight,
fontFamily: Utils.isArabicText(contactUsViewModel.cocItemsList[index].detail ?? "") ? "GESSTwo" : "Poppins"),
fontFamily: Utils.isArabicText(contactUsViewModel.cocItemsList[index].detail ?? "") ? "CairoArabic" : "Poppins"),
],
),
).paddingOnly(bottom: 16.h),

@ -193,7 +193,7 @@ class _NewReferralPageState extends State<NewReferralPage> {
children: List.generate(3, (index) {
// Fill current step and all previous completed steps
if (index <= _currentStep) {
return StepperWidget(widthOfOneState, AppColors.primaryRedColor, true, 4.h);
return StepperWidget(widthOfOneState, AppColors.primaryRedColor, index == _currentStep, 4.h);
} else {
return StepperWidget(widthOfOneState, AppColors.greyLightColor, false, 4.h);
}

@ -142,8 +142,8 @@ class _OtherDetailsStepState extends State<OtherDetailsStep> {
Widget _buildBranchField(BuildContext context, ReferralFormManager formManager) {
return DropdownWidget(
labelText: 'Branch',
hintText: formManager.formData.branch?.desciption ?? "Select Branch",
labelText: LocaleKeys.branch.tr(),
hintText: formManager.formData.branch?.desciption ?? LocaleKeys.selectBranch.tr(),
isEnable: false,
hasSelectionCustomIcon: true,
labelColor: Colors.black,

@ -1,9 +1,12 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
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/utils/validation_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/hmg_services/models/resq_models/get_all_cities_resp_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/e_referral/e_referral_form_manager.dart';
import 'package:provider/provider.dart';
@ -13,6 +16,8 @@ import 'package:hmg_patient_app_new/widgets/dropdown/dropdown_widget.dart';
import 'package:hmg_patient_app_new/widgets/input_widget.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import '../../../core/dependencies.dart' show getIt;
class PatientInformationStep extends StatefulWidget {
const PatientInformationStep({super.key});
@ -138,37 +143,40 @@ class PatientInformationStepState extends State<PatientInformationStep> {
}
Widget _buildPatientPhoneField(ReferralFormManager formManager) {
return Focus(
focusNode: _phoneFocusNode,
child: TextInputWidget(
labelText: LocaleKeys.phoneNumber.tr(context: context),
hintText: "5xxxxxxxx",
controller: _phoneController,
padding: const EdgeInsets.all(8),
keyboardType: TextInputType.number,
fontFamily: "Poppins",
onChange: (value) {
formManager.updatePatientPhone(value ?? '');
},
onCountryChange: (value) {
formManager.updatePatientCountryEnum(value);
},
prefix: '966',
isBorderAllowed: false,
isAllowLeadingIcon: true,
fontSize: 13,
isCountryDropDown: true,
leadingIcon: AppAssets.smart_phone,
errorMessage: formManager.errors.patientPhone,
hasError: !ValidationUtils.isNullOrEmpty(formManager.errors.patientPhone)
).paddingSymmetrical(0, 8),
return Directionality(
textDirection: ui.TextDirection.ltr,
child: Focus(
focusNode: _phoneFocusNode,
child: TextInputWidget(
labelText: LocaleKeys.phoneNumber.tr(context: context),
hintText: "5xxxxxxxx",
controller: _phoneController,
padding: const EdgeInsets.all(8),
keyboardType: TextInputType.number,
fontFamily: "Poppins",
onChange: (value) {
formManager.updatePatientPhone(value ?? '');
},
onCountryChange: (value) {
formManager.updatePatientCountryEnum(value);
},
prefix: '966',
isBorderAllowed: false,
isAllowLeadingIcon: true,
fontSize: 13,
isCountryDropDown: true,
leadingIcon: AppAssets.smart_phone,
errorMessage: formManager.errors.patientPhone,
hasError: !ValidationUtils.isNullOrEmpty(formManager.errors.patientPhone)
).paddingSymmetrical(0, 8),
),
);
}
Widget _buildPatientCityField(BuildContext context, ReferralFormManager formManager) {
return DropdownWidget(
labelText: 'City',
hintText: formManager.formData.patientCity?.description ?? LocaleKeys.selectCity.tr(context: context),
labelText: LocaleKeys.city.tr(),
hintText: getLocaleDescription(formManager.formData.patientCity) ?? LocaleKeys.selectCity.tr(context: context),
isEnable: false,
hasSelectionCustomIcon: true,
labelColor: Colors.black,
@ -183,6 +191,10 @@ class PatientInformationStepState extends State<PatientInformationStep> {
});
}
String? getLocaleDescription(GetAllCitiesResponseModel? patientCity){
return getIt.get<AppState>().isArabic() ? patientCity?.descriptionN: patientCity?.description;
}
void _showCityBottomSheet(BuildContext context, ReferralFormManager formManager) {
@ -213,7 +225,7 @@ class PatientInformationStepState extends State<PatientInformationStep> {
itemBuilder: (context, index) {
final city = cities[index];
return ListTile(
title: (city.description ?? 'Unknown').toText14(),
title: (getLocaleDescription(city) ?? 'Unknown').toText14(),
onTap: () {
formManager.updatePatientCity(city);
Navigator.pop(context);

@ -116,9 +116,9 @@ class RequesterFormStepState extends State<RequesterFormStep> {
Widget _buildRelationshipField(BuildContext context, ReferralFormManager formManager) {
return DropdownWidget(
labelText: LocaleKeys.relationship.tr(context: context),
hintText: formManager.formData.relationship?.textEn ?? LocaleKeys.selectRelation.tr(context: context),
hintText: formManager.formData.relationship?.text ?? LocaleKeys.selectRelation.tr(context: context),
isEnable: false,
selectedValue: formManager.formData.relationship?.textEn ?? LocaleKeys.selectRelation.tr(context: context),
selectedValue: formManager.formData.relationship?.text ?? LocaleKeys.selectRelation.tr(context: context),
errorMessage: formManager.errors.relationship,
hasError: !ValidationUtils.isNullOrEmpty(formManager.errors.relationship),
hasSelectionCustomIcon: false,
@ -182,7 +182,7 @@ class RequesterFormStepState extends State<RequesterFormStep> {
itemBuilder: (context, index) {
final relationship = hmgServicesVM.relationTypes[index];
return ListTile(
title:relationship.textEn?.toText14(),
title:relationship.text?.toText14(),
onTap: () {
formManager.updateRelationship(relationship);
Navigator.pop(context);

@ -1,6 +1,9 @@
import 'package:flutter/material.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/core/utils/validation_utils.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/e_referral/e_referral_form_manager.dart';
import 'package:provider/provider.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
@ -69,12 +72,13 @@ class SearchEReferralFormFormState extends State<SearchEReferralFormForm> {
autofocus: true,
child: TextInputWidget(
controller: _searchController,
padding: const EdgeInsets.symmetric(horizontal: 16.0),
hintText: formManager.searchCriteria == 0 ? "Enter Identification Number" : "Enter Referral Number",
labelText: formManager.searchCriteria == 0 ? "Identification Number" : "Referral Number",
hintText: formManager.searchCriteria == 0 ? LocaleKeys.enterIdentificationNumber.tr() : LocaleKeys.enterReferralNumber.tr(),
labelText: formManager.searchCriteria == 0 ? LocaleKeys.identificationNumber.tr() : LocaleKeys.referralNumber.tr(),
keyboardType: TextInputType.number,
fontFamily: "Poppins",
errorMessage: formManager.errors.searchValue,
padding: EdgeInsets.symmetric(vertical: 14.h, horizontal: 16.h),
hasError: !ValidationUtils.isNullOrEmpty(formManager.errors.searchValue),
onChange: (value) {
formManager.updateSearchValue(value ?? '');
@ -90,10 +94,10 @@ class SearchEReferralFormFormState extends State<SearchEReferralFormForm> {
autofocus: false,
child: TextInputWidget(
autoFocus: false,
labelText: 'Phone Number',
hintText: "5xxxxxxxx",
labelText: LocaleKeys.phoneNumber.tr(),
hintText: LocaleKeys.phoneHint.tr(),
controller: _phoneController,
padding: const EdgeInsets.all(8),
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
keyboardType: TextInputType.number,
fontFamily: "Poppins",
onChange: (value) {
@ -122,8 +126,8 @@ class SearchEReferralFormFormState extends State<SearchEReferralFormForm> {
Widget _buildSelectionField(BuildContext context, ReferralFormManager formManager) {
return DropdownWidget(
labelText: "Select the Search Criteria",
hintText: formManager.searchCriteria == 0 ? "Identification Number" : "Referral Number",
labelText: LocaleKeys.selectCriteria.tr(),
hintText: formManager.searchCriteria == 0 ? LocaleKeys.identificationNumber.tr() : LocaleKeys.referralNumber.tr(),
isEnable: false,
hasSelectionCustomIcon: false,
labelColor: Colors.black,
@ -137,13 +141,13 @@ class SearchEReferralFormFormState extends State<SearchEReferralFormForm> {
void _showCriteriaBottomSheet(BuildContext context, ReferralFormManager formManager) {
final criteriaList = [
{0: 'Identification Number'},
{1: 'Referral Number'},
{0: LocaleKeys.identificationNumber.tr()},
{1: LocaleKeys.referralNumber.tr()},
];
showCommonBottomSheetWithoutHeight(
context,
title: "Select Criteria",
title: LocaleKeys.selectCriteria.tr(),
child: ListView.separated(
shrinkWrap: true,
physics: const BouncingScrollPhysics(),

@ -559,7 +559,9 @@ class RrtMapScreen extends StatelessWidget {
context,
child: SizedBox(
height: MediaQuery.sizeOf(context).height * .8,
child: LocationInputBottomSheet(),
child: LocationInputBottomSheet(
moveCameraController:(location) => context.read<LocationViewModel>().moveController(location) ,
),
),
isFullScreen: false,
isCloseButtonVisible: true,

@ -140,7 +140,7 @@ class RrtRequestTypeSelect extends StatelessWidget {
),
CustomButton(text: LocaleKeys.next.tr(), onPressed: () {
Navigator.pop(context);
emergencyServicesVM.openRRT();
emergencyServicesVM.openRRT(context);
},
isDisabled: !emergencyServicesVM.agreedToTermsAndCondition,
)

@ -53,8 +53,8 @@ class CallAmbulancePage extends StatelessWidget {
ExpandableBottomSheet(
bottomSheetType: context.watch<EmergencyServicesViewModel>().bottomSheetType,
children: {
BottomSheetType.EXPANDED: ExpanedBottomSheet(context),
BottomSheetType.FIXED: FixedBottomSheet(context),
BottomSheetType.EXPANDED: ExpandedBottomSheet(context),
BottomSheetType.FIXED: pickUpDetailAndAppointementSection(context),
},
),
],
@ -92,7 +92,7 @@ class CallAmbulancePage extends StatelessWidget {
);
}
Widget FixedBottomSheet(BuildContext context) {
Widget ExpandedBottomSheet(BuildContext context) {
return GestureDetector(
onVerticalDragUpdate: (details) {
// if(details.delta.dy<0){
@ -172,7 +172,11 @@ class CallAmbulancePage extends StatelessWidget {
CustomButton(
text: LocaleKeys.selectDetails.tr(context: context),
onPressed: () {
context.read<EmergencyServicesViewModel>().updateBottomSheetState(BottomSheetType.EXPANDED);
LocationViewModel locationViewModel = context.read<LocationViewModel>();
GeocodeResponse? response = locationViewModel.geocodeResponse;
PlaceDetails? placeDetails = locationViewModel.placeDetails;
PlacePrediction? placePrediction = locationViewModel.selectedPrediction;
context.read<EmergencyServicesViewModel>().submitAmbulanceRequest(response?.results.first, placeDetails, placePrediction);
})
],
).paddingOnly(top: 24.h, bottom: 32.h, left: 24.h, right: 24.h),
@ -185,7 +189,7 @@ class CallAmbulancePage extends StatelessWidget {
);
}
Widget ExpanedBottomSheet(BuildContext context) {
Widget pickUpDetailAndAppointementSection(BuildContext context) {
return GestureDetector(
onVerticalDragUpdate: (details) {
if (details.delta.dy > 0) {
@ -324,28 +328,34 @@ class CallAmbulancePage extends StatelessWidget {
)
],
),
if(AmbulanceCallingPlace.TO_HOSPITAL == context.read<EmergencyServicesViewModel>().callingPlace)
Row(
children: [
hospitalAndPickUpItemContent(
title: LocaleKeys.appointment.tr(context: context),
subTitle: LocaleKeys.haveAnyAppointment.tr(context: context),
leadingIcon: AppAssets.appointment_calendar_icon,
),
CustomSwitch(
value: context.watch<EmergencyServicesViewModel>().haveAnAppointment,
onChanged: (value) async {
// if (value) {
// openAppointmentList(context);
// }
await context.read<EmergencyServicesViewModel>().updateAppointment(value);
if (context.read<EmergencyServicesViewModel>().appointments?.isNotEmpty == true) {
openAppointmentList(context);
}
},
)
],
),
Row(
children: [
hospitalAndPickUpItemContent(
title: LocaleKeys.appointment.tr(context: context),
subTitle: LocaleKeys.haveAnyAppointment.tr(context: context),
leadingIcon: AppAssets.appointment_calendar_icon,
),
CustomSwitch(
value: context.watch<EmergencyServicesViewModel>().haveAnAppointment,
onChanged: (value) async {
// if (value) {
// openAppointmentList(context);
// }
await context.read<EmergencyServicesViewModel>().updateAppointment(value);
if (context.read<EmergencyServicesViewModel>().appointments?.isNotEmpty == true) {
openAppointmentList(context);
}
},
)
],
),
// Visibility(
// visible: ,
// child:
// ),
],
).paddingAll(16.h),
);
@ -460,12 +470,8 @@ class CallAmbulancePage extends StatelessWidget {
CustomButton(
text: LocaleKeys.submitRequest.tr(context: context),
onPressed: () {
LocationViewModel locationViewModel = context.read<LocationViewModel>();
GeocodeResponse? response = locationViewModel.geocodeResponse;
PlaceDetails? placeDetails = locationViewModel.placeDetails;
PlacePrediction? placePrediction = locationViewModel.selectedPrediction;
context.read<EmergencyServicesViewModel>().submitAmbulanceRequest(response?.results.first, placeDetails, placePrediction);
})
context.read<EmergencyServicesViewModel>().updateBottomSheetState(BottomSheetType.EXPANDED);
})
],
).paddingOnly(top: 24.h, bottom: 12.h, left: 24.h, right: 24.h),
),
@ -571,7 +577,9 @@ class CallAmbulancePage extends StatelessWidget {
context,
child: SizedBox(
height: MediaQuery.sizeOf(context).height * .8,
child: LocationInputBottomSheet(),
child: LocationInputBottomSheet(
moveCameraController:(location) => context.read<EmergencyServicesViewModel>().moveController(location) ,
),
),
isFullScreen: false,
isCloseButtonVisible: true,
@ -582,21 +590,24 @@ class CallAmbulancePage extends StatelessWidget {
}
hospitalField(BuildContext context) {
return DecoratedBox(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h),
child: Row(
children: [
hospitalAndPickUpItemContent(
title: LocaleKeys.selectHospital.tr(context: context),
subTitle: context.read<EmergencyServicesViewModel>().getSelectedHospitalName() ?? LocaleKeys.selectHospital.tr(context: context),
leadingIcon: AppAssets.hospital,
),
Utils.buildSvgWithAssets(icon: AppAssets.down_cheveron, width: 24.h, height: 24.h).paddingAll(16.h)
],
).onPress(() {
showHospitalBottomSheet(context);
}).paddingSymmetrical(
10.w, 12.h),
return Visibility(
visible: false == context.watch<EmergencyServicesViewModel>().haveAnAppointment,
child: DecoratedBox(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h),
child: Row(
children: [
hospitalAndPickUpItemContent(
title: LocaleKeys.selectHospital.tr(context: context),
subTitle: context.read<EmergencyServicesViewModel>().getSelectedHospitalName() ?? LocaleKeys.selectHospital.tr(context: context),
leadingIcon: AppAssets.hospital,
),
Utils.buildSvgWithAssets(icon: AppAssets.down_cheveron, width: 24.h, height: 24.h).paddingAll(16.h)
],
).onPress(() {
showHospitalBottomSheet(context);
}).paddingSymmetrical(
10.w, 12.h),
),
);
}

@ -73,7 +73,10 @@ class TrackingScreen extends StatelessWidget {
child: SingleChildScrollView(
child: Column(
children: [
animationSection(),
Padding(
padding: EdgeInsetsDirectional.only(top:16.h,start: 16.w,end: 16.w ),
child: animationSection(context),
),
Column(
spacing: 16.h,
children: [orderStatus(context), orderTrackingId(context), contactSection()],
@ -83,12 +86,12 @@ class TrackingScreen extends StatelessWidget {
);
}
animationSection() {
animationSection(BuildContext context) {
switch (state) {
case OrderTrackingState.waitingForCall:
case OrderTrackingState.dispactched:
case OrderTrackingState.returning:
return Lottie.asset(AppAnimations.ambulance, repeat: true, reverse: false, width: 260.h, height: 200.h);
return showImage(order != null ? AppAssets.ambulance_section : AppAssets.rrt_section, context);
case OrderTrackingState.ended:
return Lottie.asset(AppAnimations.checkmark, repeat: true, width: 260.h, height: 200.h);
case OrderTrackingState.failed:
@ -146,7 +149,7 @@ class TrackingScreen extends StatelessWidget {
borderColor: AppColors.primaryRedColor,
textColor: Colors.white,
icon: AppAssets.cancel,
height: 40.h,
height: 56.h,
iconSize: 18.w,
),
],
@ -237,7 +240,7 @@ class TrackingScreen extends StatelessWidget {
backgroundColor: AppColors.lightRedButtonColor,
iconColor: AppColors.primaryRedColor,
borderColor: Colors.transparent,
height: 40.h,
height: 56.h,
),
)
],
@ -270,7 +273,7 @@ class TrackingScreen extends StatelessWidget {
informationRegardingTrackingSection() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [currentStatus(), SizedBox(height: 4.h), "You will receive a call from HMG for confirmation ".toText12(color: AppColors.greyTextColor, isBold: true)],
children: [currentStatus(), SizedBox(height: 4.h)],
);
}
@ -489,6 +492,18 @@ class TrackingScreen extends StatelessWidget {
);
}
showImage(String imageUrl, BuildContext context) {
return Utils.buildImgWithAssetsWithBorderRadius(
icon: imageUrl,
iconColor: Colors.transparent,
width: MediaQuery.sizeOf(context).width,
height: 380.h,
borderRadius: 24.r,
border: 1.r/// did this as the function that is being called is wrongly coupled with the border radius and border, and in some cases like the ambulance and rrt section we don't need any
/// border or border radius
);
}
// Widget rrtOrderData() {
// return Row(
// spacing: 8.h,

@ -90,7 +90,7 @@ class _EmergencyServicesPageState extends State<EmergencyServicesPage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.callAmbulance.tr().toText16(isBold: true, color: AppColors.blackColor),
LocaleKeys.requestAmbulanceInEmergency.tr().toText12(color: AppColors.greyTextColor, isBold: true),
LocaleKeys.requestAmbulanceInEmergency.tr().toText12(color: AppColors.greyTextColor, isBold: true),
],
),
),

@ -49,18 +49,21 @@ class ErOnlineCheckinHome extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Utils.buildSvgWithAssets(icon: AppAssets.immediate_service_icon, width: 58.h, height: 58.h),
SizedBox(width: 18.h),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.onlineCheckIn.tr().toText18(color: AppColors.textColor, isBold: true),
LocaleKeys.erOnlineCheckInDescription.tr().toText14(color: AppColors.greyTextColor, isBold: true),
],
),
),
Utils.getNoDataWidget(context)
// Utils.buildSvgWithAssets(icon: AppAssets.immediate_service_icon, width: 58.h, height: 58.h),
// SizedBox(width: 18.h),
// Expanded(
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// LocaleKeys.onlineCheckIn.tr().toText18(color: AppColors.textColor, isBold: true),
// LocaleKeys.erOnlineCheckInDescription.tr().toText14(color: AppColors.greyTextColor, isBold: true),
// ],
// ),
// ),
],
),
],

@ -42,7 +42,7 @@ class AmbulanceHistoryItem extends StatelessWidget {
Row(
spacing: 4.w,
children: [
chip(Utils.getDayMonthYearDateFormatted(DateUtil.convertStringToDate(order.time)), AppAssets.calendar, AppColors.blackBgColor),
chip(Utils.getDayMonthYearDateFormatted(DateUtil.parseISODate(order.created)), AppAssets.calendar, AppColors.blackBgColor),
chip(LocaleKeys.ambulancerequest.tr(context: context), AppAssets.ambulance, AppColors.blackBgColor),
],
),

@ -42,7 +42,7 @@ class RRTItem extends StatelessWidget {
Row(
spacing: 4.w,
children: [
chip(Utils.getDayMonthYearDateFormatted(DateUtil.convertStringToDate(order.time)), AppAssets.calendar, AppColors.blackBgColor),
chip(Utils.getDayMonthYearDateFormatted(DateUtil.parseISODate(order.created)), AppAssets.calendar, AppColors.blackBgColor),
chip(LocaleKeys.rapidResponseTeam.tr(context: context), AppAssets.ic_rrt_vehicle, AppColors.blackBgColor),
],
),

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

Loading…
Cancel
Save