Compare commits

...

20 Commits

Author SHA1 Message Date
Haroon6138 519ce695d6 Merge pull request 'Health Trackers Feature (faiz_dev)' (#148) from faiz_dev into main
Reviewed-on: https://34.17.182.140/Haroon6138/HMG_Patient_App_New/pulls/148
2 months ago
faizatflutter ff2bcbae3f Merge branch 'master' into faiz_dev
# Conflicts:
#	lib/core/api_consts.dart
#	lib/core/dependencies.dart
#	lib/main.dart
2 months ago
faizatflutter 001808488c Added 'send report to email' feature 2 months ago
Haroon6138 a0b07d4b5a Merge pull request 'dev_aamir' (#147) from dev_aamir into master
Reviewed-on: https://34.17.182.140/Haroon6138/HMG_Patient_App_New/pulls/147
2 months ago
aamir-csol 83f04bc0e6 search filter on select doctor page. 2 months ago
faizatflutter 3f5bd0a759 Completed the health Tracker Module, Only the email part is left. 2 months ago
aamir-csol 38d8341963 family file fix 2 months ago
aamir-csol c7e4018ddc Merge branch 'master' into dev_aamir
# Conflicts:
#	lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart
2 months ago
aamir-csol b815affe5c blood group 2 months ago
Haroon6138 7c82295e2c Merge pull request 'haroon_dev' (#144) from haroon_dev into master
Reviewed-on: https://34.17.182.140/Haroon6138/HMG_Patient_App_New/pulls/144
3 months ago
haroon amjad 1656ca58c1 Merge branch 'ambulance_service' into haroon_dev 3 months ago
haroon amjad d6b1744176 updates 3 months ago
faizatflutter 17f32e34d3 Merge branch 'refs/heads/master' into faiz_dev
# Conflicts:
#	lib/presentation/symptoms_checker/user_info_selection.dart
3 months ago
faizatflutter 4ee0d1d3c8 only explain api left. 3 months ago
faizatflutter 8c91b6830a Triage Completed. Explain API left 3 months ago
faizatflutter 49d091547a Till Triage Completed 3 months ago
faizatflutter 597579e677 refactoring 3 months ago
FaizHashmiCS22 7a5b753f63 Merge pull request 'Symptoms Checker and User Info Selection (faiz_dev)' (#113) from faiz_dev into main
Reviewed-on: #113
3 months ago
Haroon6138 a30b9872b2 Merge pull request 'haroon_dev' (#103) from haroon_dev into main
Reviewed-on: #103
4 months ago
Haroon6138 8b9e757e61 Merge pull request 'ambulance_listing_and_request' (#95) from ambulance_listing_and_request into main
Reviewed-on: #95
4 months ago

@ -0,0 +1,4 @@
<svg width="20" height="18" viewBox="0 0 20 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 10C2 5.58172 5.58172 2 10 2C14.4183 2 18 5.58172 18 10C18 10.5523 18.4477 11 19 11C19.5523 11 20 10.5523 20 10C20 4.47715 15.5228 0 10 0C4.47715 0 0 4.47715 0 10C0 10.5523 0.447715 11 1 11C1.55228 11 2 10.5523 2 10Z" fill="white"/>
<path d="M10 6C10.5523 6 11 6.44772 11 7L11 12.1707C12.1652 12.5825 13 13.6938 13 15C13 16.6569 11.6569 18 10 18C8.34315 18 7 16.6569 7 15C7 13.6938 7.83481 12.5825 9 12.1707L9 7C9 6.44772 9.44772 6 10 6Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 568 B

@ -0,0 +1,3 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 6.75C4 3.02208 7.02208 0 10.75 0C14.4779 0 17.5 3.02208 17.5 6.75C17.5 6.81385 17.5 6.84578 17.4946 6.87394C17.4654 7.02516 17.324 7.13066 17.1708 7.11566C17.1422 7.11287 17.1041 7.10157 17.028 7.07899C16.306 6.86492 15.5414 6.75 14.75 6.75C11.4211 6.75 8.56706 8.78325 7.36206 11.6756C7.21403 12.031 7.14001 12.2086 6.99327 12.2449C6.84654 12.2812 6.71484 12.1723 6.45145 11.9546L6.45144 11.9546C4.95411 10.7165 4 8.84466 4 6.75ZM8 14.75C8 11.0221 11.0221 8 14.75 8C18.4779 8 21.5 11.0221 21.5 14.75C21.5 18.4779 18.4779 21.5 14.75 21.5C11.0221 21.5 8 18.4779 8 14.75ZM6.77538 14.11C6.75857 14.3217 6.75 14.5358 6.75 14.7519C6.75 16.87 7.57313 18.7958 8.91695 20.227L8.91696 20.227C9.22203 20.5519 9.37457 20.7143 9.33165 20.8754C9.28873 21.0365 9.09982 21.0941 8.72199 21.2093C8.09819 21.3996 7.43605 21.5019 6.75 21.5019C3.02208 21.5019 0 18.4798 0 14.7519C0 12.6572 0.954182 10.7853 2.45161 9.54723C2.715 9.32946 2.8467 9.22057 2.99343 9.25688C3.14016 9.29319 3.21416 9.4708 3.36215 9.82601C3.9893 11.3312 5.0627 12.6033 6.41512 13.4766L6.4968 13.5294C6.64871 13.6275 6.72467 13.6765 6.76134 13.7507C6.79802 13.8249 6.79047 13.9199 6.77538 14.11Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@ -0,0 +1,4 @@
<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 d="M11.9998 21.25C11.5858 21.2503 11.2498 21.5859 11.2498 22C11.2498 22.4141 11.5858 22.7497 11.9998 22.75H14.3953C14.577 22.75 14.7553 22.7915 14.9158 22.8691L16.9578 23.8564C17.3233 24.0333 17.7251 24.125 18.1316 24.125H19.1746C19.601 24.1251 19.9516 24.3534 20.1277 24.6719L18.1931 25.207C17.9121 25.2847 17.6116 25.2563 17.3523 25.1309L15.1687 24.0752C14.7959 23.895 14.3471 24.051 14.1667 24.4238C13.9866 24.7966 14.1427 25.2454 14.5154 25.4258L16.699 26.4814C17.2874 26.7661 17.9622 26.8265 18.5925 26.6523L21.1335 25.9502C21.4836 25.8534 21.7498 25.5327 21.7498 25.1416C21.7498 25.1125 21.7468 25.0836 21.7458 25.0547L25.8132 23.8057L25.8162 23.8047C26.3135 23.6499 26.8624 23.8288 27.1892 24.2812C27.3052 24.4419 27.2446 24.6626 27.1033 24.7441L19.5876 29.0811C19.2867 29.2546 18.9351 29.2953 18.6082 29.1982L12.2136 27.3008C11.8165 27.1829 11.3988 27.4095 11.281 27.8066C11.1632 28.2037 11.3898 28.6214 11.7869 28.7393L18.1814 30.6367C18.9059 30.8517 19.6827 30.7578 20.3376 30.3799L27.8533 26.0439C28.7864 25.5055 29.0265 24.2639 28.405 23.4033C27.7121 22.4438 26.5013 22.0206 25.3708 22.3721L25.3699 22.373L21.241 23.6416C20.7675 23.0215 20.0117 22.6251 19.1746 22.625H18.1316C17.95 22.625 17.7716 22.5844 17.6111 22.5068L15.5691 21.5186C15.2035 21.3417 14.8017 21.25 14.3953 21.25H11.9998ZM22.6511 9.91992C21.7188 9.02657 20.2817 9.02657 19.3494 9.91992C18.7347 10.5091 17.9734 11.3294 17.3611 12.2773C16.7542 13.2169 16.2498 14.3489 16.2498 15.5469C16.2498 17.8542 18.0478 20.25 21.0007 20.25C23.9534 20.2497 25.7507 17.854 25.7507 15.5469C25.7507 14.3489 25.2463 13.2169 24.6394 12.2773C24.0271 11.3294 23.2657 10.5091 22.6511 9.91992ZM20.3875 11.0029C20.7396 10.6657 21.2609 10.6657 21.613 11.0029C22.181 11.5474 22.8538 12.2767 23.3796 13.0908C23.9109 13.9134 24.2507 14.7575 24.2507 15.5469C24.2507 17.1367 23.0181 18.7497 21.0007 18.75C18.9831 18.75 17.7498 17.1369 17.7498 15.5469C17.7498 14.7575 18.0895 13.9134 18.6208 13.0908C19.1467 12.2767 19.8195 11.5474 20.3875 11.0029Z" fill="#8F9AA3"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 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="M19.9999 9.25C17.9288 9.25 16.2499 10.9289 16.2499 13C16.2499 14.0736 16.805 15.1188 17.4741 15.8701C17.8161 16.2541 18.2118 16.5896 18.6284 16.8334C19.038 17.0732 19.5126 17.25 19.9999 17.25C20.4872 17.25 20.9617 17.0732 21.3714 16.8334C21.7879 16.5896 22.1837 16.2541 22.5257 15.8701C23.1947 15.1188 23.7499 14.0736 23.7499 13C23.7499 10.9289 22.0709 9.25 19.9999 9.25ZM17.7499 13C17.7499 11.7574 18.7572 10.75 19.9999 10.75C21.2425 10.75 22.2499 11.7574 22.2499 13C22.2499 13.5832 21.9259 14.2881 21.4054 14.8725C21.1526 15.1564 20.876 15.3853 20.6137 15.5388C20.3444 15.6964 20.1334 15.75 19.9999 15.75C19.8663 15.75 19.6554 15.6964 19.3861 15.5388C19.1238 15.3853 18.8471 15.1564 18.5943 14.8725C18.0738 14.2881 17.7499 13.5832 17.7499 13Z" fill="#8F9AA3"/>
<path d="M24.536 16.4361C24.2245 16.163 23.7507 16.1941 23.4776 16.5056C23.2045 16.817 23.2356 17.2909 23.5471 17.5639C24.916 18.7642 25.6946 20.6997 24.9738 22.4145C24.8973 22.5965 24.7277 22.7039 24.5514 22.7039C24.4876 22.7039 24.4316 22.7013 24.3593 22.6978C24.3058 22.6953 24.2432 22.6923 24.1618 22.6897C24.0028 22.6845 23.7976 22.6828 23.5892 22.7127C23.3824 22.7424 23.1208 22.8099 22.8813 22.9807C22.6237 23.1644 22.4437 23.4309 22.3633 23.7588L21.266 28.2337C21.1173 28.8402 20.5889 29.25 20.0001 29.25C19.4112 29.25 18.8828 28.8402 18.7341 28.2337L17.6368 23.7588C17.5564 23.4309 17.3764 23.1644 17.1188 22.9807C16.8793 22.8099 16.6177 22.7424 16.4109 22.7127C16.2025 22.6828 15.9973 22.6845 15.8383 22.6897C15.7569 22.6923 15.6944 22.6953 15.6408 22.6978C15.5685 22.7013 15.5125 22.7039 15.4487 22.7039C15.2724 22.7039 15.1028 22.5965 15.0263 22.4145C14.3055 20.6997 15.0841 18.7642 16.4531 17.5639C16.7645 17.2909 16.7956 16.817 16.5226 16.5056C16.2495 16.1941 15.7756 16.163 15.4642 16.4361C13.7871 17.9065 12.5919 20.4941 13.6435 22.9958C13.9489 23.7223 14.6555 24.2039 15.4487 24.2039C15.5413 24.2039 15.6638 24.1985 15.7652 24.1939C15.8118 24.1918 15.8541 24.1899 15.8869 24.1889C16.0232 24.1845 16.1226 24.1867 16.1979 24.1975L16.2 24.1978L17.2772 28.591C17.5861 29.8506 18.7022 30.75 20.0001 30.75C21.2979 30.75 22.414 29.8506 22.7229 28.591L23.8001 24.1978L23.8022 24.1975C23.8775 24.1867 23.9769 24.1845 24.1132 24.1889C24.146 24.1899 24.1881 24.1918 24.2346 24.1939C24.336 24.1984 24.4588 24.2039 24.5514 24.2039C25.3446 24.2039 26.0512 23.7223 26.3566 22.9958C27.4082 20.4941 26.213 17.9065 24.536 16.4361Z" fill="#8F9AA3"/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

@ -0,0 +1,3 @@
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.0625 16.125C3.6097 16.125 0 12.5153 0 8.0625C0 3.6097 3.6097 0 8.0625 0C12.5153 0 16.125 3.6097 16.125 8.0625C16.125 12.5153 12.5153 16.125 8.0625 16.125ZM8.0625 4.3125C8.47671 4.3125 8.8125 4.64829 8.8125 5.0625V8.43749H8.93401C9.15181 8.43742 9.38226 8.43734 9.56413 8.45912C9.69673 8.47499 10.1846 8.53481 10.4097 8.99153C10.635 9.4486 10.3763 9.85561 10.3061 9.96553C10.2094 10.1169 10.0646 10.2914 9.92764 10.4564L9.90413 10.4848C9.68853 10.7449 9.43178 11.0409 9.1743 11.2764C9.04572 11.394 8.89699 11.5151 8.73553 11.6109C8.58584 11.6996 8.34945 11.8125 8.0625 11.8125C7.77556 11.8125 7.53916 11.6996 7.38947 11.6109C7.22801 11.5151 7.07929 11.394 6.95071 11.2764C6.69322 11.0409 6.43647 10.7449 6.22087 10.4848L6.19736 10.4564C6.06041 10.2914 5.91565 10.1169 5.81894 9.96552C5.74871 9.85561 5.49001 9.4486 5.71529 8.99153C5.94038 8.53481 6.42827 8.47499 6.56087 8.45912C6.74274 8.43734 6.9732 8.43742 7.191 8.43749H7.3125L7.3125 5.0625C7.3125 4.64829 7.64829 4.3125 8.0625 4.3125Z" fill="#FF9E15"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,7 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="20" height="3" rx="1.5" fill="#18C273"/>
<rect y="4" width="20" height="3" rx="1.5" fill="#18C273" fill-opacity="0.7"/>
<rect y="8" width="20" height="3" rx="1.5" fill="#18C273" fill-opacity="0.5"/>
<rect y="12" width="20" height="3" rx="1.5" fill="#18C273" fill-opacity="0.35"/>
<rect y="16" width="20" height="3" rx="1.5" fill="#18C273" fill-opacity="0.2"/>
</svg>

After

Width:  |  Height:  |  Size: 476 B

@ -0,0 +1,3 @@
<svg width="22" height="21" viewBox="0 0 22 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.52965 0.0727261C9.98692 -0.024143 11.5101 -0.024341 12.9704 0.0727261C17.537 0.376286 21.1603 4.06258 21.4582 8.67235C21.5139 9.53477 21.5139 10.4268 21.4582 11.2892C21.1603 15.899 17.537 19.5853 12.9704 19.8889C11.5101 19.9859 9.98692 19.9857 8.52965 19.8889C7.96472 19.8513 7.34978 19.7177 6.80839 19.4948C6.5703 19.3967 6.40868 19.3304 6.29041 19.287C6.20909 19.3429 6.10108 19.4223 5.9438 19.5383C5.15136 20.1226 4.15092 20.5325 2.73117 20.498L2.68544 20.4969C2.41155 20.4903 2.11961 20.4834 1.88152 20.4373C1.59475 20.3818 1.23996 20.2431 1.01791 19.8645C0.776229 19.4525 0.873128 19.0358 0.966883 18.7734C1.05536 18.5257 1.20874 18.2352 1.36542 17.9385L1.3869 17.8978C1.85323 17.0141 1.98314 16.2919 1.73381 15.8104C0.901479 14.554 0.152725 13.0056 0.0418013 11.2892C-0.0139338 10.4268 -0.0139338 9.53477 0.0418013 8.67235C0.339716 4.06258 3.963 0.376286 8.52965 0.0727261ZM6.5 7.75C6.5 8.16421 6.83579 8.5 7.25 8.5H10.75C11.1642 8.5 11.5 8.16421 11.5 7.75C11.5 7.33579 11.1642 7 10.75 7L7.25 7C6.83579 7 6.5 7.33579 6.5 7.75ZM6.5 12.75C6.5 13.1642 6.83579 13.5 7.25 13.5L14.25 13.5C14.6642 13.5 15 13.1642 15 12.75C15 12.3358 14.6642 12 14.25 12L7.25 12C6.83579 12 6.5 12.3358 6.5 12.75Z" fill="#ED1C2B"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@ -0,0 +1,3 @@
<svg width="20" height="22" viewBox="0 0 20 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.39392 1.28104e-06L11.1061 1.28104e-06C12.009 -2.95155e-05 12.7711 -5.55068e-05 13.3749 0.0704231C14.005 0.143956 14.6207 0.308473 15.1206 0.759151C15.3458 0.962221 15.5373 1.19899 15.6876 1.46113C15.9826 1.97573 16.0312 2.52334 15.9847 3.06609C16.9603 3.1787 17.7696 3.43604 18.4021 4.06966C19.0038 4.67238 19.2625 5.43113 19.3835 6.33241C19.5 7.2007 19.5 8.30477 19.5 9.67486V14.793C19.5 16.1631 19.5 17.2672 19.3835 18.1355C19.2625 19.0367 19.0038 19.7955 18.4021 20.3982C17.8002 21.0011 17.0423 21.2605 16.1419 21.3818C15.2749 21.4986 14.1725 21.4986 12.805 21.4985L6.69504 21.4985C5.32752 21.4986 4.22513 21.4986 3.35808 21.3818C2.45774 21.2605 1.69976 21.0011 1.0979 20.3982C0.496209 19.7955 0.237469 19.0367 0.116503 18.1355C-3.37213e-05 17.2672 -1.84605e-05 16.1631 4.81163e-07 14.7931L4.81163e-07 9.67484C-1.84605e-05 8.30478 -3.37213e-05 7.20069 0.116503 6.33241C0.237469 5.43113 0.496209 4.67238 1.0979 4.06966C1.73043 3.43604 2.53975 3.1787 3.51534 3.06608C3.46879 2.52334 3.51736 1.97573 3.81239 1.46113C3.96268 1.19899 4.15422 0.962221 4.37944 0.759151C4.8793 0.308473 5.49503 0.143956 6.12506 0.0704231C6.72893 -5.54994e-05 7.49097 -2.95155e-05 8.39392 1.28104e-06ZM6.35067 1.9573C5.88641 2.01149 5.74252 2.10241 5.67575 2.1626C5.5993 2.23154 5.53534 2.31095 5.48568 2.39756C5.44637 2.46613 5.39027 2.6108 5.44746 3.05137C5.50673 3.50797 5.66092 4.11081 5.89823 5.02796C6.08725 5.75848 6.21246 6.23783 6.34555 6.59684C6.47171 6.93716 6.57647 7.08584 6.6787 7.18308C6.75978 7.26021 6.85016 7.32847 6.9482 7.38631C7.07461 7.46091 7.2556 7.52465 7.63389 7.56094C7.88784 7.58531 8.19058 7.59448 8.58009 7.59793L9.30138 5.43404C9.47603 4.9101 10.0424 4.62694 10.5663 4.80159C11.0902 4.97624 11.3734 5.54256 11.1988 6.0665L10.6878 7.59935C11.1924 7.59743 11.5638 7.58994 11.8661 7.56094C12.2444 7.52465 12.4254 7.46091 12.5518 7.38631C12.6498 7.32847 12.7402 7.26021 12.8213 7.18308C12.9235 7.08584 13.0283 6.93716 13.1545 6.59684C13.2875 6.23783 13.4128 5.75848 13.6018 5.02796C13.8391 4.11081 13.9933 3.50797 14.0525 3.05137C14.1097 2.6108 14.0536 2.46613 14.0143 2.39756C13.9647 2.31095 13.9007 2.23153 13.8242 2.1626C13.7575 2.10241 13.6136 2.01149 13.1493 1.9573C12.6718 1.90157 12.0247 1.9 11.0479 1.9L8.45213 1.9C7.47532 1.9 6.82816 1.90157 6.35067 1.9573ZM7.75 15.75C7.19772 15.75 6.75 16.1977 6.75 16.75C6.75 17.3023 7.19772 17.75 7.75 17.75H11.75C12.3023 17.75 12.75 17.3023 12.75 16.75C12.75 16.1977 12.3023 15.75 11.75 15.75L7.75 15.75Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

@ -210,7 +210,7 @@ class ApiClientImp implements ApiClient {
final int statusCode = response.statusCode;
log("uri: ${Uri.parse(url.trim())}");
log("body: ${json.encode(body)}");
// log("response.body: ${response.body}");
log("response.body: ${response.body}");
if (statusCode < 200 || statusCode >= 400) {
onFailure('Error While Fetching data', statusCode, failureType: StatusCodeFailure("Error While Fetching data"));
logApiEndpointError(endPoint, 'Error While Fetching data', statusCode);

@ -398,19 +398,6 @@ var GET_COVID_DRIVETHRU_PROCEDURES_LIST = 'Services/Doctors.svc/REST/COVID19_Get
var GET_PATIENT_LAST_RECORD = 'Services/Patients.svc/REST/Med_GetPatientLastRecord';
var INSERT_PATIENT_HEALTH_DATA = 'Services/Patients.svc/REST/Med_InsertTransactions';
///My Trackers
var GET_DIABETIC_RESULT_AVERAGE = 'Services/Patients.svc/REST/Patient_GetDiabeticResultAverage';
var GET_DIABTEC_RESULT = 'Services/Patients.svc/REST/Patient_GetDiabtecResults';
var ADD_DIABTEC_RESULT = 'Services/Patients.svc/REST/Patient_AddDiabtecResult';
var GET_BLOOD_PRESSURE_RESULT_AVERAGE = 'Services/Patients.svc/REST/Patient_GetBloodPressureResultAverage';
var GET_BLOOD_PRESSURE_RESULT = 'Services/Patients.svc/REST/Patient_GetBloodPressureResult';
var ADD_BLOOD_PRESSURE_RESULT = 'Services/Patients.svc/REST/Patient_AddBloodPressureResult';
var GET_WEIGHT_PRESSURE_RESULT_AVERAGE = 'Services/Patients.svc/REST/Patient_GetWeightMeasurementResultAverage';
var GET_WEIGHT_PRESSURE_RESULT = 'Services/Patients.svc/REST/Patient_GetWeightMeasurementResult';
var ADD_WEIGHT_PRESSURE_RESULT = 'Services/Patients.svc/REST/Patient_AddWeightMeasurementResult';
var ADD_ACTIVE_PRESCRIPTIONS_REPORT_BY_PATIENT_ID = 'Services/Patients.svc/Rest/GetActivePrescriptionReportByPatientID';
var GET_CALL_INFO_HOURS_RESULT = 'Services/Doctors.svc/REST/GetCallInfoHoursResult';
@ -420,7 +407,6 @@ var GET_QUESTION_TYPES = 'Services/OUTPs.svc/REST/getQuestionsTypes';
var UPDATE_DIABETIC_RESULT = 'Services/Patients.svc/REST/Patient_UpdateDiabeticResult';
var SEND_AVERAGE_BLOOD_SUGAR_REPORT = 'Services/Notifications.svc/REST/SendAverageBloodSugarReport';
var DEACTIVATE_DIABETIC_STATUS = 'services/Patients.svc/REST/Patient_DeactivateDiabeticStatus';
var DEACTIVATE_BLOOD_PRESSURES_STATUS = 'services/Patients.svc/REST/Patient_DeactivateBloodPressureStatus';
@ -825,11 +811,12 @@ class ApiConsts {
static final String updateHHCOrder = 'api/hhc/update';
static final String addHHCOrder = 'api/HHC/add';
// SYMPTOMS CHECKER
// SYMPTOMS CHECKER API
static final String symptomsUserLogin = '$symptomsCheckerApi/user_login';
static final String getBodySymptomsByName = '$symptomsCheckerApi/GetBodySymptomsByName';
static final String getRiskFactors = '$symptomsCheckerApi/GetRiskFactors';
static final String getSuggestions = '$symptomsCheckerApi/GetSuggestion';
static final String diagnosis = '$symptomsCheckerApi/diagnosis';
static final String diagnosis = '$symptomsCheckerApi/GetDiagnosis';
static final String explain = '$symptomsCheckerApi/explain';
//E-REFERRAL SERVICES
@ -848,6 +835,37 @@ class ApiConsts {
static String h2oUpdateUserDetail = "Services/H2ORemainder.svc/REST/H2O_UpdateUserDetails_New";
static String h2oUndoUserActivity = "Services/H2ORemainder.svc/REST/H2o_UndoUserActivity";
// HEALTH TRACKERS
// Blood Sugar (Diabetic)
static String getDiabeticResultAverage = 'Services/Patients.svc/REST/Patient_GetDiabeticResultAverage';
static String getDiabeticResult = 'Services/Patients.svc/REST/Patient_GetDiabtecResults';
static String addDiabeticResult = 'Services/Patients.svc/REST/Patient_AddDiabtecResult';
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';
// Blood Pressure
static String getBloodPressureResultAverage = 'Services/Patients.svc/REST/Patient_GetBloodPressureResultAverage';
static String getBloodPressureResult = 'Services/Patients.svc/REST/Patient_GetBloodPressureResult';
static String addBloodPressureResult = 'Services/Patients.svc/REST/Patient_AddBloodPressureResult';
static String updateBloodPressureResult = 'Services/Patients.svc/REST/Patient_UpdateBloodPressureResult';
static String deactivateBloodPressureStatus = 'Services/Patients.svc/REST/Patient_DeactivateBloodPressureStatus';
static String sendAverageBloodPressureReport = 'Services/Notifications.svc/REST/SendAverageBloodPressureReport';
// Weight Measurement
static String getWeightMeasurementResultAverage = 'Services/Patients.svc/REST/Patient_GetWeightMeasurementResultAverage';
static String getWeightMeasurementResult = 'Services/Patients.svc/REST/Patient_GetWeightMeasurementResult';
static String addWeightMeasurementResult = 'Services/Patients.svc/REST/Patient_AddWeightMeasurementResult';
static String updateWeightMeasurementResult = 'Services/Patients.svc/REST/Patient_UpdateWeightMeasurementResult';
static String deactivateWeightMeasurementStatus = 'Services/Patients.svc/REST/Patient_DeactivateWeightMeasurementStatus';
static String sendAverageBodyWeightReport = 'Services/Notifications.svc/REST/SendAverageBodyWeightReport';
//Blood Donation
static String bloodGroupUpdate = "Services/PatientVarification.svc/REST/BloodDonation_RegisterBloodType";
static String userAgreementForBloodGroupUpdate = "Services/PatientVarification.svc/REST/AddUserAgreementForBloodDonation";
static String getProjectsHaveBDClinics = "Services/OUTPs.svc/REST/BD_getProjectsHaveBDClinics";
static String getClinicsBDFreeSlots = "Services/OUTPs.svc/REST/BD_GetFreeSlots";
// ************ static values for Api ****************
static final double appVersionID = 50.3;
static final int appChannelId = 3;

@ -218,6 +218,8 @@ class AppAssets {
static const String activity = '$svgBasePath/activity.svg';
static const String age = '$svgBasePath/age_icon.svg';
static const String gender = '$svgBasePath/gender_icon.svg';
static const String genderInputIcon = '$svgBasePath/genderInputIcon.svg';
static const String bloodType = '$svgBasePath/blood_type.svg';
static const String trade_down_yellow = '$svgBasePath/trade_down_yellow.svg';
static const String trade_down_red = '$svgBasePath/trade_down_red.svg';
@ -268,16 +270,21 @@ class AppAssets {
static const String yellowArrowDownIcon = '$svgBasePath/yellow_arrow_down_icon.svg';
static const String greenTickIcon = '$svgBasePath/green_tick_icon.svg';
// PNGS
static const String bloodSugar = '$svgBasePath/bloodsugar.svg';
static const String bloodCholestrol = '$svgBasePath/bloodcholestrol.svg';
static const String triglycerides = '$svgBasePath/triglycerides.svg';
static const String bulb = '$svgBasePath/bulb.svg';
static const String switchBtn = '$svgBasePath/switch.svg';
//Health Trackers
static const String bloodPressureIcon = '$svgBasePath/blood_pressure_icon.svg';
static const String bloodSugarOnlyIcon = '$svgBasePath/blood_sugar_only_icon.svg';
static const String weightIcon = '$svgBasePath/weight_icon.svg';
static const String normalStatusGreenIcon = '$svgBasePath/normal_status_green_icon.svg';
static const String sendEmailIcon = '$svgBasePath/send_email_icon.svg';
static const String lowIndicatorIcon = '$svgBasePath/low_indicator_icon.svg';
// Health Calculators
static const String ovulationAccordion = '$svgBasePath/approximate_ovulation_accordion.svg';
static const String nextPeriodAccordion = '$svgBasePath/next_period_accordion.svg';
static const String fertileAccordion = '$svgBasePath/fertile_window_accordion.svg';
@ -286,9 +293,7 @@ class AppAssets {
static const String covid19icon = '$svgBasePath/covid_19.svg';
//vital sign
static const String heartRate = '$svgBasePath/heart_rate.svg';
static const String respRate = '$svgBasePath/resp_rate.svg';
static const String weightVital = '$svgBasePath/weight_2.svg';
@ -297,8 +302,6 @@ class AppAssets {
static const String bloodPressure = '$svgBasePath/blood_pressure.svg';
static const String temperature = '$svgBasePath/temperature.svg';
// PNGS //
static const String hmgLogo = '$pngBasePath/hmg_logo.png';
static const String liveCareService = '$pngBasePath/livecare_service.png';
@ -325,8 +328,6 @@ class AppAssets {
static const String fullBodyFront = '$pngBasePath/full_body_front.png';
static const String fullBodyBack = '$pngBasePath/full_body_back.png';
static const String bmiFullBody = '$pngBasePath/bmi_image_1.png';
}
class AppAnimations {
@ -347,6 +348,4 @@ class AppAnimations {
static const String ambulance = '$lottieBasePath/ambulance.json';
static const String ambulanceAlert = '$lottieBasePath/ambulance_alert.json';
static const String rrtAmbulance = '$lottieBasePath/rrt_ambulance.json';
}

@ -18,6 +18,7 @@ import 'package:hmg_patient_app_new/features/emergency_services/emergency_servic
import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart';
import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_repo.dart';
import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart';
import 'package:hmg_patient_app_new/features/health_trackers/health_trackers_repo.dart';
import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_repo.dart';
import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart';
import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_repo.dart';
@ -52,6 +53,7 @@ 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/presentation/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';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
@ -143,6 +145,7 @@ class AppDependencies {
getIt.registerLazySingleton<BloodDonationRepo>(() => BloodDonationRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<WaterMonitorRepo>(() => WaterMonitorRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<MyInvoicesRepo>(() => MyInvoicesRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<HealthTrackersRepo>(() => HealthTrackersRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<MonthlyReportRepo>(() => MonthlyReportRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
// ViewModels
@ -154,26 +157,20 @@ class AppDependencies {
() => RadiologyViewModel(radiologyRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt()),
);
getIt.registerLazySingleton<PrescriptionsViewModel>(
() => PrescriptionsViewModel(prescriptionsRepo: getIt(), errorHandlerService: getIt(), navServices: getIt()));
getIt.registerLazySingleton<PrescriptionsViewModel>(() => PrescriptionsViewModel(prescriptionsRepo: getIt(), errorHandlerService: getIt(), navServices: getIt()));
getIt.registerLazySingleton<InsuranceViewModel>(() => InsuranceViewModel(insuranceRepo: getIt(), errorHandlerService: getIt()));
getIt.registerLazySingleton<MyAppointmentsViewModel>(
() => MyAppointmentsViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt()));
getIt.registerLazySingleton<MyAppointmentsViewModel>(() => MyAppointmentsViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt()));
getIt.registerLazySingleton<AppointmentRatingViewModel>(
() => AppointmentRatingViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt()));
getIt.registerLazySingleton<AppointmentRatingViewModel>(() => AppointmentRatingViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt()));
getIt.registerLazySingleton<PayfortViewModel>(
() => PayfortViewModel(payfortRepo: getIt(), errorHandlerService: getIt()),
);
getIt.registerLazySingleton<HabibWalletViewModel>(
() => HabibWalletViewModel(
habibWalletRepo: getIt(),
errorHandlerService: getIt()
),
() => HabibWalletViewModel(habibWalletRepo: getIt(), errorHandlerService: getIt()),
);
getIt.registerLazySingleton<MedicalFileViewModel>(
@ -185,12 +182,7 @@ class AppDependencies {
getIt.registerLazySingleton<BookAppointmentsViewModel>(
() => BookAppointmentsViewModel(
bookAppointmentsRepo: getIt(),
errorHandlerService: getIt(),
navigationService: getIt(),
myAppointmentsViewModel: getIt(),
locationUtils: getIt(),
dialogService: getIt()),
bookAppointmentsRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt(), myAppointmentsViewModel: getIt(), locationUtils: getIt(), dialogService: getIt()),
);
getIt.registerLazySingleton<ImmediateLiveCareViewModel>(
@ -204,13 +196,7 @@ class AppDependencies {
getIt.registerLazySingleton<AuthenticationViewModel>(
() => AuthenticationViewModel(
authenticationRepo: getIt(),
cacheService: getIt(),
navigationService: getIt(),
dialogService: getIt(),
appState: getIt(),
errorHandlerService: getIt(),
localAuthService: getIt()),
authenticationRepo: getIt(), cacheService: getIt(), navigationService: getIt(), dialogService: getIt(), appState: getIt(), errorHandlerService: getIt(), localAuthService: getIt()),
);
getIt.registerLazySingleton<ProfileSettingsViewModel>(() => ProfileSettingsViewModel());
@ -271,15 +257,22 @@ class AppDependencies {
navigationService: getIt(),
dialogService: getIt(),
appState: getIt(),
navServices: getIt(),
),
);
getIt.registerLazySingleton<HealthProvider>(() => HealthProvider());
getIt.registerLazySingleton<WaterMonitorViewModel>(() => WaterMonitorViewModel(waterMonitorRepo: getIt()));
getIt.registerLazySingleton<WaterMonitorViewModel>(() => WaterMonitorViewModel(waterMonitorRepo: getIt(), errorHandlerService: getIt()));
getIt.registerLazySingleton<MyInvoicesViewModel>(() => MyInvoicesViewModel(myInvoicesRepo: getIt(), errorHandlerService: getIt(), navServices: getIt()));
getIt.registerLazySingleton<MonthlyReportViewModel>(() => MonthlyReportViewModel(errorHandlerService: getIt(), monthlyReportRepo: getIt()));
getIt.registerLazySingleton<MyInvoicesViewModel>(() => MyInvoicesViewModel(
myInvoicesRepo: getIt(),
errorHandlerService: getIt(),
navServices: getIt(),
));
getIt.registerLazySingleton<HealthTrackersViewModel>(() => HealthTrackersViewModel(healthTrackersRepo: getIt(), errorHandlerService: getIt()));
}
}

@ -16,7 +16,7 @@ enum CountryEnum { saudiArabia, unitedArabEmirates }
enum CalenderEnum { gregorian, hijri }
enum SelectionTypeEnum { dropdown, calendar, search }
enum SelectionTypeEnum { dropdown, calendar, search, time }
enum GenderTypeEnum { male, female }
@ -38,7 +38,19 @@ enum HealthCalConEnum { calculator, converter }
enum HealthCalculatorEnum { general, women }
enum HealthCalculatorsTypeEnum { bmi, calories, bmr, idealBodyWeight, bodyFat, crabsProteinFat, ovulation, deliveryDueDate, bloodSugar, bloodCholesterol, triglycerides }
enum HealthCalculatorsTypeEnum {
bmi,
calories,
bmr,
idealBodyWeight,
bodyFat,
crabsProteinFat,
ovulation,
deliveryDueDate,
bloodSugar,
bloodCholesterol,
triglycerides
}
extension HealthCalculatorExtenshion on HealthCalculatorsTypeEnum {
String get displayName {
@ -297,3 +309,5 @@ extension ServiceTypeEnumExt on ServiceTypeEnum {
// SymptomsChecker
enum PossibleConditionsSeverityEnum { seekMedicalAdvice, monitorOnly, emergency }
enum HealthTrackerTypeEnum { bloodSugar, bloodPressure, weightTracker }

@ -7,16 +7,14 @@ import 'package:flutter/material.dart'; // These are the Viewport values of your
const num figmaDesignWidth = 375; // iPhone X / 12 base width
const num figmaDesignHeight = 812; // iPhone X / 12 base height
extension ConstrainedResponsive on num {
/// Width with max cap for tablets
double get wCapped => isTablet ? math.min( w, this * 1.3) : w;
double get wCapped => isTablet ? math.min(w, this * 1.3) : w;
/// Height with max cap for tablets
double get hCapped => isTablet ? math.min(h, this * 1.3) : h;
}
extension ResponsiveExtension on num {
double get _screenWidth => SizeUtils.width;

@ -262,16 +262,6 @@ class Utils {
return await prefs.remove(key);
}
static void showLoading({bool isNeedBinding = true}) {
if (isNeedBinding) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showLoadingDialog();
});
} else {
showLoadingDialog();
}
}
static void showLoadingDialog() {
_isLoadingVisible = true;
showDialog(
@ -288,18 +278,6 @@ class Utils {
);
}
static void hideLoading() {
try {
if (_isLoadingVisible) {
_isLoadingVisible = false;
Navigator.of(navigationService.navigatorKey.currentContext!).pop();
}
_isLoadingVisible = false;
} catch (e) {
log("errr: ${e.toString()}");
}
}
static List<T> uniqueBy<T, K>(List<T> list, K Function(T) keySelector) {
final seenKeys = <K>{};
return list.where((item) => seenKeys.add(keySelector(item))).toList();

@ -3,14 +3,26 @@ import 'package:hmg_patient_app_new/core/api/api_client.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
import 'package:hmg_patient_app_new/features/blood_donation/models/blood_group_hospitals_model.dart';
import 'package:hmg_patient_app_new/features/blood_donation/models/blood_group_response_model.dart';
import 'package:hmg_patient_app_new/features/blood_donation/models/cities_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
abstract class BloodDonationRepo {
Future<Either<Failure, GenericApiModel<List<CitiesModel>>>> getAllCities();
Future<Either<Failure, GenericApiModel<List<HospitalsModel>>>> getProjectList();
Future<Either<Failure, GenericApiModel<List<BdGetProjectsHaveBdClinic>>>> getBloodDonationProjectsList();
Future<Either<Failure, GenericApiModel<List_BloodGroupDetailsModel>>> getPatientBloodGroupDetails();
Future<Either<Failure, GenericApiModel<dynamic>>> updateBloodGroup({required Map<String, dynamic> request});
Future<Either<Failure, GenericApiModel<dynamic>>> getFreeBloodDonationSlots({required Map<String, dynamic> request});
Future<Either<Failure, GenericApiModel<dynamic>>> addUserAgreementForBloodDonation({required Map<String, dynamic> request});
}
class BloodDonationRepoImp implements BloodDonationRepo {
@ -93,4 +105,186 @@ class BloodDonationRepoImp implements BloodDonationRepo {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<List<HospitalsModel>>>> getProjectList() async {
Map<String, dynamic> request = {};
try {
GenericApiModel<List<HospitalsModel>>? apiResponse;
Failure? failure;
await apiClient.post(
GET_PROJECT_LIST,
body: request,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['ListProject'];
final appointmentsList = list.map((item) => HospitalsModel.fromJson(item as Map<String, dynamic>)).toList().cast<HospitalsModel>();
apiResponse = GenericApiModel<List<HospitalsModel>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: appointmentsList,
);
} 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<List<BdGetProjectsHaveBdClinic>>>> getBloodDonationProjectsList() async {
Map<String, dynamic> request = {};
try {
GenericApiModel<List<BdGetProjectsHaveBdClinic>>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.getProjectsHaveBDClinics,
body: request,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final listData = (response['BD_getProjectsHaveBDClinics'] as List<dynamic>);
final list = listData.map((item) => BdGetProjectsHaveBdClinic.fromJson(item as Map<String, dynamic>)).toList();
apiResponse = GenericApiModel<List<BdGetProjectsHaveBdClinic>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: list,
);
} 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<List<dynamic>>>> updateBloodGroup({required Map<String, dynamic> request}) async {
try {
GenericApiModel<List<dynamic>>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.bloodGroupUpdate,
body: request,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
// final list = response['ListProject'];
// final appointmentsList = list.map((item) => HospitalsModel.fromJson(item as Map<String, dynamic>)).toList().cast<HospitalsModel>();
apiResponse = GenericApiModel<List<dynamic>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: response,
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<List<dynamic>>>> getFreeBloodDonationSlots({required Map<String, dynamic> request}) async {
try {
GenericApiModel<List<dynamic>>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.getClinicsBDFreeSlots,
body: request,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
// final list = response['ListProject'];
// final appointmentsList = list.map((item) => HospitalsModel.fromJson(item as Map<String, dynamic>)).toList().cast<HospitalsModel>();
apiResponse = GenericApiModel<List<dynamic>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: response,
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<List<dynamic>>>> addUserAgreementForBloodDonation({required Map<String, dynamic> request}) async {
try {
GenericApiModel<List<dynamic>>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.userAgreementForBloodGroupUpdate,
body: request,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
// final list = response['ListProject'];
// final appointmentsList = list.map((item) => HospitalsModel.fromJson(item as Map<String, dynamic>)).toList().cast<HospitalsModel>();
apiResponse = GenericApiModel<List<dynamic>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: response,
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
}

@ -1,15 +1,25 @@
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/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/doctor_response_mapper.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart';
import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_repo.dart';
import 'package:hmg_patient_app_new/features/blood_donation/models/blood_group_hospitals_model.dart';
import 'package:hmg_patient_app_new/features/blood_donation/models/blood_group_list_model.dart';
import 'package:hmg_patient_app_new/features/blood_donation/models/blood_group_response_model.dart';
import 'package:hmg_patient_app_new/features/blood_donation/models/cities_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/doctor_list_api_response.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
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/navigation_service.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
class BloodDonationViewModel extends ChangeNotifier {
final DialogService dialogService;
@ -17,8 +27,21 @@ class BloodDonationViewModel extends ChangeNotifier {
ErrorHandlerService errorHandlerService;
final NavigationService navigationService;
final AppState appState;
bool isTermsAccepted = false;
BdGetProjectsHaveBdClinic? selectedHospital;
CitiesModel? selectedCity;
BloodGroupListModel? selectedBloodGroup;
int _selectedHospitalIndex = 0;
int _selectedBloodTypeIndex = 0;
GenderTypeEnum? selectedGender;
String? selectedBloodType;
final NavigationService navServices;
List<BdGetProjectsHaveBdClinic> hospitalList = [];
List<CitiesModel> citiesList = [];
List_BloodGroupDetailsModel patientBloodGroupDetailsModel = List_BloodGroupDetailsModel();
List<BloodGroupListModel> bloodGroupList = [
BloodGroupListModel("O+", 0),
BloodGroupListModel("O-", 1),
@ -30,35 +53,34 @@ class BloodDonationViewModel extends ChangeNotifier {
BloodGroupListModel("B-", 7),
];
List<BloodGroupListModel> genderList = [
BloodGroupListModel(LocaleKeys.malE.tr(), 1),
BloodGroupListModel("Female".needTranslation.tr(), 2),
];
late CitiesModel selectedCity;
late BloodGroupListModel selectedBloodGroup;
int _selectedHospitalIndex = 0;
int _selectedBloodTypeIndex = 0;
String selectedBloodType = '';
List_BloodGroupDetailsModel patientBloodGroupDetailsModel = List_BloodGroupDetailsModel();
BloodDonationViewModel({required this.bloodDonationRepo, required this.errorHandlerService, required this.navigationService, required this.dialogService, required this.appState});
BloodDonationViewModel({
required this.bloodDonationRepo,
required this.errorHandlerService,
required this.navigationService,
required this.dialogService,
required this.appState,
required this.navServices,
});
setSelectedCity(CitiesModel city) {
selectedCity = city;
notifyListeners();
}
void onGenderChange(String? status) {
selectedGender = GenderTypeExtension.fromType(status)!;
notifyListeners();
}
setSelectedBloodGroup(BloodGroupListModel bloodGroup) {
selectedBloodGroup = bloodGroup;
selectedBloodType = selectedBloodGroup.name;
selectedBloodType = selectedBloodGroup!.name;
notifyListeners();
}
Future<void> getRegionSelectedClinics({Function(dynamic)? onSuccess, Function(String)? onError}) async {
citiesList.clear();
selectedCity = CitiesModel();
selectedCity = null;
notifyListeners();
final result = await bloodDonationRepo.getAllCities();
@ -71,6 +93,7 @@ class BloodDonationViewModel extends ChangeNotifier {
onError!(apiResponse.errorMessage ?? 'An unexpected error occurred');
} else if (apiResponse.messageStatus == 1) {
citiesList = apiResponse.data!;
citiesList.sort((a, b) => a.description!.compareTo(b.description!));
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
@ -100,7 +123,7 @@ class BloodDonationViewModel extends ChangeNotifier {
citiesModel.descriptionN = citiesList[_selectedHospitalIndex].descriptionN;
selectedCity = citiesModel;
selectedBloodType = patientBloodGroupDetailsModel.bloodGroup!;
_selectedBloodTypeIndex = getBloodIndex(selectedBloodType);
_selectedBloodTypeIndex = getBloodIndex(selectedBloodType ?? '');
notifyListeners();
if (onSuccess != null) {
@ -113,11 +136,11 @@ class BloodDonationViewModel extends ChangeNotifier {
int getSelectedCityID() {
int cityID = 1;
citiesList.forEach((element) {
for (var element in citiesList) {
if (element.description == patientBloodGroupDetailsModel.city) {
cityID = element.iD!;
}
});
}
return cityID;
}
@ -144,4 +167,120 @@ class BloodDonationViewModel extends ChangeNotifier {
return 0;
}
}
void onTermAccepted() {
isTermsAccepted = !isTermsAccepted;
notifyListeners();
}
bool isUserAuthanticated() {
print("the app state is ${appState.isAuthenticated}");
if (!appState.isAuthenticated) {
return false;
} else {
return true;
}
}
Future<void> fetchHospitalsList() async {
// hospitalList.clear();
notifyListeners();
final result = await bloodDonationRepo.getBloodDonationProjectsList();
result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),
(apiResponse) async {
if (apiResponse.messageStatus == 2) {
} else if (apiResponse.messageStatus == 1) {
hospitalList = apiResponse.data!;
hospitalList.sort((a, b) => a.projectName!.compareTo(b.projectName!));
notifyListeners();
}
},
);
}
Future<void> getFreeBloodDonationSlots({required Map<String, dynamic> request}) async {
final result = await bloodDonationRepo.getFreeBloodDonationSlots(request: request);
result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),
(apiResponse) async {
if (apiResponse.messageStatus == 2) {
} else if (apiResponse.messageStatus == 1) {
// TODO: Handle free slots data
print(apiResponse.data['BD_FreeSlots']);
notifyListeners();
}
},
);
}
bool isLocationEnabled() {
return appState.userLong != 0.0 && appState.userLong != 0.0;
}
setSelectedHospital(BdGetProjectsHaveBdClinic hospital) {
selectedHospital = hospital;
notifyListeners();
}
Future<bool> validateSelections() async {
if (selectedCity == null) {
await dialogService.showErrorBottomSheet(
message: "Please choose city",
);
return false;
}
if (selectedBloodGroup == null) {
await dialogService.showErrorBottomSheet(
message: "Please choose Gender",
);
return false;
}
if (selectedBloodType == null) {
await dialogService.showErrorBottomSheet(
message: "Please choose Blood Group",
);
return false;
}
if (!isTermsAccepted) {
await dialogService.showErrorBottomSheet(
message: "Please accept Terms and Conditions to continue",
);
return false;
}
return true;
}
Future<void> updateBloodGroup() async {
LoaderBottomSheet.showLoader();
// body['City'] = detailsModel.city;
// body['cityCode'] = detailsModel.cityCode;
// body['Gender'] = detailsModel.gender;
// body['BloodGroup'] = detailsModel.bloodGroup;
// body['CellNumber'] = user.mobileNumber;
// body['LanguageID'] = languageID;
// body['NationalID'] = user.nationalityID;
// body['ZipCode'] = user.zipCode ?? "+966";
// body['isDentalAllowedBackend'] = false;
Map<String, dynamic> payload = {
"City": selectedCity?.description,
"cityCode": selectedCity?.iD,
"Gender": selectedGender?.value,
"isDentalAllowedBackend": false
// "Gender": selectedGender?.value,
};
await bloodDonationRepo.updateBloodGroup(request: payload);
await addUserAgreementForBloodDonation();
LoaderBottomSheet.hideLoader();
}
Future<void> addUserAgreementForBloodDonation() async {
Map<String, dynamic> payload = {"IsAgreed": true};
await bloodDonationRepo.addUserAgreementForBloodDonation(request: payload);
}
}

@ -0,0 +1,81 @@
import 'dart:convert';
class BdProjectsHaveBdClinicsModel {
List<BdGetProjectsHaveBdClinic>? bdGetProjectsHaveBdClinics;
BdProjectsHaveBdClinicsModel({
this.bdGetProjectsHaveBdClinics,
});
factory BdProjectsHaveBdClinicsModel.fromRawJson(String str) => BdProjectsHaveBdClinicsModel.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory BdProjectsHaveBdClinicsModel.fromJson(Map<String, dynamic> json) => BdProjectsHaveBdClinicsModel(
bdGetProjectsHaveBdClinics: json["BD_getProjectsHaveBDClinics"] == null ? [] : List<BdGetProjectsHaveBdClinic>.from(json["BD_getProjectsHaveBDClinics"]!.map((x) => BdGetProjectsHaveBdClinic.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"BD_getProjectsHaveBDClinics": bdGetProjectsHaveBdClinics == null ? [] : List<dynamic>.from(bdGetProjectsHaveBdClinics!.map((x) => x.toJson())),
};
}
class BdGetProjectsHaveBdClinic {
int? rowId;
int? id;
int? projectId;
int? numberOfRooms;
bool? isActive;
int? createdBy;
String? createdOn;
dynamic editedBy;
dynamic editedOn;
String? projectName;
dynamic projectNameN;
BdGetProjectsHaveBdClinic({
this.rowId,
this.id,
this.projectId,
this.numberOfRooms,
this.isActive,
this.createdBy,
this.createdOn,
this.editedBy,
this.editedOn,
this.projectName,
this.projectNameN,
});
factory BdGetProjectsHaveBdClinic.fromRawJson(String str) => BdGetProjectsHaveBdClinic.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory BdGetProjectsHaveBdClinic.fromJson(Map<String, dynamic> json) => BdGetProjectsHaveBdClinic(
rowId: json["RowID"],
id: json["ID"],
projectId: json["ProjectID"],
numberOfRooms: json["NumberOfRooms"],
isActive: json["IsActive"],
createdBy: json["CreatedBy"],
createdOn: json["CreatedOn"],
editedBy: json["EditedBy"],
editedOn: json["EditedON"],
projectName: json["ProjectName"],
projectNameN: json["ProjectNameN"],
);
Map<String, dynamic> toJson() => {
"RowID": rowId,
"ID": id,
"ProjectID": projectId,
"NumberOfRooms": numberOfRooms,
"IsActive": isActive,
"CreatedBy": createdBy,
"CreatedOn": createdOn,
"EditedBy": editedBy,
"EditedON": editedOn,
"ProjectName": projectName,
"ProjectNameN": projectNameN,
};
}

@ -0,0 +1,86 @@
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';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_view_model.dart';
import 'package:hmg_patient_app_new/features/blood_donation/models/blood_group_hospitals_model.dart';
import 'package:hmg_patient_app_new/theme/colors.dart' show AppColors;
import 'package:provider/provider.dart';
class HospitalBottomSheetBodySelection extends StatelessWidget {
final Function(BdGetProjectsHaveBdClinic userSelection) onUserHospitalSelection;
const HospitalBottomSheetBodySelection({super.key, required this.onUserHospitalSelection(BdGetProjectsHaveBdClinic userSelection)});
@override
Widget build(BuildContext context) {
final bloodDonationVm = Provider.of<BloodDonationViewModel>(context, listen: false);
AppState appState = getIt.get<AppState>();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Please select the hospital you want to make an appointment.".needTranslation,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: AppColors.greyTextColor,
),
),
SizedBox(height: 16.h),
SizedBox(
height: MediaQuery.sizeOf(context).height * .4,
child: ListView.separated(
itemBuilder: (_, index) {
return DecoratedBox(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: false,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8.h,
children: [
hospitalName(bloodDonationVm.hospitalList[index]).onPress(() {
onUserHospitalSelection(bloodDonationVm.hospitalList[index]);
Navigator.of(context).pop();
})
],
),
),
Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.blackColor, width: 40.h, height: 40.h, fit: BoxFit.contain),
),
],
).paddingSymmetrical(16.h, 16.h),
).onPress(() {
bloodDonationVm.setSelectedHospital(bloodDonationVm.hospitalList[index]);
Navigator.of(context).pop();
});
},
separatorBuilder: (_, __) => SizedBox(height: 16.h),
itemCount: bloodDonationVm.hospitalList.length),
)
],
);
}
Widget hospitalName(dynamic hospital) => Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.hmg).paddingOnly(right: 10),
Expanded(
child: Text(hospital.projectName ?? "", style: TextStyle(fontWeight: FontWeight.w600, fontSize: 16, color: AppColors.blackColor)),
)
],
);
}

@ -49,6 +49,7 @@ class BookAppointmentsViewModel extends ChangeNotifier {
bool isLiveCareSchedule = false;
bool isGetDocForHealthCal = false;
bool showSortFilterButtons = false;
int? calculationID = 0;
bool isSortByClinic = true;
@ -200,8 +201,10 @@ class BookAppointmentsViewModel extends ChangeNotifier {
void filterClinics(String? query) {
if (query!.isEmpty) {
_filteredClinicsList = List.from(clinicsList);
showSortFilterButtons = false;
} else {
_filteredClinicsList = clinicsList.where((clinic) => clinic.clinicDescription?.toLowerCase().contains(query!.toLowerCase()) ?? false).toList();
showSortFilterButtons = query.length >= 3;
}
notifyListeners();
}

@ -0,0 +1,873 @@
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/services/logger_service.dart';
/// Progress types to request different ranges from the progress API.
enum ProgressType { today, week, month }
abstract class HealthTrackersRepo {
// ==================== BLOOD SUGAR (DIABETIC) ====================
/// Get blood sugar result averages (week, month, year).
Future<Either<Failure, GenericApiModel<dynamic>>> getDiabeticResultAverage();
/// Get blood sugar results (week, month, year).
Future<Either<Failure, GenericApiModel<dynamic>>> getDiabeticResults();
/// Add new blood sugar result.
Future<Either<Failure, GenericApiModel<dynamic>>> addDiabeticResult({
required String bloodSugarDateChart,
required String bloodSugarResult,
required String diabeticUnit,
required int measuredTime,
});
/// Update existing blood sugar result.
Future<Either<Failure, GenericApiModel<dynamic>>> updateDiabeticResult({
required DateTime month,
required DateTime hour,
required String bloodSugarResult,
required String diabeticUnit,
required int measuredTime,
required int lineItemNo,
});
/// Deactivate blood sugar record.
Future<Either<Failure, GenericApiModel<dynamic>>> deactivateDiabeticStatus({
required int lineItemNo,
});
/// Send blood sugar report by email.
Future<Either<Failure, GenericApiModel<dynamic>>> sendBloodSugarReportByEmail({
required String email,
});
// ==================== BLOOD PRESSURE ====================
/// Get blood pressure result averages (week, month, year).
Future<Either<Failure, GenericApiModel<dynamic>>> getBloodPressureResultAverage();
/// Get blood pressure results (week, month, year).
Future<Either<Failure, GenericApiModel<dynamic>>> getBloodPressureResults();
/// Add new blood pressure result.
Future<Either<Failure, GenericApiModel<dynamic>>> addBloodPressureResult({
required String bloodPressureDate,
required String diastolicPressure,
required String systolicePressure,
required int measuredArm,
});
/// Update existing blood pressure result.
Future<Either<Failure, GenericApiModel<dynamic>>> updateBloodPressureResult({
required String bloodPressureDate,
required String diastolicPressure,
required String systolicePressure,
required int measuredArm,
required int lineItemNo,
});
/// Deactivate blood pressure record.
Future<Either<Failure, GenericApiModel<dynamic>>> deactivateBloodPressureStatus({
required int lineItemNo,
});
/// Send blood pressure report by email.
Future<Either<Failure, GenericApiModel<dynamic>>> sendBloodPressureReportByEmail({
required String email,
});
// ==================== WEIGHT MEASUREMENT ====================
/// Get weight measurement result averages (week, month, year).
Future<Either<Failure, GenericApiModel<dynamic>>> getWeightMeasurementResultAverage();
/// Get weight measurement results (week, month, year).
Future<Either<Failure, GenericApiModel<dynamic>>> getWeightMeasurementResults();
/// Add new weight measurement result.
Future<Either<Failure, GenericApiModel<dynamic>>> addWeightMeasurementResult({
required String weightDate,
required String weightMeasured,
required int weightUnit,
});
/// Update existing weight measurement result.
Future<Either<Failure, GenericApiModel<dynamic>>> updateWeightMeasurementResult({
required int lineItemNo,
required int weightUnit,
required String weightMeasured,
required String weightDate,
});
/// Deactivate weight measurement record.
Future<Either<Failure, GenericApiModel<dynamic>>> deactivateWeightMeasurementStatus({
required int lineItemNo,
});
/// Send weight report by email.
Future<Either<Failure, GenericApiModel<dynamic>>> sendWeightReportByEmail({
required String email,
});
}
class HealthTrackersRepoImp implements HealthTrackersRepo {
final ApiClient apiClient;
final LoggerService loggerService;
HealthTrackersRepoImp({required this.loggerService, required this.apiClient});
// ==================== BLOOD SUGAR (DIABETIC) METHODS ====================
@override
Future<Either<Failure, GenericApiModel<dynamic>>> getDiabeticResultAverage() async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.getDiabeticResultAverage,
body: {},
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
dynamic extracted;
if (response is Map) {
// Extract average lists
extracted = {
'monthAverageList': response['List_MonthDiabtectResultAverage'] ?? [],
'weekAverageList': response['List_WeekDiabtectResultAverage'] ?? [],
'yearAverageList': response['List_YearDiabtecResultAverage'] ?? [],
};
} else {
extracted = response;
}
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: extracted,
);
} 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>>> getDiabeticResults() async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.getDiabeticResult,
body: {},
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
dynamic extracted;
if (response is Map) {
// Extract patient result lists
extracted = {
'monthResultList': response['List_MonthDiabtecPatientResult'] ?? [],
'weekResultList': response['List_WeekDiabtecPatientResult'] ?? [],
'yearResultList': response['List_YearDiabtecPatientResult'] ?? [],
};
} else {
extracted = response;
}
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: extracted,
);
} 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,
required String bloodSugarResult,
required String diabeticUnit,
required int measuredTime,
}) async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
Map<String, dynamic> body = {
'BloodSugerDateChart': bloodSugarDateChart,
'BloodSugerResult': bloodSugarResult,
'DiabtecUnit': diabeticUnit,
'MeasuredTime': measuredTime + 1, // Add 1 as per old service
};
await apiClient.post(
ApiConsts.addDiabeticResult,
body: body,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: response,
);
},
);
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>>> updateDiabeticResult({
required DateTime month,
required DateTime hour,
required String bloodSugarResult,
required String diabeticUnit,
required int measuredTime,
required int lineItemNo,
}) async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
// Format: 'YYYY-MM-DD HH:MM:SS' as per old service
String formattedDate = '${month.year}-${month.month}-${month.day} ${hour.hour}:${hour.minute}:00';
Map<String, dynamic> body = {
'BloodSugerDateChart': formattedDate,
'BloodSugerResult': bloodSugarResult,
'DiabtecUnit': diabeticUnit,
'MeasuredTime': measuredTime + 1, // Add 1 as per old service
'LineItemNo': lineItemNo,
};
await apiClient.post(
ApiConsts.updateDiabeticResult,
body: body,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: response,
);
},
);
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>>> deactivateDiabeticStatus({
required int lineItemNo,
}) async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
Map<String, dynamic> body = {
'LineItemNo': lineItemNo,
};
await apiClient.post(
ApiConsts.deactivateDiabeticStatus,
body: body,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: response,
);
},
);
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>>> sendBloodSugarReportByEmail({
required String email,
}) async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
Map<String, dynamic> body = {
'To': email,
};
await apiClient.post(
ApiConsts.sendAverageBloodSugarReport,
body: body,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: response,
);
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
// ==================== BLOOD PRESSURE METHODS ====================
@override
Future<Either<Failure, GenericApiModel<dynamic>>> getBloodPressureResultAverage() async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.getBloodPressureResultAverage,
body: {},
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
dynamic extracted;
if (response is Map) {
// Extract all three list types
extracted = {
'monthList': response['List_MonthBloodPressureResultAverage'] ?? [],
'weekList': response['List_WeekBloodPressureResultAverage'] ?? [],
'yearList': response['List_YearBloodPressureResultAverage'] ?? [],
};
} else {
extracted = response;
}
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: extracted,
);
} 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>>> getBloodPressureResults() async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.getBloodPressureResult,
body: {},
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
dynamic extracted;
if (response is Map) {
// Extract all three list types
extracted = {
'weekList': response['List_WeekBloodPressureResult'] ?? [],
'monthList': response['List_MonthBloodPressureResult'] ?? [],
'yearList': response['List_YearBloodPressureResult'] ?? [],
};
} else {
extracted = response;
}
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: extracted,
);
} 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>>> addBloodPressureResult({
required String bloodPressureDate,
required String diastolicPressure,
required String systolicePressure,
required int measuredArm,
}) async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
Map<String, dynamic> body = {
'BloodPressureDate': bloodPressureDate,
'DiastolicPressure': diastolicPressure,
'SystolicePressure': systolicePressure,
'MeasuredArm': measuredArm,
};
await apiClient.post(
ApiConsts.addBloodPressureResult,
body: body,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: response,
);
},
);
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>>> updateBloodPressureResult({
required String bloodPressureDate,
required String diastolicPressure,
required String systolicePressure,
required int measuredArm,
required int lineItemNo,
}) async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
Map<String, dynamic> body = {
'BloodPressureDate': bloodPressureDate,
'DiastolicPressure': diastolicPressure,
'SystolicePressure': systolicePressure,
'MeasuredArm': measuredArm,
'LineItemNo': lineItemNo,
};
await apiClient.post(
ApiConsts.updateBloodPressureResult,
body: body,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: response,
);
},
);
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>>> deactivateBloodPressureStatus({
required int lineItemNo,
}) async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
Map<String, dynamic> body = {
'LineItemNo': lineItemNo,
};
await apiClient.post(
ApiConsts.deactivateBloodPressureStatus,
body: body,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: response,
);
},
);
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>>> sendBloodPressureReportByEmail({
required String email,
}) async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
Map<String, dynamic> body = {
'To': email,
};
await apiClient.post(
ApiConsts.sendAverageBloodPressureReport,
body: body,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: response,
);
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
// ==================== WEIGHT MEASUREMENT METHODS ====================
@override
Future<Either<Failure, GenericApiModel<dynamic>>> getWeightMeasurementResultAverage() async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.getWeightMeasurementResultAverage,
body: {},
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
dynamic extracted;
if (response is Map) {
// Extract average lists
extracted = {
'monthAverageList': response['List_MonthWeightMeasurementResultAverage'] ?? [],
'weekAverageList': response['List_WeekWeightMeasurementResultAverage'] ?? [],
'yearAverageList': response['List_YearWeightMeasurementResultAverage'] ?? [],
};
} else {
extracted = response;
}
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: extracted,
);
} 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>>> getWeightMeasurementResults() async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.getWeightMeasurementResult,
body: {},
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
dynamic extracted;
if (response is Map) {
// Extract result lists
extracted = {
'weekResultList': response['List_WeekWeightMeasurementResult'] ?? [],
'monthResultList': response['List_MonthWeightMeasurementResult'] ?? [],
'yearResultList': response['List_YearWeightMeasurementResult'] ?? [],
};
} else {
extracted = response;
}
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: extracted,
);
} 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>>> addWeightMeasurementResult({
required String weightDate,
required String weightMeasured,
required int weightUnit,
}) async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
Map<String, dynamic> body = {
'WeightDate': weightDate,
'WeightMeasured': weightMeasured,
'weightUnit': weightUnit,
};
await apiClient.post(
ApiConsts.addWeightMeasurementResult,
body: body,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: response,
);
},
);
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>>> updateWeightMeasurementResult({
required int lineItemNo,
required int weightUnit,
required String weightMeasured,
required String weightDate,
}) async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
Map<String, dynamic> body = {
'LineItemNo': lineItemNo,
'weightUnit': '$weightUnit', // Convert to string as per old service
'WeightMeasured': weightMeasured,
'WeightDate': weightDate,
};
await apiClient.post(
ApiConsts.updateWeightMeasurementResult,
body: body,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: response,
);
},
);
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>>> deactivateWeightMeasurementStatus({required int lineItemNo}) async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
Map<String, dynamic> body = {
'LineItemNo': lineItemNo,
};
await apiClient.post(
ApiConsts.deactivateWeightMeasurementStatus,
body: body,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: response,
);
},
);
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>>> sendWeightReportByEmail({
required String email,
}) async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
Map<String, dynamic> body = {
'To': email,
};
await apiClient.post(
ApiConsts.sendAverageBodyWeightReport,
body: body,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: response,
);
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
}

@ -0,0 +1,87 @@
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
class BloodPressureResult {
int? patientID;
int? lineItemNo;
DateTime? bloodPressureDate;
int? measuredArm;
int? systolicePressure;
int? diastolicPressure;
dynamic remark;
bool? isActive;
int? chartYear;
String? chartMonth;
dynamic yearSystolicePressureAverageResult;
dynamic monthSystolicePressureResult;
dynamic weekSystolicePressureResult;
int? yearDiastolicPressureAverageResult;
dynamic monthDiastolicPressureResult;
dynamic weekDiastolicPressureResult;
String? measuredArmDesc;
dynamic weekDesc;
BloodPressureResult(
{this.patientID,
this.lineItemNo,
this.bloodPressureDate,
this.measuredArm,
this.systolicePressure,
this.diastolicPressure,
this.remark,
this.isActive,
this.chartYear,
this.chartMonth,
this.yearSystolicePressureAverageResult,
this.monthSystolicePressureResult,
this.weekSystolicePressureResult,
this.yearDiastolicPressureAverageResult,
this.monthDiastolicPressureResult,
this.weekDiastolicPressureResult,
this.measuredArmDesc,
this.weekDesc});
BloodPressureResult.fromJson(Map<String, dynamic> json) {
patientID = json['PatientID'];
lineItemNo = json['LineItemNo'];
bloodPressureDate = DateUtil.convertStringToDate(json['BloodPressureDate']);
measuredArm = json['MeasuredArm'];
systolicePressure = json['SystolicePressure'];
diastolicPressure = json['DiastolicPressure'];
remark = json['Remark'];
isActive = json['IsActive'];
chartYear = json['ChartYear'];
chartMonth = json['ChartMonth'];
yearSystolicePressureAverageResult = json['YearSystolicePressureAverageResult'];
monthSystolicePressureResult = json['MonthSystolicePressureResult'];
weekSystolicePressureResult = json['WeekSystolicePressureResult'];
yearDiastolicPressureAverageResult = json['YearDiastolicPressureAverageResult'];
monthDiastolicPressureResult = json['MonthDiastolicPressureResult'];
weekDiastolicPressureResult = json['WeekDiastolicPressureResult'];
measuredArmDesc = json['MeasuredArmDesc'];
weekDesc = json['WeekDesc'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['PatientID'] = patientID;
data['LineItemNo'] = lineItemNo;
data['BloodPressureDate'] = bloodPressureDate;
data['MeasuredArm'] = measuredArm;
data['SystolicePressure'] = systolicePressure;
data['DiastolicPressure'] = diastolicPressure;
data['Remark'] = remark;
data['IsActive'] = isActive;
data['ChartYear'] = chartYear;
data['ChartMonth'] = chartMonth;
data['YearSystolicePressureAverageResult'] = yearSystolicePressureAverageResult;
data['MonthSystolicePressureResult'] = monthSystolicePressureResult;
data['WeekSystolicePressureResult'] = weekSystolicePressureResult;
data['YearDiastolicPressureAverageResult'] = yearDiastolicPressureAverageResult;
data['MonthDiastolicPressureResult'] = monthDiastolicPressureResult;
data['WeekDiastolicPressureResult'] = weekDiastolicPressureResult;
data['MeasuredArmDesc'] = measuredArmDesc;
data['WeekDesc'] = weekDesc;
return data;
}
}

@ -0,0 +1,58 @@
class MonthBloodPressureResultAverage {
dynamic weekfourSystolicePressureAverageResult;
dynamic weekfourDiastolicPressureAverageResult;
dynamic weekthreeSystolicePressureAverageResult;
dynamic weekthreeDiastolicPressureAverageResult;
dynamic weektwoSystolicePressureAverageResult;
dynamic weektwoDiastolicPressureAverageResult;
dynamic weekoneSystolicePressureAverageResult;
dynamic weekoneDiastolicPressureAverageResult;
String? weekDesc;
int? weekDiastolicPressureAverageResult;
int? weekSystolicePressureAverageResult;
MonthBloodPressureResultAverage({
this.weekfourSystolicePressureAverageResult,
this.weekfourDiastolicPressureAverageResult,
this.weekthreeSystolicePressureAverageResult,
this.weekthreeDiastolicPressureAverageResult,
this.weektwoSystolicePressureAverageResult,
this.weektwoDiastolicPressureAverageResult,
this.weekoneSystolicePressureAverageResult,
this.weekoneDiastolicPressureAverageResult,
this.weekDesc,
this.weekDiastolicPressureAverageResult,
this.weekSystolicePressureAverageResult,
});
MonthBloodPressureResultAverage.fromJson(Map<String, dynamic> json) {
weekfourSystolicePressureAverageResult = json['weekfourSystolicePressureAverageResult'];
weekfourDiastolicPressureAverageResult = json['weekfourDiastolicPressureAverageResult'];
weekthreeSystolicePressureAverageResult = json['weekthreeSystolicePressureAverageResult'];
weekthreeDiastolicPressureAverageResult = json['weekthreeDiastolicPressureAverageResult'];
weektwoSystolicePressureAverageResult = json['weektwoSystolicePressureAverageResult'];
weektwoDiastolicPressureAverageResult = json['weektwoDiastolicPressureAverageResult'];
weekoneSystolicePressureAverageResult = json['weekoneSystolicePressureAverageResult'];
weekoneDiastolicPressureAverageResult = json['weekoneDiastolicPressureAverageResult'];
weekDesc = json['WeekDesc'];
weekDiastolicPressureAverageResult = json['WeekDiastolicPressureAverageResult'];
weekSystolicePressureAverageResult = json['WeekSystolicePressureAverageResult'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['weekfourSystolicePressureAverageResult'] = weekfourSystolicePressureAverageResult;
data['weekfourDiastolicPressureAverageResult'] = weekfourDiastolicPressureAverageResult;
data['weekthreeSystolicePressureAverageResult'] = weekthreeSystolicePressureAverageResult;
data['weekthreeDiastolicPressureAverageResult'] = weekthreeDiastolicPressureAverageResult;
data['weektwoSystolicePressureAverageResult'] = weektwoSystolicePressureAverageResult;
data['weektwoDiastolicPressureAverageResult'] = weektwoDiastolicPressureAverageResult;
data['weekoneSystolicePressureAverageResult'] = weekoneSystolicePressureAverageResult;
data['weekoneDiastolicPressureAverageResult'] = weekoneDiastolicPressureAverageResult;
data['WeekDesc'] = weekDesc;
data['WeekDiastolicPressureAverageResult'] = weekDiastolicPressureAverageResult;
data['WeekSystolicePressureAverageResult'] = weekSystolicePressureAverageResult;
return data;
}
}

@ -0,0 +1,24 @@
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
class WeekBloodPressureResultAverage {
int? dailySystolicePressureAverageResult;
int? dailyDiastolicPressureAverageResult;
DateTime? bloodPressureDate;
WeekBloodPressureResultAverage({this.dailySystolicePressureAverageResult, this.dailyDiastolicPressureAverageResult, this.bloodPressureDate});
WeekBloodPressureResultAverage.fromJson(Map<String, dynamic> json) {
dailySystolicePressureAverageResult = json['DailySystolicePressureAverageResult'];
dailyDiastolicPressureAverageResult = json['DailyDiastolicPressureAverageResult'];
bloodPressureDate = DateUtil.convertStringToDate(json['BloodPressureDate']);
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['DailySystolicePressureAverageResult'] = dailySystolicePressureAverageResult;
data['DailyDiastolicPressureAverageResult'] = dailyDiastolicPressureAverageResult;
data['BloodPressureDate'] = bloodPressureDate;
return data;
}
}

@ -0,0 +1,37 @@
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
class YearBloodPressureResultAverage {
int? monthSystolicePressureAverageResult;
int? monthDiastolicPressureAverageResult;
dynamic monthNumber;
String? monthName;
String? yearName;
DateTime? date;
YearBloodPressureResultAverage({
this.monthSystolicePressureAverageResult,
this.monthDiastolicPressureAverageResult,
this.monthNumber,
this.monthName,
this.yearName,
});
YearBloodPressureResultAverage.fromJson(Map<String, dynamic> json) {
monthSystolicePressureAverageResult = json['monthSystolicePressureAverageResult'];
monthDiastolicPressureAverageResult = json['monthDiastolicPressureAverageResult'];
monthNumber = json['monthNumber'];
monthName = json['monthName'];
yearName = json['yearName'];
date = DateUtil.getMonthDateTime(monthName!, yearName);
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['monthSystolicePressureAverageResult'] = monthSystolicePressureAverageResult;
data['monthDiastolicPressureAverageResult'] = monthDiastolicPressureAverageResult;
data['monthNumber'] = monthNumber;
data['monthName'] = monthName;
data['yearName'] = yearName;
return data;
}
}

@ -0,0 +1,99 @@
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
class DiabeticPatientResult {
String? chartMonth;
var chartYear;
DateTime? dateChart;
var description;
var descriptionN;
int? diabtecAvarage;
bool? isActive;
int? lineItemNo;
var listMonth;
var listWeek;
int? measured;
String? measuredDesc;
var monthAverageResult;
int? patientID;
var remark;
var resultDesc;
dynamic resultValue;
String? unit;
var weekAverageResult;
String? weekDesc;
var yearAverageResult;
DiabeticPatientResult(
{this.chartMonth,
this.chartYear,
this.dateChart,
this.description,
this.descriptionN,
this.diabtecAvarage,
this.isActive,
this.lineItemNo,
this.listMonth,
this.listWeek,
this.measured,
this.measuredDesc,
this.monthAverageResult,
this.patientID,
this.remark,
this.resultDesc,
this.resultValue,
this.unit,
this.weekAverageResult,
this.weekDesc,
this.yearAverageResult});
DiabeticPatientResult.fromJson(Map<String, dynamic> json) {
chartMonth = json['ChartMonth'];
chartYear = json['ChartYear'];
dateChart = DateUtil.convertStringToDate(json['DateChart']);
description = json['Description'];
descriptionN = json['DescriptionN'];
diabtecAvarage = json['DiabtecAvarage'];
isActive = json['IsActive'];
lineItemNo = json['LineItemNo'];
listMonth = json['List_Month'];
listWeek = json['List_Week'];
measured = json['Measured'];
measuredDesc = json['MeasuredDesc'];
monthAverageResult = json['MonthAverageResult'];
patientID = json['PatientID'];
remark = json['Remark'];
resultDesc = json['ResultDesc'];
resultValue = json['ResultValue'];
unit = json['Unit'];
weekAverageResult = json['WeekAverageResult'];
weekDesc = json['WeekDesc'];
yearAverageResult = json['YearAverageResult'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['ChartMonth'] = chartMonth;
data['ChartYear'] = chartYear;
data['DateChart'] = DateUtil.convertDateToString(dateChart!);
data['Description'] = description;
data['DescriptionN'] = descriptionN;
data['DiabtecAvarage'] = diabtecAvarage;
data['IsActive'] = isActive;
data['LineItemNo'] = lineItemNo;
data['List_Month'] = listMonth;
data['List_Week'] = listWeek;
data['Measured'] = measured;
data['MeasuredDesc'] = measuredDesc;
data['MonthAverageResult'] = monthAverageResult;
data['PatientID'] = patientID;
data['Remark'] = remark;
data['ResultDesc'] = resultDesc;
data['ResultValue'] = resultValue;
data['Unit'] = unit;
data['WeekAverageResult'] = weekAverageResult;
data['WeekDesc'] = weekDesc;
data['YearAverageResult'] = yearAverageResult;
return data;
}
}

@ -0,0 +1,37 @@
class MonthDiabeticResultAverage {
var weekfourAverageResult;
var weekthreeAverageResult;
var weektwoAverageResult;
var weekoneAverageResult;
dynamic weekAverageResult;
String? weekDesc;
MonthDiabeticResultAverage(
{this.weekfourAverageResult,
this.weekthreeAverageResult,
this.weektwoAverageResult,
this.weekoneAverageResult,
this.weekAverageResult,
this.weekDesc});
MonthDiabeticResultAverage.fromJson(Map<String, dynamic> json) {
weekfourAverageResult = json['weekfourAverageResult'];
weekthreeAverageResult = json['weekthreeAverageResult'];
weektwoAverageResult = json['weektwoAverageResult'];
weekoneAverageResult = json['weekoneAverageResult'];
weekAverageResult = json['WeekAverageResult'];
weekDesc = json['WeekDesc'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['weekfourAverageResult'] = weekfourAverageResult;
data['weekthreeAverageResult'] = weekthreeAverageResult;
data['weektwoAverageResult'] = weektwoAverageResult;
data['weekoneAverageResult'] = weekoneAverageResult;
data['WeekAverageResult'] = weekAverageResult;
data['WeekDesc'] = weekDesc;
return data;
}
}

@ -0,0 +1,21 @@
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
class WeekDiabeticResultAverage {
dynamic dailyAverageResult;
DateTime? dateChart;
WeekDiabeticResultAverage({this.dailyAverageResult, this.dateChart});
WeekDiabeticResultAverage.fromJson(Map<String, dynamic> json) {
dailyAverageResult = json['DailyAverageResult'];
dateChart = DateUtil.convertStringToDate(json['DateChart']);
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['DailyAverageResult'] = dailyAverageResult;
data['DateChart'] = DateUtil.convertDateToString(dateChart!);
return data;
}
}

@ -0,0 +1,35 @@
import 'dart:developer';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
class YearDiabeticResultAverage {
dynamic monthAverageResult;
var monthNumber;
String? monthName;
String? yearName;
DateTime? date;
YearDiabeticResultAverage({this.monthAverageResult, this.monthNumber, this.monthName, this.yearName});
YearDiabeticResultAverage.fromJson(Map<String, dynamic> json) {
try {
monthAverageResult = json['monthAverageResult'];
monthNumber = json['monthNumber'];
monthName = json['monthName'];
yearName = json['yearName'];
date = DateUtil.getMonthDateTime(monthName!, yearName);
} catch (e) {
log(e.toString());
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['monthAverageResult'] = monthAverageResult;
data['monthNumber'] = monthNumber;
data['monthName'] = monthName;
data['yearName'] = yearName;
return data;
}
}

@ -0,0 +1,37 @@
class MonthWeightMeasurementResultAverage {
dynamic weekfourAverageResult;
dynamic weekthreeAverageResult;
dynamic weektwoAverageResult;
dynamic weekoneAverageResult;
dynamic weekAverageResult;
String? weekDesc;
MonthWeightMeasurementResultAverage(
{this.weekfourAverageResult,
this.weekthreeAverageResult,
this.weektwoAverageResult,
this.weekoneAverageResult,
this.weekAverageResult,
this.weekDesc});
MonthWeightMeasurementResultAverage.fromJson(Map<String, dynamic> json) {
weekfourAverageResult = json['weekfourAverageResult'];
weekthreeAverageResult = json['weekthreeAverageResult'];
weektwoAverageResult = json['weektwoAverageResult'];
weekoneAverageResult = json['weekoneAverageResult'];
weekAverageResult = json['WeekAverageResult'];
weekDesc = json['WeekDesc'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['weekfourAverageResult'] = weekfourAverageResult;
data['weekthreeAverageResult'] = weekthreeAverageResult;
data['weektwoAverageResult'] = weektwoAverageResult;
data['weekoneAverageResult'] = weekoneAverageResult;
data['WeekAverageResult'] = weekAverageResult;
data['WeekDesc'] = weekDesc;
return data;
}
}

@ -0,0 +1,21 @@
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
class WeekWeightMeasurementResultAverage {
dynamic dailyAverageResult;
DateTime? weightDate;
WeekWeightMeasurementResultAverage({this.dailyAverageResult, this.weightDate});
WeekWeightMeasurementResultAverage.fromJson(Map<String, dynamic> json) {
dailyAverageResult = json['DailyAverageResult'];
weightDate = DateUtil.convertStringToDate(json['WeightDate']);
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['DailyAverageResult'] = dailyAverageResult;
data['WeightDate'] = weightDate;
return data;
}
}

@ -0,0 +1,77 @@
import 'dart:developer';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
class WeightMeasurementResult {
int? patientID;
int? lineItemNo;
int? weightMeasured;
DateTime? weightDate;
dynamic remark;
bool? isActive;
int? measured;
dynamic unit;
int? chartYear;
dynamic chartMonth;
double? yearAverageResult;
dynamic monthAverageResult;
dynamic weekAverageResult;
dynamic weekDesc;
WeightMeasurementResult(
{this.patientID,
this.lineItemNo,
this.weightMeasured,
this.weightDate,
this.remark,
this.isActive,
this.measured,
this.unit,
this.chartYear,
this.chartMonth,
this.yearAverageResult,
this.monthAverageResult,
this.weekAverageResult,
this.weekDesc});
WeightMeasurementResult.fromJson(Map<String, dynamic> json) {
try {
patientID = json['PatientID'];
lineItemNo = json['LineItemNo'];
weightMeasured = json['WeightMeasured'];
weightDate = DateUtil.convertStringToDate(json['WeightDate']);
remark = json['Remark'];
isActive = json['IsActive'];
measured = json['Measured'];
unit = json['Unit'];
chartYear = json['ChartYear'];
chartMonth = json['ChartMonth'];
// Convert to double safely since API may return int
yearAverageResult = json['YearAverageResult'] != null ? (json['YearAverageResult'] as num).toDouble() : null;
monthAverageResult = json['MonthAverageResult'];
weekAverageResult = json['WeekAverageResult'];
weekDesc = json['WeekDesc'];
} catch (e) {
log(e.toString());
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['PatientID'] = patientID;
data['LineItemNo'] = lineItemNo;
data['WeightMeasured'] = weightMeasured;
data['WeightDate'] = weightDate;
data['Remark'] = remark;
data['IsActive'] = isActive;
data['Measured'] = measured;
data['Unit'] = unit;
data['ChartYear'] = chartYear;
data['ChartMonth'] = chartMonth;
data['YearAverageResult'] = yearAverageResult;
data['MonthAverageResult'] = monthAverageResult;
data['WeekAverageResult'] = weekAverageResult;
data['WeekDesc'] = weekDesc;
return data;
}
}

@ -0,0 +1,29 @@
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
class YearWeightMeasurementResultAverage {
dynamic monthAverageResult;
int? monthNumber;
String? monthName;
String? yearName;
DateTime? date;
YearWeightMeasurementResultAverage({this.monthAverageResult, this.monthNumber, this.monthName, this.yearName});
YearWeightMeasurementResultAverage.fromJson(Map<String, dynamic> json) {
monthAverageResult = json['monthAverageResult'];
monthNumber = json['monthNumber'];
monthName = json['monthName'];
yearName = json['yearName'];
date = DateUtil.getMonthDateTime(monthName!, yearName);
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['monthAverageResult'] = monthAverageResult;
data['monthNumber'] = monthNumber;
data['monthName'] = monthName;
data['yearName'] = yearName;
return data;
}
}

@ -293,14 +293,13 @@ class MedicalFileRepoImp implements MedicalFileRepo {
Failure? failure;
await apiClient.post(
ApiConsts.getAllSharedRecordsByStatus,
body: {if (status != null) "Status": status, "PatientID": patientID},
body: {if (status != null) "Status": status, "PatientID": patientID.toString()},
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['GetAllSharedRecordsByStatusList'];
final list = response['GetAllSharedRecordsByStatusList'] ?? [];
final familyLists = list.map((item) => FamilyFileResponseModelLists.fromJson(item as Map<String, dynamic>)).toList().cast<FamilyFileResponseModelLists>();
@ -336,7 +335,7 @@ class MedicalFileRepoImp implements MedicalFileRepo {
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['GetAllPendingRecordsList'];
final list = response['GetAllPendingRecordsList'] ?? [];
final familyLists = list.map((item) => FamilyFileResponseModelLists.fromJson(item as Map<String, dynamic>)).toList().cast<FamilyFileResponseModelLists>();
apiResponse = GenericApiModel<List<FamilyFileResponseModelLists>>(

@ -0,0 +1,188 @@
class SymptomsUserDetailsResponseModel {
final TokenDetails? tokenDetails;
final UserDetails? userDetails;
final String? sessionId;
SymptomsUserDetailsResponseModel({
this.tokenDetails,
this.userDetails,
this.sessionId,
});
factory SymptomsUserDetailsResponseModel.fromJson(Map<String, dynamic> json) {
return SymptomsUserDetailsResponseModel(
tokenDetails: json['tokenDetails'] != null ? TokenDetails.fromJson(json['tokenDetails']) : null,
userDetails: json['userDetails'] != null ? UserDetails.fromJson(json['userDetails']) : null,
sessionId: json['sessionId'],
);
}
Map<String, dynamic> toJson() {
return {
'tokenDetails': tokenDetails?.toJson(),
'userDetails': userDetails?.toJson(),
'sessionId': sessionId,
};
}
}
class TokenDetails {
final String? id;
final String? authToken;
final int? expiresIn;
TokenDetails({
this.id,
this.authToken,
this.expiresIn,
});
factory TokenDetails.fromJson(Map<String, dynamic> json) {
return TokenDetails(
id: json['id'],
authToken: json['auth_token'],
expiresIn: json['expires_in'],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'auth_token': authToken,
'expires_in': expiresIn,
};
}
}
class UserDetails {
final String? fileNo;
final String? nationalId;
final String? email;
final String? dateOfBirth;
final String? dateOfBirthHijri;
final int? age;
final UserName? name;
final int? maritalStatus;
final String? maritalStatusCode;
final String? nationality;
final String? nationalityIsoCode;
final String? occupation;
final int? idType;
final int? gender;
final String? jwtToken;
final String? countryDialCode;
final String? phoneNo;
UserDetails({
this.fileNo,
this.nationalId,
this.email,
this.dateOfBirth,
this.dateOfBirthHijri,
this.age,
this.name,
this.maritalStatus,
this.maritalStatusCode,
this.nationality,
this.nationalityIsoCode,
this.occupation,
this.idType,
this.gender,
this.jwtToken,
this.countryDialCode,
this.phoneNo,
});
factory UserDetails.fromJson(Map<String, dynamic> json) {
return UserDetails(
fileNo: json['FileNo'],
nationalId: json['national_id'],
email: json['email'],
dateOfBirth: json['date_of_birth'],
dateOfBirthHijri: json['date_of_birth_hijri'],
age: json['age'],
name: json['name'] != null ? UserName.fromJson(json['name']) : null,
maritalStatus: json['marital_status'],
maritalStatusCode: json['marital_status_code'],
nationality: json['nationality'],
nationalityIsoCode: json['nationality_iso_code'],
occupation: json['occupation'],
idType: json['id_type'],
gender: json['gender'],
jwtToken: json['jwt_token'],
countryDialCode: json['country_dial_code'],
phoneNo: json['phone_no'],
);
}
Map<String, dynamic> toJson() {
return {
'FileNo': fileNo,
'national_id': nationalId,
'email': email,
'date_of_birth': dateOfBirth,
'date_of_birth_hijri': dateOfBirthHijri,
'age': age,
'name': name?.toJson(),
'marital_status': maritalStatus,
'marital_status_code': maritalStatusCode,
'nationality': nationality,
'nationality_iso_code': nationalityIsoCode,
'occupation': occupation,
'id_type': idType,
'gender': gender,
'jwt_token': jwtToken,
'country_dial_code': countryDialCode,
'phone_no': phoneNo,
};
}
// Helper method to get full name
String getFullName(bool isArabic) {
if (name == null) return '';
if (isArabic) {
return '${name!.firstNameAr ?? ''} ${name!.middleNameAr ?? ''} ${name!.lastNameAr ?? ''}'.trim();
}
return '${name!.firstName ?? ''} ${name!.middleName ?? ''} ${name!.lastName ?? ''}'.trim();
}
}
class UserName {
final String? firstName;
final String? middleName;
final String? lastName;
final String? firstNameAr;
final String? middleNameAr;
final String? lastNameAr;
UserName({
this.firstName,
this.middleName,
this.lastName,
this.firstNameAr,
this.middleNameAr,
this.lastNameAr,
});
factory UserName.fromJson(Map<String, dynamic> json) {
return UserName(
firstName: json['first_name'],
middleName: json['middle_name'],
lastName: json['last_name'],
firstNameAr: json['first_name_ar'],
middleNameAr: json['middle_name_ar'],
lastNameAr: json['last_name_ar'],
);
}
Map<String, dynamic> toJson() {
return {
'first_name': firstName,
'middle_name': middleName,
'last_name': lastName,
'first_name_ar': firstNameAr,
'middle_name_ar': middleNameAr,
'last_name_ar': lastNameAr,
};
}
}

@ -0,0 +1,209 @@
class TriageDataDetails {
final TriageQuestion? question;
final List<TriageCondition>? conditions;
final bool? hasEmergencyEvidence;
final bool? shouldStop;
final String? interviewToken;
final String? message;
final List<String>? errorList;
final int? id;
final String? language;
final String? generalId;
final String? createDate;
final String? lastEditDate;
final String? createdBy;
final String? lastEditBy;
final bool? active;
final int? sortOrder;
final int? userType;
final String? userId;
TriageDataDetails({
this.question,
this.conditions,
this.hasEmergencyEvidence,
this.shouldStop,
this.interviewToken,
this.message,
this.errorList,
this.id,
this.language,
this.generalId,
this.createDate,
this.lastEditDate,
this.createdBy,
this.lastEditBy,
this.active,
this.sortOrder,
this.userType,
this.userId,
});
factory TriageDataDetails.fromJson(Map<String, dynamic> json) {
return TriageDataDetails(
question: json['question'] != null ? TriageQuestion.fromJson(json['question']) : null,
conditions: json['conditions'] != null ? (json['conditions'] as List).map((item) => TriageCondition.fromJson(item)).toList() : null,
hasEmergencyEvidence: json['has_emergency_evidence'],
shouldStop: json['should_stop'],
interviewToken: json['interview_token'],
message: json['Message'],
errorList: json['ErrorList'] != null ? List<String>.from(json['ErrorList']) : null,
id: json['Id'],
language: json['language'],
generalId: json['generalId'],
createDate: json['CreateDate'],
lastEditDate: json['LastEditDate'],
createdBy: json['CreatedBy'],
lastEditBy: json['LastEditBy'],
active: json['Active'],
sortOrder: json['SortOrder'],
userType: json['userType'],
userId: json['userId'],
);
}
Map<String, dynamic> toJson() {
return {
'question': question?.toJson(),
'conditions': conditions?.map((item) => item.toJson()).toList(),
'has_emergency_evidence': hasEmergencyEvidence,
'should_stop': shouldStop,
'interview_token': interviewToken,
'Message': message,
'ErrorList': errorList,
'Id': id,
'language': language,
'generalId': generalId,
'CreateDate': createDate,
'LastEditDate': lastEditDate,
'CreatedBy': createdBy,
'LastEditBy': lastEditBy,
'Active': active,
'SortOrder': sortOrder,
'userType': userType,
'userId': userId,
};
}
}
class TriageQuestion {
final int? type;
final String? text;
final List<TriageQuestionItem>? items;
TriageQuestion({
this.type,
this.text,
this.items,
});
factory TriageQuestion.fromJson(Map<String, dynamic> json) {
return TriageQuestion(
type: json['type'],
text: json['text'],
items: json['items'] != null ? (json['items'] as List).map((item) => TriageQuestionItem.fromJson(item)).toList() : null,
);
}
Map<String, dynamic> toJson() {
return {
'type': type,
'text': text,
'items': items?.map((item) => item.toJson()).toList(),
};
}
}
class TriageQuestionItem {
final String? id;
final String? name;
final List<TriageChoice>? choices;
TriageQuestionItem({
this.id,
this.name,
this.choices,
});
factory TriageQuestionItem.fromJson(Map<String, dynamic> json) {
return TriageQuestionItem(
id: json['id'],
name: json['name'],
choices: json['choices'] != null ? (json['choices'] as List).map((item) => TriageChoice.fromJson(item)).toList() : null,
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'choices': choices?.map((item) => item.toJson()).toList(),
};
}
}
class TriageChoice {
final String? id;
final String? label;
TriageChoice({
this.id,
this.label,
});
factory TriageChoice.fromJson(Map<String, dynamic> json) {
return TriageChoice(
id: json['id'],
label: json['label'],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'label': label,
};
}
}
class TriageCondition {
final String? id;
final String? name;
final String? commonName;
final double? probability;
final dynamic conditionDetails;
TriageCondition({
this.id,
this.name,
this.commonName,
this.probability,
this.conditionDetails,
});
factory TriageCondition.fromJson(Map<String, dynamic> json) {
return TriageCondition(
id: json['id'],
name: json['name'],
commonName: json['common_name'],
probability: json['probability']?.toDouble(),
conditionDetails: json['condition_details'],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'common_name': commonName,
'probability': probability,
'condition_details': conditionDetails,
};
}
/// Get probability as percentage
String getProbabilityPercentage() {
if (probability == null) return '0%';
return '${(probability! * 100).toStringAsFixed(1)}%';
}
}

@ -1,5 +1,4 @@
import 'dart:convert';
import 'dart:developer';
import 'package:dartz/dartz.dart';
import 'package:hmg_patient_app_new/core/api/api_client.dart';
@ -8,25 +7,56 @@ import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/body_symptom_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/risk_and_suggestions_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/symptoms_user_details_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/triage_response_model.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
abstract class SymptomsCheckerRepo {
Future<Either<Failure, GenericApiModel<BodySymptomResponseModel>>> getBodySymptomsByName({
Future<Either<Failure, GenericApiModel<SymptomsUserDetailsResponseModel>>>
getUserDetails({
required String userName,
required String password,
});
Future<Either<Failure, GenericApiModel<BodySymptomResponseModel>>>
getBodySymptomsByName({
required List<String> organNames,
required String userSessionToken,
required int gender,
});
Future<Either<Failure, GenericApiModel<RiskAndSuggestionsResponseModel>>>
getRiskFactors({
required int age,
required String sex,
required List<String> evidenceIds,
required String language,
required String userSessionToken,
required int gender,
required String sessionId,
});
Future<Either<Failure, GenericApiModel<RiskAndSuggestionsResponseModel>>> getRiskFactors({
Future<Either<Failure, GenericApiModel<RiskAndSuggestionsResponseModel>>>
getSuggestions({
required int age,
required String sex,
required List<String> evidenceIds,
required String language,
required String userSessionToken,
required String sessionId,
required int gender,
});
Future<Either<Failure, GenericApiModel<RiskAndSuggestionsResponseModel>>> getSuggestions({
Future<Either<Failure, GenericApiModel<TriageDataDetails>>>
getDiagnosisForTriage({
required int age,
required String sex,
required List<String> evidenceIds,
List<Map<String, String>>? triageEvidence,
required String language,
required String userSessionToken,
required int gender,
required String sessionId,
});
}
@ -34,14 +64,79 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
final ApiClient apiClient;
final LoggerService loggerService;
SymptomsCheckerRepoImp({required this.apiClient, required this.loggerService});
SymptomsCheckerRepoImp(
{required this.apiClient, required this.loggerService});
@override
Future<Either<Failure, GenericApiModel<BodySymptomResponseModel>>> getBodySymptomsByName({required List<String> organNames}) async {
log("GetBodySymptomsByName Request URL: ${ApiConsts.getBodySymptomsByName}");
log("GetBodySymptomsByName Request Body: ${jsonEncode(organNames)}");
Future<Either<Failure, GenericApiModel<SymptomsUserDetailsResponseModel>>>
getUserDetails({
required String userName,
required String password,
}) async {
Map<String, String> body = {"userName": userName, "password": password};
try {
GenericApiModel<SymptomsUserDetailsResponseModel>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.symptomsUserLogin,
body: body,
isExternal: true,
isAllowAny: true,
isBodyPlainText: false,
onFailure: (error, statusCode, {messageStatus, failureType}) {
loggerService.logError("getUserDetails API Failed: $error");
failure = failureType ?? ServerFailure(error);
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
// Parse response if it's a string
final Map<String, dynamic> responseData =
response is String ? jsonDecode(response) : response;
Map<String, String> headers = {'Content-Type': 'application/json', 'Accept': 'text/plain'};
SymptomsUserDetailsResponseModel symptomsUserDetailsResponseModel =
SymptomsUserDetailsResponseModel.fromJson(responseData);
apiResponse = GenericApiModel<SymptomsUserDetailsResponseModel>(
messageStatus: messageStatus ?? 1,
statusCode: statusCode,
errorMessage: errorMessage,
data: symptomsUserDetailsResponseModel,
);
} catch (e, stackTrace) {
loggerService.logError("Error parsing getUserDetails response: $e");
loggerService.logError("StackTrace: $stackTrace");
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e, stackTrace) {
loggerService.logError("Exception in getUserDetails: $e");
loggerService.logError("StackTrace: $stackTrace");
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<BodySymptomResponseModel>>>
getBodySymptomsByName({
required List<String> organNames,
required String userSessionToken,
required int gender,
}) async {
Map<String, String> headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer $userSessionToken',
};
Map<String, dynamic> body = {
'bodyPartName': organNames,
'gender': gender,
};
try {
GenericApiModel<BodySymptomResponseModel>? apiResponse;
@ -50,22 +145,17 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
await apiClient.post(
ApiConsts.getBodySymptomsByName,
apiHeaders: headers,
body: jsonEncode(organNames),
body: body,
isExternal: true,
isAllowAny: true,
isBodyPlainText: true,
isBodyPlainText: false,
onFailure: (error, statusCode, {messageStatus, failureType}) {
loggerService.logError("GetBodySymptomsByName API Failed: $error");
log("GetBodySymptomsByName Failed: $error, Status: $statusCode");
failure = failureType ?? ServerFailure(error);
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
log("GetBodySymptomsByName Response Status: $statusCode");
loggerService.logInfo("GetBodySymptomsByName API Success: $response");
log("GetBodySymptomsByName Response: $response");
BodySymptomResponseModel bodySymptomResponse = BodySymptomResponseModel.fromJson(response);
BodySymptomResponseModel bodySymptomResponse =
BodySymptomResponseModel.fromJson(response);
apiResponse = GenericApiModel<BodySymptomResponseModel>(
messageStatus: messageStatus ?? 1,
@ -74,9 +164,9 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
data: bodySymptomResponse,
);
} catch (e, stackTrace) {
loggerService.logError("Error parsing GetBodySymptomsByName response: $e");
loggerService
.logError("Error parsing GetBodySymptomsByName response: $e");
loggerService.logError("StackTrace: $stackTrace");
log("Parse Error: $e");
failure = DataParsingFailure(e.toString());
}
},
@ -88,17 +178,20 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
} catch (e, stackTrace) {
loggerService.logError("Exception in getBodySymptomsByName: $e");
loggerService.logError("StackTrace: $stackTrace");
log("Exception: $e");
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<RiskAndSuggestionsResponseModel>>> getRiskFactors({
Future<Either<Failure, GenericApiModel<RiskAndSuggestionsResponseModel>>>
getRiskFactors({
required int age,
required String sex,
required List<String> evidenceIds,
required String language,
required String userSessionToken,
required int gender,
required String sessionId,
}) async {
final Map<String, dynamic> body = {
"age": {
@ -107,6 +200,12 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
"sex": sex,
"evidence": evidenceIds.map((id) => {"id": id}).toList(),
"language": language,
"generalId": sessionId,
};
Map<String, String> headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer $userSessionToken',
};
try {
@ -115,21 +214,22 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
await apiClient.post(
ApiConsts.getRiskFactors,
apiHeaders: headers,
body: body,
isExternal: true,
isAllowAny: true,
onFailure: (error, statusCode, {messageStatus, failureType}) {
log("GetRiskFactors Failed: $error, Status: $statusCode");
loggerService.logError("GetRiskFactors API Failed: $error");
failure = failureType ?? ServerFailure(error);
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
log("GetRiskFactors Response: $response");
// Parse response if it's a string
final Map<String, dynamic> responseData = response is String ? jsonDecode(response) : response;
final Map<String, dynamic> responseData =
response is String ? jsonDecode(response) : response;
RiskAndSuggestionsResponseModel riskFactorsResponse = RiskAndSuggestionsResponseModel.fromJson(responseData);
RiskAndSuggestionsResponseModel riskFactorsResponse =
RiskAndSuggestionsResponseModel.fromJson(responseData);
apiResponse = GenericApiModel<RiskAndSuggestionsResponseModel>(
messageStatus: messageStatus ?? 1,
@ -140,7 +240,6 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
} catch (e, stackTrace) {
loggerService.logError("Error parsing GetRiskFactors response: $e");
loggerService.logError("StackTrace: $stackTrace");
log("Parse Error: $e");
failure = DataParsingFailure(e.toString());
}
},
@ -152,17 +251,111 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
} catch (e, stackTrace) {
loggerService.logError("Exception in getRiskFactors: $e");
loggerService.logError("StackTrace: $stackTrace");
log("Exception: $e");
return Left(UnknownFailure(e.toString()));
}
}
Future<Either<Failure, GenericApiModel<TriageDataDetails>>>
getDiagnosisForTriage({
required int age,
required String sex,
required List<String> evidenceIds,
List<Map<String, String>>?
triageEvidence, // Additional triage-specific evidence
required String language,
required String userSessionToken,
required int gender,
required String sessionId,
}) async {
// Build evidence list: combine initial symptoms with triage evidence
List<Map<String, dynamic>> evidenceList = [];
// Add initial evidence as simple IDs
for (var id in evidenceIds) {
evidenceList.add({"id": id});
}
// Add triage evidence with proper format (id, choice_id, source)
if (triageEvidence != null && triageEvidence.isNotEmpty) {
evidenceList.addAll(triageEvidence);
}
final Map<String, dynamic> body = {
"age": {
"value": age,
},
"sex": sex,
"evidence": evidenceList,
"language": language,
"suggest_method": "diagnosis",
"generalId": sessionId,
};
Map<String, String> headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer $userSessionToken',
};
try {
GenericApiModel<TriageDataDetails>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.diagnosis,
apiHeaders: headers,
body: body,
isExternal: true,
isAllowAny: true,
onFailure: (error, statusCode, {messageStatus, failureType}) {
loggerService.logError("getDiagnosisForTriage API Failed: $error");
failure = failureType ?? ServerFailure(error);
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
// Parse response if it's a string
final Map<String, dynamic> responseData =
response is String ? jsonDecode(response) : response;
final updatedResponseData = responseData['dataDetails'];
TriageDataDetails riskFactorsResponse =
TriageDataDetails.fromJson(updatedResponseData);
apiResponse = GenericApiModel<TriageDataDetails>(
messageStatus: messageStatus ?? 1,
statusCode: statusCode,
errorMessage: errorMessage,
data: riskFactorsResponse,
);
} catch (e, stackTrace) {
loggerService
.logError("Error parsing getDiagnosisForTriage response: $e");
loggerService.logError("StackTrace: $stackTrace");
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e, stackTrace) {
loggerService.logError("Exception in getDiagnosisForTriage: $e");
loggerService.logError("StackTrace: $stackTrace");
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<RiskAndSuggestionsResponseModel>>> getSuggestions({
Future<Either<Failure, GenericApiModel<RiskAndSuggestionsResponseModel>>>
getSuggestions({
required int age,
required String sex,
required List<String> evidenceIds,
required String language,
required String userSessionToken,
required String sessionId,
required int gender,
}) async {
final Map<String, dynamic> body = {
"age": {
@ -171,6 +364,12 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
"sex": sex,
"evidence": evidenceIds.map((id) => {"id": id}).toList(),
"language": language,
"generalId": sessionId,
};
Map<String, String> headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer $userSessionToken',
};
try {
@ -179,21 +378,22 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
await apiClient.post(
ApiConsts.getSuggestions,
apiHeaders: headers,
body: body,
isExternal: true,
isAllowAny: true,
onFailure: (error, statusCode, {messageStatus, failureType}) {
log("getSuggestions Failed: $error, Status: $statusCode");
loggerService.logError("GetSuggestions API Failed: $error");
failure = failureType ?? ServerFailure(error);
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
log("getSuggestions Response: $response");
// Parse response if it's a string
final Map<String, dynamic> responseData = response is String ? jsonDecode(response) : response;
final Map<String, dynamic> responseData =
response is String ? jsonDecode(response) : response;
RiskAndSuggestionsResponseModel riskFactorsResponse = RiskAndSuggestionsResponseModel.fromJson(responseData);
RiskAndSuggestionsResponseModel riskFactorsResponse =
RiskAndSuggestionsResponseModel.fromJson(responseData);
apiResponse = GenericApiModel<RiskAndSuggestionsResponseModel>(
messageStatus: messageStatus ?? 1,
@ -202,9 +402,8 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
data: riskFactorsResponse,
);
} catch (e, stackTrace) {
loggerService.logError("Error parsing getSuggestions response: $e");
loggerService.logError("Error parsing GetSuggestions response: $e");
loggerService.logError("StackTrace: $stackTrace");
log("Parse Error: $e");
failure = DataParsingFailure(e.toString());
}
},
@ -216,7 +415,6 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
} catch (e, stackTrace) {
loggerService.logError("Exception in getSuggestions: $e");
loggerService.logError("StackTrace: $stackTrace");
log("Exception: $e");
return Left(UnknownFailure(e.toString()));
}
}

@ -7,6 +7,8 @@ import 'package:hmg_patient_app_new/features/symptoms_checker/data/organ_mapping
import 'package:hmg_patient_app_new/features/symptoms_checker/models/organ_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/body_symptom_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/risk_and_suggestions_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/symptoms_user_details_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/triage_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_repo.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
@ -35,11 +37,23 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
bool isBodySymptomsLoading = false;
bool isRiskFactorsLoading = false;
bool isSuggestionsLoading = false;
bool isTriageDiagnosisLoading = false;
// API data storage - using API models directly
SymptomsUserDetailsResponseModel? symptomsUserDetailsResponseModel;
BodySymptomResponseModel? bodySymptomResponse;
RiskAndSuggestionsResponseModel? riskFactorsResponse;
RiskAndSuggestionsResponseModel? suggestionsResponse;
TriageDataDetails? triageDataDetails;
// Triage state
int?
_selectedTriageChoiceIndex; // Deprecated - keeping for backward compatibility
final Map<String, int> _selectedTriageChoicesByItemId =
{}; // Map of itemId -> choiceIndex for multi-item questions
final List<Map<String, String>> _triageEvidenceList =
[]; // Store triage evidence with proper format
int _triageQuestionCount = 0; // Track number of triage questions answered
// Selected risk factors tracking
final Set<String> _selectedRiskFactorIds = {};
@ -52,7 +66,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
// User Info Flow State
int _userInfoCurrentPage = 0;
bool _isSinglePageEditMode = false; // Track if editing single page or full flow
bool _isSinglePageEditMode =
false; // Track if editing single page or full flow
String? _selectedGender;
DateTime? _dateOfBirth;
int? _selectedAge;
@ -92,8 +107,54 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
String? get tooltipOrganId => _tooltipOrganId;
String get currentSessionAuthToken =>
symptomsUserDetailsResponseModel?.tokenDetails?.authToken ?? "";
String get currentSessionId =>
symptomsUserDetailsResponseModel?.sessionId ?? "";
// Triage-related getters
bool get shouldStopTriage => triageDataDetails?.shouldStop ?? false;
bool get hasEmergencyEvidence =>
triageDataDetails?.hasEmergencyEvidence ?? false;
String? get currentInterviewToken => triageDataDetails?.interviewToken;
TriageQuestion? get currentTriageQuestion => triageDataDetails?.question;
List<TriageCondition>? get currentConditions => triageDataDetails?.conditions;
int? get selectedTriageChoiceIndex => _selectedTriageChoiceIndex;
/// Get the number of triage questions answered
int get triageQuestionCount => _triageQuestionCount;
/// Get choice index for a specific item
int? getTriageChoiceForItem(String itemId) {
return _selectedTriageChoicesByItemId[itemId];
}
/// Check if all items in current question have been answered
bool get areAllTriageItemsAnswered {
if (currentTriageQuestion?.items == null ||
currentTriageQuestion!.items!.isEmpty) {
return false;
}
// Check if we have an answer for each item
for (var item in currentTriageQuestion!.items!) {
if (item.id != null &&
!_selectedTriageChoicesByItemId.containsKey(item.id)) {
return false;
}
}
return true;
}
/// Get organs for current view
List<OrganModel> get currentOrgans => OrganData.getOrgansForView(_currentView);
List<OrganModel> get currentOrgans =>
OrganData.getOrgansForView(_currentView);
/// Get all selected organs from both views
List<OrganModel> get selectedOrgans {
@ -101,7 +162,9 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
...OrganData.frontViewOrgans,
...OrganData.backViewOrgans,
];
return allOrgans.where((organ) => _selectedOrganIds.contains(organ.id)).toList();
return allOrgans
.where((organ) => _selectedOrganIds.contains(organ.id))
.toList();
}
/// Check if any organs are selected
@ -118,11 +181,13 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
}
int get totalSelectedSymptomsCount {
return _selectedSymptomsByOrgan.values.fold(0, (sum, symptomIds) => sum + symptomIds.length);
return _selectedSymptomsByOrgan.values
.fold(0, (sum, symptomIds) => sum + symptomIds.length);
}
bool get hasSelectedSymptoms {
return _selectedSymptomsByOrgan.values.any((symptomIds) => symptomIds.isNotEmpty);
return _selectedSymptomsByOrgan.values
.any((symptomIds) => symptomIds.isNotEmpty);
}
/// Get risk factors list
@ -148,7 +213,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
}
void toggleView() {
_currentView = _currentView == BodyView.front ? BodyView.back : BodyView.front;
_currentView =
_currentView == BodyView.front ? BodyView.back : BodyView.front;
notifyListeners();
}
@ -251,7 +317,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
return;
}
List<String> organNames = selectedOrgans.map((organ) => organ.name).toList();
List<String> organNames =
selectedOrgans.map((organ) => organ.name).toList();
await getBodySymptomsByName(
organNames: organNames,
@ -301,7 +368,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
}
}
if (matchingOrganId != null && _selectedSymptomsByOrgan.containsKey(matchingOrganId)) {
if (matchingOrganId != null &&
_selectedSymptomsByOrgan.containsKey(matchingOrganId)) {
final selectedIds = _selectedSymptomsByOrgan[matchingOrganId]!;
if (organResult.bodySymptoms != null) {
@ -352,7 +420,10 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
/// Get all selected risk factors
List<RiskAndSuggestionsItemModel> getAllSelectedRiskFactors() {
return riskFactorsList.where((factor) => factor.id != null && _selectedRiskFactorIds.contains(factor.id)).toList();
return riskFactorsList
.where((factor) =>
factor.id != null && _selectedRiskFactorIds.contains(factor.id))
.toList();
}
/// Clear all risk factor selections
@ -385,12 +456,14 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
}
// Extract symptom IDs
List<String> evidenceIds = selectedSymptoms.where((s) => s.id != null).map((s) => s.id!).toList();
List<String> evidenceIds =
selectedSymptoms.where((s) => s.id != null).map((s) => s.id!).toList();
await getRiskFactors(
age: _selectedAge!,
sex: _selectedGender!.toLowerCase(),
evidenceIds: evidenceIds,
sessionId: currentSessionId,
language: appState.isArabic() ? 'ar' : 'en',
onSuccess: (response) {
if (onSuccess != null) {
@ -409,6 +482,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
Future<void> getRiskFactors({
required int age,
required String sex,
required String sessionId,
required List<String> evidenceIds,
required String language,
Function(RiskAndSuggestionsResponseModel)? onSuccess,
@ -422,6 +496,9 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
sex: sex,
evidenceIds: evidenceIds,
language: language,
sessionId: sessionId,
userSessionToken: currentSessionAuthToken,
gender: (selectedGender ?? "Male").toLowerCase() == "male" ? 1 : 2,
);
result.fold(
@ -438,8 +515,10 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
if (apiResponse.messageStatus == 1 && apiResponse.data != null) {
riskFactorsResponse = apiResponse.data;
if (riskFactorsResponse != null && riskFactorsResponse!.dataDetails != null) {
RiskAndSuggestionsItemModel riskFactorItem = RiskAndSuggestionsItemModel(
if (riskFactorsResponse != null &&
riskFactorsResponse!.dataDetails != null) {
RiskAndSuggestionsItemModel riskFactorItem =
RiskAndSuggestionsItemModel(
id: "not_applicable",
commonName: "Not Applicable",
name: "Not Applicable",
@ -493,7 +572,10 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
/// Get all selected risk factors
List<RiskAndSuggestionsItemModel> getAllSelectedSuggestions() {
return suggestionsList.where((factor) => factor.id != null && _selectedSuggestionsIds.contains(factor.id)).toList();
return suggestionsList
.where((factor) =>
factor.id != null && _selectedSuggestionsIds.contains(factor.id))
.toList();
}
/// Clear all risk factor selections
@ -502,6 +584,30 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
notifyListeners();
}
/// Get all evidence IDs (symptoms + risk factors + suggestions) for triage/diagnosis
List<String> getAllEvidenceIds() {
List<String> evidenceIds = [];
// Add selected symptoms
final selectedSymptoms = getAllSelectedSymptoms();
evidenceIds
.addAll(selectedSymptoms.where((s) => s.id != null).map((s) => s.id!));
// Add selected risk factors (excluding "not_applicable")
final selectedRiskFactors = getAllSelectedRiskFactors();
evidenceIds.addAll(selectedRiskFactors
.where((rf) => rf.id != null && rf.id != "not_applicable")
.map((rf) => rf.id!));
// Add selected suggestions (excluding "not_applicable")
final selectedSuggestions = getAllSelectedSuggestions();
evidenceIds.addAll(selectedSuggestions
.where((s) => s.id != null && s.id != "not_applicable")
.map((s) => s.id!));
return evidenceIds;
}
/// Fetch risk factors based on selected symptoms
Future<void> fetchSuggestions({
Function()? onSuccess,
@ -526,13 +632,17 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
}
// Extract symptom IDs
List<String> evidenceIds = selectedSymptoms.where((s) => s.id != null).map((s) => s.id!).toList();
List<String> evidenceIds =
selectedSymptoms.where((s) => s.id != null).map((s) => s.id!).toList();
// Get all selected symptoms
final selectedRisks = getAllSelectedRiskFactors();
if (selectedRisks.isNotEmpty) {
List<String> evidenceRisksIds = selectedRisks.where((s) => s.id != null && s.id != "not_applicable").map((s) => s.id!).toList();
List<String> evidenceRisksIds = selectedRisks
.where((s) => s.id != null && s.id != "not_applicable")
.map((s) => s.id!)
.toList();
evidenceIds.addAll(evidenceRisksIds);
}
@ -571,6 +681,9 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
sex: sex,
evidenceIds: evidenceIds,
language: language,
sessionId: currentSessionId,
userSessionToken: currentSessionAuthToken,
gender: (selectedGender ?? "Male").toLowerCase() == "male" ? 1 : 2,
);
result.fold(
@ -587,8 +700,10 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
if (apiResponse.messageStatus == 1 && apiResponse.data != null) {
suggestionsResponse = apiResponse.data;
if (suggestionsResponse != null && suggestionsResponse!.dataDetails != null) {
RiskAndSuggestionsItemModel riskFactorItem = RiskAndSuggestionsItemModel(
if (suggestionsResponse != null &&
suggestionsResponse!.dataDetails != null) {
RiskAndSuggestionsItemModel riskFactorItem =
RiskAndSuggestionsItemModel(
id: "not_applicable",
commonName: "Not Applicable",
name: "Not Applicable",
@ -612,15 +727,155 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
);
}
/// Call Diagnosis API for Triage - This is called iteratively until shouldStop is true
Future<void> getDiagnosisForTriage({
required int age,
required String sex,
required List<String> evidenceIds,
List<Map<String, String>>? triageEvidence,
required String language,
Function(TriageDataDetails)? onSuccess,
Function(String)? onError,
}) async {
isTriageDiagnosisLoading = true;
notifyListeners();
final result = await symptomsCheckerRepo.getDiagnosisForTriage(
age: age,
sex: sex,
evidenceIds: evidenceIds,
triageEvidence: triageEvidence,
language: language,
sessionId: currentSessionId,
userSessionToken: currentSessionAuthToken,
gender: (selectedGender ?? "Male").toLowerCase() == "male" ? 1 : 2,
);
result.fold(
(failure) async {
isTriageDiagnosisLoading = false;
notifyListeners();
await errorHandlerService.handleError(failure: failure);
if (onError != null) {
onError(failure.toString());
}
},
(apiResponse) {
isTriageDiagnosisLoading = false;
if (apiResponse.messageStatus == 1 && apiResponse.data != null) {
triageDataDetails = apiResponse.data;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse.data!);
}
} else {
notifyListeners();
if (onError != null) {
onError(apiResponse.errorMessage ?? 'Failed to fetch diagnosis');
}
}
},
);
}
/// Convenience method to start or continue the triage process
/// This automatically uses all selected evidence (symptoms + risk factors + suggestions)
Future<void> startOrContinueTriage({
Function()? onSuccess,
Function(String)? onError,
}) async {
// Validate user info
if (_selectedAge == null || _selectedGender == null) {
if (onError != null) {
onError('User information is incomplete');
}
return;
}
// Get all evidence IDs
final evidenceIds = getAllEvidenceIds();
if (evidenceIds.isEmpty) {
if (onError != null) {
onError('No evidence selected');
}
return;
}
await getDiagnosisForTriage(
age: _selectedAge!,
sex: _selectedGender!.toLowerCase(),
evidenceIds: evidenceIds,
language: appState.isArabic() ? 'ar' : 'en',
onSuccess: (response) {
if (onSuccess != null) {
onSuccess();
}
},
onError: (error) {
if (onError != null) {
onError(error);
}
},
);
}
/// Select a triage choice option (for backward compatibility with single-item questions)
void selectTriageChoice(int choiceIndex) {
_selectedTriageChoiceIndex = choiceIndex;
notifyListeners();
}
/// Select a choice for a specific item (for multi-item questions)
void selectTriageChoiceForItem(String itemId, int choiceIndex) {
_selectedTriageChoicesByItemId[itemId] = choiceIndex;
notifyListeners();
}
/// Reset triage choice selection and increment question count
void resetTriageChoice() {
_selectedTriageChoiceIndex = null;
_selectedTriageChoicesByItemId.clear();
_triageQuestionCount++; // Increment question count
notifyListeners();
}
/// Add triage evidence in the proper format
void addTriageEvidence(String itemId, String choiceId) {
_triageEvidenceList.add({
"id": itemId,
"choice_id": choiceId,
"source": "triage",
});
notifyListeners();
}
/// Get all triage evidence
List<Map<String, String>> getTriageEvidence() {
return List.from(_triageEvidenceList);
}
/// Clear triage evidence
void clearTriageEvidence() {
_triageEvidenceList.clear();
notifyListeners();
}
void reset() {
_currentView = BodyView.front;
_selectedOrganIds.clear();
_selectedSymptomsByOrgan.clear();
_selectedRiskFactorIds.clear();
_selectedSuggestionsIds.clear();
_triageEvidenceList.clear();
_selectedTriageChoicesByItemId.clear();
_triageQuestionCount = 0; // Reset question count
bodySymptomResponse = null;
riskFactorsResponse = null;
suggestionsResponse = null;
triageDataDetails = null;
isTriageDiagnosisLoading = false;
_selectedTriageChoiceIndex = null;
_isBottomSheetExpanded = false;
_tooltipTimer?.cancel();
_tooltipOrganId = null;
@ -686,7 +941,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
// Calculate age from date of birth
final now = DateTime.now();
int age = now.year - dateOfBirth.year;
if (now.month < dateOfBirth.month || (now.month == dateOfBirth.month && now.day < dateOfBirth.day)) {
if (now.month < dateOfBirth.month ||
(now.month == dateOfBirth.month && now.day < dateOfBirth.day)) {
age--;
}
_selectedAge = age;
@ -725,6 +981,44 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
};
}
Future<void> getSymptomsUserDetails({
required String userName,
required String password,
Function()? onSuccess,
Function(String)? onError,
}) async {
isBodySymptomsLoading = true;
notifyListeners();
final result = await symptomsCheckerRepo.getUserDetails(
userName: userName, password: password);
result.fold(
(failure) async {
isBodySymptomsLoading = false;
notifyListeners();
await errorHandlerService.handleError(failure: failure);
if (onError != null) {
onError(failure.toString());
}
},
(apiResponse) {
isBodySymptomsLoading = false;
if (apiResponse.messageStatus == 1 && apiResponse.data != null) {
symptomsUserDetailsResponseModel = apiResponse.data;
notifyListeners();
if (onSuccess != null) {
onSuccess();
}
} else {
notifyListeners();
if (onError != null) {
onError(apiResponse.errorMessage ?? 'Failed to fetch symptoms');
}
}
},
);
}
Future<void> getBodySymptomsByName({
required List<String> organNames,
Function(BodySymptomResponseModel)? onSuccess,
@ -735,6 +1029,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
final result = await symptomsCheckerRepo.getBodySymptomsByName(
organNames: organNames,
userSessionToken: currentSessionAuthToken,
gender: (selectedGender ?? "Male").toLowerCase() == "male" ? 1 : 2,
);
result.fold(

@ -16,14 +16,19 @@ import 'package:hmg_patient_app_new/features/water_monitor/models/water_cup_mode
import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_repo.dart';
import 'package:hmg_patient_app_new/routes/app_routes.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart';
import 'package:hmg_patient_app_new/services/error_handler_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/theme/colors.dart';
class WaterMonitorViewModel extends ChangeNotifier {
WaterMonitorRepo waterMonitorRepo;
ErrorHandlerService errorHandlerService;
WaterMonitorViewModel({required this.waterMonitorRepo});
WaterMonitorViewModel({
required this.waterMonitorRepo,
required this.errorHandlerService,
});
// Controllers
final TextEditingController nameController = TextEditingController();
@ -190,9 +195,9 @@ class WaterMonitorViewModel extends ChangeNotifier {
identificationNo: identification,
);
result.fold((failure) {
log('Error fetching user progress: ${failure.message}');
}, (apiModel) {
result.fold(
(failure) => errorHandlerService.handleError(failure: failure),
(apiModel) {
log("User Progress Data ($_selectedDuration): ${apiModel.data.toString()}");
// Parse the response based on progress type
@ -300,10 +305,13 @@ class WaterMonitorViewModel extends ChangeNotifier {
identificationNo: identification,
);
result.fold((failure) {
_userDetailData = null;
if (onError != null) onError(failure.message);
}, (apiModel) {
result.fold(
(failure) {
errorHandlerService.handleError(failure: failure);
_userDetailData = null;
if (onError != null) onError(failure.message);
},
(apiModel) {
_userDetailData = apiModel.data;
// Populate form fields from the fetched data
@ -322,7 +330,7 @@ class WaterMonitorViewModel extends ChangeNotifier {
if (_userDetailData == null) {
try {
_navigationService.pushAndReplace(AppRoutes.waterMonitorSettingsScreen);
_navigationService.pushAndReplace(AppRoutes.waterMonitorSettingsPage);
} catch (navErr) {
log('Navigation to water monitor settings failed: $navErr');
}
@ -686,6 +694,7 @@ class WaterMonitorViewModel extends ChangeNotifier {
return result.fold(
(failure) {
errorHandlerService.handleError(failure: failure);
_validationError = failure.message;
_isLoading = false;
notifyListeners();
@ -1060,14 +1069,14 @@ class WaterMonitorViewModel extends ChangeNotifier {
return result.fold(
(failure) {
errorHandlerService.handleError(failure: failure);
log('Error inserting user activity: ${failure.message}');
_isLoading = false;
notifyListeners();
return false;
},
(apiModel) {
(apiModel) async {
log("Insert user activity success: ${apiModel.data.toString()}");
// Update consumed amount and goal from the response
if (apiModel.data != null && apiModel.data is List && (apiModel.data as List).isNotEmpty) {
final progressData = (apiModel.data as List).first;
if (progressData is Map) {
@ -1090,7 +1099,7 @@ class WaterMonitorViewModel extends ChangeNotifier {
}
// Refresh progress data to ensure consistency
fetchUserProgressForMonitoring();
await fetchUserProgressForMonitoring();
}
_isLoading = false;
@ -1135,12 +1144,13 @@ class WaterMonitorViewModel extends ChangeNotifier {
return result.fold(
(failure) {
errorHandlerService.handleError(failure: failure);
log('Error undoing user activity: ${failure.message}');
_isLoading = false;
notifyListeners();
return false;
},
(apiModel) {
(apiModel) async {
log("Undo user activity success: ${apiModel.data.toString()}");
// Update consumed amount and goal from the response
@ -1164,8 +1174,8 @@ class WaterMonitorViewModel extends ChangeNotifier {
}
}
}
await fetchUserProgressForMonitoring();
}
fetchUserProgressForMonitoring();
_isLoading = false;
notifyListeners();
return true;

@ -36,6 +36,7 @@ import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_v
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_view_model.dart';
import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart';
import 'package:hmg_patient_app_new/presentation/health_trackers/health_trackers_view_model.dart';
import 'package:hmg_patient_app_new/routes/app_routes.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
@ -175,6 +176,9 @@ void main() async {
ChangeNotifierProvider<MyInvoicesViewModel>(
create: (_) => getIt.get<MyInvoicesViewModel>(),
),
ChangeNotifierProvider<HealthTrackersViewModel>(
create: (_) => getIt.get<HealthTrackersViewModel>(),
),
ChangeNotifierProvider<MonthlyReportViewModel>(
create: (_) => getIt.get<MonthlyReportViewModel>(),
)
@ -219,3 +223,4 @@ class MyApp extends StatelessWidget {
}
}
// flutter pub run easy_localization:generate -S assets/langs -f keys -o locale_keys.g.dart

@ -7,16 +7,26 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart';
import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_view_model.dart';
import 'package:hmg_patient_app_new/features/blood_donation/models/blood_group_hospitals_model.dart';
import 'package:hmg_patient_app_new/features/blood_donation/widgets/hospital_selection.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/blood_donation/widgets/select_blood_group_widget.dart';
import 'package:hmg_patient_app_new/presentation/blood_donation/widgets/select_city_widget.dart';
import 'package:hmg_patient_app_new/presentation/blood_donation/widgets/select_gender_widget.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/select_clinic_page.dart';
import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:lottie/lottie.dart';
import 'package:provider/provider.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart';
class BloodDonationPage extends StatelessWidget {
BloodDonationPage({super.key});
@ -25,7 +35,7 @@ class BloodDonationPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
appState = getIt.get<AppState>();
appState = getIt<AppState>();
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: Consumer<BloodDonationViewModel>(builder: (context, bloodDonationVM, child) {
@ -34,15 +44,83 @@ class BloodDonationPage extends StatelessWidget {
Expanded(
child: CollapsingListView(
title: LocaleKeys.bloodDonation.tr(),
trailing: CustomButton(
text: "Book",
onPressed: () {
// if (bloodDonationVM.isUserAuthanticated()) {
bloodDonationVM.fetchHospitalsList().then((value) {
showCommonBottomSheetWithoutHeight(context, title: "Select Hospital", isDismissible: false, child: Consumer<BloodDonationViewModel>(builder: (_, data, __) {
return HospitalBottomSheetBodySelection(
onUserHospitalSelection: (BdGetProjectsHaveBdClinic userChoice) {
print("============User Choice===============");
bloodDonationVM.getFreeBloodDonationSlots(request: {"ClinicID": 134, "ProjectID": userChoice.projectId});
},
);
}), callBackFunc: () {});
});
// } else {
// return showCommonBottomSheetWithoutHeight(
// context,
// title: LocaleKeys.notice.tr(context: context),
// child: Column(
// mainAxisAlignment: MainAxisAlignment.center,
// crossAxisAlignment: CrossAxisAlignment.center,
// children: [
// Lottie.asset(AppAnimations.errorAnimation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill),
// SizedBox(height: 8.h),
// (LocaleKeys.loginToUseService.tr()).toText16(color: AppColors.blackColor),
// SizedBox(height: 16.h),
// Row(
// children: [
// Expanded(
// child: CustomButton(
// text: LocaleKeys.cancel.tr(),
// onPressed: () {
// Navigator.of(context).pop();
// },
// backgroundColor: AppColors.secondaryLightRedColor,
// borderColor: AppColors.secondaryLightRedColor,
// textColor: AppColors.primaryRedColor,
// icon: AppAssets.cancel,
// iconColor: AppColors.primaryRedColor,
// ),
// ),
// SizedBox(width: 8.h),
// Expanded(
// child: CustomButton(
// text: LocaleKeys.confirm.tr(),
// onPressed: () async {
// Navigator.of(context).pop();
// // Navigator.pushAndRemoveUntil(context, CustomPageRoute(page: LandingNavigation()), (r) => false);
// await getIt<AuthenticationViewModel>().onLoginPressed();
// },
// backgroundColor: AppColors.bgGreenColor,
// borderColor: AppColors.bgGreenColor,
// textColor: Colors.white,
// icon: AppAssets.confirm,
// ),
// ),
// ],
// ),
// SizedBox(height: 16.h),
// ],
// ).center,
// callBackFunc: () {},
// isFullScreen: false,
// isCloseButtonVisible: true,
// );
// }
},
backgroundColor: AppColors.bgRedLightColor,
borderColor: AppColors.bgRedLightColor,
textColor: AppColors.primaryRedColor,
padding: EdgeInsetsGeometry.symmetric(vertical: 0.h, horizontal: 20.h)),
child: Padding(
padding: EdgeInsets.all(24.w),
child: SingleChildScrollView(
child: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: false,
),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: false),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
@ -60,8 +138,8 @@ class BloodDonationPage extends StatelessWidget {
children: [
LocaleKeys.city.tr().toText16(color: AppColors.textColor, weight: FontWeight.w500),
(appState.isArabic()
? (bloodDonationVM.selectedCity.descriptionN ?? LocaleKeys.select.tr())
: bloodDonationVM.selectedCity.description ?? LocaleKeys.select.tr(context: context))
? (bloodDonationVM.selectedCity?.descriptionN ?? LocaleKeys.select.tr())
: bloodDonationVM.selectedCity?.description ?? LocaleKeys.select.tr(context: context))
.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500),
],
),
@ -71,12 +149,7 @@ class BloodDonationPage extends StatelessWidget {
],
).onPress(() async {
showCommonBottomSheetWithoutHeight(context,
title: LocaleKeys.selectCity.tr(context: context),
isDismissible: true,
child: SelectCityWidget(
bloodDonationViewModel: bloodDonationVM,
),
callBackFunc: () {});
title: LocaleKeys.selectCity.tr(context: context), isDismissible: true, child: SelectCityWidget(bloodDonationViewModel: bloodDonationVM), callBackFunc: () {});
}),
SizedBox(height: 16.h),
Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h),
@ -86,13 +159,16 @@ class BloodDonationPage extends StatelessWidget {
children: [
Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.my_account_icon, width: 40.h, height: 40.h),
Utils.buildSvgWithAssets(icon: AppAssets.genderInputIcon, width: 40.h, height: 40.h),
SizedBox(width: 12.w),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.gender.tr().toText16(color: AppColors.textColor, weight: FontWeight.w500),
"Male".toText14(color: AppColors.greyTextColor, weight: FontWeight.w500),
(appState.isArabic()
? (bloodDonationVM.selectedGender?.typeAr ?? LocaleKeys.select.tr())
: bloodDonationVM.selectedGender?.type ?? LocaleKeys.select.tr(context: context))
.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500),
],
),
],
@ -103,10 +179,7 @@ class BloodDonationPage extends StatelessWidget {
showCommonBottomSheetWithoutHeight(context,
title: LocaleKeys.selectGender.tr(context: context),
isDismissible: true,
child: SelectGenderWidget(
isArabic: appState.isArabic(),
bloodDonationViewModel: bloodDonationVM,
),
child: SelectGenderWidget(isArabic: appState.isArabic(), bloodDonationViewModel: bloodDonationVM),
callBackFunc: () {});
}),
SizedBox(height: 16.h),
@ -117,13 +190,17 @@ class BloodDonationPage extends StatelessWidget {
children: [
Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.my_account_icon, width: 40.h, height: 40.h),
Utils.buildSvgWithAssets(icon: AppAssets.bloodType, width: 40.h, height: 40.h),
SizedBox(width: 12.w),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.bloodType.tr().toText16(color: AppColors.textColor, weight: FontWeight.w500),
bloodDonationVM.selectedBloodType.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500),
// bloodDonationVM.selectedBloodType?.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500),
(appState.isArabic()
? (bloodDonationVM.selectedBloodType ?? LocaleKeys.select.tr())
: bloodDonationVM.selectedBloodType ?? LocaleKeys.select.tr(context: context))
.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500)
],
),
],
@ -134,10 +211,7 @@ class BloodDonationPage extends StatelessWidget {
showCommonBottomSheetWithoutHeight(context,
title: LocaleKeys.select.tr(context: context),
isDismissible: true,
child: SelectBloodGroupWidget(
isArabic: appState.isArabic(),
bloodDonationViewModel: bloodDonationVM,
),
child: SelectBloodGroupWidget(isArabic: appState.isArabic(), bloodDonationViewModel: bloodDonationVM),
callBackFunc: () {});
}),
],
@ -149,19 +223,73 @@ class BloodDonationPage extends StatelessWidget {
),
),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: true,
),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true),
child: SizedBox(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
onTap: bloodDonationVM.onTermAccepted,
child: Row(
children: [
Selector<BloodDonationViewModel, bool>(
selector: (_, viewModel) => viewModel.isTermsAccepted,
shouldRebuild: (previous, next) => previous != next,
builder: (context, isTermsAccepted, child) {
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
height: 24.h,
width: 24.h,
decoration: BoxDecoration(
color: isTermsAccepted ? AppColors.primaryRedColor : Colors.transparent,
borderRadius: BorderRadius.circular(6),
border: Border.all(color: isTermsAccepted ? AppColors.primaryRedBorderColor : AppColors.greyColor, width: 2.h),
),
child: isTermsAccepted ? Icon(Icons.check, size: 16.f, color: Colors.white) : null,
);
},
),
SizedBox(width: 12.h),
Row(
children: [
Text(
LocaleKeys.iAcceptThe.tr(),
style: context.dynamicTextStyle(fontSize: 14.f, fontWeight: FontWeight.w500, color: Color(0xFF2E3039)),
),
GestureDetector(
onTap: () {
// Navigate to terms and conditions page
Navigator.of(context).pushNamed('/terms');
},
child: Text(
LocaleKeys.termsConditoins.tr(),
style: context.dynamicTextStyle(
fontSize: 14.f,
fontWeight: FontWeight.w500,
color: AppColors.primaryRedColor,
decoration: TextDecoration.underline,
decorationColor: AppColors.primaryRedBorderColor,
),
),
),
],
),
// Expanded(
// child: Text(
// LocaleKeys.iAcceptTermsConditions.tr().split("the").first,
// style: context.dynamicTextStyle(fontSize: 14.fSize, fontWeight: FontWeight.w500, color: Color(0xFF2E3039)),
// ),
// ),
],
),
).paddingOnly(left: 16.h, right: 16.h, top: 24.h),
CustomButton(
text: LocaleKeys.save.tr(),
onPressed: () {
// openDoctorScheduleCalendar();
onPressed: () async {
DialogService dialogService = getIt.get<DialogService>();
if (await bloodDonationVM.validateSelections()) {
bloodDonationVM.updateBloodGroup();
}
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,

@ -26,9 +26,7 @@ class SelectCityWidget extends StatelessWidget {
Navigator.of(context).pop();
});
},
separatorBuilder: (_, __) => SizedBox(
height: 8.h,
),
separatorBuilder: (_, __) => SizedBox(height: 8.h),
itemCount: bloodDonationViewModel.citiesList.length),
)
],

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/enums.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';
@ -8,10 +9,10 @@ import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_view_
import 'package:hmg_patient_app_new/theme/colors.dart';
class SelectGenderWidget extends StatelessWidget {
SelectGenderWidget({super.key, required this.bloodDonationViewModel, required this.isArabic});
const SelectGenderWidget({super.key, required this.bloodDonationViewModel, required this.isArabic});
BloodDonationViewModel bloodDonationViewModel;
bool isArabic;
final BloodDonationViewModel bloodDonationViewModel;
final bool isArabic;
@override
Widget build(BuildContext context) {
@ -20,46 +21,45 @@ class SelectGenderWidget extends StatelessWidget {
children: [
SizedBox(height: 8.h),
SizedBox(
height: MediaQuery.sizeOf(context).height * .4,
child: ListView.separated(
itemBuilder: (_, index) {
return DecoratedBox(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: false,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8.h,
children: [bloodDonationViewModel.genderList[index].name.toText16(color: AppColors.textColor, isBold: true)],
height: MediaQuery.sizeOf(context).height * .4,
child: ListView.separated(
itemBuilder: (_, index) {
return DecoratedBox(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: false,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8.h,
children: [GenderTypeEnum.values[index].name.toCamelCase.toText16(color: AppColors.textColor, isBold: true)],
),
),
),
Transform.flip(
flipX: isArabic,
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon,
iconColor: AppColors.blackColor,
width: 40.h,
height: 40.h,
fit: BoxFit.contain,
Transform.flip(
flipX: isArabic,
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon,
iconColor: AppColors.blackColor,
width: 40.h,
height: 40.h,
fit: BoxFit.contain,
),
),
),
],
).paddingSymmetrical(16.h, 16.h).onPress(() {
// bloodDonationViewModel.setSelectedCity(bloodDonationViewModel.citiesList[index]);
Navigator.of(context).pop();
}));
},
separatorBuilder: (_, __) => SizedBox(
height: 8.h,
),
itemCount: bloodDonationViewModel.genderList.length),
)
],
).paddingSymmetrical(16.h, 16.h).onPress(() {
bloodDonationViewModel.onGenderChange(GenderTypeEnum.values[index].name.toCamelCase);
Navigator.of(context).pop();
}));
},
separatorBuilder: (_, __) => SizedBox(
height: 8.h,
),
itemCount: GenderTypeEnum.values.length))
],
);
}

@ -112,10 +112,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
fit: BoxFit.cover,
).circle(100).toShimmer2(isShow: true, radius: 50.r),
SizedBox(height: 8.h),
("Dr. John Smith Smith Smith")
.toString()
.toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2)
.toShimmer2(isShow: true),
("Dr. John Smith Smith Smith").toString().toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2).toShimmer2(isShow: true),
],
)
: myAppointmentsVM.patientMyDoctorsList.isEmpty
@ -241,10 +238,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
),
],
),
Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
Transform.flip(flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
],
).onPress(() {
bookAppointmentsViewModel.setIsClinicsListLoading(true);
@ -276,10 +270,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
),
],
),
Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
Transform.flip(flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
],
).onPress(() {
bookAppointmentsViewModel.setIsDoctorSearchByNameStarted(false);
@ -309,10 +300,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
),
],
),
Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
Transform.flip(flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
],
).onPress(() {
bookAppointmentsViewModel.setProjectID(null);
@ -332,124 +320,115 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
Column(
children: [
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: false,
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.search_by_clinic_icon, width: 40.h, height: 40.h),
SizedBox(width: 12.h),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Immediate Consultation".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500),
"Tap to select clinic".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500),
],
),
],
),
Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
],
).onPress(() async {
//TODO Implement API to check for existing LiveCare Requests
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: false,
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.search_by_clinic_icon, width: 40.h, height: 40.h),
SizedBox(width: 12.h),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Immediate Consultation".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500),
"Tap to select clinic".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500),
],
),
],
),
Transform.flip(flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
],
).onPress(() async {
//TODO Implement API to check for existing LiveCare Requests
LoaderBottomSheet.showLoader();
await immediateLiveCareViewModel.getPatientLiveCareHistory();
LoaderBottomSheet.hideLoader();
LoaderBottomSheet.showLoader();
await immediateLiveCareViewModel.getPatientLiveCareHistory();
LoaderBottomSheet.hideLoader();
if (immediateLiveCareViewModel.patientHasPendingLiveCareRequest) {
Navigator.of(context).push(
CustomPageRoute(
page: ImmediateLiveCarePendingRequestPage(),
),
);
} else {
Navigator.of(context).push(
CustomPageRoute(
page: SelectImmediateLiveCareClinicPage(),
),
);
}
}),
SizedBox(height: 16.h),
Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h),
SizedBox(height: 16.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.search_by_doctor_icon, width: 40.h, height: 40.h),
SizedBox(width: 12.h),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Scheduled Consultation".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500),
"Tap to select clinic".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500),
],
),
],
),
Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
],
).onPress(() {
bookAppointmentsViewModel.setIsClinicsListLoading(true);
bookAppointmentsViewModel.setIsLiveCareSchedule(true);
Navigator.of(context).push(
CustomPageRoute(
page: SelectClinicPage(),
),
);
}),
SizedBox(height: 16.h),
Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h),
SizedBox(height: 16.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.search_by_region_icon, width: 40.h, height: 40.h),
SizedBox(width: 12.h),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Pharma LiveCare".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500),
"".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500),
],
),
],
),
Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
],
).onPress(() {
openRegionListBottomSheet(context, RegionBottomSheetType.FOR_REGION);
}),
],
),
),
),
],
).paddingSymmetrical(24.h, 0.h)
if (immediateLiveCareViewModel.patientHasPendingLiveCareRequest) {
Navigator.of(context).push(
CustomPageRoute(
page: ImmediateLiveCarePendingRequestPage(),
),
);
} else {
Navigator.of(context).push(
CustomPageRoute(
page: SelectImmediateLiveCareClinicPage(),
),
);
}
}),
SizedBox(height: 16.h),
Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h),
SizedBox(height: 16.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.search_by_doctor_icon, width: 40.h, height: 40.h),
SizedBox(width: 12.h),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Scheduled Consultation".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500),
"Tap to select clinic".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500),
],
),
],
),
Transform.flip(flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
],
).onPress(() {
bookAppointmentsViewModel.setIsClinicsListLoading(true);
bookAppointmentsViewModel.setIsLiveCareSchedule(true);
Navigator.of(context).push(
CustomPageRoute(
page: SelectClinicPage(),
),
);
}),
SizedBox(height: 16.h),
Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h),
SizedBox(height: 16.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.search_by_region_icon, width: 40.h, height: 40.h),
SizedBox(width: 12.h),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Pharma LiveCare".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500),
"".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500),
],
),
],
),
Transform.flip(flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
],
).onPress(() {
openRegionListBottomSheet(context, RegionBottomSheetType.FOR_REGION);
}),
],
),
),
),
],
).paddingSymmetrical(24.h, 0.h)
// : getLiveCareNotLoggedInUI()
;
default:
@ -493,10 +472,8 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
regionalViewModel.flush();
regionalViewModel.setBottomSheetType(type);
// AppointmentViaRegionViewmodel? viewmodel = null;
showCommonBottomSheetWithoutHeight(context,
title: "",
titleWidget: Consumer<AppointmentViaRegionViewmodel>(builder: (_, data, __) => getTitle(data)),
isDismissible: false, child: Consumer<AppointmentViaRegionViewmodel>(builder: (_, data, __) {
showCommonBottomSheetWithoutHeight(context, title: "", titleWidget: Consumer<AppointmentViaRegionViewmodel>(builder: (_, data, __) => getTitle(data)), isDismissible: false,
child: Consumer<AppointmentViaRegionViewmodel>(builder: (_, data, __) {
return getRegionalSelectionWidget(data);
}), callBackFunc: () {});
}
@ -582,9 +559,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Immediate service".needTranslation.toText18(color: AppColors.textColor, isBold: true),
"No need to wait, you will get medical consultation immediately via video call"
.needTranslation
.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500),
"No need to wait, you will get medical consultation immediately via video call".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500),
],
),
),
@ -616,9 +591,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Doctor will contact".needTranslation.toText18(color: AppColors.textColor, isBold: true),
"A specialised doctor will contact you and will be able to view your medical history"
.needTranslation
.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500),
"A specialised doctor will contact you and will be able to view your medical history".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500),
],
),
),
@ -634,9 +607,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Free medicine delivery".needTranslation.toText18(color: AppColors.textColor, isBold: true),
"Offers free medicine delivery for the LiveCare appointment"
.needTranslation
.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500),
"Offers free medicine delivery for the LiveCare appointment".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500),
],
),
),

@ -1,6 +1,7 @@
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/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart' show CapExtension;
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';

@ -1211,9 +1211,7 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
bookAppointmentsViewModel.setIsContinueDentalPlan(true);
Navigator.of(context).pop();
Navigator.of(context).push(
CustomPageRoute(
page: SelectDoctorPage(),
),
CustomPageRoute(page: SelectDoctorPage()),
);
},
backgroundColor: AppColors.bgGreenColor,

@ -40,9 +40,7 @@ class _SelectDoctorPageState extends State<SelectDoctorPage> {
late AppState appState;
late BookAppointmentsViewModel bookAppointmentsViewModel;
// Scroll controller to control page scrolling when a group expands
late ScrollController _scrollController;
// Map of keys for each item to allow scrolling to them
final Map<int, GlobalKey> _itemKeys = {};
@override
@ -79,6 +77,20 @@ class _SelectDoctorPageState extends State<SelectDoctorPage> {
backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView(
title: "Choose Doctor".needTranslation,
// 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.search.tr(),
// onPressed: () {
// },
// icon: null,
// fontSize: 16.f,
// backgroundColor: AppColors.primaryRedColor,
// borderColor: AppColors.primaryRedColor,
// borderRadius: 12.r,
// fontWeight: FontWeight.w500),
// ),
child: SingleChildScrollView(
controller: _scrollController,
child: Padding(
@ -124,40 +136,42 @@ class _SelectDoctorPageState extends State<SelectDoctorPage> {
],
),
SizedBox(height: 16.h),
Row(
children: [
CustomButton(
text: LocaleKeys.byClinic.tr(context: context),
onPressed: () {
bookAppointmentsVM.setIsSortByClinic(true);
},
backgroundColor: bookAppointmentsVM.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor,
borderColor: bookAppointmentsVM.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2),
textColor: bookAppointmentsVM.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor,
fontSize: 12,
fontWeight: FontWeight.w500,
borderRadius: 10,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
SizedBox(width: 8.h),
CustomButton(
text: LocaleKeys.byHospital.tr(context: context),
onPressed: () {
bookAppointmentsVM.setIsSortByClinic(false);
},
backgroundColor: bookAppointmentsVM.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor,
borderColor: bookAppointmentsVM.isSortByClinic ? AppColors.textColor.withOpacity(0.2) : AppColors.primaryRedColor,
textColor: bookAppointmentsVM.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor,
fontSize: 12,
fontWeight: FontWeight.w500,
borderRadius: 10,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
],
).paddingSymmetrical(0.h, 0.h),
SizedBox(height: 16.h),
if (bookAppointmentsViewModel.isGetDocForHealthCal && bookAppointmentsVM.showSortFilterButtons)
Row(
children: [
CustomButton(
text: LocaleKeys.byClinic.tr(context: context),
onPressed: () {
bookAppointmentsVM.setIsSortByClinic(true);
},
backgroundColor: bookAppointmentsVM.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor,
borderColor: bookAppointmentsVM.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2),
textColor: bookAppointmentsVM.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor,
fontSize: 12,
fontWeight: FontWeight.w500,
borderRadius: 10,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
SizedBox(width: 8.h),
CustomButton(
text: LocaleKeys.byHospital.tr(context: context),
onPressed: () {
bookAppointmentsVM.setIsSortByClinic(false);
},
backgroundColor: bookAppointmentsVM.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor,
borderColor: bookAppointmentsVM.isSortByClinic ? AppColors.textColor.withOpacity(0.2) : AppColors.primaryRedColor,
textColor: bookAppointmentsVM.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor,
fontSize: 12,
fontWeight: FontWeight.w500,
borderRadius: 10,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
],
).paddingSymmetrical(0.h, 0.h),
if (bookAppointmentsViewModel.isGetDocForHealthCal && bookAppointmentsVM.showSortFilterButtons)
SizedBox(height: 16.h),
Row(
mainAxisSize: MainAxisSize.max,
children: [

@ -1,4 +1,9 @@
import 'package:flutter/material.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/core/utils/size_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/presentation/book_appointment/select_clinic_page.dart';
import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart';
import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/bf.dart';
@ -12,17 +17,12 @@ import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts
import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/ibw.dart';
import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/ovulation.dart';
import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/triglycerides.dart';
import 'package:provider/provider.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/core/utils/size_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/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:provider/provider.dart';
class HealthCalculatorDetailedPage extends StatefulWidget {
HealthCalculatorsTypeEnum calculatorType;
@ -50,8 +50,8 @@ class _HealthCalculatorDetailedPageState extends State<HealthCalculatorDetailedP
widget.calculatorType == HealthCalculatorsTypeEnum.triglycerides
? SizedBox()
: Container(
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.only(topLeft: Radius.circular(24.r), topRight: Radius.circular(24.r))),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor, customBorder: BorderRadius.only(topLeft: Radius.circular(24.r), topRight: Radius.circular(24.r))),
padding: EdgeInsets.symmetric(vertical: 20.h, horizontal: 20.h),
child: CustomButton(
text: widget.calculatorType == HealthCalculatorsTypeEnum.bloodSugar ||
@ -172,58 +172,69 @@ class _HealthCalculatorDetailedPageState extends State<HealthCalculatorDetailedP
switch (widget.calculatorType) {
case HealthCalculatorsTypeEnum.bmi:
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: BMIWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.calories:
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: CaloriesWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.bmr:
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: BMRWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.idealBodyWeight:
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: IdealBodyWeightWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.bodyFat:
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: BodyFatWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.crabsProteinFat:
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: CrabsWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.ovulation:
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: OvulationWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.deliveryDueDate:
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: DeliveryDueDWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.bloodSugar:
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: BloodSugarWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.bloodCholesterol:
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: BloodCholesterolWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.triglycerides:
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: TriglyceridesWidget(onChange: onCalculate),
);
}

@ -282,7 +282,12 @@ class HealthCalcualtorViewModel extends ChangeNotifier {
}
// IBW
void calculateIBW({required String heightText, required String weightText, required String heightUnit, required String weightUnit, required String bodyFrameSize}) {
void calculateIBW(
{required String heightText,
required String weightText,
required String heightUnit,
required String weightUnit,
required String bodyFrameSize}) {
if (heightText.trim().isEmpty || weightText.trim().isEmpty) {
ibwResult = null;
weightDifference = null;
@ -609,7 +614,20 @@ class HealthCalcualtorViewModel extends ChangeNotifier {
}
String _getMonthName(int month) {
const List<String> months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
const List<String> months = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
];
return months[month - 1];
}
@ -828,7 +846,8 @@ class HealthCalcualtorViewModel extends ChangeNotifier {
// expose map-like results for widgets to forward to parent
Map<String, dynamic>? get bmiResultMap => bmiResult == null ? null : {'bmiResult': bmiResult, 'bmiCategory': bmiCategory};
Map<String, dynamic>? get caloriesResultMap => caloriesResult == null ? null : {'calories': caloriesResult, 'bmr': bmrResult, 'calorieRange': calorieRange, 'bmrRange': bmrRange};
Map<String, dynamic>? get caloriesResultMap =>
caloriesResult == null ? null : {'calories': caloriesResult, 'bmr': bmrResult, 'calorieRange': calorieRange, 'bmrRange': bmrRange};
Map<String, dynamic>? get ibwResultMap => ibwResult == null ? null : {'ibw': ibwResult, 'difference': weightDifference, 'status': weightStatus};

@ -0,0 +1,567 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/presentation/health_trackers/health_trackers_view_model.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
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:intl/intl.dart';
import 'package:provider/provider.dart';
class AddHealthTrackerEntryPage extends StatefulWidget {
final HealthTrackerTypeEnum trackerType;
const AddHealthTrackerEntryPage({
super.key,
required this.trackerType,
});
@override
State<AddHealthTrackerEntryPage> createState() => _AddHealthTrackerEntryPageState();
}
class _AddHealthTrackerEntryPageState extends State<AddHealthTrackerEntryPage> {
late DialogService dialogService;
// Controllers for date and time
final TextEditingController dateController = TextEditingController();
final TextEditingController timeController = TextEditingController();
@override
void initState() {
super.initState();
dialogService = getIt.get<DialogService>();
}
@override
void dispose() {
dateController.dispose();
timeController.dispose();
super.dispose();
}
/// Get page title based on tracker type
String _getPageTitle() {
switch (widget.trackerType) {
case HealthTrackerTypeEnum.bloodSugar:
return "Add Blood Sugar".needTranslation;
case HealthTrackerTypeEnum.bloodPressure:
return "Add Blood Pressure".needTranslation;
case HealthTrackerTypeEnum.weightTracker:
return "Add Weight".needTranslation;
}
}
/// Get success message based on tracker type
String _getSuccessMessage() {
switch (widget.trackerType) {
case HealthTrackerTypeEnum.bloodSugar:
return "Blood Sugar Data saved successfully".needTranslation;
case HealthTrackerTypeEnum.bloodPressure:
return "Blood Pressure Data saved successfully".needTranslation;
case HealthTrackerTypeEnum.weightTracker:
return "Weight Data saved successfully".needTranslation;
}
}
/// Save entry based on tracker type
Future<void> _saveEntry(HealthTrackersViewModel viewModel) async {
switch (widget.trackerType) {
case HealthTrackerTypeEnum.bloodSugar:
await _saveBloodSugarEntry(viewModel);
break;
case HealthTrackerTypeEnum.bloodPressure:
await _saveBloodPressureEntry(viewModel);
break;
case HealthTrackerTypeEnum.weightTracker:
await _saveWeightEntry(viewModel);
break;
}
}
// Save Blood Sugar entry
Future<void> _saveBloodSugarEntry(HealthTrackersViewModel viewModel) async {
LoaderBottomSheet.showLoader(loadingText: "Please wait".needTranslation);
// Combine date and time
final dateTime = "${dateController.text} ${timeController.text}";
// Call ViewModel method with callbacks
await viewModel.saveBloodSugarEntry(
dateTime: dateTime,
measureTime: viewModel.selectedBloodSugarMeasureTime,
onSuccess: () {
LoaderBottomSheet.hideLoader();
_showSuccessAndPop();
},
onFailure: (error) {
LoaderBottomSheet.hideLoader();
dialogService.showErrorBottomSheet(message: error);
},
);
}
// Save Weight entry
Future<void> _saveWeightEntry(HealthTrackersViewModel viewModel) async {
LoaderBottomSheet.showLoader(loadingText: "Please wait".needTranslation);
// Combine date and time
final dateTime = "${dateController.text} ${timeController.text}";
// Call ViewModel method with callbacks
await viewModel.saveWeightEntry(
dateTime: dateTime,
onSuccess: () {
LoaderBottomSheet.hideLoader();
_showSuccessAndPop();
},
onFailure: (error) {
LoaderBottomSheet.hideLoader();
dialogService.showErrorBottomSheet(message: error);
},
);
}
// Save Blood Pressure entry
Future<void> _saveBloodPressureEntry(HealthTrackersViewModel viewModel) async {
LoaderBottomSheet.showLoader(loadingText: "Please wait".needTranslation);
// Combine date and time
final dateTime = "${dateController.text} ${timeController.text}";
// Call ViewModel method with callbacks
await viewModel.saveBloodPressureEntry(
dateTime: dateTime,
onSuccess: () {
LoaderBottomSheet.hideLoader();
_showSuccessAndPop();
},
onFailure: (error) {
LoaderBottomSheet.hideLoader();
dialogService.showErrorBottomSheet(message: error);
},
);
}
// Show success message and pop back
void _showSuccessAndPop() {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getSuccessWidget(
loadingText: _getSuccessMessage(),
),
callBackFunc: () {
Navigator.pop(context);
},
isCloseButtonVisible: false,
isDismissible: true,
isFullScreen: false,
);
}
// Reusable method to build selection row widget
Widget _buildSelectionRow({
required String value,
required String groupValue,
required VoidCallback onTap,
bool useUpperCase = false,
}) {
return SizedBox(
height: 70.h,
child: Row(
spacing: 8.h,
children: [
Radio<String>(
value: value,
groupValue: groupValue,
activeColor: AppColors.errorColor,
onChanged: (_) => onTap(),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
(useUpperCase ? value.toUpperCase() : value.toCamelCase)
.toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1)
.expanded,
],
).onPress(onTap),
);
}
// Reusable method to show selection bottom sheet
void _showSelectionBottomSheet({
required BuildContext context,
required String title,
required List<String> items,
required String selectedValue,
required Function(String) onSelected,
bool useUpperCase = false,
}) {
dialogService.showFamilyBottomSheetWithoutHWithChild(
label: title.needTranslation,
message: "",
child: Container(
constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.7),
padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20.r)),
child: ListView.separated(
shrinkWrap: true,
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return _buildSelectionRow(
value: item,
groupValue: selectedValue,
useUpperCase: useUpperCase,
onTap: () {
onSelected(item);
Navigator.pop(context);
},
);
},
separatorBuilder: (_, __) => Divider(height: 1, color: AppColors.dividerColor),
),
),
onOkPressed: () {},
);
}
// Blood Sugar unit selection
void _showBloodSugarUnitSelectionBottomSheet(BuildContext context, HealthTrackersViewModel viewModel) {
FocusScope.of(context).unfocus();
_showSelectionBottomSheet(
context: context,
title: "Select Unit".needTranslation,
items: viewModel.bloodSugarUnit,
selectedValue: viewModel.selectedBloodSugarUnit,
onSelected: viewModel.setBloodSugarUnit,
useUpperCase: false,
);
}
// Blood Sugar measure time selection
void _showBloodSugarEntryTimeBottomSheet(BuildContext context, HealthTrackersViewModel viewModel) {
FocusScope.of(context).unfocus();
_showSelectionBottomSheet(
context: context,
title: "Select Measure Time".needTranslation,
items: viewModel.bloodSugarMeasureTimeEnList,
selectedValue: viewModel.selectedBloodSugarMeasureTime,
onSelected: viewModel.setBloodSugarMeasureTime,
useUpperCase: false,
);
}
// Weight unit selection
void _showWeightUnitSelectionBottomSheet(BuildContext context, HealthTrackersViewModel viewModel) {
FocusScope.of(context).unfocus();
_showSelectionBottomSheet(
context: context,
title: "Select Unit".needTranslation,
items: viewModel.weightUnits,
selectedValue: viewModel.selectedWeightUnit,
onSelected: viewModel.setWeightUnit,
useUpperCase: false,
);
}
// Blood Pressure measured arm selection
void _showMeasuredArmSelectionBottomSheet(BuildContext context, HealthTrackersViewModel viewModel) {
FocusScope.of(context).unfocus();
_showSelectionBottomSheet(
context: context,
title: "Select Arm".needTranslation,
items: viewModel.measuredArmList,
selectedValue: viewModel.selectedMeasuredArm,
onSelected: viewModel.setMeasuredArm,
useUpperCase: false,
);
}
// Reusable method to build text field
Widget _buildTextField(TextEditingController controller, String hintText, {TextInputType keyboardType = TextInputType.name}) {
return TextField(
controller: controller,
keyboardType: keyboardType,
maxLines: 1,
cursorHeight: 14.h,
textAlignVertical: TextAlignVertical.center,
decoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.zero,
isCollapsed: true,
hintText: hintText,
hintStyle: const TextStyle(color: Colors.grey),
),
style: TextStyle(
fontSize: 14.f,
fontWeight: FontWeight.w500,
color: AppColors.textColor,
),
);
}
// Reusable method to build settings row
Widget _buildSettingsRow({
required String icon,
required String label,
String? value,
Widget? inputField,
String? unit,
VoidCallback? onUnitTap,
VoidCallback? onRowTap,
Color? iconColor,
bool showDivider = true,
}) {
return Column(
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
height: 40.w,
width: 40.w,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.greyColor,
borderRadius: 10.r,
hasShadow: false,
),
child: Center(child: Utils.buildSvgWithAssets(icon: icon, height: 22.w, width: 22.w, iconColor: iconColor)),
),
SizedBox(width: 12.w),
Expanded(
flex: unit != null ? 2 : 1,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
label.toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
if (inputField != null)
Container(
height: 20.w,
alignment: Alignment.centerLeft,
child: inputField,
)
else if (value != null && value.isNotEmpty)
value.toCamelCase.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor),
],
),
),
if (unit != null) ...[
Container(
width: 1.w,
height: 30.w,
color: AppColors.dividerColor,
).paddingOnly(right: 10.w),
Expanded(
child: Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
"Unit".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
unit.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor),
],
),
SizedBox(width: 12.w),
Utils.buildSvgWithAssets(icon: AppAssets.arrow_down)
],
).onPress(onUnitTap ?? () {}),
),
] else if (onRowTap != null) ...[
Utils.buildSvgWithAssets(icon: AppAssets.arrow_down),
SizedBox(width: 8.w),
],
],
).paddingSymmetrical(0.w, 16.w).onPress(onRowTap ?? () {}),
if (showDivider) Divider(height: 1, color: AppColors.dividerColor),
],
);
}
/// Build form fields based on tracker type
Widget _buildFormFields(HealthTrackersViewModel viewModel) {
switch (widget.trackerType) {
case HealthTrackerTypeEnum.bloodSugar:
return _buildBloodSugarForm(viewModel);
case HealthTrackerTypeEnum.bloodPressure:
return _buildBloodPressureForm(viewModel);
case HealthTrackerTypeEnum.weightTracker:
return _buildWeightForm(viewModel);
}
}
/// Blood Sugar form fields
Widget _buildBloodSugarForm(HealthTrackersViewModel viewModel) {
return Column(
children: [
_buildSettingsRow(
icon: AppAssets.heightIcon,
label: "Enter Blood Sugar".needTranslation,
inputField: _buildTextField(viewModel.bloodSugarController, '', keyboardType: TextInputType.number),
unit: viewModel.selectedBloodSugarUnit,
onUnitTap: () => _showBloodSugarUnitSelectionBottomSheet(context, viewModel),
),
_buildDateTimeFields(),
Divider(height: 1, color: AppColors.dividerColor),
_buildSettingsRow(
icon: AppAssets.weight_tracker_icon,
label: "Select Measure Time".needTranslation,
value: viewModel.selectedBloodSugarMeasureTime,
onRowTap: () => _showBloodSugarEntryTimeBottomSheet(context, viewModel),
),
],
);
}
/// Blood Pressure form fields
Widget _buildBloodPressureForm(HealthTrackersViewModel viewModel) {
return Column(
children: [
_buildSettingsRow(
icon: AppAssets.bloodPressureIcon,
iconColor: AppColors.greyTextColor,
label: "Enter Systolic Value".needTranslation,
inputField: _buildTextField(viewModel.systolicController, '', keyboardType: TextInputType.number),
),
_buildSettingsRow(
icon: AppAssets.bloodPressureIcon,
iconColor: AppColors.greyTextColor,
label: "Enter Diastolic Value".needTranslation,
inputField: _buildTextField(viewModel.diastolicController, '', keyboardType: TextInputType.number),
),
_buildSettingsRow(
icon: AppAssets.bodyIcon,
iconColor: AppColors.greyTextColor,
label: "Select Arm".needTranslation,
value: viewModel.selectedMeasuredArm,
onRowTap: () => _showMeasuredArmSelectionBottomSheet(context, viewModel),
),
_buildDateTimeFields(),
],
);
}
/// Weight form fields
Widget _buildWeightForm(HealthTrackersViewModel viewModel) {
return Column(
children: [
_buildSettingsRow(
icon: AppAssets.weightScale,
label: "Enter Weight".needTranslation,
inputField: _buildTextField(viewModel.weightController, '', keyboardType: TextInputType.number),
unit: viewModel.selectedWeightUnit,
onUnitTap: () => _showWeightUnitSelectionBottomSheet(context, viewModel),
),
_buildDateTimeFields(),
],
);
}
/// Common date and time fields
Widget _buildDateTimeFields() {
return Column(
children: [
TextInputWidget(
controller: dateController,
isReadOnly: true,
isArrowTrailing: true,
labelText: "Date",
hintText: "Select date".needTranslation,
focusNode: FocusNode(),
isEnable: true,
prefix: null,
isAllowRadius: true,
isBorderAllowed: false,
isAllowLeadingIcon: true,
padding: EdgeInsets.symmetric(vertical: 8.h),
leadingIcon: AppAssets.calendarGrey,
selectionType: SelectionTypeEnum.calendar,
isHideSwitcher: true,
onCalendarTypeChanged: (val) {},
onChange: (val) {
if (val == null) return;
try {
final parsedDate = DateTime.parse(val);
final formattedDate = DateFormat('dd MMM yyyy').format(parsedDate);
dateController.text = formattedDate;
log("date: $formattedDate");
} catch (e) {
dateController.text = val;
log("date: $val");
}
},
),
TextInputWidget(
controller: timeController,
isReadOnly: true,
isArrowTrailing: true,
labelText: "Time",
hintText: "Select time".needTranslation,
focusNode: FocusNode(),
isEnable: true,
prefix: null,
isAllowRadius: true,
isBorderAllowed: false,
isAllowLeadingIcon: true,
padding: EdgeInsets.symmetric(vertical: 8.h),
leadingIcon: AppAssets.calendarGrey,
selectionType: SelectionTypeEnum.time,
isHideSwitcher: true,
onCalendarTypeChanged: (val) {},
onChange: (val) {
if (val == null) return;
timeController.text = val;
log("time: $val");
},
),
],
);
}
@override
Widget build(BuildContext context) {
final viewModel = context.watch<HealthTrackersViewModel>();
return GestureDetector(
onTap: () {
FocusScope.of(context).unfocus();
},
child: Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView(
title: _getPageTitle(),
bottomChild: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: true,
),
child: Padding(
padding: EdgeInsets.all(24.w),
child: CustomButton(
text: "Save".needTranslation,
onPressed: () async => await _saveEntry(viewModel),
borderRadius: 12.r,
padding: EdgeInsets.symmetric(vertical: 14.h),
),
),
),
child: Container(
margin: EdgeInsets.symmetric(horizontal: 24.w, vertical: 24.h),
padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true),
child: _buildFormFields(viewModel),
),
),
),
);
}
}

@ -0,0 +1,118 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/route_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
class HealthTrackersPage extends StatefulWidget {
const HealthTrackersPage({super.key});
@override
State<HealthTrackersPage> createState() => _HealthTrackersPageState();
}
Widget buildHealthTrackerCard({
required String icon,
required String title,
required String description,
required Color iconBgColor,
required VoidCallback onTap,
}) {
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.white, borderRadius: 20.r),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: iconBgColor, borderRadius: 10.r),
height: 40.w,
width: 40.w,
child: Utils.buildSvgWithAssets(
icon: icon,
fit: BoxFit.none,
height: 22.w,
width: 22.w,
),
),
SizedBox(width: 12.w),
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
title.toText16(weight: FontWeight.w600),
description.toText12(
fontWeight: FontWeight.w500,
color: Color(0xFF8F9AA3),
),
],
),
),
SizedBox(width: 12.w),
Utils.buildSvgWithAssets(
icon: AppAssets.arrowRight,
width: 24.w,
height: 24.h,
fit: BoxFit.contain,
iconColor: AppColors.textColor,
),
],
).paddingAll(16.w),
).onPress(onTap);
}
class _HealthTrackersPageState extends State<HealthTrackersPage> {
@override
Widget build(BuildContext context) {
return CollapsingListView(
title: "Health Trackers".needTranslation,
child: Column(
children: [
buildHealthTrackerCard(
iconBgColor: AppColors.primaryRedColor,
icon: AppAssets.bloodSugarOnlyIcon,
title: "Blood Sugar".needTranslation,
description: "Track your glucose levels, understand trends, and get personalized insights for better health.".needTranslation,
onTap: () {
context.navigateWithName(
AppRoutes.healthTrackerDetailPage,
arguments: HealthTrackerTypeEnum.bloodSugar,
);
},
),
SizedBox(height: 16.h),
buildHealthTrackerCard(
iconBgColor: AppColors.infoColor,
icon: AppAssets.bloodPressureIcon,
title: "Blood Pressure".needTranslation,
description: "Monitor your blood pressure levels, track systolic and diastolic readings, and maintain a healthy heart.".needTranslation,
onTap: () {
context.navigateWithName(
AppRoutes.healthTrackerDetailPage,
arguments: HealthTrackerTypeEnum.bloodPressure,
);
},
),
SizedBox(height: 16.h),
buildHealthTrackerCard(
iconBgColor: AppColors.successColor,
icon: AppAssets.weightIcon,
title: "Weight".needTranslation,
description: "Track your weight progress, set goals, and maintain a healthy body mass for overall wellness.".needTranslation,
onTap: () {
context.navigateWithName(
AppRoutes.healthTrackerDetailPage,
arguments: HealthTrackerTypeEnum.weightTracker,
);
},
),
],
).paddingSymmetrical(20.w, 24.h),
);
}
}

@ -0,0 +1,271 @@
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_export.dart';
import 'package:hmg_patient_app_new/core/enums.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/presentation/health_trackers/health_trackers_view_model.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'package:provider/provider.dart';
import 'package:shimmer/shimmer.dart';
class TrackerLastValueCard extends StatelessWidget {
final HealthTrackerTypeEnum trackerType;
const TrackerLastValueCard({super.key, required this.trackerType});
/// Get status text and color based on blood sugar value
(String status, Color color, Color bgColor) _getBloodSugarStatus(double value) {
if (value < 70) {
return ('Low'.needTranslation, AppColors.errorColor, AppColors.errorColor.withValues(alpha: 0.5));
} else if (value <= 100) {
return ('Normal'.needTranslation, AppColors.successColor, AppColors.successLightBgColor);
} else if (value <= 125) {
return ('Pre-diabetic'.needTranslation, AppColors.ratingColorYellow, AppColors.errorColor.withValues(alpha: 0.4));
} else {
return ('High'.needTranslation, AppColors.errorColor, AppColors.errorColor.withValues(alpha: 0.4));
}
}
/// Get status text and color based on blood pressure value (systolic)
(String status, Color color, Color bgColor) _getBloodPressureStatus(int systolic) {
if (systolic < 90) {
return ('Low'.needTranslation, AppColors.errorColor, AppColors.errorColor.withValues(alpha: 0.5));
} else if (systolic <= 120) {
return ('Normal'.needTranslation, AppColors.successColor, AppColors.successLightBgColor);
} else if (systolic <= 140) {
return ('Elevated'.needTranslation, AppColors.ratingColorYellow, AppColors.errorColor.withValues(alpha: 0.4));
} else {
return ('High'.needTranslation, AppColors.errorColor, AppColors.errorColor.withValues(alpha: 0.4));
}
}
/// Get status for weight (neutral - no good/bad status)
(String status, Color color, Color bgColor) _getWeightStatus() {
return ('Recorded'.needTranslation, AppColors.successColor, AppColors.successLightBgColor);
}
/// Get default unit based on tracker type
String _getDefaultUnit() {
switch (trackerType) {
case HealthTrackerTypeEnum.bloodSugar:
return 'mg/dL';
case HealthTrackerTypeEnum.bloodPressure:
return 'mmHg';
case HealthTrackerTypeEnum.weightTracker:
return 'kg';
}
}
@override
Widget build(BuildContext context) {
return Consumer<HealthTrackersViewModel>(
builder: (context, viewModel, child) {
// Get the last record based on tracker type
dynamic lastRecord;
String displayValue = '--';
String unit = _getDefaultUnit();
DateTime? lastDate;
String status = '';
Color statusColor = AppColors.greyTextColor;
switch (trackerType) {
case HealthTrackerTypeEnum.bloodSugar:
final allResults = [
...viewModel.weekDiabeticPatientResult,
...viewModel.monthDiabeticPatientResult,
...viewModel.yearDiabeticPatientResult,
];
if (allResults.isNotEmpty) {
allResults.sort((a, b) {
final dateA = a.dateChart ?? DateTime(1900);
final dateB = b.dateChart ?? DateTime(1900);
return dateB.compareTo(dateA);
});
lastRecord = allResults.first;
final lastValue = lastRecord.resultValue?.toDouble() ?? 0.0;
displayValue = lastValue.toStringAsFixed(0);
unit = lastRecord.unit ?? 'mg/dL';
lastDate = lastRecord.dateChart;
final (s, c, _) = _getBloodSugarStatus(lastValue);
status = s;
statusColor = c;
}
break;
case HealthTrackerTypeEnum.bloodPressure:
final allResults = [
...viewModel.weekBloodPressureResult,
...viewModel.monthBloodPressureResult,
...viewModel.yearBloodPressureResult,
];
if (allResults.isNotEmpty) {
allResults.sort((a, b) {
final dateA = a.bloodPressureDate ?? DateTime(1900);
final dateB = b.bloodPressureDate ?? DateTime(1900);
return dateB.compareTo(dateA);
});
lastRecord = allResults.first;
final systolic = lastRecord.systolicePressure ?? 0;
final diastolic = lastRecord.diastolicPressure ?? 0;
displayValue = '$systolic/$diastolic';
unit = 'mmHg';
lastDate = lastRecord.bloodPressureDate;
final (s, c, _) = _getBloodPressureStatus(systolic);
status = s;
statusColor = c;
}
break;
case HealthTrackerTypeEnum.weightTracker:
final allResults = [
...viewModel.weekWeightMeasurementResult,
...viewModel.monthWeightMeasurementResult,
...viewModel.yearWeightMeasurementResult,
];
if (allResults.isNotEmpty) {
allResults.sort((a, b) {
final dateA = a.weightDate ?? DateTime(1900);
final dateB = b.weightDate ?? DateTime(1900);
return dateB.compareTo(dateA);
});
lastRecord = allResults.first;
final weightValue = lastRecord.weightMeasured?.toDouble() ?? 0.0;
displayValue = weightValue.toStringAsFixed(0);
unit = lastRecord.unit ?? 'kg';
lastDate = lastRecord.weightDate;
final (s, c, _) = _getWeightStatus();
status = s;
statusColor = c;
}
break;
}
final formattedDate = lastDate != null ? DateFormat('EEE DD MMM, yy').format(lastDate) : DateFormat('EEE DD MMM, yy').format(DateTime.now());
// Show shimmer while loading
if (viewModel.isLoading) {
return Container(
margin: EdgeInsets.symmetric(horizontal: 24.w),
padding: EdgeInsets.all(16.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: true,
),
child: Shimmer.fromColors(
baseColor: AppColors.shimmerBaseColor,
highlightColor: AppColors.shimmerHighlightColor,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: 40.h,
width: 120.w,
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.circular(8.r),
),
),
SizedBox(height: 8.h),
Row(
children: [
Container(
height: 32.h,
width: 150.w,
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.circular(16.r),
),
),
SizedBox(width: 8.w),
Container(
height: 32.h,
width: 80.w,
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.circular(16.r),
),
),
],
),
],
),
),
);
}
// Show empty state if no records
if (lastRecord == null) {
return Container(
margin: EdgeInsets.symmetric(horizontal: 24.w),
padding: EdgeInsets.all(16.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: true,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
"--".toText32(isBold: true, color: AppColors.greyTextColor),
SizedBox(width: 6.w),
unit.toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500).paddingOnly(top: 8.h),
],
),
SizedBox(height: 8.h),
AppCustomChipWidget(
labelText: "No records yet".needTranslation,
icon: AppAssets.doctor_calendar_icon,
),
],
),
);
}
return Container(
margin: EdgeInsets.symmetric(horizontal: 24.w),
padding: EdgeInsets.all(16.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: true,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
displayValue.toText32(isBold: true, color: statusColor),
SizedBox(width: 6.w),
unit.toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500).paddingOnly(top: 8.h),
],
),
SizedBox(height: 8.h),
Row(
children: [
AppCustomChipWidget(
labelText: "${"Last Record".needTranslation}: $formattedDate",
icon: AppAssets.doctor_calendar_icon,
),
SizedBox(width: 8.w),
if (trackerType != HealthTrackerTypeEnum.weightTracker) ...[
AppCustomChipWidget(
labelText: status.needTranslation,
icon: AppAssets.normalStatusGreenIcon,
iconColor: statusColor,
),
]
],
),
],
),
);
},
);
}
}

@ -45,12 +45,13 @@ class ServicesPage extends StatelessWidget {
late final List<HmgServicesComponentModel> hmgServices = [
HmgServicesComponentModel(
11,
"Emergency Services".needTranslation,
"".needTranslation,
AppAssets.emergency_services_icon,
bgColor: AppColors.primaryRedColor,
true, route: null, onTap: () {
11,
"Emergency Services".needTranslation,
"".needTranslation,
AppAssets.emergency_services_icon,
bgColor: AppColors.primaryRedColor,
true,
route: null, onTap: () {
getIt.get<EmergencyServicesViewModel>().flushData();
getIt.get<EmergencyServicesViewModel>().getTransportationOrders(
showLoader: false,
@ -83,6 +84,16 @@ class ServicesPage extends StatelessWidget {
true,
route: AppRoutes.comprehensiveCheckupPage,
),
HmgServicesComponentModel(
11,
"Indoor Navigation".needTranslation,
"".needTranslation,
AppAssets.indoor_nav_icon,
bgColor: Color(0xff45A2F8),
true,
route: null,
onTap: () {},
),
HmgServicesComponentModel(
11,
"E-Referral Services".needTranslation,
@ -162,6 +173,15 @@ class ServicesPage extends StatelessWidget {
];
late final List<HmgServicesComponentModel> hmgHealthToolServices = [
HmgServicesComponentModel(
11,
"Health Trackers".needTranslation,
"".needTranslation,
AppAssets.general_health,
bgColor: AppColors.whiteColor,
true,
route: AppRoutes.healthTrackersPage,
),
HmgServicesComponentModel(
11,
"Daily Water Monitor".needTranslation,
@ -179,14 +199,14 @@ class ServicesPage extends StatelessWidget {
LoaderBottomSheet.hideLoader();
if (userDetail == null) {
waterMonitorVM.populateFromAuthenticatedUser();
context.navigateWithName(AppRoutes.waterMonitorSettingsScreen);
context.navigateWithName(AppRoutes.waterMonitorSettingsPage);
} else {
context.navigateWithName(AppRoutes.waterConsumptionScreen);
context.navigateWithName(AppRoutes.waterConsumptionPage);
}
},
onError: (error) {
LoaderBottomSheet.hideLoader();
context.navigateWithName(AppRoutes.waterConsumptionScreen);
context.navigateWithName(AppRoutes.waterConsumptionPage);
},
);
},

@ -192,7 +192,7 @@ class _LandingPageState extends State<LandingPage> {
),
);
}),
Utils.buildSvgWithAssets(icon: AppAssets.search_icon, height: 18.h, width: 18.h).onPress(() {
Utils.buildSvgWithAssets(icon: AppAssets.indoor_nav_icon, height: 18.h, width: 18.h).onPress(() {
// Navigator.of(context).push(
// CustomPageRoute(
// page: MedicalFilePage(),
@ -537,7 +537,7 @@ class _LandingPageState extends State<LandingPage> {
width: double.infinity,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true),
child: Padding(
padding: EdgeInsets.all(12.h),
padding: EdgeInsets.all(16.h),
child: Column(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h),

@ -9,10 +9,11 @@ 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/cache_consts.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/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/size_config.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/route_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
@ -243,9 +244,9 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w),
onChipTap: () {
navigationService.pushPage(
page: FamilyMedicalScreen(
profiles: medicalFileViewModel.patientFamilyFiles,
onSelect: (FamilyFileResponseModelLists p1) {},
page: FamilyMedicalScreen(
profiles: medicalFileViewModel.patientFamilyFiles,
onSelect: (FamilyFileResponseModelLists p1) {},
),
);
},
@ -288,7 +289,8 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
iconColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor,
textColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor,
iconSize: 12.w,
backgroundColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.successColor.withOpacity(0.1),
backgroundColor:
insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.successColor.withOpacity(0.1),
labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w),
);
}),
@ -390,9 +392,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
width: hmgServicesVM.vitalSignCurrentPage == index ? 24.w : 8.w,
height: 8.h,
decoration: BoxDecoration(
color: hmgServicesVM.vitalSignCurrentPage == index
? AppColors.primaryRedColor
: AppColors.dividerColor,
color: hmgServicesVM.vitalSignCurrentPage == index ? AppColors.primaryRedColor : AppColors.dividerColor,
borderRadius: BorderRadius.circular(4.r),
),
),
@ -596,7 +596,8 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
? Container(
padding: EdgeInsets.all(12.w),
width: MediaQuery.of(context).size.width,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, hasShadow: false),
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, hasShadow: false),
child: Column(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h),
@ -633,57 +634,58 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
itemCount: myAppointmentsVM.patientAppointmentsHistoryList.length,
itemBuilder: (context, index) {
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
horizontalOffset: 100.0,
child: FadeInAnimation(
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
child: MedicalFileAppointmentCard(
patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index],
myAppointmentsViewModel: myAppointmentsViewModel,
onRescheduleTap: () {
openDoctorScheduleCalendar(myAppointmentsVM.patientAppointmentsHistoryList[index]);
},
onAskDoctorTap: () async {
LoaderBottomSheet.showLoader(loadingText: "Checking doctor availability...".needTranslation);
await myAppointmentsViewModel.isDoctorAvailable(
projectID: myAppointmentsVM.patientAppointmentsHistoryList[index].projectID,
doctorId: myAppointmentsVM.patientAppointmentsHistoryList[index].doctorID,
clinicId: myAppointmentsVM.patientAppointmentsHistoryList[index].clinicID,
onSuccess: (value) async {
if (value) {
await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) {
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
horizontalOffset: 100.0,
child: FadeInAnimation(
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
child: MedicalFileAppointmentCard(
patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index],
myAppointmentsViewModel: myAppointmentsViewModel,
onRescheduleTap: () {
openDoctorScheduleCalendar(myAppointmentsVM.patientAppointmentsHistoryList[index]);
},
onAskDoctorTap: () async {
LoaderBottomSheet.showLoader(loadingText: "Checking doctor availability...".needTranslation);
await myAppointmentsViewModel.isDoctorAvailable(
projectID: myAppointmentsVM.patientAppointmentsHistoryList[index].projectID,
doctorId: myAppointmentsVM.patientAppointmentsHistoryList[index].doctorID,
clinicId: myAppointmentsVM.patientAppointmentsHistoryList[index].clinicID,
onSuccess: (value) async {
if (value) {
await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
title: LocaleKeys.askDoctor.tr(context: context),
child: AskDoctorRequestTypeSelect(
askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList,
myAppointmentsViewModel: myAppointmentsViewModel,
patientAppointmentHistoryResponseModel:
myAppointmentsVM.patientAppointmentsHistoryList[index],
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
title: LocaleKeys.askDoctor.tr(context: context),
child: AskDoctorRequestTypeSelect(
askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList,
myAppointmentsViewModel: myAppointmentsViewModel,
patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index],
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else {
print("Doctor is not available");
}
},
onError: (_) {
LoaderBottomSheet.hideLoader();
print("Doctor is not available");
}
},
onError: (_) {
LoaderBottomSheet.hideLoader();
},
);
},
},
);
},
),
),
),
),
));
));
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 12.h),
),
@ -742,116 +744,125 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
child: Column(
children: [
ListView.separated(
itemCount: prescriptionVM.patientPrescriptionOrders.length <= 2 ? prescriptionVM.patientPrescriptionOrders.length : 2,
itemCount:
prescriptionVM.patientPrescriptionOrders.length <= 2 ? prescriptionVM.patientPrescriptionOrders.length : 2,
shrinkWrap: true,
padding: EdgeInsets.only(left: 0, right: 8.w),
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: Row(
children: [
Image.network(
prescriptionVM.patientPrescriptionOrders[index].doctorImageURL!,
width: 40.w,
height: 40.h,
fit: BoxFit.cover,
).circle(100.r),
SizedBox(width: 16.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
prescriptionVM.patientPrescriptionOrders[index].doctorName!.toText16(isBold: true),
SizedBox(height: 4.h),
Wrap(
direction: Axis.horizontal,
spacing: 3.w,
runSpacing: 4.w,
children: [
AppCustomChipWidget(labelText: prescriptionVM.patientPrescriptionOrders[index].clinicDescription!),
AppCustomChipWidget(
icon: AppAssets.doctor_calendar_icon,
labelText: DateUtil.formatDateToDate(
DateUtil.convertStringToDate(prescriptionVM.patientPrescriptionOrders[index].appointmentDate),
false,
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: Row(
children: [
Image.network(
prescriptionVM.patientPrescriptionOrders[index].doctorImageURL!,
width: 40.w,
height: 40.h,
fit: BoxFit.cover,
).circle(100.r),
SizedBox(width: 16.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
prescriptionVM.patientPrescriptionOrders[index].doctorName!.toText16(isBold: true),
SizedBox(height: 4.h),
Wrap(
direction: Axis.horizontal,
spacing: 3.w,
runSpacing: 4.w,
children: [
AppCustomChipWidget(
labelText: prescriptionVM.patientPrescriptionOrders[index].clinicDescription!),
AppCustomChipWidget(
icon: AppAssets.doctor_calendar_icon,
labelText: DateUtil.formatDateToDate(
DateUtil.convertStringToDate(
prescriptionVM.patientPrescriptionOrders[index].appointmentDate),
false,
),
),
),
],
),
],
],
),
],
),
),
),
// SizedBox(width: 40.h),
Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon_small, width: 15.w, height: 15.h, fit: BoxFit.contain, iconColor: AppColors.textColor)),
],
).onPress(() {
prescriptionVM.setPrescriptionsDetailsLoading();
Navigator.of(context).push(
CustomPageRoute(
page: PrescriptionDetailPage(isFromAppointments: false, prescriptionsResponseModel: prescriptionVM.patientPrescriptionOrders[index]),
),
);
}),
),
));
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
),
SizedBox(height: 16.h),
const Divider(color: AppColors.dividerColor),
SizedBox(height: 16.h),
Row(
children: [
Expanded(
child: CustomButton(
text: "All Prescriptions".needTranslation,
onPressed: () {
Navigator.of(context).push(
CustomPageRoute(
page: PrescriptionsListPage(),
),
);
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 12.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
height: 40.h,
icon: AppAssets.requests,
iconColor: AppColors.primaryRedColor,
iconSize: 16.w,
),
// SizedBox(width: 40.h),
Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon_small,
width: 15.w,
height: 15.h,
fit: BoxFit.contain,
iconColor: AppColors.textColor)),
],
).onPress(() {
prescriptionVM.setPrescriptionsDetailsLoading();
Navigator.of(context).push(
CustomPageRoute(
page: PrescriptionDetailPage(
isFromAppointments: false,
prescriptionsResponseModel: prescriptionVM.patientPrescriptionOrders[index]),
),
);
}),
),
));
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
),
SizedBox(height: 16.h),
const Divider(color: AppColors.dividerColor),
SizedBox(height: 16.h),
Row(
children: [
Expanded(
child: CustomButton(
text: "All Prescriptions".needTranslation,
onPressed: () {
Navigator.of(context).push(
CustomPageRoute(
page: PrescriptionsListPage(),
),
);
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 12.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
height: 40.h,
icon: AppAssets.requests,
iconColor: AppColors.primaryRedColor,
iconSize: 16.w,
),
SizedBox(width: 6.w),
Expanded(
child: CustomButton(
text: "All Medications".needTranslation,
onPressed: () {},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 12.f,
fontWeight: FontWeight.w500,
borderRadius: 12.h,
height: 40.h,
icon: AppAssets.all_medications_icon,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
),
),
SizedBox(width: 6.w),
Expanded(
child: CustomButton(
text: "All Medications".needTranslation,
onPressed: () {},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 12.f,
fontWeight: FontWeight.w500,
borderRadius: 12.h,
height: 40.h,
icon: AppAssets.all_medications_icon,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
),
],
),
],
),
],
),
],
),
),
).paddingSymmetrical(0.w, 0.h)
@ -905,7 +916,10 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
fit: BoxFit.cover,
).circle(100).toShimmer2(isShow: true, radius: 50.r),
SizedBox(height: 8.h),
("Dr. John Smith Smith Smith").toString().toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2).toShimmer2(isShow: true),
("Dr. John Smith Smith Smith")
.toString()
.toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2)
.toShimmer2(isShow: true),
],
)
: myAppointmentsVM.patientMyDoctorsList.isEmpty
@ -932,58 +946,58 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
shrinkWrap: true,
itemBuilder: (context, index) {
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 1000),
child: SlideAnimation(
horizontalOffset: 100.0,
child: FadeInAnimation(
child: SizedBox(
// width: 80.w,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.network(
myAppointmentsVM.patientMyDoctorsList[index].doctorImageURL!,
width: 64.w,
height: 64.h,
fit: BoxFit.cover,
).circle(100).toShimmer2(isShow: false, radius: 50.r),
SizedBox(height: 8.h),
Expanded(
child: (myAppointmentsVM.patientMyDoctorsList[index].doctorName)
.toString()
.toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2)
.toShimmer2(isShow: false),
),
],
),
).onPress(() async {
bookAppointmentsViewModel.setSelectedDoctor(DoctorsListResponseModel(
clinicID: myAppointmentsVM.patientMyDoctorsList[index].clinicID,
projectID: myAppointmentsVM.patientMyDoctorsList[index].projectID,
doctorID: myAppointmentsVM.patientMyDoctorsList[index].doctorID,
));
LoaderBottomSheet.showLoader();
await bookAppointmentsViewModel.getDoctorProfile(onSuccess: (dynamic respData) {
LoaderBottomSheet.hideLoader();
Navigator.of(context).push(
CustomPageRoute(
page: DoctorProfilePage(),
),
);
}, onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
}),
),
));
position: index,
duration: const Duration(milliseconds: 1000),
child: SlideAnimation(
horizontalOffset: 100.0,
child: FadeInAnimation(
child: SizedBox(
// width: 80.w,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.network(
myAppointmentsVM.patientMyDoctorsList[index].doctorImageURL!,
width: 64.w,
height: 64.h,
fit: BoxFit.cover,
).circle(100).toShimmer2(isShow: false, radius: 50.r),
SizedBox(height: 8.h),
Expanded(
child: (myAppointmentsVM.patientMyDoctorsList[index].doctorName)
.toString()
.toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2)
.toShimmer2(isShow: false),
),
],
),
).onPress(() async {
bookAppointmentsViewModel.setSelectedDoctor(DoctorsListResponseModel(
clinicID: myAppointmentsVM.patientMyDoctorsList[index].clinicID,
projectID: myAppointmentsVM.patientMyDoctorsList[index].projectID,
doctorID: myAppointmentsVM.patientMyDoctorsList[index].doctorID,
));
LoaderBottomSheet.showLoader();
await bookAppointmentsViewModel.getDoctorProfile(onSuccess: (dynamic respData) {
LoaderBottomSheet.hideLoader();
Navigator.of(context).push(
CustomPageRoute(
page: DoctorProfilePage(),
),
);
}, onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
}),
),
));
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 8.h),
),
@ -1092,9 +1106,14 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}",
onPressed: () {
insuranceViewModel.setIsInsuranceUpdateDetailsLoading(true);
insuranceViewModel.getPatientInsuranceDetailsForUpdate(
appState.getAuthenticatedUser()!.patientId.toString(), appState.getAuthenticatedUser()!.patientIdentificationNo.toString());
showCommonBottomSheetWithoutHeight(context, child: PatientInsuranceCardUpdateCard(), callBackFunc: () {}, title: "", isCloseButtonVisible: false, isFullScreen: false);
insuranceViewModel.getPatientInsuranceDetailsForUpdate(appState.getAuthenticatedUser()!.patientId.toString(),
appState.getAuthenticatedUser()!.patientIdentificationNo.toString());
showCommonBottomSheetWithoutHeight(context,
child: PatientInsuranceCardUpdateCard(),
callBackFunc: () {},
title: "",
isCloseButtonVisible: false,
isFullScreen: false);
},
backgroundColor: AppColors.bgGreenColor.withOpacity(0.20),
borderColor: AppColors.bgGreenColor.withOpacity(0.0),
@ -1298,7 +1317,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
svgIcon: AppAssets.blood_sugar_icon,
isLargeText: true,
iconSize: 36.w,
).onPress(() {}),
).onPress(() => context.navigateWithName(AppRoutes.healthTrackerDetailPage, arguments: HealthTrackerTypeEnum.bloodSugar)),
MedicalFileCard(
label: "Blood Pressure".needTranslation,
textColor: AppColors.blackColor,
@ -1306,7 +1325,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
svgIcon: AppAssets.lab_result_icon,
isLargeText: true,
iconSize: 36.w,
).onPress(() {}),
).onPress(() => context.navigateWithName(AppRoutes.healthTrackerDetailPage, arguments: HealthTrackerTypeEnum.bloodPressure)),
MedicalFileCard(
label: "Weight Tracker".needTranslation,
textColor: AppColors.blackColor,
@ -1314,7 +1333,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
svgIcon: AppAssets.weight_tracker_icon,
isLargeText: true,
iconSize: 36.w,
).onPress(() {}),
).onPress(() => context.navigateWithName(AppRoutes.healthTrackerDetailPage, arguments: HealthTrackerTypeEnum.weightTracker)),
],
).paddingSymmetrical(0.w, 0.0),
SizedBox(height: 16.h),
@ -1563,7 +1582,6 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
],
),
SizedBox(height: 14.h),
Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 6.h),
decoration: BoxDecoration(
@ -1601,7 +1619,6 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
),
),
SizedBox(height: 8.h),
Align(
alignment: AlignmentDirectional.centerEnd,
child: Utils.buildSvgWithAssets(
@ -1619,6 +1636,3 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
);
}
}

@ -10,9 +10,11 @@ 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/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/interactive_body_widget.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:provider/provider.dart';
class OrganSelectorPage extends StatefulWidget {
@ -24,25 +26,38 @@ class OrganSelectorPage extends StatefulWidget {
class _OrganSelectorPageState extends State<OrganSelectorPage> {
late final AppState _appState;
late final DialogService dialogService;
@override
void initState() {
super.initState();
_appState = getIt.get<AppState>();
dialogService = getIt<DialogService>();
}
void _onNextPressed(SymptomsCheckerViewModel viewModel) {
void _onNextPressed(SymptomsCheckerViewModel viewModel) async {
if (!viewModel.validateSelection()) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Please select at least one organ'.needTranslation),
backgroundColor: AppColors.errorColor,
),
dialogService.showErrorBottomSheet(
message: 'Please select at least one organ'.needTranslation,
);
return;
}
LoaderBottomSheet.showLoader(loadingText: "Please wait".needTranslation);
final String userName = 'guest_user';
final String password = '123456';
context.navigateWithName(AppRoutes.symptomsSelectorScreen);
await viewModel.getSymptomsUserDetails(
userName: userName,
password: password,
onSuccess: () {
LoaderBottomSheet.hideLoader();
context.navigateWithName(AppRoutes.symptomsSelectorPage);
},
onError: (String error) {
LoaderBottomSheet.hideLoader();
},
);
}
@override

@ -11,6 +11,7 @@ import 'package:hmg_patient_app_new/features/symptoms_checker/models/conditions_
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/condition_card.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
@ -18,8 +19,8 @@ import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:provider/provider.dart';
import 'package:shimmer/shimmer.dart';
class PossibleConditionsScreen extends StatelessWidget {
const PossibleConditionsScreen({super.key});
class PossibleConditionsPage extends StatelessWidget {
const PossibleConditionsPage({super.key});
Widget _buildLoadingShimmer() {
return ListView.separated(
@ -44,7 +45,7 @@ class PossibleConditionsScreen extends StatelessWidget {
);
}
Widget _buildPredictionsList(List<ConditionsModel> conditions) {
Widget _buildPredictionsList(BuildContext context, List<ConditionsModel> conditions) {
if (conditions.isEmpty) {
return Center(
child: Padding(
@ -60,6 +61,8 @@ class PossibleConditionsScreen extends StatelessWidget {
);
}
final dialogService = getIt<DialogService>();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -82,11 +85,8 @@ class PossibleConditionsScreen extends StatelessWidget {
description: conditionModel.description,
possibleConditionsSeverityEnum: conditionModel.possibleConditionsSeverityEnum,
onActionPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('We are not available for a week. May you Rest In Peace :('),
backgroundColor: AppColors.primaryRedColor,
),
dialogService.showErrorBottomSheet(
message: 'We are not available for a week. May you Rest In Peace :(',
);
},
);
@ -168,7 +168,7 @@ class PossibleConditionsScreen extends StatelessWidget {
if (symptomsCheckerViewModel.isPossibleConditionsLoading || symptomsCheckerViewModel.isPossibleConditionsLoading) {
return _buildLoadingShimmer();
}
return _buildPredictionsList(dummyConditions);
return _buildPredictionsList(context, dummyConditions);
},
),
),

@ -2,11 +2,13 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/route_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
@ -20,9 +22,12 @@ class RiskFactorsScreen extends StatefulWidget {
}
class _RiskFactorsScreenState extends State<RiskFactorsScreen> {
late DialogService dialogService;
@override
void initState() {
super.initState();
dialogService = getIt<DialogService>();
// Fetch risk factors based on selected symptoms
WidgetsBinding.instance.addPostFrameCallback((_) {
final viewModel = context.read<SymptomsCheckerViewModel>();
@ -36,13 +41,10 @@ class _RiskFactorsScreenState extends State<RiskFactorsScreen> {
void _onNextPressed(SymptomsCheckerViewModel viewModel) {
if (viewModel.hasSelectedRiskFactors) {
context.navigateWithName(AppRoutes.suggestionsScreen);
context.navigateWithName(AppRoutes.suggestionsPage);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Please select at least one risk before proceeding'.needTranslation),
backgroundColor: AppColors.errorColor,
),
dialogService.showErrorBottomSheet(
message: 'Please select at least one risk before proceeding'.needTranslation,
);
}
}

@ -1,11 +1,13 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/route_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
@ -19,9 +21,12 @@ class SuggestionsScreen extends StatefulWidget {
}
class _SuggestionsScreenState extends State<SuggestionsScreen> {
late DialogService dialogService;
@override
void initState() {
super.initState();
dialogService = getIt<DialogService>();
// Initialize symptom groups based on selected organs
WidgetsBinding.instance.addPostFrameCallback((_) {
final viewModel = context.read<SymptomsCheckerViewModel>();
@ -38,13 +43,10 @@ class _SuggestionsScreenState extends State<SuggestionsScreen> {
void _onNextPressed(SymptomsCheckerViewModel viewModel) {
if (viewModel.hasSelectedSuggestions) {
// Navigate to triage screen
context.navigateWithName(AppRoutes.triageScreen);
context.navigateWithName(AppRoutes.triagePage);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Please select at least one option before proceeding'.needTranslation),
backgroundColor: AppColors.errorColor,
),
dialogService.showErrorBottomSheet(
message: 'Please select at least one option before proceeding'.needTranslation,
);
}
}

@ -1,6 +1,7 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/route_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
@ -8,6 +9,7 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/body_symptom_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
@ -15,17 +17,20 @@ import 'package:hmg_patient_app_new/widgets/chip/custom_selectable_chip.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:provider/provider.dart';
class SymptomsSelectorScreen extends StatefulWidget {
const SymptomsSelectorScreen({super.key});
class SymptomsSelectorPage extends StatefulWidget {
const SymptomsSelectorPage({super.key});
@override
State<SymptomsSelectorScreen> createState() => _SymptomsSelectorScreenState();
State<SymptomsSelectorPage> createState() => _SymptomsSelectorPageState();
}
class _SymptomsSelectorScreenState extends State<SymptomsSelectorScreen> {
class _SymptomsSelectorPageState extends State<SymptomsSelectorPage> {
late DialogService dialogService;
@override
void initState() {
super.initState();
dialogService = getIt<DialogService>();
// Initialize symptom groups based on selected organs
WidgetsBinding.instance.addPostFrameCallback((_) {
final viewModel = context.read<SymptomsCheckerViewModel>();
@ -36,13 +41,10 @@ class _SymptomsSelectorScreenState extends State<SymptomsSelectorScreen> {
void _onNextPressed(SymptomsCheckerViewModel viewModel) {
if (viewModel.hasSelectedSymptoms) {
// Navigate to triage screen
context.navigateWithName(AppRoutes.riskFactorsScreen);
context.navigateWithName(AppRoutes.riskFactorsPage);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Please select at least one symptom before proceeding'.needTranslation),
backgroundColor: AppColors.errorColor,
),
dialogService.showErrorBottomSheet(
message: 'Please select at least one symptom before proceeding'.needTranslation,
);
}
}

@ -1,89 +1,213 @@
import 'dart:developer';
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_export.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/route_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/data/triage_questions_data.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/triage_question_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/triage_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/custom_progress_bar.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:lottie/lottie.dart';
import 'package:provider/provider.dart';
class TriageScreen extends StatefulWidget {
const TriageScreen({super.key});
class TriagePage extends StatefulWidget {
const TriagePage({super.key});
@override
State<TriageScreen> createState() => _TriageScreenState();
State<TriagePage> createState() => _TriagePageState();
}
class _TriageScreenState extends State<TriageScreen> {
late List<TriageQuestionModel> triageQuestions;
int currentQuestionIndex = 0;
class _TriagePageState extends State<TriagePage> {
late SymptomsCheckerViewModel viewModel;
late DialogService dialogService;
@override
void initState() {
super.initState();
triageQuestions = TriageQuestionsData.getSampleTriageQuestions();
viewModel = context.read<SymptomsCheckerViewModel>();
dialogService = getIt.get<DialogService>();
// Start triage process when screen loads
WidgetsBinding.instance.addPostFrameCallback((_) {
_startTriage();
});
}
TriageQuestionModel get currentQuestion => triageQuestions[currentQuestionIndex];
void _startTriage() {
viewModel.startOrContinueTriage(
onSuccess: () {
_handleTriageResponse();
},
onError: (error) {
dialogService.showErrorBottomSheet(
message: error,
onOkPressed: () => context.pop(),
);
},
);
}
bool get isFirstQuestion => currentQuestionIndex == 0;
void _handleTriageResponse() {
// Case 1: Emergency evidence detected
if (viewModel.hasEmergencyEvidence) {
_showEmergencyDialog();
return;
}
bool get isLastQuestion => currentQuestionIndex == triageQuestions.length - 1;
// Get the highest probability condition
final conditions = viewModel.currentConditions ?? [];
double highestProbability = 0.0;
void _onOptionSelected(int optionIndex) {
setState(() {
currentQuestion.selectOption(optionIndex);
});
if (conditions.isNotEmpty) {
final sortedConditions = List<TriageCondition>.from(conditions);
sortedConditions.sort((a, b) => (b.probability ?? 0.0).compareTo(a.probability ?? 0.0));
highestProbability = (sortedConditions.first.probability ?? 0.0) * 100;
}
// Case 2: Should stop flag is true OR Case 3: Probability >= 70% OR Case 4: 7 or more questions answered
if (viewModel.shouldStopTriage || highestProbability >= 70.0 || viewModel.triageQuestionCount >= 7) {
// Navigate to results/possible conditions screen
context.navigateWithName(AppRoutes.possibleConditionsPage);
return;
}
// Continue triage - question is loaded, reset selection for new question
viewModel.resetTriageChoice();
}
void _showEmergencyDialog() {
showCommonBottomSheetWithoutHeight(
context,
child: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.primaryRedColor,
borderRadius: 24.h,
),
child: Padding(
padding: EdgeInsets.all(24.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"".toText14(),
Utils.buildSvgWithAssets(
icon: AppAssets.cancel_circle_icon,
iconColor: AppColors.whiteColor,
width: 24.h,
height: 24.h,
fit: BoxFit.contain,
).onPress(() {
Navigator.of(context).pop();
}),
],
),
Lottie.asset(AppAnimations.ambulanceAlert,
repeat: false, reverse: false, frameRate: FrameRate(60), width: 120.h, height: 120.h, fit: BoxFit.contain),
SizedBox(height: 8.h),
"Emergency".needTranslation.toText28(color: AppColors.whiteColor, isBold: true),
SizedBox(height: 8.h),
"Emergency evidence detected. Please seek medical attention."
.needTranslation
.toText14(color: AppColors.whiteColor, weight: FontWeight.w500),
SizedBox(height: 24.h),
CustomButton(
text: LocaleKeys.confirm.tr(context: context),
onPressed: () async => Navigator.of(context).pop(),
backgroundColor: AppColors.whiteColor,
borderColor: AppColors.whiteColor,
textColor: AppColors.primaryRedColor,
icon: AppAssets.checkmark_icon,
iconColor: AppColors.primaryRedColor,
),
SizedBox(height: 8.h),
],
),
),
),
isFullScreen: false,
isCloseButtonVisible: false,
hasBottomPadding: false,
backgroundColor: AppColors.primaryRedColor,
callBackFunc: () {},
);
}
bool get isFirstQuestion => viewModel.getTriageEvidence().isEmpty;
void _onOptionSelectedForItem(String itemId, int choiceIndex) {
viewModel.selectTriageChoiceForItem(itemId, choiceIndex);
}
void _onPreviousPressed() {
if (!isFirstQuestion) {
setState(() {
currentQuestionIndex--;
});
}
context.pop();
}
void _onNextPressed() {
if (currentQuestion.isAnswered) {
currentQuestion.confirmSelection();
if (isLastQuestion) {
context.navigateWithName(AppRoutes.possibleConditionsScreen);
} else {
setState(() {
currentQuestionIndex++;
});
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Please select an option before proceeding'.needTranslation),
backgroundColor: AppColors.errorColor,
),
final currentQuestion = viewModel.currentTriageQuestion;
if (currentQuestion?.items == null || currentQuestion!.items!.isEmpty) {
dialogService.showErrorBottomSheet(
message: 'No question items available'.needTranslation,
);
return;
}
}
_buildConfirmationBottomSheet({required BuildContext context, required VoidCallback onConfirm}) {
return showCommonBottomSheetWithoutHeight(
title: LocaleKeys.notice.tr(context: context),
context,
child: Utils.getWarningWidget(
loadingText: "Are you sure you want to restart the organ selection?".needTranslation,
isShowActionButtons: true,
onCancelTap: () => Navigator.pop(context),
onConfirmTap: () => onConfirm(),
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
// Check if all items have been answered
if (!viewModel.areAllTriageItemsAnswered) {
dialogService.showErrorBottomSheet(message: 'Please answer all questions before proceeding'.needTranslation);
return;
}
// Collect all evidence from all items
for (var item in currentQuestion.items!) {
final itemId = item.id ?? "";
if (itemId.isEmpty) continue;
final selectedChoiceIndex = viewModel.getTriageChoiceForItem(itemId);
if (selectedChoiceIndex == null) continue;
if (item.choices != null && selectedChoiceIndex < item.choices!.length) {
final selectedChoice = item.choices![selectedChoiceIndex];
final choiceId = selectedChoice.id ?? "";
if (choiceId.isNotEmpty) {
viewModel.addTriageEvidence(itemId, choiceId);
}
}
}
// Get all evidence: initial symptoms + risk factors + suggestions + triage evidence
List<String> initialEvidenceIds = viewModel.getAllEvidenceIds();
List<Map<String, String>> triageEvidence = viewModel.getTriageEvidence();
log("initialEvidenceIds: ${initialEvidenceIds.toString()}");
log("triageEvidence: ${triageEvidence.toString()}");
// Call API with updated evidence
viewModel.getDiagnosisForTriage(
age: viewModel.selectedAge!,
sex: viewModel.selectedGender!.toLowerCase(),
evidenceIds: initialEvidenceIds,
triageEvidence: triageEvidence,
language: viewModel.appState.isArabic() ? 'ar' : 'en',
onSuccess: (response) {
_handleTriageResponse();
},
onError: (error) {
dialogService.showErrorBottomSheet(message: error);
},
);
}
@ -91,45 +215,137 @@ class _TriageScreenState extends State<TriageScreen> {
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: Column(
children: [
Expanded(
child: CollapsingListView(
title: "Triage".needTranslation,
// onLeadingTapped: () => _buildConfirmationBottomSheet(
// context: context,
// onConfirm: () => {
// context.pop(),
// context.pop(),
// }),
leadingCallback: () => context.pop(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 16.h),
_buildQuestionCard(),
],
body: Consumer<SymptomsCheckerViewModel>(
builder: (context, viewModel, child) {
// Show normal question UI
return Column(
children: [
Expanded(
child: CollapsingListView(
title: "Triage".needTranslation,
leadingCallback: () => _showConfirmationBeforeExit(context),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 16.h),
_buildQuestionCard(viewModel),
],
),
),
),
_buildStickyBottomCard(context, viewModel),
],
);
},
),
);
}
Widget _buildLoadingShimmer() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 16.h),
// Create 2-3 shimmer cards
...List.generate(1, (index) {
return Padding(
padding: EdgeInsets.only(bottom: 16.h),
child: _buildShimmerCard(),
);
}),
],
);
}
Widget _buildShimmerCard() {
return Container(
width: double.infinity,
margin: EdgeInsets.symmetric(horizontal: 24.w),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r),
padding: EdgeInsets.symmetric(vertical: 24.h, horizontal: 16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Shimmer title
Container(
height: 40.h,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24.r),
),
).toShimmer2(isShow: true, radius: 24.r),
SizedBox(height: 16.h),
// Shimmer chips
Wrap(
runSpacing: 12.h,
spacing: 8.w,
children: List.generate(4, (index) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h),
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.circular(24.r),
border: Border.all(color: AppColors.bottomNAVBorder, width: 1),
),
child: Text(
'Not Applicable Suggestion',
style: TextStyle(fontSize: 14.f, color: AppColors.textColor),
),
).toShimmer2(isShow: true, radius: 24.r);
}),
),
_buildStickyBottomCard(context),
],
),
);
}
Widget _buildQuestionCard() {
void _showConfirmationBeforeExit(BuildContext context) {
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.notice.tr(context: context),
context,
child: Utils.getWarningWidget(
loadingText: "Are you sure you want to exit? Your progress will be lost.".needTranslation,
isShowActionButtons: true,
onCancelTap: () => Navigator.pop(context),
onConfirmTap: () {
Navigator.pop(context);
context.pop();
},
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
Widget _buildQuestionCard(SymptomsCheckerViewModel viewModel) {
if (viewModel.isTriageDiagnosisLoading) {
return _buildLoadingShimmer();
}
if (viewModel.currentTriageQuestion == null) {
return Center(
child: "No question available".needTranslation.toText16(weight: FontWeight.w500),
);
}
final question = viewModel.currentTriageQuestion;
if (question == null || question.items == null || question.items!.isEmpty) {
return SizedBox.shrink();
}
return AnimatedSwitcher(
duration: const Duration(milliseconds: 400),
transitionBuilder: (Widget child, Animation<double> animation) {
final offsetAnimation = Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).animate(CurvedAnimation(
parent: animation,
curve: Curves.easeInOut,
));
).animate(
CurvedAnimation(
parent: animation,
curve: Curves.easeInOut,
),
);
return SlideTransition(
position: offsetAnimation,
@ -140,7 +356,7 @@ class _TriageScreenState extends State<TriageScreen> {
);
},
child: Container(
key: ValueKey<int>(currentQuestionIndex),
key: ValueKey<String>(question.items!.first.id ?? viewModel.getTriageEvidence().length.toString()),
width: double.infinity,
margin: EdgeInsets.symmetric(horizontal: 24.w),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r),
@ -148,11 +364,36 @@ class _TriageScreenState extends State<TriageScreen> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
currentQuestion.question.toText16(weight: FontWeight.w500),
// Main question text
(question.text ?? "").toText16(weight: FontWeight.w600, color: AppColors.textColor),
SizedBox(height: 24.h),
...List.generate(currentQuestion.options.length, (index) {
bool selected = currentQuestion.selectedOptionIndex == index;
return _buildOptionItem(index, selected, currentQuestion.options[index].text);
// Show all items with dividers
...List.generate(question.items!.length, (itemIndex) {
final item = question.items![itemIndex];
final itemId = item.id ?? "";
final choices = item.choices ?? [];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Item name (sub-question)
(item.name ?? "").toText14(weight: FontWeight.w600, color: AppColors.textColor),
SizedBox(height: 8.h),
// Choices for this item
...List.generate(choices.length, (choiceIndex) {
bool selected = viewModel.getTriageChoiceForItem(itemId) == choiceIndex;
return _buildOptionItem(itemId, choiceIndex, selected, choices[choiceIndex].label ?? "");
}),
// Add divider between items (but not after the last one)
if (itemIndex < question.items!.length - 1) ...[
SizedBox(height: 8.h),
Divider(color: AppColors.bottomNAVBorder, thickness: 1),
SizedBox(height: 10.h),
],
],
);
}),
],
),
@ -160,9 +401,9 @@ class _TriageScreenState extends State<TriageScreen> {
);
}
Widget _buildOptionItem(int index, bool selected, String optionText) {
Widget _buildOptionItem(String itemId, int choiceIndex, bool selected, String optionText) {
return GestureDetector(
onTap: () => _onOptionSelected(index),
onTap: () => _onOptionSelectedForItem(itemId, choiceIndex),
child: Container(
margin: EdgeInsets.only(bottom: 12.h),
child: Row(
@ -181,16 +422,29 @@ class _TriageScreenState extends State<TriageScreen> {
child: selected ? Icon(Icons.check, size: 16.f, color: AppColors.whiteColor) : null,
),
SizedBox(width: 12.w),
Expanded(child: optionText.toText14(weight: FontWeight.w500)),
Expanded(child: optionText.toText13(weight: FontWeight.w500)),
],
),
),
);
}
Widget _buildStickyBottomCard(BuildContext context) {
final currentScore = TriageQuestionsData.calculateTotalScore(triageQuestions);
final suggestedCondition = TriageQuestionsData.getSuggestedCondition(currentScore);
Widget _buildStickyBottomCard(BuildContext context, SymptomsCheckerViewModel viewModel) {
// Get the top condition with highest probability
final conditions = viewModel.currentConditions ?? [];
String suggestedCondition = "Analyzing...";
double probability = 0.0;
if (conditions.isNotEmpty) {
// Sort by probability descending
final sortedConditions = List<TriageCondition>.from(conditions);
sortedConditions.sort((a, b) => (b.probability ?? 0.0).compareTo(a.probability ?? 0.0));
final topCondition = sortedConditions.first;
suggestedCondition = topCondition.commonName ?? topCondition.name ?? "Unknown";
probability = (topCondition.probability ?? 0.0) * 100; // Convert to percentage
}
// final bool isHighConfidence = probability >= 70.0;
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r),
@ -223,14 +477,14 @@ class _TriageScreenState extends State<TriageScreen> {
),
SizedBox(height: 16.h),
CustomRoundedProgressBar(
percentage: currentScore,
percentage: probability.toInt(),
paddingBetween: 5.h,
color: AppColors.primaryRedColor,
backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.17),
height: 8.h,
titleWidget: RichText(
text: TextSpan(
text: "$currentScore% ",
text: "${probability.toStringAsFixed(1)}% ",
style: TextStyle(
color: AppColors.primaryRedColor,
fontWeight: FontWeight.w600,
@ -249,6 +503,8 @@ class _TriageScreenState extends State<TriageScreen> {
),
),
),
// Show high confidence message
SizedBox(height: 12.h),
Row(
children: [
@ -256,7 +512,7 @@ class _TriageScreenState extends State<TriageScreen> {
child: CustomButton(
text: "Previous".needTranslation,
onPressed: isFirstQuestion ? () {} : _onPreviousPressed,
isDisabled: isFirstQuestion,
isDisabled: isFirstQuestion || viewModel.isTriageDiagnosisLoading,
backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.11),
borderColor: Colors.transparent,
textColor: AppColors.primaryRedColor,
@ -266,7 +522,8 @@ class _TriageScreenState extends State<TriageScreen> {
SizedBox(width: 12.w),
Expanded(
child: CustomButton(
text: isLastQuestion ? "Finish".needTranslation : "Next".needTranslation,
text: "Next".needTranslation,
isDisabled: viewModel.isTriageDiagnosisLoading,
onPressed: _onNextPressed,
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,

@ -3,6 +3,7 @@ import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/route_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
@ -90,8 +91,8 @@ class _UserInfoSelectionScreenState extends State<UserInfoSelectionScreen> {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
title.toText16(weight: FontWeight.w500),
subTitle.toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500),
title.toText14(weight: FontWeight.w500),
subTitle.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500),
],
),
],
@ -106,7 +107,7 @@ class _UserInfoSelectionScreenState extends State<UserInfoSelectionScreen> {
Widget _getDivider() {
return Divider(
color: AppColors.dividerColor,
).paddingSymmetrical(0, 16.h);
).paddingSymmetrical(0, 8.h);
}
@override
@ -114,8 +115,25 @@ class _UserInfoSelectionScreenState extends State<UserInfoSelectionScreen> {
AppState appState = getIt.get<AppState>();
String name = "";
int? userAgeFromDOB;
if (appState.isAuthenticated) {
name = "${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!} ";
final user = appState.getAuthenticatedUser();
name = "${user!.firstName!} ${user.lastName!} ";
// Calculate age from authenticated user's DOB if available
if (user.dateofBirth != null && user.dateofBirth!.isNotEmpty) {
try {
DateTime dob = DateUtil.convertStringToDate(user.dateofBirth!);
final now = DateTime.now();
int age = now.year - dob.year;
if (now.month < dob.month || (now.month == dob.month && now.day < dob.day)) {
age--;
}
userAgeFromDOB = age;
} catch (e) {
// If date parsing fails, ignore
}
}
} else {
name = "Guest";
}
@ -132,8 +150,9 @@ class _UserInfoSelectionScreenState extends State<UserInfoSelectionScreen> {
// Get display values
String genderText = viewModel.selectedGender ?? "Not set";
// Show age calculated from DOB, not the DOB itself
String ageText = viewModel.selectedAge != null ? "${viewModel.selectedAge} Years" : "Not set";
// Show age calculated from DOB (prefer viewModel's age, fallback to calculated from user's DOB)
int? displayAge = viewModel.selectedAge ?? userAgeFromDOB;
String ageText = displayAge != null ? "$displayAge Years" : "Not set";
String heightText =
viewModel.selectedHeight != null ? "${viewModel.selectedHeight!.round()} ${viewModel.isHeightCm ? 'cm' : 'ft'}" : "Not set";
String weightText =
@ -144,7 +163,7 @@ class _UserInfoSelectionScreenState extends State<UserInfoSelectionScreen> {
Expanded(
child: CollapsingListView(
title: "Symptoms Checker".needTranslation,
isLeading: Navigator.canPop(context),
isLeading: true,
child: SingleChildScrollView(
child: Column(
children: [
@ -154,11 +173,11 @@ class _UserInfoSelectionScreenState extends State<UserInfoSelectionScreen> {
padding: EdgeInsets.symmetric(vertical: 24.h, horizontal: 16.w),
child: Column(
children: [
"Hello $name, Is your information up to date?".needTranslation.toText18(
"Hello $name, Is your information up to date?".needTranslation.toText16(
weight: FontWeight.w600,
color: AppColors.textColor,
),
SizedBox(height: 24.h),
SizedBox(height: 32.h),
_buildEditInfoTile(
context: context,
leadingIcon: AppAssets.genderIcon,
@ -228,48 +247,46 @@ class _UserInfoSelectionScreenState extends State<UserInfoSelectionScreen> {
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r),
child: SafeArea(
top: false,
child: Padding(
padding: EdgeInsets.all(8.h),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Expanded(
child: CustomButton(
text: "No, Edit all".needTranslation,
icon: AppAssets.edit_icon,
iconColor: AppColors.primaryRedColor,
onPressed: () {
context.read<SymptomsCheckerViewModel>().setUserInfoPage(0, isSinglePageEdit: false);
context.navigateWithName(AppRoutes.userInfoFlowManager);
},
backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.11),
borderColor: Colors.transparent,
textColor: AppColors.primaryRedColor,
fontSize: 16.f,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(height: 24.h),
Row(
children: [
Expanded(
child: CustomButton(
text: "No, Edit all".needTranslation,
icon: AppAssets.edit_icon,
iconColor: AppColors.primaryRedColor,
onPressed: () {
context.read<SymptomsCheckerViewModel>().setUserInfoPage(0, isSinglePageEdit: false);
context.navigateWithName(AppRoutes.userInfoFlowManager);
},
backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.11),
borderColor: Colors.transparent,
textColor: AppColors.primaryRedColor,
fontSize: 16.f,
),
SizedBox(width: 12.w),
Expanded(
child: CustomButton(
text: "Yes, It is".needTranslation,
icon: AppAssets.tickIcon,
iconColor: hasEmptyFields ? AppColors.greyTextColor : AppColors.whiteColor,
onPressed: hasEmptyFields
? () {} // Empty function for disabled state
: () => context.navigateWithName(AppRoutes.organSelectorPage),
backgroundColor: hasEmptyFields ? AppColors.greyLightColor : AppColors.primaryRedColor,
borderColor: hasEmptyFields ? AppColors.greyLightColor : AppColors.primaryRedColor,
textColor: hasEmptyFields ? AppColors.greyTextColor : AppColors.whiteColor,
fontSize: 16.f,
),
),
SizedBox(width: 12.w),
Expanded(
child: CustomButton(
text: "Yes, It is".needTranslation,
icon: AppAssets.tickIcon,
iconColor: hasEmptyFields ? AppColors.greyTextColor : AppColors.whiteColor,
onPressed: hasEmptyFields
? () {} // Empty function for disabled state
: () => context.navigateWithName(AppRoutes.organSelectorPage),
backgroundColor: hasEmptyFields ? AppColors.greyLightColor : AppColors.primaryRedColor,
borderColor: hasEmptyFields ? AppColors.greyLightColor : AppColors.primaryRedColor,
textColor: hasEmptyFields ? AppColors.greyTextColor : AppColors.whiteColor,
fontSize: 16.f,
),
],
),
],
).paddingSymmetrical(24.w, 0),
),
),
],
),
],
).paddingSymmetrical(24.w, 0),
),
);
}

@ -1,5 +1,3 @@
import 'dart:developer';
import 'package:flutter/cupertino.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
@ -35,7 +33,6 @@ class AgeSelectionPage extends StatelessWidget {
initialDate: symptomsViewModel.dateOfBirth ?? DateTime(2000, 1, 1),
onDateChanged: (date) {
symptomsViewModel.setDateOfBirth(date);
log('DOB saved: $date, Age: ${symptomsViewModel.selectedAge}');
},
)
],

@ -21,14 +21,14 @@ import 'package:hmg_patient_app_new/widgets/graph/custom_graph.dart';
import 'package:provider/provider.dart';
import 'package:shimmer/shimmer.dart';
class WaterConsumptionScreen extends StatefulWidget {
const WaterConsumptionScreen({super.key});
class WaterConsumptionPage extends StatefulWidget {
const WaterConsumptionPage({super.key});
@override
State<WaterConsumptionScreen> createState() => _WaterConsumptionScreenState();
State<WaterConsumptionPage> createState() => _WaterConsumptionPageState();
}
class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
class _WaterConsumptionPageState extends State<WaterConsumptionPage> {
@override
void initState() {
super.initState();
@ -98,37 +98,14 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"History".needTranslation.toText16(isBold: true),
Row(
children: [
InkWell(
onTap: () => viewModel.setGraphView(!viewModel.isGraphView),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
transitionBuilder: (Widget child, Animation<double> animation) {
return FadeTransition(
opacity: animation,
child: ScaleTransition(
scale: animation,
child: child,
),
);
},
child: Container(
key: ValueKey<bool>(viewModel.isGraphView),
child: Utils.buildSvgWithAssets(
icon: viewModel.isGraphView ? AppAssets.listIcon : AppAssets.graphIcon,
height: 24.h,
width: 24.h,
),
),
),
),
"History".needTranslation.toText16(isBold: true),
SizedBox(width: 8.w),
InkWell(
onTap: () => _showHistoryDurationBottomsheet(context, viewModel),
child: Container(
padding: EdgeInsets.symmetric(vertical: 6.h, horizontal: 6.h),
padding: EdgeInsets.symmetric(vertical: 4.h, horizontal: 6.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
backgroundColor: AppColors.greyColor,
borderRadius: 8.r,
@ -137,13 +114,36 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
child: Row(
children: [
viewModel.selectedDurationFilter.toText12(fontWeight: FontWeight.w500),
Utils.buildSvgWithAssets(icon: AppAssets.arrow_down),
Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, height: 16.h),
],
),
),
)
],
),
InkWell(
onTap: () => viewModel.setGraphView(!viewModel.isGraphView),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
transitionBuilder: (Widget child, Animation<double> animation) {
return FadeTransition(
opacity: animation,
child: ScaleTransition(
scale: animation,
child: child,
),
);
},
child: Container(
key: ValueKey<bool>(viewModel.isGraphView),
child: Utils.buildSvgWithAssets(
icon: viewModel.isGraphView ? AppAssets.listIcon : AppAssets.graphIcon,
height: 24.h,
width: 24.h,
),
),
),
),
],
),
if (!viewModel.isGraphView) _buildHistoryListView(viewModel) else ...[SizedBox(height: 16.h), _buildHistoryGraph()]

@ -10,16 +10,25 @@ import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:provider/provider.dart';
class WaterMonitorSettingsScreen extends StatefulWidget {
const WaterMonitorSettingsScreen({super.key});
class WaterMonitorSettingsPage extends StatefulWidget {
const WaterMonitorSettingsPage({super.key});
@override
State<WaterMonitorSettingsScreen> createState() => _WaterMonitorSettingsScreenState();
State<WaterMonitorSettingsPage> createState() => _WaterMonitorSettingsPageState();
}
class _WaterMonitorSettingsScreenState extends State<WaterMonitorSettingsScreen> {
class _WaterMonitorSettingsPageState extends State<WaterMonitorSettingsPage> {
late DialogService dialogService;
@override
void initState() {
super.initState();
dialogService = getIt.get<DialogService>();
}
// No need to call initialize() here since it's already called in water_consumption_screen
// The ViewModel is shared via Provider, so data is already loaded
@ -59,8 +68,6 @@ class _WaterMonitorSettingsScreenState extends State<WaterMonitorSettingsScreen>
required Function(String) onSelected,
bool useUpperCase = false,
}) {
final dialogService = getIt.get<DialogService>();
dialogService.showFamilyBottomSheetWithoutHWithChild(
label: title.needTranslation,
message: "",
@ -263,9 +270,20 @@ class _WaterMonitorSettingsScreenState extends State<WaterMonitorSettingsScreen>
onPressed: () async {
final success = await viewModel.saveSettings();
if (!success && viewModel.validationError != null) {
_showSnackBar(context, viewModel.validationError!);
dialogService.showErrorBottomSheet(
message: viewModel.validationError!,
);
} else if (success) {
_showSnackBar(context, "Settings saved successfully");
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getSuccessWidget(
loadingText: "Settings saved successfully".needTranslation,
),
callBackFunc: () {},
isCloseButtonVisible: false,
isDismissible: true,
isFullScreen: false,
);
}
},
borderRadius: 12.r,
@ -340,16 +358,4 @@ class _WaterMonitorSettingsScreenState extends State<WaterMonitorSettingsScreen>
),
);
}
// Show snackbar for validation errors and success messages
void _showSnackBar(BuildContext context, String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
duration: const Duration(seconds: 3),
behavior: SnackBarBehavior.floating,
backgroundColor: message.contains('successfully') ? Colors.green : AppColors.errorColor,
),
);
}
}

@ -18,23 +18,29 @@ class WaterActionButtonsWidget extends StatelessWidget {
return Consumer<WaterMonitorViewModel>(builder: (context, vm, _) {
final cupAmount = vm.selectedCupCapacityMl;
final isGoalAchieved = vm.progressPercent >= 100 || vm.nextDrinkTime.toLowerCase().contains('goal achieved');
final isDisabled = vm.isLoading || isGoalAchieved;
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
InkWell(
onTap: () async {
if (cupAmount > 0) {
await vm.undoUserActivity();
}
},
child: Utils.buildSvgWithAssets(
icon: AppAssets.minimizeIcon,
height: 20.h,
width: 20.h,
iconColor: AppColors.textColor,
Opacity(
opacity: vm.isLoading ? 0.4 : 1.0,
child: InkWell(
onTap: vm.isLoading
? null
: () async {
if (cupAmount > 0) {
await vm.undoUserActivity();
}
},
child: Utils.buildSvgWithAssets(
icon: AppAssets.minimizeIcon,
height: 20.h,
width: 20.h,
iconColor: AppColors.textColor,
),
),
),
Container(
@ -51,9 +57,9 @@ class WaterActionButtonsWidget extends StatelessWidget {
),
),
Opacity(
opacity: isGoalAchieved ? 0.4 : 1.0,
opacity: isDisabled ? 0.4 : 1.0,
child: InkWell(
onTap: isGoalAchieved
onTap: isDisabled
? null
: () async {
if (cupAmount > 0) {
@ -88,7 +94,7 @@ class WaterActionButtonsWidget extends StatelessWidget {
),
_buildActionButton(
context: context,
onTap: () => context.navigateWithName(AppRoutes.waterMonitorSettingsScreen),
onTap: () => context.navigateWithName(AppRoutes.waterMonitorSettingsPage),
title: "Settings".needTranslation,
icon: Icon(
Icons.settings,

@ -6,8 +6,12 @@ import 'package:hmg_patient_app_new/presentation/authentication/register_step2.d
import 'package:hmg_patient_app_new/presentation/blood_donation/blood_donation_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart';
import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/comprehensive_checkup_page.dart';
import 'package:hmg_patient_app_new/presentation/covid19test/covid19_landing_page.dart';
import 'package:hmg_patient_app_new/presentation/e_referral/new_e_referral.dart';
import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculators_page.dart';
import 'package:hmg_patient_app_new/presentation/health_trackers/add_health_tracker_entry_page.dart';
import 'package:hmg_patient_app_new/presentation/health_trackers/health_tracker_detail_page.dart';
import 'package:hmg_patient_app_new/presentation/health_trackers/health_trackers_page.dart';
import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
import 'package:hmg_patient_app_new/presentation/home_health_care/hhc_procedures_page.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart';
@ -23,12 +27,10 @@ import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_sele
import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_selection/user_info_flow_manager.dart';
import 'package:hmg_patient_app_new/presentation/tele_consultation/zoom/call_screen.dart';
import 'package:hmg_patient_app_new/presentation/vital_sign/vital_sign_page.dart';
import 'package:hmg_patient_app_new/presentation/water_monitor/water_consumption_screen.dart';
import 'package:hmg_patient_app_new/presentation/water_monitor/water_monitor_settings_screen.dart';
import 'package:hmg_patient_app_new/presentation/water_monitor/water_consumption_page.dart';
import 'package:hmg_patient_app_new/presentation/water_monitor/water_monitor_settings_page.dart';
import 'package:hmg_patient_app_new/splashPage.dart';
import '../presentation/covid19test/covid19_landing_page.dart';
class AppRoutes {
static const String initialRoute = '/initialRoute';
static const String loginScreen = '/loginScreen';
@ -47,25 +49,29 @@ class AppRoutes {
static const String huaweiHealthExample = '/huaweiHealthExample';
static const String covid19Test = '/covid19Test';
static const String vitalSign = '/vitalSign';
//appointments
static const String bookAppointmentPage = '/bookAppointmentPage';
// Water Monitor
static const String waterConsumptionScreen = '/waterConsumptionScreen';
static const String waterMonitorSettingsScreen = '/waterMonitorSettingsScreen';
static const String waterConsumptionPage = '/waterConsumptionScreen';
static const String waterMonitorSettingsPage = '/waterMonitorSettingsScreen';
// Symptoms Checker
static const String organSelectorPage = '/organSelectorPage';
static const String symptomsSelectorScreen = '/symptomsCheckerScreen';
static const String suggestionsScreen = '/suggestionsScreen';
static const String riskFactorsScreen = '/riskFactorsScreen';
static const String possibleConditionsScreen = '/possibleConditionsScreen';
static const String triageScreen = '/triageProgressScreen';
//UserInfoSelection
static const String symptomsSelectorPage = '/symptomsCheckerScreen';
static const String suggestionsPage = '/suggestionsScreen';
static const String riskFactorsPage = '/riskFactorsScreen';
static const String possibleConditionsPage = '/possibleConditionsScreen';
static const String triagePage = '/triageProgressScreen';
static const String userInfoSelection = '/userInfoSelection';
static const String userInfoFlowManager = '/userInfoFlowManager';
// Health Trackers
static const String healthTrackersPage = '/healthTrackersListScreen';
static const String addHealthTrackerEntryPage = '/addHealthTrackerEntryPage';
static const String healthTrackerDetailPage = '/healthTrackerDetailPage';
static Map<String, WidgetBuilder> get routes => {
initialRoute: (context) => SplashPage(),
loginScreen: (context) => LoginScreen(),
@ -78,11 +84,11 @@ class AppRoutes {
comprehensiveCheckupPage: (context) => ComprehensiveCheckupPage(),
homeHealthCarePage: (context) => HhcProceduresPage(),
organSelectorPage: (context) => OrganSelectorPage(),
symptomsSelectorScreen: (context) => SymptomsSelectorScreen(),
riskFactorsScreen: (context) => RiskFactorsScreen(),
suggestionsScreen: (context) => SuggestionsScreen(),
possibleConditionsScreen: (context) => PossibleConditionsScreen(),
triageScreen: (context) => TriageScreen(),
symptomsSelectorPage: (context) => SymptomsSelectorPage(),
riskFactorsPage: (context) => RiskFactorsScreen(),
suggestionsPage: (context) => SuggestionsScreen(),
possibleConditionsPage: (context) => PossibleConditionsPage(),
triagePage: (context) => TriagePage(),
bloodDonationPage: (context) => BloodDonationPage(),
bookAppointmentPage: (context) => BookAppointmentPage(),
userInfoSelection: (context) => UserInfoSelectionScreen(),
@ -90,11 +96,23 @@ class AppRoutes {
smartWatches: (context) => SmartwatchInstructionsPage(),
huaweiHealthExample: (context) => HuaweiHealthExample(),
covid19Test: (context) => Covid19LandingPage(),
//
waterConsumptionScreen: (context) => WaterConsumptionScreen(),
waterMonitorSettingsScreen: (context) => WaterMonitorSettingsScreen(),
waterConsumptionPage: (context) => WaterConsumptionPage(),
waterMonitorSettingsPage: (context) => WaterMonitorSettingsPage(),
healthCalculatorsPage: (context) => HealthCalculatorsPage(type: HealthCalConEnum.calculator),
healthConvertersPage: (context) => HealthCalculatorsPage(type: HealthCalConEnum.converter),
vitalSign: (context) => VitalSignPage()
healthTrackersPage: (context) => HealthTrackersPage(),
vitalSign: (context) => VitalSignPage(),
addHealthTrackerEntryPage: (context) {
final args = ModalRoute.of(context)?.settings.arguments as HealthTrackerTypeEnum?;
return AddHealthTrackerEntryPage(
trackerType: args ?? HealthTrackerTypeEnum.bloodSugar,
);
},
healthTrackerDetailPage: (context) {
final args = ModalRoute.of(context)?.settings.arguments as HealthTrackerTypeEnum?;
return HealthTrackerDetailPage(
trackerType: args ?? HealthTrackerTypeEnum.bloodSugar,
);
},
};
}

@ -62,18 +62,16 @@ class DialogServiceImp implements DialogService {
message: message,
showCancel: onCancelPressed != null ? true : false,
onOkPressed: () {
print('ok button is pressed');
if (onOkPressed != null) {
print('onOkPressed is not null');
onOkPressed();
}else {
} else {
Navigator.pop(context);
}
},
onCancelPressed: () {
if (onCancelPressed != null) {
onCancelPressed();
}else {
} else {
Navigator.pop(context);
}
},
@ -108,7 +106,8 @@ class DialogServiceImp implements DialogService {
}
@override
Future<void> showCommonBottomSheetWithoutH({String? label, required String message, required Function() onOkPressed, Function()? onCancelPressed}) async {
Future<void> showCommonBottomSheetWithoutH(
{String? label, required String message, required Function() onOkPressed, Function()? onCancelPressed}) async {
final context = navigationService.navigatorKey.currentContext;
if (context == null) return;
showCommonBottomSheetWithoutHeight(
@ -162,7 +161,8 @@ class DialogServiceImp implements DialogService {
}
@override
Future<void> showPhoneNumberPickerSheet({String? label, String? message, required Function() onSMSPress, required Function() onWhatsappPress}) async {
Future<void> showPhoneNumberPickerSheet(
{String? label, String? message, required Function() onSMSPress, required Function() onWhatsappPress}) async {
final context = navigationService.navigatorKey.currentContext;
if (context == null) return;
showCommonBottomSheetWithoutHeight(context,
@ -184,7 +184,8 @@ class DialogServiceImp implements DialogService {
}
}
Widget exceptionBottomSheetWidget({required BuildContext context, required String message, required Function() onOkPressed, Function()? onCancelPressed}) {
Widget exceptionBottomSheetWidget(
{required BuildContext context, required String message, required Function() onOkPressed, Function()? onCancelPressed}) {
return Column(
children: [
(message).toText16(isBold: false, color: AppColors.textColor),
@ -239,7 +240,8 @@ Widget exceptionBottomSheetWidget({required BuildContext context, required Strin
);
}
Widget showPhoneNumberPickerWidget({required BuildContext context, String? message, required Function() onSMSPress, required Function() onWhatsappPress}) {
Widget showPhoneNumberPickerWidget(
{required BuildContext context, String? message, required Function() onSMSPress, required Function() onWhatsappPress}) {
return StatefulBuilder(builder: (BuildContext context, StateSetter setModalState) {
return Column(
children: [

@ -2,111 +2,112 @@ import 'package:flutter/material.dart';
class AppColors {
static const transparent = Colors.transparent;
static const mainPurple = Color(0xFF7954F7);
static const mainPurple = Color(0xFF7954F7); // #7954F7
static const scaffoldBgColor = Color(0xFFF8F8F8);
static const bottomSheetBgColor = Color(0xFFF8F8FA);
static const lightGreyEFColor = Color(0xffeaeaff);
static const greyF7Color = Color(0xffF7F7F7);
static const greyInfoTextColor = Color(0xff777777);
static const lightGrayColor = Color(0xff808080);
static const greyTextColorLight = Color(0xFFA2A2A2);
static const scaffoldBgColor = Color(0xFFF8F8F8); // #F8F8F8
static const bottomSheetBgColor = Color(0xFFF8F8FA); // #F8F8FA
static const lightGreyEFColor = Color(0xffeaeaff); // #EAEAFF
static const greyF7Color = Color(0xffF7F7F7); // #F7F7F7
static const greyInfoTextColor = Color(0xff777777); // #777777
static const lightGrayColor = Color(0xff808080); // #808080
static const greyTextColorLight = Color(0xFFA2A2A2); // #A2A2A2
// New UI Colors
static const whiteColor = Color(0xFFffffff);
static const Color bgScaffoldColor = Color(0xffF8F8F8);
static const Color primaryRedColor = Color(0xFFED1C2B);
static const Color primaryRedBorderColor = Color(0xFFED1C2B);
static const Color secondaryLightRedColor = Color(0xFFFEE9EA);
static const Color secondaryLightRedBorderColor = Color(0xFFFEE9EA);
static const Color bgRedLightColor = Color(0xFFFEE9EA);
static const Color bgGreenColor = Color(0xFF18C273);
static const Color textColor = Color(0xFF2E3039);
static const Color borderGrayColor = Color(0x332E3039);
static const Color textColorLight = Color(0xFF5E5E5E);
static const Color borderOnlyColor = Color(0xFF2E3039);
static const Color chipBorderColorOpacity20 = Color(0x332E3039);
static const Color dividerColor = Color(0x40D2D2D2);
static const Color warningColorYellow = Color(0xFFF4A308);
static const Color blackBgColor = Color(0xFF2E3039);
static const blackColor = textColor;
static const Color inputLabelTextColor = Color(0xff898A8D);
static const Color greyTextColor = Color(0xFF8F9AA3);
static const Color lightGrayBGColor = Color(0x142E3039);
static const Color checkBoxBorderColor = Color(0xffD2D2D2);
static const Color pharmacyBGColor = Color(0xFF359846);
static const lightGreenColor = Color(0xFF0ccedde);
static const textGreenColor = Color(0xFF18C273);
static const Color ratingColorYellow = Color(0xFFFFAF15);
static const Color spacerLineColor = Color(0x2E30391A);
static const whiteColor = Color(0xFFffffff); // #FFFFFF
static const Color bgScaffoldColor = Color(0xffF8F8F8); // #F8F8F8
static const Color primaryRedColor = Color(0xFFED1C2B); // #ED1C2B
static const Color primaryRedBorderColor = Color(0xFFED1C2B); // #ED1C2B
static const Color secondaryLightRedColor = Color(0xFFFEE9EA); // #FEE9EA
static const Color secondaryLightRedBorderColor = Color(0xFFFEE9EA); // #FEE9EA
static const Color bgRedLightColor = Color(0xFFFEE9EA); // #FEE9EA
static const Color bgGreenColor = Color(0xFF18C273); // #18C273
static const Color textColor = Color(0xFF2E3039); // #2E3039
static const Color borderGrayColor = Color(0x332E3039); // #2E3039 (20% opacity)
static const Color textColorLight = Color(0xFF5E5E5E); // #5E5E5E
static const Color borderOnlyColor = Color(0xFF2E3039); // #2E3039
static const Color chipBorderColorOpacity20 = Color(0x332E3039); // #2E3039 (20% opacity)
static const Color dividerColor = Color(0x40D2D2D2); // #D2D2D2 (25% opacity)
static const Color warningColorYellow = Color(0xFFF4A308); // #F4A308
static const Color blackBgColor = Color(0xFF2E3039); // #2E3039
static const blackColor = textColor; // #2E3039
static const Color inputLabelTextColor = Color(0xff898A8D); // #898A8D
static const Color greyTextColor = Color(0xFF8F9AA3); // #8F9AA3
static const Color lightGrayBGColor = Color(0x142E3039); // #2E3039 (8% opacity)
static const Color checkBoxBorderColor = Color(0xffD2D2D2); // #D2D2D2
static const Color pharmacyBGColor = Color(0xFF359846); // #359846
static const lightGreenColor = Color(0xFF0ccedde); // #0CCEDDE
static const textGreenColor = Color(0xFF18C273); // #18C273
static const Color ratingColorYellow = Color(0xFFFFAF15); // #FFAF15
static const Color spacerLineColor = Color(0x2E30391A); // #2E3039 (10% opacity)
//Chips
static const Color successColor = Color(0xff18C273);
static const Color successLightBgColor = Color(0xffDDF6EA);
static const Color errorColor = Color(0xFFED1C2B);
static const Color alertColor = Color(0xFFD48D05);
static const Color infoColor = Color(0xFF0B85F7);
static const Color warningColor = Color(0xFFFFCC00);
static const Color greyColor = Color(0xFFEFEFF0);
static const Color chipPrimaryRedBorderColor = Color(0xFFED1C2B);
static const Color chipSecondaryLightRedColor = Color(0xFFFEE9EA);
static const Color successLightColor = Color(0xFF18C273);
static const Color errorLightColor = Color(0xFFED1C2B);
static const Color alertLightColor = Color(0xFFD48D05);
static const Color infoLightColor = Color(0xFF0B85F7);
static const Color warningLightColor = Color(0xFFFFCC00);
static const Color greyLightColor = Color(0xFFEFEFF0);
static const Color thumbColor = Color(0xFF18C273);
static const Color switchBackgroundColor = Color(0x2618C273);
static const Color bottomNAVBorder = Color(0xFFEEEEEE);
static const Color quickLoginColor = Color(0xFF666666);
static const Color tooltipTextColor = Color(0xFF414D55);
static const Color graphGridColor = Color(0x4D18C273);
static const Color criticalLowAndHigh = Color(0xFFED1C2B);
static const Color highAndLow = Color(0xFFFFAF15);
static const Color labelTextColor = Color(0xFF838383);
static const Color calenderTextColor = Color(0xFFD0D0D0);
static const Color lightGreenButtonColor = Color(0x2618C273);
static const Color lightRedButtonColor = Color(0x1AED1C2B);
static const Color successColor = Color(0xff18C273); // #18C273
static const Color successLightBgColor = Color(0xffDDF6EA); // #DDF6EA
static const Color errorColor = Color(0xFFED1C2B); // #ED1C2B
static const Color alertColor = Color(0xFFD48D05); // #D48D05
static const Color infoColor = Color(0xFF0B85F7); // #0B85F7
static const Color warningColor = Color(0xFFFFCC00); // #FFCC00
static const Color greyColor = Color(0xFFEFEFF0); // #EFEFF0
static const Color chipPrimaryRedBorderColor = Color(0xFFED1C2B); // #ED1C2B
static const Color chipSecondaryLightRedColor = Color(0xFFFEE9EA); // #FEE9EA
// static const Color chipSecondaryLightRedColor = Color(0xFFFF9E15); // #FEE9EA
static const Color successLightColor = Color(0xFF18C273); // #18C273
static const Color errorLightColor = Color(0xFFED1C2B); // #ED1C2B
static const Color alertLightColor = Color(0xFFD48D05); // #D48D05
static const Color infoLightColor = Color(0xFF0B85F7); // #0B85F7
static const Color warningLightColor = Color(0xFFFFCC00); // #FFCC00
static const Color greyLightColor = Color(0xFFEFEFF0); // #EFEFF0
static const Color thumbColor = Color(0xFF18C273); // #18C273
static const Color switchBackgroundColor = Color(0x2618C273); // #18C273 (15% opacity)
static const Color bottomNAVBorder = Color(0xFFEEEEEE); // #EEEEEE
static const Color quickLoginColor = Color(0xFF666666); // #666666
static const Color tooltipTextColor = Color(0xFF414D55); // #414D55
static const Color graphGridColor = Color(0x4D18C273); // #18C273 (30% opacity)
static const Color criticalLowAndHigh = Color(0xFFED1C2B); // #ED1C2B
static const Color highAndLow = Color(0xFFFFAF15); // #FFAF15
static const Color labelTextColor = Color(0xFF838383); // #838383
static const Color calenderTextColor = Color(0xFFD0D0D0); // #D0D0D0
static const Color lightGreenButtonColor = Color(0x2618C273); // #18C273 (15% opacity)
static const Color lightRedButtonColor = Color(0x1AED1C2B); // #ED1C2B (10% opacity)
// Status Colors
static const Color statusPendingColor = Color(0xffCC9B14);
static const Color statusProcessingColor = Color(0xff2E303A);
static const Color statusCompletedColor = Color(0xff359846);
static const Color statusRejectedColor = Color(0xffD02127);
static const Color statusPendingColor = Color(0xffCC9B14); // #CC9B14
static const Color statusProcessingColor = Color(0xff2E303A); // #2E303A
static const Color statusCompletedColor = Color(0xff359846); // #359846
static const Color statusRejectedColor = Color(0xffD02127); // #D02127
// Info Banner Colors
static const Color infoBannerBgColor = Color(0xFFFFF4E6);
static const Color infoBannerBorderColor = Color(0xFFFFE5B4);
static const Color infoBannerIconColor = Color(0xFFCC9B14);
static const Color infoBannerTextColor = Color(0xFF856404);
static const Color infoBannerBgColor = Color(0xFFFFF4E6); // #FFF4E6
static const Color infoBannerBorderColor = Color(0xFFFFE5B4); // #FFE5B4
static const Color infoBannerIconColor = Color(0xFFCC9B14); // #CC9B14
static const Color infoBannerTextColor = Color(0xFF856404); // #856404
// SymptomsChecker
static const Color chipColorSeekMedicalAdvice = Color(0xFFFFAF15);
static const Color chipTextColorSeekMedicalAdvice = Color(0xFFAB7103);
static const Color chipColorMonitor = Color(0xFF18C273);
static const Color chipColorEmergency = Color(0xFFED1C2B);
static const Color chipColorSeekMedicalAdvice = Color(0xFFFFAF15); // #FFAF15
static const Color chipTextColorSeekMedicalAdvice = Color(0xFFAB7103); // #AB7103
static const Color chipColorMonitor = Color(0xFF18C273); // #18C273
static const Color chipColorEmergency = Color(0xFFED1C2B); // #ED1C2B
// Services Page Colors
static const Color eReferralCardColor = Color(0xFFFF8012);
static const Color bloodDonationCardColor = Color(0xFFFF5662);
static const Color bookAppointment = Color(0xFF415364);
static const Color eReferralCardColor = Color(0xFFFF8012); // #FF8012
static const Color bloodDonationCardColor = Color(0xFFFF5662); // #FF5662
static const Color bookAppointment = Color(0xFF415364); // #415364
// Water Monitor
static const Color blueColor = Color(0xFF4EB5FF);
static const Color blueGradientColorOne = Color(0xFFF1F7FD);
static const Color blueGradientColorTwo = Color(0xFFD9EFFF);
static const Color blueColor = Color(0xFF4EB5FF); // #4EB5FF
static const Color blueGradientColorOne = Color(0xFFF1F7FD); // #F1F7FD
static const Color blueGradientColorTwo = Color(0xFFD9EFFF); // #D9EFFF
// Shimmer
static const Color shimmerBaseColor = Color(0xFFE0E0E0);
static const Color shimmerHighlightColor = Color(0xFFF5F5F5);
static const Color covid29Color = Color(0xff2563EB);
static const Color shimmerBaseColor = Color(0xFFE0E0E0); // #E0E0E0
static const Color shimmerHighlightColor = Color(0xFFF5F5F5); // #F5F5F5
static const Color covid29Color = Color(0xff2563EB); // #2563EB
}

@ -11,6 +11,7 @@ import 'package:hmg_patient_app_new/theme/colors.dart';
///
/// **Parameters:**
/// - [dataPoints]: List of `DataPoint` objects to plot.
/// - [secondaryDataPoints]: Optional list for a second line (e.g., diastolic in blood pressure).
/// - [leftLabelFormatter]: Function to build left axis labels.
/// - [bottomLabelFormatter]: Function to build bottom axis labels.
/// - [width]: Optional width of the chart.
@ -18,6 +19,7 @@ import 'package:hmg_patient_app_new/theme/colors.dart';
/// - [maxY], [maxX], [minX]: Axis bounds.
/// - [spotColor]: Color of the touched spot marker.
/// - [graphColor]: Color of the line.
/// - [secondaryGraphColor]: Color of the secondary line.
/// - [graphShadowColor]: Color of the area below the line.
/// - [graphGridColor]: Color of the grid lines.
/// - [bottomLabelColor]: Color of bottom axis labels.
@ -43,6 +45,7 @@ import 'package:hmg_patient_app_new/theme/colors.dart';
/// )
class CustomGraph extends StatelessWidget {
final List<DataPoint> dataPoints;
final List<DataPoint>? secondaryDataPoints; // For dual-line graphs (e.g., blood pressure)
final double? width;
final double height;
final double? maxY;
@ -50,6 +53,7 @@ class CustomGraph extends StatelessWidget {
final double? minX;
final Color spotColor;
final Color graphColor;
final Color? secondaryGraphColor; // Color for secondary line
final Color graphShadowColor;
final Color graphGridColor;
final Color bottomLabelColor;
@ -79,6 +83,7 @@ class CustomGraph extends StatelessWidget {
const CustomGraph(
{super.key,
required this.dataPoints,
this.secondaryDataPoints,
required this.leftLabelFormatter,
this.width,
required this.scrollDirection,
@ -89,6 +94,7 @@ class CustomGraph extends StatelessWidget {
this.isFullScreeGraph = false,
this.spotColor = AppColors.bgGreenColor,
this.graphColor = AppColors.bgGreenColor,
this.secondaryGraphColor,
this.graphShadowColor = AppColors.graphGridColor,
this.graphGridColor = AppColors.graphGridColor,
this.bottomLabelColor = AppColors.textColor,
@ -225,7 +231,7 @@ class CustomGraph extends StatelessWidget {
return FlSpot(entry.key.toDouble(), value);
}).toList();
var data = [
var data = <LineChartBarData>[
LineChartBarData(
spots: allSpots,
isCurved: true,
@ -254,6 +260,31 @@ class CustomGraph extends StatelessWidget {
)
];
// Add secondary line if provided (for dual-line graphs like blood pressure)
if (secondaryDataPoints != null && secondaryDataPoints!.isNotEmpty) {
final List<FlSpot> secondarySpots = secondaryDataPoints!.asMap().entries.map((entry) {
double value = (makeGraphBasedOnActualValue) ? double.tryParse(entry.value.actualValue) ?? 0.0 : entry.value.value;
return FlSpot(entry.key.toDouble(), value);
}).toList();
data.add(
LineChartBarData(
spots: secondarySpots,
isCurved: true,
isStrokeCapRound: true,
isStrokeJoinRound: true,
barWidth: 2,
gradient: LinearGradient(
colors: [secondaryGraphColor ?? AppColors.blueColor, secondaryGraphColor ?? AppColors.blueColor],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
),
dotData: FlDotData(show: showLinePoints),
belowBarData: BarAreaData(show: false),
),
);
}
return data;
}
}

@ -3,17 +3,14 @@ import 'package:hijri_gregorian_calendar/hijri_gregorian_calendar.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
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/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/dropdown/country_dropdown_widget.dart';
import '../core/dependencies.dart';
// TODO: Import AppColors if bgRedColor is defined there
// import 'package:hmg_patient_app_new/core/ui_utils/app_colors.dart';
import 'package:hmg_patient_app_new/widgets/time_picker_widget.dart';
class TextInputWidget extends StatelessWidget {
final String labelText;
@ -49,6 +46,7 @@ class TextInputWidget extends StatelessWidget {
final int maxLines;
final Color? hintColor;
final bool? isHideSwitcher;
final bool? isArrowTrailing;
// final List<Country> countryList;
// final Function(Country)? onCountryChange;
@ -87,6 +85,7 @@ class TextInputWidget extends StatelessWidget {
this.minLines = 3,
this.maxLines = 6,
this.isHideSwitcher,
this.isArrowTrailing,
// this.countryList = const [],
// this.onCountryChange,
});
@ -167,7 +166,8 @@ class TextInputWidget extends StatelessWidget {
],
),
),
if (selectionType == SelectionTypeEnum.calendar) _buildTrailingIcon(context),
if (selectionType == SelectionTypeEnum.calendar) _buildTrailingIcon(context, isArrowTrailing: isArrowTrailing ?? false),
if (selectionType == SelectionTypeEnum.time) _buildTimePickerIcon(context, isArrowTrailing: isArrowTrailing ?? false),
if (selectionType == SelectionTypeEnum.search) _buildTrailingIconForSearch(context),
],
),
@ -200,7 +200,7 @@ class TextInputWidget extends StatelessWidget {
child: Utils.buildSvgWithAssets(icon: leadingIcon!));
}
Widget _buildTrailingIcon(BuildContext context) {
Widget _buildTrailingIcon(BuildContext context, {bool isArrowTrailing = false}) {
final AppState appState = getIt.get<AppState>();
return Container(
height: 40.h,
@ -233,7 +233,47 @@ class TextInputWidget extends StatelessWidget {
onChange!(picked.toString());
}
},
child: Utils.buildSvgWithAssets(icon: AppAssets.calendar),
child: Utils.buildSvgWithAssets(icon: isArrowTrailing ? AppAssets.arrow_down : AppAssets.calendar),
),
);
}
Widget _buildTimePickerIcon(BuildContext context, {bool isArrowTrailing = false}) {
return Container(
height: 40.h,
width: 40.h,
margin: EdgeInsets.zero,
padding: EdgeInsets.all(8.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
borderRadius: 12.r,
color: AppColors.whiteColor,
),
child: GestureDetector(
onTap: () async {
// Parse existing time if available
TimeOfDay? initialTime;
if (controller?.text.isNotEmpty ?? false) {
initialTime = TimePickerWidget.parseTime(controller!.text);
}
final picked = await TimePickerWidget.show(
context,
initialTime: initialTime,
use24HourFormat: false, // You can make this configurable if needed
onTimeSelected: (time) {
if (onChange != null) {
final formattedTime = TimePickerWidget.formatTime(time);
onChange!(formattedTime);
}
},
);
// Update controller if time was picked
if (picked != null && controller != null) {
controller!.text = TimePickerWidget.formatTime(picked);
}
},
child: Utils.buildSvgWithAssets(icon: isArrowTrailing ? AppAssets.arrow_down : AppAssets.alarm_clock_icon),
),
);
}

@ -0,0 +1,348 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
/// A reusable time picker widget that can be used anywhere in the app
/// Shows a bottom sheet with iOS-style time picker
class TimePickerWidget {
/// Shows a time picker bottom sheet
///
/// [context] - BuildContext for showing the bottom sheet
/// [initialTime] - Initial time to display (defaults to current time)
/// [use24HourFormat] - Whether to use 24-hour format (defaults to false)
/// [onTimeSelected] - Callback when time is selected
///
/// Returns the selected TimeOfDay or null if cancelled
static Future<TimeOfDay?> show(
BuildContext context, {
TimeOfDay? initialTime,
bool use24HourFormat = false,
bool displaySelectedTime = false,
Function(TimeOfDay)? onTimeSelected,
}) async {
final selectedTime = initialTime ?? TimeOfDay.now();
final result = await showModalBottomSheet<TimeOfDay>(
context: context,
backgroundColor: Colors.transparent,
isScrollControlled: true,
builder: (BuildContext context) {
return _TimePickerBottomSheet(
initialTime: selectedTime,
use24HourFormat: use24HourFormat,
displaySelectedTime: displaySelectedTime,
onTimeChanged: (time) {
// Time is being changed in real-time
},
);
},
);
if (result != null && onTimeSelected != null) {
onTimeSelected(result);
}
return result;
}
/// Formats TimeOfDay to string (HH:mm format)
static String formatTime(TimeOfDay time, {bool use24HourFormat = false}) {
if (use24HourFormat) {
return '${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}';
} else {
final hour = time.hourOfPeriod == 0 ? 12 : time.hourOfPeriod;
final period = time.period == DayPeriod.am ? 'AM' : 'PM';
return '${hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')} $period';
}
}
/// Parses time string to TimeOfDay
static TimeOfDay? parseTime(String timeString) {
try {
final parts = timeString.split(':');
if (parts.length == 2) {
final hour = int.parse(parts[0]);
final minute = int.parse(parts[1].split(' ')[0]);
return TimeOfDay(hour: hour, minute: minute);
}
} catch (e) {
return null;
}
return null;
}
}
class _TimePickerBottomSheet extends StatefulWidget {
final TimeOfDay initialTime;
final bool use24HourFormat;
final bool displaySelectedTime;
final Function(TimeOfDay) onTimeChanged;
const _TimePickerBottomSheet({
required this.initialTime,
required this.use24HourFormat,
required this.displaySelectedTime,
required this.onTimeChanged,
});
@override
State<_TimePickerBottomSheet> createState() => _TimePickerBottomSheetState();
}
class _TimePickerBottomSheetState extends State<_TimePickerBottomSheet> {
late int selectedHour;
late int selectedMinute;
late DayPeriod selectedPeriod;
@override
void initState() {
super.initState();
selectedHour = widget.use24HourFormat ? widget.initialTime.hour : widget.initialTime.hourOfPeriod;
if (selectedHour == 0 && !widget.use24HourFormat) selectedHour = 12;
selectedMinute = widget.initialTime.minute;
selectedPeriod = widget.initialTime.period;
}
TimeOfDay _getCurrentTime() {
if (widget.use24HourFormat) {
return TimeOfDay(hour: selectedHour, minute: selectedMinute);
} else {
int hour = selectedHour;
if (selectedPeriod == DayPeriod.pm && hour != 12) {
hour += 12;
} else if (selectedPeriod == DayPeriod.am && hour == 12) {
hour = 0;
}
return TimeOfDay(hour: hour, minute: selectedMinute);
}
}
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20.r),
topRight: Radius.circular(20.r),
),
),
child: SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Header
Container(
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 16.h),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: AppColors.dividerColor,
width: 1,
),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"Select Time".needTranslation.toText18(
weight: FontWeight.w600,
color: AppColors.textColor,
),
GestureDetector(
onTap: () => Navigator.pop(context),
child: Utils.buildSvgWithAssets(
icon: AppAssets.cancel,
width: 24.h,
height: 24.h,
iconColor: AppColors.textColor,
),
),
],
),
),
// Time Picker
SizedBox(
height: 250.h,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Hour Picker
Expanded(
child: CupertinoPicker(
scrollController: FixedExtentScrollController(
initialItem: widget.use24HourFormat ? selectedHour : (selectedHour - 1),
),
itemExtent: 50.h,
onSelectedItemChanged: (index) {
setState(() {
if (widget.use24HourFormat) {
selectedHour = index;
} else {
selectedHour = index + 1;
}
widget.onTimeChanged(_getCurrentTime());
});
},
children: List.generate(
widget.use24HourFormat ? 24 : 12,
(index) {
final hour = widget.use24HourFormat ? index : index + 1;
return Center(
child: Text(
hour.toString().padLeft(2, '0'),
style: TextStyle(
fontSize: 24.f,
fontWeight: FontWeight.w500,
color: AppColors.textColor,
),
),
);
},
),
),
),
// Separator
Text(
':',
style: TextStyle(
fontSize: 24.f,
fontWeight: FontWeight.w500,
color: AppColors.textColor,
),
),
// Minute Picker
Expanded(
child: CupertinoPicker(
scrollController: FixedExtentScrollController(
initialItem: selectedMinute,
),
itemExtent: 50.h,
onSelectedItemChanged: (index) {
setState(() {
selectedMinute = index;
widget.onTimeChanged(_getCurrentTime());
});
},
children: List.generate(
60,
(index) => Center(
child: Text(
index.toString().padLeft(2, '0'),
style: TextStyle(
fontSize: 24.f,
fontWeight: FontWeight.w500,
color: AppColors.textColor,
),
),
),
),
),
),
// AM/PM Picker (only for 12-hour format)
if (!widget.use24HourFormat)
Expanded(
child: CupertinoPicker(
scrollController: FixedExtentScrollController(
initialItem: selectedPeriod == DayPeriod.am ? 0 : 1,
),
itemExtent: 50.h,
onSelectedItemChanged: (index) {
setState(() {
selectedPeriod = index == 0 ? DayPeriod.am : DayPeriod.pm;
widget.onTimeChanged(_getCurrentTime());
});
},
children: [
Center(
child: Text(
'AM',
style: TextStyle(
fontSize: 24.f,
fontWeight: FontWeight.w500,
color: AppColors.textColor,
),
),
),
Center(
child: Text(
'PM',
style: TextStyle(
fontSize: 24.f,
fontWeight: FontWeight.w500,
color: AppColors.textColor,
),
),
),
],
),
),
],
),
),
if (widget.displaySelectedTime)
// Current Time Display
Container(
margin: EdgeInsets.symmetric(horizontal: 20.w, vertical: 16.h),
padding: EdgeInsets.symmetric(vertical: 12.h),
decoration: BoxDecoration(
color: AppColors.lightGrayBGColor,
borderRadius: BorderRadius.circular(12.r),
),
child: Center(
child: TimePickerWidget.formatTime(
_getCurrentTime(),
use24HourFormat: widget.use24HourFormat,
).toText20(
weight: FontWeight.w600,
color: AppColors.textColor,
),
),
),
// Action Buttons
Padding(
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 16.h),
child: Row(
children: [
Expanded(
child: CustomButton(
height: 56.h,
text: "Cancel".needTranslation,
onPressed: () => Navigator.pop(context),
textColor: AppColors.textColor,
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
),
),
SizedBox(width: 12.w),
Expanded(
child: CustomButton(
height: 56.h,
text: "Confirm".needTranslation,
onPressed: () {
Navigator.pop(context, _getCurrentTime());
},
textColor: AppColors.whiteColor,
backgroundColor: AppColors.primaryRedColor,
),
),
],
),
),
],
),
),
);
}
}

@ -0,0 +1,165 @@
// Example usage of TimePickerWidget
//
// This file demonstrates how to use the TimePickerWidget in your Flutter app.
// The TimePickerWidget is a reusable component that shows a bottom sheet with
// an iOS-style time picker.
// ============================================================================
// EXAMPLE 1: Using with TextInputWidget
// ============================================================================
/*
TextInputWidget(
labelText: "Appointment Time",
hintText: "Select time",
controller: timeController,
selectionType: SelectionTypeEnum.time,
isReadOnly: true,
onChange: (value) {
print("Selected time: $value");
},
)
*/
// ============================================================================
// EXAMPLE 2: Direct usage with custom button
// ============================================================================
/*
ElevatedButton(
onPressed: () async {
final selectedTime = await TimePickerWidget.show(
context,
initialTime: TimeOfDay.now(),
use24HourFormat: false,
onTimeSelected: (time) {
print("Time selected: ${TimePickerWidget.formatTime(time)}");
},
);
if (selectedTime != null) {
print("Final time: ${TimePickerWidget.formatTime(selectedTime)}");
}
},
child: Text("Pick Time"),
)
*/
// ============================================================================
// EXAMPLE 3: Using with 24-hour format
// ============================================================================
/*
TextInputWidget(
labelText: "Meeting Time",
hintText: "Select time (24h)",
controller: timeController,
selectionType: SelectionTypeEnum.time,
isReadOnly: true,
onChange: (value) {
// The value will be formatted as "14:30" for 2:30 PM in 24h format
print("Selected time (24h): $value");
},
)
// Or programmatically:
final time = await TimePickerWidget.show(
context,
use24HourFormat: true, // Enable 24-hour format
);
*/
// ============================================================================
// EXAMPLE 4: Parsing and formatting times
// ============================================================================
/*
// Parse time string to TimeOfDay
String timeString = "02:30 PM";
TimeOfDay? parsedTime = TimePickerWidget.parseTime(timeString);
// Format TimeOfDay to string
TimeOfDay time = TimeOfDay(hour: 14, minute: 30);
String formatted12h = TimePickerWidget.formatTime(time); // "02:30 PM"
String formatted24h = TimePickerWidget.formatTime(time, use24HourFormat: true); // "14:30"
*/
// ============================================================================
// EXAMPLE 5: Complete form example with date and time
// ============================================================================
/*
class AppointmentForm extends StatefulWidget {
@override
_AppointmentFormState createState() => _AppointmentFormState();
}
class _AppointmentFormState extends State<AppointmentForm> {
final TextEditingController dateController = TextEditingController();
final TextEditingController timeController = TextEditingController();
@override
Widget build(BuildContext context) {
return Column(
children: [
// Date picker
TextInputWidget(
labelText: "Appointment Date",
hintText: "Select date",
controller: dateController,
selectionType: SelectionTypeEnum.calendar,
isReadOnly: true,
onChange: (value) {
print("Date selected: $value");
},
),
SizedBox(height: 16),
// Time picker
TextInputWidget(
labelText: "Appointment Time",
hintText: "Select time",
controller: timeController,
selectionType: SelectionTypeEnum.time,
isReadOnly: true,
onChange: (value) {
print("Time selected: $value");
},
),
],
);
}
}
*/
// ============================================================================
// Features:
// ============================================================================
// iOS-style cupertino picker (works on both iOS and Android)
// Support for 12-hour format (with AM/PM)
// Support for 24-hour format
// Beautiful bottom sheet UI matching app design
// Real-time preview of selected time
// Confirm/Cancel buttons
// Easy integration with TextInputWidget
// Parse and format time utilities
// Fully customizable and reusable
// ============================================================================
// API Reference:
// ============================================================================
// TimePickerWidget.show() - Shows the time picker bottom sheet
// Parameters:
// - context: BuildContext (required)
// - initialTime: TimeOfDay? (optional, defaults to current time)
// - use24HourFormat: bool (optional, defaults to false)
// - onTimeSelected: Function(TimeOfDay)? (optional callback)
// Returns: Future<TimeOfDay?>
//
// TimePickerWidget.formatTime() - Formats TimeOfDay to string
// Parameters:
// - time: TimeOfDay (required)
// - use24HourFormat: bool (optional, defaults to false)
// Returns: String (e.g., "02:30 PM" or "14:30")
//
// TimePickerWidget.parseTime() - Parses time string to TimeOfDay
// Parameters:
// - timeString: String (required, e.g., "02:30 PM")
// Returns: TimeOfDay?
Loading…
Cancel
Save