Merge branch 'master' into dev_aamir

# Conflicts:
#	lib/core/api/api_client.dart
#	lib/core/app_assets.dart
#	lib/features/medical_file/medical_file_repo.dart
#	lib/features/medical_file/medical_file_view_model.dart
#	lib/presentation/home/landing_page.dart
#	lib/presentation/medical_file/medical_file_page.dart
#	lib/presentation/profile_settings/profile_settings.dart
#	lib/widgets/chip/app_custom_chip_widget.dart
pull/76/head
aamir-csol 3 weeks ago
commit aaa8222b1a

@ -0,0 +1 @@
{"nm":"Comp 1","ddd":0,"h":100,"w":100,"meta":{"g":"@lottiefiles/toolkit-js 0.33.2"},"layers":[{"ty":4,"nm":"Shape Layer 2","sr":1,"st":0,"op":300,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[5.277,-32.723,0],"ix":1},"s":{"a":0,"k":[4.91,4.91,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[50.2,50.18,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 2","ix":1,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[102.555,102.555],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.0941,0.7608,0.451],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[3.277,-34.527],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 1","ix":2,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[102.555,102.555],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.0941,0.7608,0.451],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[100,100],"t":0},{"s":[295,295],"t":60}],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[3.277,-34.527],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[100],"t":0},{"s":[0],"t":60}],"ix":7}}]}],"ind":1}],"v":"5.5.7","fr":60,"op":61,"ip":0,"assets":[]}

File diff suppressed because one or more lines are too long

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 1.25C6.06294 1.25 1.25 6.06294 1.25 12C1.25 17.9371 6.06294 22.75 12 22.75C17.9371 22.75 22.75 17.9371 22.75 12C22.75 6.06294 17.9371 1.25 12 1.25ZM9.20711 7.79289C8.81658 7.40237 8.18342 7.40237 7.79289 7.79289C7.40237 8.18342 7.40237 8.81658 7.79289 9.20711L10.5858 12L7.79289 14.7929C7.40237 15.1834 7.40237 15.8166 7.79289 16.2071C8.18342 16.5976 8.81658 16.5976 9.20711 16.2071L12 13.4142L14.7929 16.2071C15.1834 16.5976 15.8166 16.5976 16.2071 16.2071C16.5976 15.8166 16.5976 15.1834 16.2071 14.7929L13.4142 12L16.2071 9.20711C16.5976 8.81658 16.5976 8.18342 16.2071 7.79289C15.8166 7.40237 15.1834 7.40237 14.7929 7.79289L12 10.5858L9.20711 7.79289Z" fill="#8F9AA3"/>
</svg>

After

Width:  |  Height:  |  Size: 835 B

@ -0,0 +1,5 @@
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2.75H11.9278C9.92082 2.74998 8.31557 2.74996 7.0558 2.91933C5.75291 3.0945 4.67453 3.46676 3.82064 4.32064C2.96676 5.17453 2.5945 6.25291 2.41933 7.5558C2.24996 8.81557 2.24998 10.4208 2.25 12.4278V12.5722C2.24998 14.5792 2.24996 16.1844 2.41933 17.4442C2.5945 18.7471 2.96676 19.8255 3.82064 20.6794C4.67453 21.5332 5.75291 21.9055 7.0558 22.0807C8.31558 22.25 9.92083 22.25 11.9278 22.25H12.0722C14.0792 22.25 15.6844 22.25 16.9442 22.0807C18.2471 21.9055 19.3255 21.5332 20.1794 20.6794C21.0332 19.8255 21.4055 18.7471 21.5807 17.4442C21.75 16.1844 21.75 14.5792 21.75 12.5722V12.5C21.75 11.9615 21.3135 11.525 20.775 11.525C20.2365 11.525 19.8 11.9615 19.8 12.5C19.8 14.5959 19.7979 16.0697 19.6481 17.1844C19.502 18.271 19.2317 18.8693 18.8005 19.3005C18.3693 19.7317 17.771 20.002 16.6844 20.1481C15.5697 20.2979 14.0959 20.3 12 20.3C9.90415 20.3 8.43035 20.2979 7.31564 20.1481C6.22898 20.002 5.63068 19.7317 5.1995 19.3005C4.76831 18.8693 4.49804 18.271 4.35194 17.1844C4.20207 16.0697 4.2 14.5959 4.2 12.5C4.2 10.4042 4.20207 8.93035 4.35194 7.81564C4.49804 6.72898 4.76831 6.13068 5.1995 5.6995C5.63068 5.26831 6.22898 4.99804 7.31564 4.85194C8.43035 4.70207 9.90415 4.7 12 4.7C12.5385 4.7 12.975 4.26348 12.975 3.725C12.975 3.18652 12.5385 2.75 12 2.75Z" fill="#2E3039"/>
<path d="M17.0855 3.5503C18.1526 2.48323 19.8827 2.48323 20.9497 3.5503C22.0168 4.61736 22.0168 6.34742 20.9497 7.41448L20.078 8.28615L16.2139 4.42188L17.0855 3.5503Z" fill="#2E3039"/>
<path d="M15.1533 5.48254L19.0174 9.34682L14.2337 14.1306C13.2653 15.0992 12.6734 15.6912 11.9448 16.0983C11.5383 16.3255 10.9218 16.5239 10.291 16.6994C9.64198 16.88 8.89524 17.0578 8.18242 17.2275L8.17373 17.2296C7.92037 17.2899 7.65385 17.2145 7.46969 17.0303C7.28552 16.8462 7.21009 16.5796 7.27041 16.3263L7.27249 16.3175C7.44221 15.6047 7.62 14.858 7.8006 14.209C7.97615 13.5782 8.17455 12.9617 8.40167 12.5553C8.80884 11.8266 9.40083 11.2348 10.3694 10.2664L15.1533 5.48254Z" fill="#2E3039"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@ -0,0 +1,8 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.75 3.47825C16.75 3.26399 16.75 3.06711 16.7387 2.90179C16.7266 2.72415 16.699 2.52881 16.6168 2.33031C16.4392 1.90151 16.0985 1.56083 15.6697 1.38321C15.4712 1.30099 15.2759 1.27338 15.0982 1.26126C14.9329 1.24998 14.7361 1.24999 14.5218 1.25L14.4782 1.25C14.264 1.24999 14.0671 1.24998 13.9018 1.26126C13.7241 1.27338 13.5288 1.30099 13.3303 1.38321C12.9015 1.56083 12.5608 1.90151 12.3832 2.33031C12.301 2.52881 12.2734 2.72415 12.2613 2.90179C12.2541 3.00643 12.2515 3.12369 12.2505 3.25L3 3.25C2.58579 3.25 2.25 3.58579 2.25 4C2.25 4.41421 2.58579 4.75 3 4.75L12.2505 4.75C12.2515 4.87632 12.2541 4.99358 12.2613 5.09821C12.2734 5.27585 12.301 5.4712 12.3832 5.6697C12.5608 6.0985 12.9015 6.43918 13.3303 6.61679C13.5288 6.69902 13.7241 6.72663 13.9018 6.73875C14.0671 6.75003 14.264 6.75002 14.4782 6.75H14.5218C14.736 6.75002 14.9329 6.75003 15.0982 6.73875C15.2759 6.72663 15.4712 6.69902 15.6697 6.61679C16.0985 6.43918 16.4392 6.0985 16.6168 5.6697C16.699 5.4712 16.7266 5.27586 16.7387 5.09821C16.75 4.9329 16.75 4.73607 16.75 4.52182V3.47825ZM13.75 4.00171V4.5C13.75 4.74323 13.7504 4.8881 13.7578 4.9961C13.7623 5.06293 13.7684 5.09134 13.7703 5.09869C13.7956 5.15753 13.8425 5.20442 13.9013 5.2297C13.9087 5.23157 13.9371 5.23767 14.0039 5.24223C14.1119 5.2496 14.2568 5.25 14.5 5.25C14.7432 5.25 14.8881 5.2496 14.9961 5.24223C15.0629 5.23767 15.0913 5.23157 15.0987 5.2297C15.1575 5.20442 15.2044 5.15753 15.2297 5.09869C15.2316 5.09134 15.2377 5.06294 15.2422 4.9961C15.2496 4.8881 15.25 4.74323 15.25 4.5V3.5C15.25 3.25677 15.2496 3.11191 15.2422 3.0039C15.2377 2.93707 15.2316 2.90867 15.2297 2.90131C15.2044 2.84248 15.1575 2.79558 15.0987 2.77031C15.0913 2.76844 15.0629 2.76234 14.9961 2.75778C14.8881 2.75041 14.7432 2.75 14.5 2.75C14.2568 2.75 14.1119 2.75041 14.0039 2.75778C13.9371 2.76234 13.9087 2.76844 13.9013 2.77031C13.8425 2.79558 13.7956 2.84248 13.7703 2.90131C13.7684 2.90867 13.7623 2.93707 13.7578 3.0039C13.7504 3.11191 13.75 3.25677 13.75 3.5V3.99829L13.75 4L13.75 4.00171Z" fill="#2E3039"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.2505 19.75L3 19.75C2.58579 19.75 2.25 19.4142 2.25 19C2.25 18.5858 2.58579 18.25 3 18.25L10.2505 18.25C10.2515 18.1237 10.2541 18.0064 10.2613 17.9018C10.2734 17.7241 10.301 17.5288 10.3832 17.3303C10.5608 16.9015 10.9015 16.5608 11.3303 16.3832C11.5288 16.301 11.7241 16.2734 11.9018 16.2613C12.0671 16.25 12.264 16.25 12.4782 16.25H12.5218C12.736 16.25 12.9329 16.25 13.0982 16.2613C13.2759 16.2734 13.4712 16.301 13.6697 16.3832C14.0985 16.5608 14.4392 16.9015 14.6168 17.3303C14.699 17.5288 14.7266 17.7241 14.7387 17.9018C14.75 18.0671 14.75 18.2639 14.75 18.4782L14.75 19.5218C14.75 19.736 14.75 19.9329 14.7387 20.0982C14.7266 20.2759 14.699 20.4712 14.6168 20.6697C14.4392 21.0985 14.0985 21.4392 13.6697 21.6168C13.4712 21.699 13.2759 21.7266 13.0982 21.7387C12.9329 21.75 12.736 21.75 12.5218 21.75H12.4782C12.264 21.75 12.0671 21.75 11.9018 21.7387C11.7241 21.7266 11.5288 21.699 11.3303 21.6168C10.9015 21.4392 10.5608 21.0985 10.3832 20.6697C10.301 20.4712 10.2734 20.2759 10.2613 20.0982C10.2541 19.9936 10.2515 19.8763 10.2505 19.75ZM11.75 19.5L11.75 19.0017L11.75 19L11.75 18.9983V18.5C11.75 18.2568 11.7504 18.1119 11.7578 18.0039C11.7623 17.9371 11.7684 17.9087 11.7703 17.9013C11.7956 17.8425 11.8425 17.7956 11.9013 17.7703C11.9087 17.7684 11.9371 17.7623 12.0039 17.7578C12.1119 17.7504 12.2568 17.75 12.5 17.75C12.7432 17.75 12.8881 17.7504 12.9961 17.7578C13.0629 17.7623 13.0913 17.7684 13.0987 17.7703C13.1575 17.7956 13.2044 17.8425 13.2297 17.9013C13.2316 17.9087 13.2377 17.9371 13.2422 18.0039C13.2496 18.1119 13.25 18.2568 13.25 18.5L13.25 19.5C13.25 19.7432 13.2496 19.8881 13.2422 19.9961C13.2377 20.0629 13.2316 20.0913 13.2297 20.0987C13.2044 20.1575 13.1575 20.2044 13.0987 20.2297C13.0913 20.2316 13.0629 20.2377 12.9961 20.2422C12.8881 20.2496 12.7432 20.25 12.5 20.25C12.2568 20.25 12.1119 20.2496 12.0039 20.2422C11.9371 20.2377 11.9087 20.2316 11.9013 20.2297C11.8425 20.2044 11.7956 20.1575 11.7703 20.0987C11.7684 20.0913 11.7623 20.0629 11.7578 19.9961C11.7504 19.8881 11.75 19.7432 11.75 19.5Z" fill="#2E3039"/>
<path d="M21 19.75C21.4142 19.75 21.75 19.4142 21.75 19C21.75 18.5858 21.4142 18.25 21 18.25H17C16.5858 18.25 16.25 18.5858 16.25 19C16.25 19.4142 16.5858 19.75 17 19.75H21Z" fill="#2E3039"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M21.75 11.5C21.75 11.9142 21.4142 12.25 21 12.25L11.7495 12.25C11.7485 12.3763 11.7459 12.4936 11.7387 12.5982C11.7266 12.7759 11.699 12.9712 11.6168 13.1697C11.4392 13.5985 11.0985 13.9392 10.6697 14.1168C10.4712 14.199 10.2759 14.2266 10.0982 14.2387C9.93288 14.25 9.73603 14.25 9.52176 14.25H9.47824C9.26396 14.25 9.06711 14.25 8.90179 14.2387C8.72415 14.2266 8.52881 14.199 8.3303 14.1168C7.9015 13.9392 7.56082 13.5985 7.38321 13.1697C7.30099 12.9712 7.27337 12.7759 7.26125 12.5982C7.24997 12.4329 7.24998 12.236 7.25 12.0218V10.9782C7.24998 10.764 7.24997 10.5671 7.26125 10.4018C7.27337 10.2242 7.30099 10.0288 7.38321 9.83031C7.56082 9.40151 7.9015 9.06083 8.3303 8.88321C8.52881 8.80099 8.72415 8.77338 8.90179 8.76126C9.06711 8.74998 9.26396 8.74999 9.47824 8.75H9.52175C9.73603 8.74999 9.93288 8.74998 10.0982 8.76126C10.2759 8.77338 10.4712 8.80099 10.6697 8.88321C11.0985 9.06083 11.4392 9.40151 11.6168 9.83031C11.699 10.0288 11.7266 10.2242 11.7387 10.4018C11.7459 10.5064 11.7485 10.6237 11.7495 10.75L21 10.75C21.4142 10.75 21.75 11.0858 21.75 11.5ZM10.0987 10.2703C10.1575 10.2956 10.2044 10.3425 10.2297 10.4013C10.2316 10.4087 10.2377 10.4371 10.2422 10.5039C10.2496 10.6119 10.25 10.7568 10.25 11V12C10.25 12.2432 10.2496 12.3881 10.2422 12.4961C10.2377 12.5629 10.2316 12.5913 10.2297 12.5987C10.2044 12.6575 10.1575 12.7044 10.0987 12.7297C10.0913 12.7316 10.0629 12.7377 9.9961 12.7422C9.88809 12.7496 9.74323 12.75 9.5 12.75C9.25677 12.75 9.1119 12.7496 9.0039 12.7422C8.93707 12.7377 8.90867 12.7316 8.90131 12.7297C8.84248 12.7044 8.79558 12.6575 8.7703 12.5987C8.76843 12.5913 8.76233 12.5629 8.75777 12.4961C8.7504 12.3881 8.75 12.2432 8.75 12V11C8.75 10.7568 8.7504 10.6119 8.75777 10.5039C8.76233 10.4371 8.76843 10.4087 8.7703 10.4013C8.79558 10.3425 8.84247 10.2956 8.90131 10.2703C8.90866 10.2684 8.93706 10.2623 9.0039 10.2578C9.1119 10.2504 9.25677 10.25 9.5 10.25C9.74323 10.25 9.88809 10.2504 9.9961 10.2578C10.0629 10.2623 10.0913 10.2684 10.0987 10.2703Z" fill="#2E3039"/>
<path d="M21 4.75C21.4142 4.75 21.75 4.41421 21.75 4C21.75 3.58579 21.4142 3.25 21 3.25L19 3.25C18.5858 3.25 18.25 3.58579 18.25 4C18.25 4.41421 18.5858 4.75 19 4.75L21 4.75Z" fill="#2E3039"/>
<path d="M5.75 11.5C5.75 11.9142 5.41421 12.25 5 12.25H3C2.58579 12.25 2.25 11.9142 2.25 11.5C2.25 11.0858 2.58579 10.75 3 10.75H5C5.41421 10.75 5.75 11.0858 5.75 11.5Z" fill="#2E3039"/>
</svg>

After

Width:  |  Height:  |  Size: 6.8 KiB

@ -1,3 +1,3 @@
<svg width="18" height="12" viewBox="0 0 18 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.7495 6.00047C17.7495 5.62691 17.5839 5.28034 17.427 5.02011C17.2577 4.7393 17.0301 4.44937 16.7803 4.16669C16.2794 3.59962 15.6257 2.99064 14.9913 2.43977C14.3532 1.88567 13.7173 1.37546 13.2423 1.00468C13.0044 0.818986 12.8059 0.66755 12.6665 0.562239C12.5968 0.509569 12.5418 0.468397 12.5039 0.440203L12.4604 0.407818L12.4488 0.399264L12.4448 0.396304C12.1113 0.150642 11.6414 0.221533 11.3957 0.555035C11.1501 0.888521 11.2213 1.358 11.5547 1.60367L11.5674 1.61308L11.6075 1.64285C11.6429 1.66925 11.6953 1.7085 11.7623 1.75911C11.8964 1.86036 12.0885 2.00695 12.3193 2.18713C12.7818 2.5481 13.3959 3.04099 14.0078 3.57235C14.6234 4.10694 15.2197 4.66576 15.6562 5.15983C15.6838 5.19108 15.7105 5.2218 15.7364 5.25195L0.999512 5.25196C0.585298 5.25196 0.249512 5.58774 0.249512 6.00195C0.249512 6.41617 0.585299 6.75196 0.999512 6.75196L15.7338 6.75195C15.7088 6.78116 15.6829 6.81088 15.6562 6.84111C15.2197 7.33518 14.6234 7.89401 14.0078 8.4286C13.3959 8.95995 12.7818 9.45285 12.3193 9.81382C12.0885 9.99399 11.8964 10.1406 11.7623 10.2418C11.6953 10.2925 11.6429 10.3317 11.6075 10.3581L11.5674 10.3879L11.5547 10.3973C11.2213 10.6429 11.1501 11.1124 11.3957 11.4459C11.6414 11.7794 12.1113 11.8503 12.4448 11.6046L12.4488 11.6017L12.4604 11.5931L12.5039 11.5607C12.5418 11.5326 12.5968 11.4914 12.6665 11.4387C12.8059 11.3334 13.0044 11.182 13.2423 10.9963C13.7173 10.6255 14.3532 10.1153 14.9913 9.56118C15.6257 9.01031 16.2794 8.40133 16.7803 7.83425C17.0301 7.55158 17.2577 7.26165 17.427 6.98083C17.5829 6.72217 17.7475 6.37819 17.7495 6.0072" fill="white"/>
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M28.7495 19.9995C28.7495 19.6259 28.5839 19.2794 28.427 19.0191C28.2577 18.7383 28.0301 18.4484 27.7803 18.1657C27.2794 17.5986 26.6257 16.9897 25.9913 16.4388C25.3532 15.8847 24.7173 15.3745 24.2423 15.0037C24.0044 14.818 23.8059 14.6666 23.6665 14.5613C23.5968 14.5086 23.5418 14.4674 23.5039 14.4392L23.4604 14.4068L23.4488 14.3983L23.4448 14.3953C23.1113 14.1497 22.6414 14.2206 22.3957 14.5541C22.1501 14.8875 22.2213 15.357 22.5547 15.6027L22.5674 15.6121L22.6075 15.6419C22.6429 15.6683 22.6953 15.7075 22.7623 15.7581C22.8964 15.8594 23.0885 16.006 23.3193 16.1862C23.7818 16.5471 24.3959 17.04 25.0078 17.5714C25.6234 18.106 26.2197 18.6648 26.6562 19.1589C26.6838 19.1901 26.7105 19.2208 26.7364 19.251L11.9995 19.251C11.5853 19.251 11.2495 19.5868 11.2495 20.001C11.2495 20.4152 11.5853 20.751 11.9995 20.751L26.7338 20.751C26.7088 20.7802 26.6829 20.8099 26.6562 20.8401C26.2197 21.3342 25.6234 21.893 25.0078 22.4276C24.3959 22.959 23.7818 23.4519 23.3193 23.8128C23.0885 23.993 22.8964 24.1396 22.7623 24.2409C22.6953 24.2915 22.6429 24.3307 22.6075 24.3571L22.5674 24.3869L22.5547 24.3963C22.2213 24.642 22.1501 25.1115 22.3957 25.4449C22.6414 25.7784 23.1113 25.8493 23.4448 25.6037L23.4488 25.6007L23.4604 25.5922L23.5039 25.5598C23.5418 25.5316 23.5968 25.4904 23.6665 25.4377C23.8059 25.3324 24.0044 25.181 24.2423 24.9953C24.7173 24.6245 25.3532 24.1143 25.9913 23.5602C26.6257 23.0093 27.2794 22.4003 27.7803 21.8333C28.0301 21.5506 28.2577 21.2607 28.427 20.9799C28.5829 20.7212 28.7475 20.3772 28.7495 20.0062" fill="#2E3039"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

@ -0,0 +1,4 @@
<svg width="20" height="14" viewBox="0 0 20 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.43 13.823C12.24 13.823 12.05 13.753 11.9 13.603C11.61 13.313 11.61 12.833 11.9 12.543L17.44 7.00305L11.9 1.46305C11.61 1.17305 11.61 0.693047 11.9 0.403047C12.19 0.113047 12.67 0.113047 12.96 0.403047L19.03 6.47305C19.32 6.76305 19.32 7.24305 19.03 7.53305L12.96 13.603C12.81 13.753 12.62 13.823 12.43 13.823Z" fill="#2E3039"/>
<path d="M18.33 7.75305H1.5C1.09 7.75305 0.75 7.41305 0.75 7.00305C0.75 6.59305 1.09 6.25305 1.5 6.25305H18.33C18.74 6.25305 19.08 6.59305 19.08 7.00305C19.08 7.41305 18.74 7.75305 18.33 7.75305Z" fill="#2E3039"/>
</svg>

After

Width:  |  Height:  |  Size: 666 B

@ -0,0 +1,3 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.29289 7.29289C7.68342 6.90237 8.31658 6.90237 8.70711 7.29289L16 14.5858L23.2929 7.29289C23.6834 6.90237 24.3166 6.90237 24.7071 7.29289C25.0976 7.68342 25.0976 8.31658 24.7071 8.70711L17.4142 16L24.7071 23.2929C25.0976 23.6834 25.0976 24.3166 24.7071 24.7071C24.3166 25.0976 23.6834 25.0976 23.2929 24.7071L16 17.4142L8.70711 24.7071C8.31658 25.0976 7.68342 25.0976 7.29289 24.7071C6.90237 24.3166 6.90237 23.6834 7.29289 23.2929L14.5858 16L7.29289 8.70711C6.90237 8.31658 6.90237 7.68342 7.29289 7.29289Z" fill="#2E3039"/>
</svg>

After

Width:  |  Height:  |  Size: 684 B

@ -0,0 +1,4 @@
<svg width="19" height="19" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="9.5" cy="9.5" r="6.5" fill="#18C273"/>
<circle cx="9.5" cy="9.5" r="8" stroke="#18C273" stroke-opacity="0.31" stroke-width="3"/>
</svg>

After

Width:  |  Height:  |  Size: 252 B

@ -0,0 +1,3 @@
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 1.75C3.58579 1.75 3.25 2.08579 3.25 2.5C3.25 2.91421 3.58579 3.25 4 3.25H4.25L4.25 5.5C4.25 8.58811 6.05618 11.2544 8.66976 12.5C6.05618 13.7456 4.25 16.4119 4.25 19.5L4.25 21.75H4C3.58579 21.75 3.25 22.0858 3.25 22.5C3.25 22.9142 3.58579 23.25 4 23.25L20 23.25C20.4142 23.25 20.75 22.9142 20.75 22.5C20.75 22.0858 20.4142 21.75 20 21.75H19.75V19.5C19.75 16.4119 17.9438 13.7456 15.3302 12.5C17.9438 11.2544 19.75 8.58811 19.75 5.5V3.25H20C20.4142 3.25 20.75 2.91421 20.75 2.5C20.75 2.08579 20.4142 1.75 20 1.75L4 1.75ZM18.25 3.25L5.75 3.25L5.75 5.5C5.75 8.95178 8.54822 11.75 12 11.75C15.4518 11.75 18.25 8.95178 18.25 5.5V3.25ZM18.25 21.75L18.25 19.5C18.25 16.0482 15.4518 13.25 12 13.25C8.54822 13.25 5.75 16.0482 5.75 19.5L5.75 21.75L18.25 21.75Z" fill="#2B353E"/>
</svg>

After

Width:  |  Height:  |  Size: 927 B

@ -856,5 +856,23 @@
"onboardingHeading1": "حجز المواعيد لم يكن أسهل من قبل",
"onboardingBody1": "ببضع نقرات فقط يمكنك استشارة الطبيب الذي تختاره.",
"onboardingHeading2": "الوصول إلى السجل الطبي بين يديك",
"onboardingBody2": "تتبع تاريخك الطبي بما في ذلك الفحوصات المخبرية، الوصفات الطبية، التأمين، وغيرها."
"onboardingBody2": "تتبع تاريخك الطبي بما في ذلك الفحوصات المخبرية، الوصفات الطبية، التأمين، وغيرها.",
"hmgHospitals": "مستشفيات HMG",
"hmcMedicalClinic": "مراكز HMC الطبية",
"applyFilter": "تطبيق الفلتر",
"facilityAndLocation": "المرفق والموقع",
"regionAndLocation": "المنطقة والمواقع",
"clearAllFilters": "مسح جميع الفلاتر",
"filters": "فلاتر",
"searchClinic": "بحث عن عيادة",
"normal": "عادي",
"attention": "انتباه",
"monitor": "مراقبة",
"noSpecialResult": "لا توجد نتائج خاصة",
"setTheDateRange": "تعيين النطاق الزمني",
"historyFlowchart": "مخطط تدفق التاريخ",
"to": "إلى",
"startDate": "تاريخ البدء",
"endDate": "تاريخ الانتهاء",
"walkin": "زيارة بدون موعد"
}

@ -852,5 +852,24 @@
"onboardingHeading1": "Booking appointment has never been easy",
"onboardingBody1": "In few clicks find yourself having consultation with the doctor of your choice.",
"onboardingHeading2": "Access the medical history on finger tips",
"onboardingBody2": "Keep track on your medical history including labs, prescription, insurance, etc"
"onboardingBody2": "Keep track on your medical history including labs, prescription, insurance, etc",
"normal": "Normal",
"attention": "Attention",
"monitor": "Monitor",
"noSpecialResult": "No Special Results",
"setTheDateRange": "Set The Date Range",
"historyFlowchart": "History FlowChart",
"to": "to",
"startDate" : "Start Date",
"endDate": "End Date",
"hmgHospitals": "HMG Hospitals",
"hmcMedicalClinic": "HMC Medical Centers",
"applyFilter": "AppLy Filter",
"facilityAndLocation": "Facility and Location",
"regionAndLocation": "Region And Locations",
"clearAllFilters": "Clear all filters",
"filters": "Filters",
"searchClinic": "Search Clinic",
"walkin": "Walk In"
}

@ -10,12 +10,12 @@
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
40721B3F0DC8DB0598443DBF /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E824BDA50EF77318022F59D /* Pods_Runner.framework */; };
478CFA942E638C8E0064F3D7 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 478CFA932E638C8E0064F3D7 /* GoogleService-Info.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
B976FB9C47411C32B24D5E01 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ACE60DF9393168FD748550B3 /* Pods_Runner.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -44,16 +44,17 @@
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
1E824BDA50EF77318022F59D /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
478CFA932E638C8E0064F3D7 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
478CFA952E6E20A60064F3D7 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
62D069322AC3B532E0B4F137 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7595037DD52211B91157B0F3 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
769C9BF82E6F106D009F68A9 /* RunnerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerDebug.entitlements; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
8E12CEEB8E334EE22D5259D7 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
@ -61,8 +62,8 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
B35471C63C8DD1B1DECDB3A5 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
D0FB40CE52522242F351743B /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
ACE60DF9393168FD748550B3 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D6BB17A036DF7FCE75271203 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -70,7 +71,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
40721B3F0DC8DB0598443DBF /* Pods_Runner.framework in Frameworks */,
B976FB9C47411C32B24D5E01 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -88,9 +89,9 @@
79DD2093A1D9674C94359FC8 /* Pods */ = {
isa = PBXGroup;
children = (
62D069322AC3B532E0B4F137 /* Pods-Runner.debug.xcconfig */,
D0FB40CE52522242F351743B /* Pods-Runner.release.xcconfig */,
B35471C63C8DD1B1DECDB3A5 /* Pods-Runner.profile.xcconfig */,
8E12CEEB8E334EE22D5259D7 /* Pods-Runner.debug.xcconfig */,
7595037DD52211B91157B0F3 /* Pods-Runner.release.xcconfig */,
D6BB17A036DF7FCE75271203 /* Pods-Runner.profile.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
@ -114,7 +115,7 @@
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
79DD2093A1D9674C94359FC8 /* Pods */,
C1312CE4ABEB9ED47FF21174 /* Frameworks */,
A07D637C76A0ABB38659D189 /* Frameworks */,
);
sourceTree = "<group>";
};
@ -130,6 +131,7 @@
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
769C9BF82E6F106D009F68A9 /* RunnerDebug.entitlements */,
478CFA952E6E20A60064F3D7 /* Runner.entitlements */,
478CFA932E638C8E0064F3D7 /* GoogleService-Info.plist */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
@ -144,10 +146,10 @@
path = Runner;
sourceTree = "<group>";
};
C1312CE4ABEB9ED47FF21174 /* Frameworks */ = {
A07D637C76A0ABB38659D189 /* Frameworks */ = {
isa = PBXGroup;
children = (
1E824BDA50EF77318022F59D /* Pods_Runner.framework */,
ACE60DF9393168FD748550B3 /* Pods_Runner.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@ -176,15 +178,15 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
E3E41B3FF6C30233949963A9 /* [CP] Check Pods Manifest.lock */,
BFED6CCFE59BB148875A533B /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
968C3FBB62CE1E0E307ECBCE /* [CP] Embed Pods Frameworks */,
CC3E3923FDE40035D0CA6762 /* [CP] Copy Pods Resources */,
8372B02399CDF54531650AD4 /* [CP] Embed Pods Frameworks */,
81DE7C26F41956799E954FCE /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@ -273,7 +275,24 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
968C3FBB62CE1E0E307ECBCE /* [CP] Embed Pods Frameworks */ = {
81DE7C26F41956799E954FCE /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
8372B02399CDF54531650AD4 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@ -305,24 +324,7 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
CC3E3923FDE40035D0CA6762 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
E3E41B3FF6C30233949963A9 /* [CP] Check Pods Manifest.lock */ = {
BFED6CCFE59BB148875A533B /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@ -635,7 +637,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements;
CURRENT_PROJECT_VERSION = 2;
DEVELOPMENT_TEAM = 3A359E86ZF;
ENABLE_BITCODE = NO;

@ -18,25 +18,26 @@ abstract class ApiClient {
static final NavigationService _navigationService = getIt.get<NavigationService>();
Future<void> post(
String endPoint, {
required Map<String, dynamic> body,
required Function(dynamic response, int statusCode, {int? messageStatus, String? errorMessage}) onSuccess,
required Function(String error, int statusCode, {int? messageStatus, Failure? failureType}) onFailure,
bool isAllowAny,
bool isExternal,
bool isRCService,
bool isPaymentServices,
bool bypassConnectionCheck,
});
String endPoint, {
required Map<String, dynamic> body,
required Function(dynamic response, int statusCode, {int? messageStatus, String? errorMessage}) onSuccess,
required Function(String error, int statusCode, {int? messageStatus, Failure? failureType}) onFailure,
bool isAllowAny,
bool isExternal,
bool isRCService,
bool isPaymentServices,
bool bypassConnectionCheck,
});
Future<void> get(
String endPoint, {
required Function(dynamic response, int statusCode) onSuccess,
required Function(String error, int statusCode) onFailure,
Map<String, dynamic>? queryParams,
bool isExternal,
bool isRCService,
});
String endPoint, {
required Function(dynamic response, int statusCode) onSuccess,
required Function(String error, int statusCode) onFailure,
Map<String, dynamic>? queryParams,
bool isAllowAny,
bool isExternal,
bool isRCService,
});
String getSessionId(String id);
@ -173,7 +174,11 @@ class ApiClientImp implements ApiClient {
}
// body['TokenID'] = "@dm!n";
// body['PatientID'] = 4767477;
// body['PatientID'] = 4767884;
// body['PatientTypeID'] = 1;
//
// body['PatientOutSA'] = 0;
// body['SessionID'] = "45786230487560q";
}
body.removeWhere((key, value) => value == null);
@ -322,10 +327,11 @@ class ApiClientImp implements ApiClient {
@override
get(String endPoint,
{required Function(dynamic response, int statusCode) onSuccess,
required Function(String error, int statusCode) onFailure,
Map<String, dynamic>? queryParams,
bool isExternal = false,
bool isRCService = false}) async {
required Function(String error, int statusCode) onFailure,
Map<String, dynamic>? queryParams,
bool isAllowAny = false,
bool isExternal = false,
bool isRCService = false}) async {
String url;
if (isExternal) {
url = endPoint;

@ -119,7 +119,7 @@ var GET_STATUS_FOR_COCO = 'Services/COCWS.svc/REST/GetStatusforCOC';
// var GET_PATIENT_AppointmentHistory = 'Services'
// '/Doctors.svc/REST/PateintHasAppoimentHistory';
var GET_PATIENT_AppointmentHistory = 'Services'
var GET_PATIENT_APPOINTMENT_HISTORY_ASYNC = 'Services'
'/Doctors.svc/REST/PateintHasAppoimentHistory_Async';
///VITAL SIGN
@ -743,6 +743,12 @@ class ApiConsts {
static String SERVICE_URL = 'https://hmgwebservices.com/PayFortWebLive/pages/SendPayFortRequest.aspx'; //Payfort Payment Gateway URL LIVE
// static String SERVICE_URL = 'https://hmgwebservices.com/PayFortWeb/pages/SendPayFortRequest.aspx'; // Payfort Payment Gateway URL UAT
static String TAMARA_URL = "https://mdlaboratories.com/tamaralive/Home/Checkout";
static String GET_TAMARA_INSTALLMENTS_URL = "https://mdlaboratories.com/tamaralive/Home/GetInstallments";
static String GET_TAMARA_PAYMENT_STATUS = 'https://mdlaboratories.com/tamaralive/api/OnlineTamara/order_status?orderid=';
// static String GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments";
// var payFortEnvironment = FortEnvironment.test;
// var applePayMerchantId = "merchant.com.hmgwebservices.uat";
@ -753,37 +759,55 @@ class ApiConsts {
payFortEnvironment = FortEnvironment.production;
applePayMerchantId = "merchant.com.hmgwebservices";
SERVICE_URL = "https://hmgwebservices.com/PayFortWebLive/pages/SendPayFortRequest.aspx";
TAMARA_URL = "https://mdlaboratories.com/tamaralive/Home/Checkout";
GET_TAMARA_INSTALLMENTS_URL = "https://mdlaboratories.com/tamaralive/Home/GetInstallments";
GET_TAMARA_PAYMENT_STATUS = 'https://mdlaboratories.com/tamaralive/api/OnlineTamara/order_status?orderid=';
break;
case AppEnvironmentTypeEnum.dev:
baseUrl = "https://uat.hmgwebservices.com/";
payFortEnvironment = FortEnvironment.test;
applePayMerchantId = "merchant.com.hmgwebservices.uat";
SERVICE_URL = 'https://hmgwebservices.com/PayFortWeb/pages/SendPayFortRequest.aspx';
TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout";
GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments";
GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid=';
break;
case AppEnvironmentTypeEnum.uat:
baseUrl = "https://uat.hmgwebservices.com/";
payFortEnvironment = FortEnvironment.test;
applePayMerchantId = "merchant.com.hmgwebservices.uat";
SERVICE_URL = 'https://hmgwebservices.com/PayFortWeb/pages/SendPayFortRequest.aspx';
TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout";
GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments";
GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid=';
break;
case AppEnvironmentTypeEnum.preProd:
baseUrl = "https://webservices.hmg.com/";
payFortEnvironment = FortEnvironment.production;
applePayMerchantId = "merchant.com.hmgwebservices";
SERVICE_URL = "https://hmgwebservices.com/PayFortWebLive/pages/SendPayFortRequest.aspx";
TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout";
GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments";
GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid=';
break;
case AppEnvironmentTypeEnum.qa:
baseUrl = "https://uat.hmgwebservices.com/";
payFortEnvironment = FortEnvironment.test;
applePayMerchantId = "merchant.com.hmgwebservices.uat";
SERVICE_URL = 'https://hmgwebservices.com/PayFortWeb/pages/SendPayFortRequest.aspx';
TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout";
GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments";
GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid=';
break;
case AppEnvironmentTypeEnum.staging:
baseUrl = "https://uat.hmgwebservices.com/";
payFortEnvironment = FortEnvironment.test;
applePayMerchantId = "merchant.com.hmgwebservices.uat";
SERVICE_URL = 'https://hmgwebservices.com/PayFortWeb/pages/SendPayFortRequest.aspx';
TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout";
GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments";
GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid=';
break;
}
}

@ -145,6 +145,13 @@ class AppAssets {
static const String ic_normal_result = '$svgBasePath/normal_result.svg';
static const String ic_low_result = '$svgBasePath/low_result.svg';
static const String ic_critical_low_result = '$svgBasePath/critical_low_result.svg';
static const String livecare_online_icon = '$svgBasePath/livecare_online_icon.svg';
static const String edit_icon = '$svgBasePath/edit_icon.svg';
static const String waiting_icon = '$svgBasePath/waiting_icon.svg';
static const String forward_arrow_icon_small = '$svgBasePath/forward_arrow_icon_small.svg';
static const String ic_filters = '$svgBasePath/filters.svg';
static const String ic_close = '$svgBasePath/ic_close.svg';
static const String ic_cross_circle = '$svgBasePath/cross_circle.svg';
static const String switch_user = '$svgBasePath/switch_user.svg';
static const String activeCheck = '$svgBasePath/active-check.svg';
static const String deleteIcon = '$svgBasePath/delete_icon.svg';
@ -190,4 +197,6 @@ class AppAnimations {
static const String warningAnimation = '$lottieBasePath/warningAnimation.json';
static const String splashLaunching = '$lottieBasePath/splash_launching.json';
static const String noData = '$lottieBasePath/Nodata.json';
static const String ripple = '$lottieBasePath/Ripple.json';
static const String pending_loading_animation = '$lottieBasePath/pending_loading_animation.json';
}

@ -10,6 +10,8 @@ import 'package:hmg_patient_app_new/features/book_appointments/book_appointments
import 'package:hmg_patient_app_new/features/common/common_repo.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/immediate_livecare/immediate_livecare_repo.dart';
import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart';
import 'package:hmg_patient_app_new/features/insurance/insurance_repo.dart';
import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart';
import 'package:hmg_patient_app_new/features/lab/lab_repo.dart';
@ -92,6 +94,7 @@ class AppDependencies {
getIt.registerLazySingleton<LocalAuthService>(() => LocalAuthService(loggerService: getIt<LoggerService>(), localAuth: getIt<LocalAuthentication>()));
getIt.registerLazySingleton<HabibWalletRepo>(() => HabibWalletRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<MedicalFileRepo>(() => MedicalFileRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<ImmediateLiveCareRepo>(() => ImmediateLiveCareRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
// ViewModels
// Global/shared VMs LazySingleton
@ -150,6 +153,15 @@ class AppDependencies {
() => BookAppointmentsViewModel(bookAppointmentsRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt(), myAppointmentsViewModel: getIt(), locationUtils: getIt()),
);
getIt.registerLazySingleton<ImmediateLiveCareViewModel>(
() => ImmediateLiveCareViewModel(
immediateLiveCareRepo: getIt(),
errorHandlerService: getIt(),
navigationService: getIt(),
myAppointmentsViewModel: getIt(),
),
);
getIt.registerLazySingleton<AuthenticationViewModel>(
() => AuthenticationViewModel(
authenticationRepo: getIt(), cacheService: getIt(), navigationService: getIt(), dialogService: getIt(), appState: getIt(), errorHandlerService: getIt(), localAuthService: getIt()),

@ -485,3 +485,11 @@ class DateUtil {
return "";
}
}
extension OnlyDate on DateTime{
DateTime provideDateOnly(){
return DateTime(this.year, month, day);
}
}

@ -8,11 +8,14 @@ import 'package:firebase_messaging/firebase_messaging.dart' as fir;
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_ios_voip_kit_karmm/call_state_type.dart';
import 'package:flutter_ios_voip_kit_karmm/flutter_ios_voip_kit.dart';
// import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:hmg_patient_app_new/core/utils/local_notifications.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart';
import 'package:permission_handler/permission_handler.dart';
import '../cache_consts.dart';
@ -139,7 +142,7 @@ class PushNotificationHandler {
// late HmsApiAvailability hmsApiAvailability;
// final voIPKit = FlutterIOSVoIPKit.instance;
final voIPKit = FlutterIOSVoIPKit.instance;
late Timer timeOutTimer;
bool isTalking = false;
@ -188,55 +191,56 @@ class PushNotificationHandler {
this.context = context;
if (Platform.isIOS) {
// voIPKit.getVoIPToken().then((value) {
// print("APNS VOIP KIT TOKEN: $value");
// AppSharedPreferences().setString(APNS_TOKEN, value!);
// });
//
// voIPKit.onDidUpdatePushToken = (String token) {
// print('🎈 example: onDidUpdatePushToken: $token');
// };
//
// voIPKit.onDidReceiveIncomingPush = (
// Map<String, dynamic> payload,
// ) async {
// print('🎈 example: onDidReceiveIncomingPush $payload');
// _timeOut();
// };
//
// voIPKit.onDidRejectIncomingCall = (
// String uuid,
// String callerId,
// ) async {
// try {
// print('🎈 example: onDidRejectIncomingCall $uuid - $callerId');
// timeOutTimer.cancel();
// } catch (err) {}
// };
//
// voIPKit.onDidAcceptIncomingCall = (
// String uuid,
// String callerId,
// ) async {
// print('🎈 example: onDidAcceptIncomingCall $uuid - $callerId');
// await voIPKit.acceptIncomingCall(callerState: CallStateType.calling);
// await voIPKit.callConnected();
// await Future.delayed(Duration(seconds: 1));
//
// Navigator.pushNamed(
// locator<NavigationService>().navigatorKey.currentContext!,
// "zoom_call_page",
// arguments: CallArguments("hoover-dam", "123", "Patient", "40", "1", false),
// );
//
// await voIPKit.endCall();
//
// // Navigator.pushNamed(navigatorKey.currentContext!, VIDEO_CALL_SCREEN,
// // arguments: VideoArgus(
// // reservationId: int.parse(callerId), token: null, isVideo: true));
//
// timeOutTimer.cancel();
// };
voIPKit.getVoIPToken().then((value) {
print("🎈 APNS VOIP KIT TOKEN: $value");
Utils.saveStringFromPrefs(CacheConst.voipToken, value ?? "");
// AppSharedPreferences().setString(APNS_TOKEN, value!);
});
voIPKit.onDidUpdatePushToken = (String token) {
print('🎈 example: onDidUpdatePushToken: $token');
};
voIPKit.onDidReceiveIncomingPush = (
Map<String, dynamic> payload,
) async {
print('🎈 example: onDidReceiveIncomingPush $payload');
// _timeOut();
};
voIPKit.onDidRejectIncomingCall = (
String uuid,
String callerId,
) async {
try {
print('🎈 example: onDidRejectIncomingCall $uuid - $callerId');
timeOutTimer.cancel();
} catch (err) {}
};
voIPKit.onDidAcceptIncomingCall = (
String uuid,
String callerId,
) async {
print('🎈 example: onDidAcceptIncomingCall $uuid - $callerId');
await voIPKit.acceptIncomingCall(callerState: CallStateType.calling);
await voIPKit.callConnected();
await Future.delayed(Duration(seconds: 1));
// Navigator.pushNamed(
// locator<NavigationService>().navigatorKey.currentContext!,
// "zoom_call_page",
// arguments: CallArguments("hoover-dam", "123", "Patient", "40", "1", false),
// );
await voIPKit.endCall();
// Navigator.pushNamed(navigatorKey.currentContext!, VIDEO_CALL_SCREEN,
// arguments: VideoArgus(
// reservationId: int.parse(callerId), token: null, isVideo: true));
// timeOutTimer.cancel();
};
}
if (Platform.isAndroid) {

@ -1,8 +1,8 @@
import 'package:flutter/material.dart'; // These are the Viewport values of your Figma Design.
// These are used in the code as a reference to create your UI Responsively.
const num FIGMA_DESIGN_WIDTH = 375;
const num FIGMA_DESIGN_HEIGHT = 667;
final num FIGMA_DESIGN_WIDTH = SizeUtils.width;
final num FIGMA_DESIGN_HEIGHT = SizeUtils.height;
const num FIGMA_DESIGN_STATUS_BAR = 0;
extension ResponsiveExtension on num {
@ -70,10 +70,10 @@ class SizeUtils {
static late DeviceType deviceType;
/// Device's Height
static late double height;
static double height = 667;
/// Device's Width
static late double width;
static double width = 375;
static void setScreenSize(
BoxConstraints constraints,

@ -323,7 +323,7 @@ class Utils {
children: [
Lottie.asset(AppAnimations.loadingAnimation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill),
SizedBox(height: 8.h),
(loadingText ?? LocaleKeys.loadingText.tr()).toText16(color: AppColors.blackColor),
(loadingText ?? LocaleKeys.loadingText.tr()).toText16(color: AppColors.blackColor, isCenter: true),
SizedBox(height: 8.h),
],
).center;
@ -355,15 +355,17 @@ class Utils {
).center;
}
static Widget getWarningWidget({String? loadingText, bool isShowActionButtons = false, Function? onConfirmTap, Function? onCancelTap}) {
static Widget getWarningWidget({String? loadingText, bool isShowActionButtons = false, Widget? bodyWidget, Function? onConfirmTap, Function? onCancelTap}) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Lottie.asset(AppAnimations.warningAnimation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 128.h, height: 128.h, fit: BoxFit.fill),
Lottie.asset(AppAnimations.warningAnimation, repeat: false, reverse: false, frameRate: FrameRate(60), width: 128.h, height: 128.h, fit: BoxFit.fill),
SizedBox(height: 8.h),
(loadingText ?? LocaleKeys.loadingText.tr()).toText14(color: AppColors.blackColor, letterSpacing: 0),
SizedBox(height: 16.h),
bodyWidget ?? SizedBox.shrink(),
SizedBox(height: 16.h),
isShowActionButtons
? Row(
children: [
@ -667,7 +669,6 @@ class Utils {
static Widget getPaymentAmountWithSymbol(Widget paymentAmountWidget, Color iconColor, double iconSize, {bool isSaudiCurrency = true, bool isExpanded = true}) {
return Row(
mainAxisAlignment: isExpanded ? MainAxisAlignment.spaceBetween : MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
appState.isArabic()
? Container()
@ -765,4 +766,5 @@ class Utils {
}
return isHavePrivilege;
}
}

@ -1,13 +1,17 @@
import 'dart:io';
import 'package:dartz/dartz.dart';
import 'package:hmg_patient_app_new/core/api/api_client.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/dental_chief_complaints_response_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctor_profile_response_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_livecare_clinics_response_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_patient_dental_plan_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
@ -21,7 +25,7 @@ abstract class BookAppointmentsRepo {
Future<Either<Failure, GenericApiModel<DoctorsProfileResponseModel>>> getDoctorProfile(int clinicID, int projectID, int doctorId, {Function(dynamic)? onSuccess, Function(String)? onError});
Future<Either<Failure, GenericApiModel<dynamic>>> getDoctorFreeSlots(int clinicID, int projectID, int doctorId, bool isBookingForLiveCare, {Function(dynamic)? onSuccess, Function(String)? onError});
Future<Either<Failure, GenericApiModel<dynamic>>> getDoctorFreeSlots(int clinicID, int projectID, int doctorId, bool isBookingForLiveCare, {bool continueDentalPlan = false, Function(dynamic)? onSuccess, Function(String)? onError});
Future<Either<Failure, GenericApiModel<dynamic>>> cancelAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel});
@ -66,6 +70,15 @@ abstract class BookAppointmentsRepo {
required int serviceID,
Function(dynamic)? onSuccess,
Function(String)? onError});
Future<Either<Failure, GenericApiModel<List<PatientDentalPlanEstimationResponseModel>>>> getPatientDentalEstimation(
{required int projectID, Function(dynamic)? onSuccess, Function(String)? onError});
Future<Either<Failure, GenericApiModel<List<DentalChiefComplaintsListResponseModel>>>> getDentalChiefComplaintsList(
{required int projectID, required int clinicID, required int patientID, Function(dynamic)? onSuccess, Function(String)? onError});
Future<Either<Failure, GenericApiModel<List<DoctorsListResponseModel>>>> getDentalChiefComplaintDoctorsList(int projectID, int chiefComplaintID,
{Function(dynamic)? onSuccess, Function(String)? onError});
}
class BookAppointmentsRepoImp implements BookAppointmentsRepo {
@ -149,9 +162,6 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo {
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['DoctorList'];
// if (list == null || list.isEmpty) {
// throw Exception("lab list is empty");
// }
final doctorsList = list.map((item) => DoctorsListResponseModel.fromJson(item as Map<String, dynamic>)).toList().cast<DoctorsListResponseModel>();
@ -226,7 +236,7 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo {
//TODO: Implement the logic for Dental & laser clinics
@override
Future<Either<Failure, GenericApiModel<dynamic>>> getDoctorFreeSlots(int clinicID, int projectID, int doctorId, bool isBookingForLiveCare,
{Function(dynamic)? onSuccess, Function(String)? onError}) async {
{bool continueDentalPlan = false, Function(dynamic)? onSuccess, Function(String)? onError}) async {
Map<String, dynamic> mapDevice = {
"DoctorID": doctorId,
"IsBookingForLiveCare": isBookingForLiveCare,
@ -235,6 +245,7 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo {
"OriginalClinicID": clinicID,
"days": 0,
"isReschadual": false,
"ContinueDentalPlan": continueDentalPlan
};
try {
@ -657,4 +668,136 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<List<PatientDentalPlanEstimationResponseModel>>>> getPatientDentalEstimation(
{required int projectID, Function(dynamic)? onSuccess, Function(String)? onError}) async {
Map<String, dynamic> mapDevice = {
"ProjectID": projectID,
};
try {
GenericApiModel<List<PatientDentalPlanEstimationResponseModel>>? apiResponse;
Failure? failure;
await apiClient.post(
HAS_DENTAL_PLAN,
body: mapDevice,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
onError!(error);
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['List_IsPatientHasOnGoingEstimation'];
final estimationList = list.map((item) => PatientDentalPlanEstimationResponseModel.fromJson(item as Map<String, dynamic>)).toList().cast<PatientDentalPlanEstimationResponseModel>();
apiResponse = GenericApiModel<List<PatientDentalPlanEstimationResponseModel>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: estimationList,
);
} 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<DentalChiefComplaintsListResponseModel>>>> getDentalChiefComplaintsList(
{required int projectID, required int clinicID, required int patientID, Function(dynamic)? onSuccess, Function(String)? onError}) async {
Map<String, dynamic> mapDevice = {
"PatientID": patientID,
"ClinicID": clinicID,
"ProjectID": projectID,
"isDentalAllowedBackend": true,
"ContinueDentalPlan": false,
"IsSearchAppointmnetByClinicID": false,
};
try {
GenericApiModel<List<DentalChiefComplaintsListResponseModel>>? apiResponse;
Failure? failure;
await apiClient.post(
GET_DOCTORS_LIST_URL,
body: mapDevice,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
onError!(error);
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['List_DentalChiefComplain'];
final chiefComplaintsList = list.map((item) => DentalChiefComplaintsListResponseModel.fromJson(item as Map<String, dynamic>)).toList().cast<DentalChiefComplaintsListResponseModel>();
apiResponse = GenericApiModel<List<DentalChiefComplaintsListResponseModel>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: chiefComplaintsList,
);
} 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<DoctorsListResponseModel>>>> getDentalChiefComplaintDoctorsList(int projectID, int chiefComplaintID,
{Function(dynamic)? onSuccess, Function(String)? onError}) async {
Map<String, dynamic> mapDevice = {
"ProjectID": projectID,
"ChiefComplaintID": chiefComplaintID,
"isDentalAllowedBackend": true,
"IsPublicRequest": true,
};
try {
GenericApiModel<List<DoctorsListResponseModel>>? apiResponse;
Failure? failure;
await apiClient.post(
GET_DENTAL_DOCTORS_LIST_URL,
body: mapDevice,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
onError!(error);
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['List_DentalDoctorChiefComplaintMapping'];
final doctorsList = list.map((item) => DoctorsListResponseModel.fromJson(item as Map<String, dynamic>)).toList().cast<DoctorsListResponseModel>();
apiResponse = GenericApiModel<List<DoctorsListResponseModel>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: doctorsList,
);
} 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()));
}
}
}

@ -12,9 +12,11 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_repo.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/free_slot.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/dental_chief_complaints_response_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctor_profile_response_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_patient_dental_plan_response_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/timeslots.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';
@ -26,8 +28,6 @@ import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart';
import 'package:location/location.dart' show Location;
import 'models/resp_models/get_livecare_clinics_response_model.dart';
@ -53,9 +53,17 @@ class BookAppointmentsViewModel extends ChangeNotifier {
List<GetClinicsListResponseModel> get filteredClinicsList => _filteredClinicsList;
List<DoctorsListResponseModel> doctorsList = [];
List<DoctorsListResponseModel> filteredDoctorList = [];
List<DoctorsListResponseModel> liveCareDoctorsList = [];
List<PatientDentalPlanEstimationResponseModel> patientDentalPlanEstimationList = [];
List<DentalChiefComplaintsListResponseModel> dentalChiefComplaintsList = [];
int totalTimeNeededForDentalProcedure = 0;
bool isContinueDentalPlan = false;
bool isChiefComplaintsListLoading = false;
int selectedChiefComplaintID = 0;
GetClinicsListResponseModel selectedClinic = GetClinicsListResponseModel();
DoctorsListResponseModel selectedDoctor = DoctorsListResponseModel();
GetLiveCareClinicsResponseModel selectedLiveCareClinic = GetLiveCareClinicsResponseModel();
@ -87,9 +95,21 @@ class BookAppointmentsViewModel extends ChangeNotifier {
bool shouldLoadSpecificClinic = false;
String? currentlySelectedHospitalFromRegionFlow;
///variables for doctor filter
List<String> searchedRegionList = [];
List<String> facilityList = ["hmgHospitals", "hmcMedicalClinic"];
List<String> searchedHospitalList = [];
List<PatientDoctorAppointmentList>
searchedPatientDoctorAppointmentHospitalsList = [];
List<String> searchedClinicList = [];
PatientDoctorAppointmentList? selectedHospitalForFilters;
List<String>? selectedFacilityForFilters = [], selectedRegionForFilters = [];
String? selectedClinicForFilters;
bool applyFilters = false;
BookAppointmentsViewModel(
{required this.bookAppointmentsRepo, required this.errorHandlerService, required this.navigationService, required this.myAppointmentsViewModel, required this.locationUtils}) {
;
initBookAppointmentViewModel();
}
@ -117,6 +137,10 @@ class BookAppointmentsViewModel extends ChangeNotifier {
clinicsList.clear();
doctorsList.clear();
liveCareClinicsList.clear();
patientDentalPlanEstimationList.clear();
dentalChiefComplaintsList.clear();
isContinueDentalPlan = false;
isChiefComplaintsListLoading = true;
// getLocation();
notifyListeners();
}
@ -178,6 +202,21 @@ class BookAppointmentsViewModel extends ChangeNotifier {
notifyListeners();
}
setIsContinueDentalPlan(bool value) {
isContinueDentalPlan = value;
notifyListeners();
}
setIsChiefComplaintsListLoading(bool value) {
isChiefComplaintsListLoading = value;
notifyListeners();
}
setSelectedChiefComplaintID(int id) {
selectedChiefComplaintID = id;
notifyListeners();
}
void onTabChanged(int index) {
selectedTabIndex = index;
notifyListeners();
@ -264,11 +303,10 @@ class BookAppointmentsViewModel extends ChangeNotifier {
}
//TODO: Make the API dynamic with parameters for ProjectID, isNearest, languageID, doctorId, doctorName
Future<void> getDoctorsList(
{int projectID = 0, bool isNearest = false, int doctorId = 0, String doctorName = "", isContinueDentalPlan = false, Function(dynamic)? onSuccess, Function(String)? onError}) async {
Future<void> getDoctorsList({int projectID = 0, bool isNearest = false, int doctorId = 0, String doctorName = "", Function(dynamic)? onSuccess, Function(String)? onError}) async {
doctorsList.clear();
projectID = currentlySelectedHospitalFromRegionFlow != null ? int.parse(currentlySelectedHospitalFromRegionFlow!) : projectID;
final result = await bookAppointmentsRepo.getDoctorsList(selectedClinic.clinicID ?? 0, projectID, isNearest, doctorId, doctorName);
final result = await bookAppointmentsRepo.getDoctorsList(selectedClinic.clinicID ?? 0, projectID, isNearest, doctorId, doctorName, isContinueDentalPlan: isContinueDentalPlan);
result.fold(
(failure) async {
@ -279,8 +317,11 @@ class BookAppointmentsViewModel extends ChangeNotifier {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
doctorsList = apiResponse.data!;
filteredDoctorList = doctorsList;
isDoctorsListLoading = false;
initializeFilteredList();
clearSearchFilters();
getFiltersFromDoctorList();
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
@ -353,7 +394,14 @@ class BookAppointmentsViewModel extends ChangeNotifier {
final DateFormat dateFormatter = DateFormat('yyyy-MM-dd');
Map<DateTime, List> _eventsParsed;
final result = await bookAppointmentsRepo.getDoctorFreeSlots(selectedDoctor.clinicID ?? 0, selectedDoctor.projectID ?? 0, selectedDoctor.doctorID ?? 0, isBookingForLiveCare, onError: onError);
final result = await bookAppointmentsRepo.getDoctorFreeSlots(
selectedDoctor.clinicID ?? 0,
selectedDoctor.projectID ?? 0,
selectedDoctor.doctorID ?? 0,
isBookingForLiveCare,
continueDentalPlan: isContinueDentalPlan,
onError: onError,
);
result.fold(
(failure) async {
@ -757,4 +805,216 @@ class BookAppointmentsViewModel extends ChangeNotifier {
void getLocation() {
locationUtils.getLocation();
}
void clearSearchFilters() {
searchedRegionList.clear();
searchedHospitalList.clear();
searchedPatientDoctorAppointmentHospitalsList.clear();
searchedClinicList.clear();
notifyListeners();
}
void clearSelection() {
selectedFacilityForFilters = [];
selectedClinicForFilters = null;
selectedHospitalForFilters = null;
selectedRegionForFilters = [];
applyFilters = false;
notifyListeners();
}
void setSelections(
List<String>? selectedFacilityForFilters,
List<String>? selectedRegionForFilters,
String? selectedClinicForFilters,
PatientDoctorAppointmentList? selectedHospitalForFilters,
bool applyFilters) {
this.selectedFacilityForFilters = selectedFacilityForFilters;
this.selectedClinicForFilters = selectedClinicForFilters;
this.selectedHospitalForFilters = selectedHospitalForFilters;
this.selectedRegionForFilters = selectedRegionForFilters;
this.applyFilters = applyFilters;
notifyListeners();
}
void getFiltersFromDoctorList() {
doctorsList.forEach((element) {
if (!searchedRegionList
.contains(element.getRegionName(_appState.isArabic()))) {
searchedRegionList
.add(element.getRegionName(_appState.isArabic()) ?? "");
}
if (!searchedHospitalList.contains(element.projectName)) {
searchedPatientDoctorAppointmentHospitalsList
.add(PatientDoctorAppointmentList()
..filterName = element.projectName
..isHMC = element.isHMC
..distanceInKMs = "0");
searchedHospitalList.add(element.projectName ?? "");
}
if (!searchedClinicList.contains(element.clinicName)) {
searchedClinicList.add(element.clinicName ?? "");
}
});
}
void updateApplyFilters(bool applyFilters) {
this.applyFilters = applyFilters;
notifyListeners();
}
void setSelectedRegion(String region) {
if (selectedRegionForFilters?.contains(region) == true) {
selectedRegionForFilters?.remove(region);
} else {
selectedRegionForFilters?.add(region);
}
notifyListeners();
}
void setSelectedHospital(PatientDoctorAppointmentList? hospital) {
selectedHospitalForFilters = hospital;
notifyListeners();
}
void setSelectedFacilityForFilter(String facility) {
if (selectedFacilityForFilters?.contains(facility) == true) {
selectedFacilityForFilters?.remove(facility);
} else {
selectedFacilityForFilters?.add(facility);
}
notifyListeners();
}
void setSelectedClinicForFilter(String? clinic) {
selectedClinicForFilters = clinic;
notifyListeners();
}
bool isArabic() {
return _appState.isArabic();
}
List<DoctorsListResponseModel> getDoctorListAsPerSelection() {
if (!applyFilters) return doctorsList;
if ((selectedRegionForFilters?.isEmpty == true) &&
(selectedFacilityForFilters?.isEmpty == true) &&
selectedClinicForFilters == null &&
selectedHospitalForFilters == null) {
return doctorsList;
}
var list = doctorsList.where((element) {
var isInSelectedRegion = (selectedRegionForFilters?.isEmpty == true)
? true
: selectedRegionForFilters
?.any((region) => region == element.getRegionName(isArabic()));
var shouldApplyFacilityFilter =
(selectedFacilityForFilters?.isEmpty == true) ? false : true;
var isHMC = (selectedFacilityForFilters?.isEmpty == true)
? true
: selectedFacilityForFilters?.any((item) => item.contains("hmc"));
var isInSelectedClinic = (selectedClinicForFilters == null)
? true
: selectedClinicForFilters == element.clinicName;
var isInSelectedHospital = (selectedHospitalForFilters == null)
? true
: element.projectName == selectedHospitalForFilters?.filterName;
var facilityFilter = ((shouldApplyFacilityFilter == true) ? isHMC : true);
return (isInSelectedRegion ?? true) &&
(facilityFilter ?? true) &&
isInSelectedClinic &&
isInSelectedHospital;
}).toList();
return list;
}
void updateList() {
filteredDoctorList = getDoctorListAsPerSelection();
notifyListeners();
}
Future<void> getPatientDentalEstimation({required int projectID, Function(dynamic)? onSuccess, Function(String)? onError}) async {
patientDentalPlanEstimationList.clear();
totalTimeNeededForDentalProcedure = 0;
isContinueDentalPlan = false;
notifyListeners();
final result = await bookAppointmentsRepo.getPatientDentalEstimation(projectID: projectID);
result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),
(apiResponse) {
if (apiResponse.messageStatus == 2) {
onError!(apiResponse.errorMessage!);
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
patientDentalPlanEstimationList = apiResponse.data!;
patientDentalPlanEstimationList.forEach((v) {
totalTimeNeededForDentalProcedure += (v.neededTime ?? 0);
});
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
Future<void> getDentalChiefComplaintsList({Function(dynamic)? onSuccess, Function(String)? onError}) async {
dentalChiefComplaintsList.clear();
notifyListeners();
int patientID = _appState.isAuthenticated ? _appState.getAuthenticatedUser()!.patientId ?? -1 : -1;
final result = await bookAppointmentsRepo.getDentalChiefComplaintsList(patientID: patientID, projectID: int.parse(currentlySelectedHospitalFromRegionFlow ?? "0"), clinicID: 17);
result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),
(apiResponse) {
if (apiResponse.messageStatus == 2) {
onError!(apiResponse.errorMessage!);
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
dentalChiefComplaintsList = apiResponse.data!;
isChiefComplaintsListLoading = false;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
Future<void> getDentalChiefComplaintDoctorsList({int projectID = 0, Function(dynamic)? onSuccess, Function(String)? onError}) async {
doctorsList.clear();
projectID = currentlySelectedHospitalFromRegionFlow != null ? int.parse(currentlySelectedHospitalFromRegionFlow!) : projectID;
final result = await bookAppointmentsRepo.getDentalChiefComplaintDoctorsList(projectID, selectedChiefComplaintID);
result.fold(
(failure) async {
onError!("No doctors found for the search criteria...".needTranslation);
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
doctorsList = apiResponse.data!;
filteredDoctorList = doctorsList;
isDoctorsListLoading = false;
// initializeFilteredList();
// clearSearchFilters();
// getFiltersFromDoctorList();
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
}

@ -0,0 +1,24 @@
class DentalChiefComplaintsListResponseModel {
int? projectID;
int? iD;
String? name;
dynamic nameN;
DentalChiefComplaintsListResponseModel({this.projectID, this.iD, this.name, this.nameN});
DentalChiefComplaintsListResponseModel.fromJson(Map<String, dynamic> json) {
projectID = json['ProjectID'];
iD = json['ID'];
name = json['Name'];
nameN = json['NameN'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['ProjectID'] = this.projectID;
data['ID'] = this.iD;
data['Name'] = this.name;
data['NameN'] = this.nameN;
return data;
}
}

@ -0,0 +1,40 @@
class PatientDentalPlanEstimationResponseModel {
dynamic setupID;
dynamic estimationNo;
int? projectID;
String? procedureId;
int? patientID;
int? sequenceNo;
int? neededTime;
String? procedureName;
String? procedureNameN;
PatientDentalPlanEstimationResponseModel(
{this.setupID, this.estimationNo, this.projectID, this.procedureId, this.patientID, this.sequenceNo, this.neededTime, this.procedureName, this.procedureNameN});
PatientDentalPlanEstimationResponseModel.fromJson(Map<String, dynamic> json) {
setupID = json['SetupID'];
estimationNo = json['EstimationNo'];
projectID = json['ProjectID'];
procedureId = json['ProcedureId'];
patientID = json['PatientID'];
sequenceNo = json['sequenceNo'];
neededTime = json['NeededTime'];
procedureName = json['ProcedureName'];
procedureNameN = json['ProcedureNameN'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['SetupID'] = this.setupID;
data['EstimationNo'] = this.estimationNo;
data['ProjectID'] = this.projectID;
data['ProcedureId'] = this.procedureId;
data['PatientID'] = this.patientID;
data['sequenceNo'] = this.sequenceNo;
data['NeededTime'] = this.neededTime;
data['ProcedureName'] = this.procedureName;
data['ProcedureNameN'] = this.procedureNameN;
return data;
}
}

@ -0,0 +1,110 @@
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/features/book_appointments/models/resp_models/doctors_list_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/doctor_list_api_response.dart' show PatientDoctorAppointmentList;
class DoctorFilterViewModel extends ChangeNotifier{
late AppState appState;
DoctorFilterViewModel(){
appState = getIt();
}
List<String> searchedRegionList = [];
List<String> facilityList = ["hmgHospitals", "hmcMedicalClinic"];
List<String> searchedHospitalList = [];
List<PatientDoctorAppointmentList>
searchedPatientDoctorAppointmentHospitalsList = [];
List<String> searchedClinicList = [];
PatientDoctorAppointmentList? selectedHospitalForFilters;
List<String>? selectedFacilityForFilters = [] , selectedRegionForFilters = [];
String? selectedClinicForFilters;
bool applyFilters = false;
void clearSearchFilters() {
searchedRegionList.clear();
searchedHospitalList.clear();
searchedPatientDoctorAppointmentHospitalsList.clear();
searchedClinicList.clear();
notifyListeners();
}
void clearSelection() {
selectedFacilityForFilters = [];
selectedClinicForFilters = null;
selectedHospitalForFilters = null;
selectedRegionForFilters = [];
applyFilters = false;
notifyListeners();
}
void getFiltersFromDoctorList(List<DoctorsListResponseModel> doctorsList) {
doctorsList.forEach((element) {
if (!searchedRegionList
.contains(element.getRegionName(appState.isArabic()))) {
searchedRegionList
.add(element.getRegionName(appState.isArabic()) ?? "");
}
if (!searchedHospitalList.contains(element.projectName)) {
searchedPatientDoctorAppointmentHospitalsList
.add(PatientDoctorAppointmentList()
..filterName = element.projectName
..isHMC = element.isHMC
..distanceInKMs = "0");
searchedHospitalList.add(element.projectName ?? "");
}
if (!searchedClinicList.contains(element.clinicName)) {
searchedClinicList.add(element.clinicName ?? "");
}
});
}
void updateApplyFilters(bool applyFilters) {
this.applyFilters = applyFilters;
notifyListeners();
}
void setSelectedRegion(String region) {
if (selectedRegionForFilters?.contains(region) == true) {
selectedRegionForFilters?.remove(region);
} else {
selectedRegionForFilters?.add(region);
}
notifyListeners();
}
void setSelectedHospital(PatientDoctorAppointmentList? hospital) {
selectedHospitalForFilters = hospital;
notifyListeners();
}
void setSelectedFacilityForFilter(String facility) {
if (selectedFacilityForFilters?.contains(facility) == true) {
selectedFacilityForFilters?.remove(facility);
} else {
selectedFacilityForFilters?.add(facility);
}
notifyListeners();
}
void setSelectedClinicForFilter(String? clinic) {
selectedClinicForFilters = clinic;
notifyListeners();
}
void setSelections(
List<String>? selectedFacilityForFilters,
List<String>? selectedRegionForFilters,
String? selectedClinicForFilters,
PatientDoctorAppointmentList? selectedHospitalForFilters,
bool applyFilters) {
this.selectedFacilityForFilters = selectedFacilityForFilters;
this.selectedClinicForFilters = selectedClinicForFilters;
this.selectedHospitalForFilters = selectedHospitalForFilters;
this.selectedRegionForFilters = selectedRegionForFilters;
this.applyFilters = applyFilters;
notifyListeners();
}
}

@ -0,0 +1,206 @@
import 'dart:io';
import 'package:dartz/dartz.dart';
import 'package:hmg_patient_app_new/core/api/api_client.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
import 'package:hmg_patient_app_new/features/immediate_livecare/models/resp_models/get_livecare_immediate_clinics_response_model.dart';
import 'package:hmg_patient_app_new/features/immediate_livecare/models/resp_models/get_livecare_immediate_fees_response_model.dart';
import 'package:hmg_patient_app_new/features/immediate_livecare/models/resp_models/get_patient_livecare_history_response_model.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
abstract class ImmediateLiveCareRepo {
Future<Either<Failure, GenericApiModel<List<GetLiveCareClinicListResponseModel>>>> getLiveCareImmediateClinicsList(int age, int genderID, {Function(dynamic)? onSuccess, Function(String)? onError});
Future<Either<Failure, GenericApiModel<LiveCareImmediateAppointmentFeesList>>> getLiveCareImmediateAppointmentFees(int age, int genderID, int serviceID,
{Function(dynamic)? onSuccess, Function(String)? onError});
Future<Either<Failure, GenericApiModel<dynamic>>> addNewCallRequestForImmediateLiveCare(
int age, int gender, int serviceID, String clientRequestID, int callTypeID, bool isPharma, String deviceToken, String voipToken,
{Function(dynamic)? onSuccess, Function(String)? onError});
Future<Either<Failure, GenericApiModel<List<PatientLiveCareHistory>>>> getPatientLiveCareHistory({Function(dynamic)? onSuccess, Function(String)? onError});
}
class ImmediateLiveCareRepoImp implements ImmediateLiveCareRepo {
final ApiClient apiClient;
final LoggerService loggerService;
ImmediateLiveCareRepoImp({required this.loggerService, required this.apiClient});
@override
Future<Either<Failure, GenericApiModel<List<GetLiveCareClinicListResponseModel>>>> getLiveCareImmediateClinicsList(int age, int genderID,
{Function(dynamic)? onSuccess, Function(String)? onError}) async {
Map<String, dynamic> mapDevice = {
"Age": age,
"Gender": genderID,
};
try {
GenericApiModel<List<GetLiveCareClinicListResponseModel>>? apiResponse;
Failure? failure;
await apiClient.post(
GET_LIVECARE_CLINICS,
body: mapDevice,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
onError!(error);
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['PatientER_GetClinicsList'];
final clinicsList = list.map((item) => GetLiveCareClinicListResponseModel.fromJson(item as Map<String, dynamic>)).toList().cast<GetLiveCareClinicListResponseModel>();
apiResponse = GenericApiModel<List<GetLiveCareClinicListResponseModel>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: clinicsList,
);
} 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<LiveCareImmediateAppointmentFeesList>>> getLiveCareImmediateAppointmentFees(int age, int genderID, int serviceID,
{Function(dynamic)? onSuccess, Function(String)? onError}) async {
Map<String, dynamic> mapDevice = {
"Age": age,
"Gender": genderID,
"ServiceID": serviceID,
};
try {
GenericApiModel<LiveCareImmediateAppointmentFeesList>? apiResponse;
Failure? failure;
await apiClient.post(
GET_ER_APPOINTMENT_FEES,
body: mapDevice,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
onError!(error);
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final respObject = response['GetERAppointmentFeesList'];
final liveCareFeesObj = LiveCareImmediateAppointmentFeesList.fromJson(respObject);
apiResponse = GenericApiModel<LiveCareImmediateAppointmentFeesList>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: liveCareFeesObj,
);
} 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>> addNewCallRequestForImmediateLiveCare(
int age, int gender, int serviceID, String clientRequestID, int callTypeID, bool isPharma, String deviceToken, String voipToken,
{Function(dynamic p1)? onSuccess, Function(String p1)? onError}) async {
Map<String, dynamic> mapDevice = {
"IsPharmacy": isPharma,
"ErServiceID": serviceID,
"ClientRequestID": clientRequestID,
"DeviceToken": deviceToken,
"VoipToken": voipToken,
"IsFlutter": true,
"DeviceType": Platform.isIOS ? 'iOS' : 'Android',
"Age": age,
"Gender": gender,
"IsVoip": Platform.isIOS ? true : false,
"CallTypeID": callTypeID
};
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
ADD_NEW_CALL_FOR_PATIENT_ER,
body: mapDevice,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
onError!(error);
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: true,
);
} 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<PatientLiveCareHistory>>>> getPatientLiveCareHistory({Function(dynamic)? onSuccess, Function(String)? onError}) async {
Map<String, dynamic> mapDevice = {};
try {
GenericApiModel<List<PatientLiveCareHistory>>? apiResponse;
Failure? failure;
await apiClient.post(
GET_LIVECARE_HISTORY,
body: mapDevice,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
onError!(error);
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['ErRequestHistoryList'];
final liveCareHistoryList = list.map((item) => PatientLiveCareHistory.fromJson(item as Map<String, dynamic>)).toList().cast<PatientLiveCareHistory>();
apiResponse = GenericApiModel<List<PatientLiveCareHistory>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: liveCareHistoryList,
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
}

@ -0,0 +1,162 @@
import 'package:flutter/cupertino.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/utils/utils.dart';
import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_repo.dart';
import 'package:hmg_patient_app_new/features/immediate_livecare/models/resp_models/get_livecare_immediate_clinics_response_model.dart';
import 'package:hmg_patient_app_new/features/immediate_livecare/models/resp_models/get_livecare_immediate_fees_response_model.dart';
import 'package:hmg_patient_app_new/features/immediate_livecare/models/resp_models/get_patient_livecare_history_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import '../../services/navigation_service.dart';
class ImmediateLiveCareViewModel extends ChangeNotifier {
ImmediateLiveCareViewModel({
required this.immediateLiveCareRepo,
required this.errorHandlerService,
required this.navigationService,
required this.myAppointmentsViewModel,
});
ImmediateLiveCareRepo immediateLiveCareRepo;
ErrorHandlerService errorHandlerService;
final NavigationService navigationService;
MyAppointmentsViewModel myAppointmentsViewModel;
List<GetLiveCareClinicListResponseModel> immediateLiveCareClinicsList = [];
bool isImmediateLiveCareClinicsLoading = false;
int liveCareSelectedCallType = 0; // 1- Video, 2- Audio, 3- Phone
late GetLiveCareClinicListResponseModel immediateLiveCareSelectedClinic;
late LiveCareImmediateAppointmentFeesList liveCareImmediateAppointmentFeesList;
List<PatientLiveCareHistory> patientLiveCareHistoryList = [];
bool patientHasPendingLiveCareRequest = false;
late AppState _appState;
initImmediateLiveCare() {
_appState = getIt<AppState>();
immediateLiveCareClinicsList = [];
patientLiveCareHistoryList = [];
isImmediateLiveCareClinicsLoading = true;
patientHasPendingLiveCareRequest = false;
liveCareSelectedCallType = 0; // 1- Video, 2- Audio, 3- Phone
immediateLiveCareSelectedClinic = GetLiveCareClinicListResponseModel();
liveCareImmediateAppointmentFeesList = LiveCareImmediateAppointmentFeesList();
}
setLiveCareSelectedCallType(int value) {
liveCareSelectedCallType = value;
notifyListeners();
}
setImmediateLiveCareSelectedClinic(GetLiveCareClinicListResponseModel clinic) {
immediateLiveCareSelectedClinic = clinic;
notifyListeners();
}
Future<void> getLiveCareImmediateClinicsList({Function(dynamic)? onSuccess, Function(String)? onError}) async {
immediateLiveCareClinicsList.clear();
isImmediateLiveCareClinicsLoading = true;
notifyListeners();
final result = await immediateLiveCareRepo.getLiveCareImmediateClinicsList(_appState.getAuthenticatedUser()!.age!, _appState.getAuthenticatedUser()!.gender!);
result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
immediateLiveCareClinicsList = apiResponse.data!;
immediateLiveCareClinicsList.sort((a, b) => b.isOnline!.compareTo(a.isOnline!));
isImmediateLiveCareClinicsLoading = false;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
Future<void> getLiveCareImmediateAppointmentFees({Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result =
await immediateLiveCareRepo.getLiveCareImmediateAppointmentFees(_appState.getAuthenticatedUser()!.age!, _appState.getAuthenticatedUser()!.gender!, immediateLiveCareSelectedClinic.serviceID!);
result.fold(
(failure) async {
onError!(failure.message);
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
onError!(apiResponse.errorMessage ?? "Unknown error occurred");
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
liveCareImmediateAppointmentFeesList = apiResponse.data!;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
Future<void> addNewCallRequestForImmediateLiveCare(String transID, {Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await immediateLiveCareRepo.addNewCallRequestForImmediateLiveCare(_appState.getAuthenticatedUser()!.age!, _appState.getAuthenticatedUser()!.gender!,
immediateLiveCareSelectedClinic.serviceID!, transID, liveCareSelectedCallType, false, _appState.deviceToken, await Utils.getStringFromPrefs(CacheConst.voipToken));
result.fold(
(failure) async {
onError!(failure.message);
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
onError!(apiResponse.errorMessage ?? "Unknown error occurred");
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
Future<void> getPatientLiveCareHistory({Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await immediateLiveCareRepo.getPatientLiveCareHistory();
result.fold(
(failure) async {
onError!(failure.message);
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
onError!(apiResponse.errorMessage ?? "Unknown error occurred");
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
patientLiveCareHistoryList = apiResponse.data!;
if (patientLiveCareHistoryList.isNotEmpty) {
if (patientLiveCareHistoryList[0].callStatus! < 4) {
patientHasPendingLiveCareRequest = true;
} else {
patientHasPendingLiveCareRequest = false;
}
} else {
patientHasPendingLiveCareRequest = false;
}
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
}

@ -0,0 +1,97 @@
class GetLiveCareClinicListResponseModel {
int? iD;
int? serviceID;
String? serviceName;
String? serviceNameN;
int? clinicID;
int? age;
bool? isCheckAgeBelow;
int? gender;
bool? isActive;
String? createdOn;
String? createdBy;
int? isOnline;
bool? projectOutSA;
List<ShiftTimings>? shiftTimings;
GetLiveCareClinicListResponseModel(
{this.iD,
this.serviceID,
this.serviceName,
this.serviceNameN,
this.clinicID,
this.age,
this.isCheckAgeBelow,
this.gender,
this.isActive,
this.createdOn,
this.createdBy,
this.isOnline,
this.projectOutSA,
this.shiftTimings});
GetLiveCareClinicListResponseModel.fromJson(Map<String, dynamic> json) {
iD = json['ID'];
serviceID = json['ServiceID'];
serviceName = json['ServiceName'];
serviceNameN = json['ServiceNameN'];
clinicID = json['ClinicID'];
age = json['Age'];
isCheckAgeBelow = json['IsCheckAgeBelow'];
gender = json['Gender'];
isActive = json['IsActive'];
createdOn = json['CreatedOn'];
createdBy = json['CreatedBy'];
isOnline = json['IsOnline'];
projectOutSA = json['ProjectOutSA'];
if (json['ShiftTimings'] != null) {
shiftTimings = <ShiftTimings>[];
json['ShiftTimings'].forEach((v) {
shiftTimings!.add(new ShiftTimings.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = Map<String, dynamic>();
data['ID'] = this.iD;
data['ServiceID'] = this.serviceID;
data['ServiceName'] = this.serviceName;
data['ServiceNameN'] = this.serviceNameN;
data['ClinicID'] = this.clinicID;
data['Age'] = this.age;
data['IsCheckAgeBelow'] = this.isCheckAgeBelow;
data['Gender'] = this.gender;
data['IsActive'] = this.isActive;
data['CreatedOn'] = this.createdOn;
data['CreatedBy'] = this.createdBy;
data['IsOnline'] = this.isOnline;
data['ProjectOutSA'] = this.projectOutSA;
if (this.shiftTimings != null) {
data['ShiftTimings'] = this.shiftTimings!.map((v) => v.toJson()).toList();
}
return data;
}
}
class ShiftTimings {
String? endTime;
int? shiftID;
String? startTime;
ShiftTimings({this.endTime, this.shiftID, this.startTime});
ShiftTimings.fromJson(Map<String, dynamic> json) {
endTime = json['EndTime'];
shiftID = json['ShiftID'];
startTime = json['StartTime'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = Map<String, dynamic>();
data['EndTime'] = this.endTime;
data['ShiftID'] = this.shiftID;
data['StartTime'] = this.startTime;
return data;
}
}

@ -0,0 +1,37 @@
class LiveCareImmediateAppointmentFeesList {
String? amount;
String? companyName;
bool? isInsured;
bool? isShowInsuranceUpdateModule;
bool? isCash;
bool? isEligible;
String? tax;
String? total;
String? currency;
LiveCareImmediateAppointmentFeesList({this.amount, this.companyName, this.isInsured, this.isShowInsuranceUpdateModule, this.tax, this.total, this.currency});
LiveCareImmediateAppointmentFeesList.fromJson(Map<String, dynamic> json) {
amount = json['Amount'];
companyName = json['CompanyName'];
isInsured = json['IsInsured'];
isCash = json['IsCash'];
isEligible = json['IsEligible'];
isShowInsuranceUpdateModule = json['IsShowInsuranceUpdateModule'];
tax = json['Tax'];
total = json['Total'];
currency = json['currency'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['Amount'] = this.amount;
data['CompanyName'] = this.companyName;
data['IsInsured'] = this.isInsured;
data['IsShowInsuranceUpdateModule'] = this.isShowInsuranceUpdateModule;
data['Tax'] = this.tax;
data['Total'] = this.total;
data['currency'] = this.currency;
return data;
}
}

@ -0,0 +1,84 @@
class PatientLiveCareHistory {
String? appointmentNo;
String? arrivalTime;
num? callDuration;
int? callStatus;
String? clientRequestID;
String? doctorID;
String? doctorName;
String? doctorNameN;
String? doctorTitle;
String? exWaitingTime;
bool? isAppointmentHaveRating;
int? patCount;
int? projectID;
String? sArrivalTime;
int? serviceID;
String? stringCallStatus;
int? vCID;
int? watingtimeInteger;
PatientLiveCareHistory(
{this.appointmentNo,
this.arrivalTime,
this.callDuration,
this.callStatus,
this.clientRequestID,
this.doctorID,
this.doctorName,
this.doctorNameN,
this.doctorTitle,
this.exWaitingTime,
this.isAppointmentHaveRating,
this.patCount,
this.projectID,
this.sArrivalTime,
this.serviceID,
this.stringCallStatus,
this.vCID,
this.watingtimeInteger});
PatientLiveCareHistory.fromJson(Map<String, dynamic> json) {
appointmentNo = json['AppointmentNo'];
arrivalTime = json['ArrivalTime'];
callDuration = json['CallDuration'];
callStatus = json['CallStatus'];
clientRequestID = json['ClientRequestID'];
doctorID = json['DoctorID'];
doctorName = json['DoctorName'];
doctorNameN = json['DoctorNameN'];
doctorTitle = json['DoctorTitle'];
exWaitingTime = json['Ex_WaitingTime'];
isAppointmentHaveRating = json['IsAppointmentHaveRating'];
patCount = json['Pat_Count'];
projectID = json['ProjectID'];
sArrivalTime = json['SArrivalTime'];
serviceID = json['ServiceID'];
stringCallStatus = json['StringCallStatus'];
vCID = json['VC_ID'];
watingtimeInteger = json['WatingtimeInteger'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['AppointmentNo'] = this.appointmentNo;
data['ArrivalTime'] = this.arrivalTime;
data['CallDuration'] = this.callDuration;
data['CallStatus'] = this.callStatus;
data['ClientRequestID'] = this.clientRequestID;
data['DoctorID'] = this.doctorID;
data['DoctorName'] = this.doctorName;
data['DoctorNameN'] = this.doctorNameN;
data['DoctorTitle'] = this.doctorTitle;
data['Ex_WaitingTime'] = this.exWaitingTime;
data['IsAppointmentHaveRating'] = this.isAppointmentHaveRating;
data['Pat_Count'] = this.patCount;
data['ProjectID'] = this.projectID;
data['SArrivalTime'] = this.sArrivalTime;
data['ServiceID'] = this.serviceID;
data['StringCallStatus'] = this.stringCallStatus;
data['VC_ID'] = this.vCID;
data['WatingtimeInteger'] = this.watingtimeInteger;
return data;
}
}

@ -11,7 +11,7 @@ import 'package:hmg_patient_app_new/services/logger_service.dart';
abstract class InsuranceRepo {
Future<Either<Failure, GenericApiModel<List<PatientInsuranceDetailsResponseModel>>>> getPatientInsuranceDetails();
Future<Either<Failure, GenericApiModel<List<PatientInsuranceCardHistoryResponseModel>>>> getPatientInsuranceCardHistory({required String patientId});
Future<Either<Failure, GenericApiModel<List<PatientInsuranceCardHistoryResponseModel>>>> getPatientInsuranceCardHistory();
Future<Either<Failure, GenericApiModel<PatientInsuranceUpdateResponseModel>>> getPatientInsuranceDetailsForUpdate({required String patientId, required String identificationNo});
}
@ -64,7 +64,7 @@ class InsuranceRepoImp implements InsuranceRepo {
}
@override
Future<Either<Failure, GenericApiModel<List<PatientInsuranceCardHistoryResponseModel>>>> getPatientInsuranceCardHistory({required String patientId}) async {
Future<Either<Failure, GenericApiModel<List<PatientInsuranceCardHistoryResponseModel>>>> getPatientInsuranceCardHistory() async {
Map<String, dynamic> mapDevice = {};
try {

@ -12,6 +12,8 @@ class InsuranceViewModel extends ChangeNotifier {
bool isInsuranceDetailsLoading = false;
bool isInsuranceUpdateDetailsLoading = false;
bool isInsuranceDataToBeLoaded = true;
InsuranceRepo insuranceRepo;
ErrorHandlerService errorHandlerService;
@ -23,13 +25,15 @@ class InsuranceViewModel extends ChangeNotifier {
InsuranceViewModel({required this.insuranceRepo, required this.errorHandlerService});
initInsuranceProvider() {
patientInsuranceList.clear();
if (isInsuranceDataToBeLoaded) {
patientInsuranceList.clear();
isInsuranceLoading = true;
getPatientInsuranceDetails();
}
patientInsuranceCardHistoryList.clear();
isInsuranceLoading = true;
isInsuranceHistoryLoading = true;
isInsuranceDetailsLoading = true;
isInsuranceUpdateDetailsLoading = true;
getPatientInsuranceDetails();
notifyListeners();
}
@ -48,13 +52,21 @@ class InsuranceViewModel extends ChangeNotifier {
notifyListeners();
}
setIsInsuranceDataToBeLoaded(bool val) {
isInsuranceDataToBeLoaded = val;
notifyListeners();
}
Future<void> getPatientInsuranceDetails({Function(dynamic)? onSuccess, Function(String)? onError}) async {
if (!isInsuranceDataToBeLoaded) return;
final result = await insuranceRepo.getPatientInsuranceDetails();
result.fold(
// (failure) async => await errorHandlerService.handleError(failure: failure),
(failure) async {
isInsuranceLoading = false;
notifyListeners();
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
@ -62,6 +74,7 @@ class InsuranceViewModel extends ChangeNotifier {
} else if (apiResponse.messageStatus == 1) {
patientInsuranceList = apiResponse.data!;
isInsuranceLoading = false;
isInsuranceDataToBeLoaded = false;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
@ -72,10 +85,13 @@ class InsuranceViewModel extends ChangeNotifier {
}
Future<void> getPatientInsuranceCardHistory({Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await insuranceRepo.getPatientInsuranceCardHistory(patientId: "1231755");
final result = await insuranceRepo.getPatientInsuranceCardHistory();
result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),
(failure) async {
isInsuranceHistoryLoading = false;
notifyListeners();
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
@ -96,7 +112,10 @@ class InsuranceViewModel extends ChangeNotifier {
final result = await insuranceRepo.getPatientInsuranceDetailsForUpdate(patientId: patientID, identificationNo: identificationNo);
result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),
(failure) async {
isInsuranceUpdateDetailsLoading = false;
notifyListeners();
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});

@ -4,6 +4,7 @@ import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
import 'package:dartz/dartz.dart';
import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart';
import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_special_result.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
import 'models/resp_models/lab_result.dart' show LabResult;
@ -11,6 +12,14 @@ import 'models/resp_models/lab_result.dart' show LabResult;
abstract class LabRepo {
Future<Either<Failure, GenericApiModel<List<PatientLabOrdersResponseModel>>>> getPatientLabOrders();
Future<Either<Failure, GenericApiModel<List<LabResult>>>> getPatientLabResults(PatientLabOrdersResponseModel laborder, bool isVidaPlus, String procedureName);
Future<Either<Failure, GenericApiModel<List<LabResult>>>>
getPatientLabResultsByHospitals(
PatientLabOrdersResponseModel laborder, bool isVidaPlus);
Future<Either<Failure, GenericApiModel<List<PatientLabSpecialResult>>>>
getSpecialLabResult(
PatientLabOrdersResponseModel laborder, bool isVidaPlus);
}
class LabRepoImp implements LabRepo {
@ -73,7 +82,6 @@ class LabRepoImp implements LabRepo {
request['ProjectID'] = laborder.projectID;
request['ClinicID'] = laborder.clinicID;
request['Procedure'] = procedureName;
request['LanguageID'] = 1;
try {
GenericApiModel<List<LabResult>>? apiResponse;
Failure? failure;
@ -90,6 +98,58 @@ class LabRepoImp implements LabRepo {
throw Exception("lab list is empty");
}
final labOrders = list
.map((item) => LabResult.fromJson(item as Map<String, dynamic>))
.toList()
.cast<LabResult>();
apiResponse = GenericApiModel<List<LabResult>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: labOrders,
);
} 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<LabResult>>>>
getPatientLabResultsByHospitals(
PatientLabOrdersResponseModel laborder, bool isVidaPlus) async {
Map<String, dynamic> request = Map();
request['InvoiceNo_VP'] = isVidaPlus ? laborder!.invoiceNo : "0";
request['InvoiceNo'] = isVidaPlus ? "0" : laborder!.invoiceNo;
request['OrderNo'] = laborder!.orderNo;
request['isDentalAllowedBackend'] = false;
request['SetupID'] = laborder!.setupID;
request['ProjectID'] = laborder.projectID;
request['ClinicID'] = laborder.clinicID;
try {
GenericApiModel<List<LabResult>>? apiResponse;
Failure? failure;
await apiClient.post(
GET_Patient_LAB_RESULT,
body: request,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['ListPLR'];
if (list == null || list.isEmpty) {
throw Exception("lab list is empty");
}
final labOrders = list.map((item) => LabResult.fromJson(item as Map<String, dynamic>)).toList().cast<LabResult>();
apiResponse = GenericApiModel<List<LabResult>>(
@ -110,4 +170,57 @@ class LabRepoImp implements LabRepo {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<List<PatientLabSpecialResult>>>>
getSpecialLabResult(
PatientLabOrdersResponseModel laborder, bool isVidaPlus) async {
Map<String, dynamic> request = Map();
request['InvoiceNo_VP'] = isVidaPlus ? laborder!.invoiceNo : "0";
request['InvoiceNo'] = isVidaPlus ? "0" : laborder!.invoiceNo;
request['OrderNo'] = laborder!.orderNo;
request['isDentalAllowedBackend'] = false;
request['SetupID'] = laborder!.setupID;
request['ProjectID'] = laborder.projectID;
request['ClinicID'] = laborder.clinicID;
try {
GenericApiModel<List<PatientLabSpecialResult>>? apiResponse;
Failure? failure;
await apiClient.post(
GET_Patient_LAB_SPECIAL_RESULT,
body: request,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['ListPLSR'];
if (list == null || list.isEmpty) {
throw Exception("lab list is empty");
}
final labOrders = list
.map((item) => PatientLabSpecialResult.fromJson(
item as Map<String, dynamic>))
.toList()
.cast<PatientLabSpecialResult>();
apiResponse = GenericApiModel<List<PatientLabSpecialResult>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: labOrders,
);
} 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()));
}
}
}

@ -20,6 +20,10 @@ import 'package:logger/logger.dart';
class LabViewModel extends ChangeNotifier {
bool isLabOrdersLoading = false;
bool isLabResultsLoading = false;
bool isLabResultByHospitalLoading = false;
bool isSpecialResultsLoading = false;
bool isGraphVisible = true;
bool shouldShowGraph = true;
LabRepo labRepo;
ErrorHandlerService errorHandlerService;
@ -28,6 +32,11 @@ class LabViewModel extends ChangeNotifier {
List<PatientLabOrdersResponseModel> patientLabOrders = [];
List<PatientLabOrdersResponseModel> filteredLabOrders = [];
List<PatientLabOrdersResponseModel> tempLabOrdersList = [];
String labSpecialResult = "";
PatientLabOrdersResponseModel? currentlySelectedPatientOrder;
List<LabResult> mainLabResultsByHospitals = [];
List<LabResult> mainLabResults = [];
List<DataPoint> mainGraphPoints = [];
@ -51,7 +60,7 @@ class LabViewModel extends ChangeNotifier {
List<String> get labSuggestions => _labSuggestionsList;
Set<TestDetails> uniqueTests = {};
Set<TestDetails> uniqueTests = {};
double maxY = 0.0;
double maxX = double.infinity;
@ -71,6 +80,11 @@ class LabViewModel extends ChangeNotifier {
}
Future<void> getPatientLabOrders({Function(dynamic)? onSuccess, Function(String)? onError}) async {
patientLabOrders.clear();
uniqueTests.clear();
uniqueTests = {};
notifyListeners();
final result = await labRepo.getPatientLabOrders();
result.fold(
@ -125,11 +139,12 @@ class LabViewModel extends ChangeNotifier {
}
getUniqueTestDescription() {
uniqueTests = {
uniqueTests = {
for (var item in patientLabOrders)
if (item.testDetails != null)
...?item.testDetails?.map<TestDetails>((test) => TestDetails(
testDescriptionEn: test.testDescriptionEn.toString(),
testDescriptionAr: test.testDescriptionAr.toString(),
description: test.description.toString(),
testCode: test.testCode.toString(),
testID: test.testID,
@ -138,8 +153,32 @@ class LabViewModel extends ChangeNotifier {
};
}
Future<void> getPatientLabResult(
PatientLabOrdersResponseModel laborder, String procedureName) async {
Future<void> getPatientLabResultByHospital(
PatientLabOrdersResponseModel laborder) async {
isLabResultByHospitalLoading = true;
notifyListeners();
mainLabResultsByHospitals.clear;
final result = await labRepo.getPatientLabResultsByHospitals(laborder,
Utils.isVidaPlusProject(int.parse(laborder.projectID ?? "0")));
result.fold(
(failure) async {
isLabResultByHospitalLoading = false;
// await errorHandlerService.handleError(failure: failure);
},
(apiResponse) {
isLabResultByHospitalLoading = false;
if (apiResponse.messageStatus == 2) {
} else if (apiResponse.messageStatus == 1) {
mainLabResultsByHospitals = apiResponse.data ?? [];
notifyListeners();
}
},
);
}
Future<void> getPatientLabResult(PatientLabOrdersResponseModel laborder, String procedureName, String testDescription) async {
LoaderBottomSheet.showLoader();
mainLabResults.clear();
filteredGraphValues.clear();
@ -188,13 +227,57 @@ class LabViewModel extends ChangeNotifier {
} catch (e) {}
});
LabResult recentResult = recentThree.first;
checkIfGraphShouldBeDisplayed(recentResult);
recentResult.verifiedOn = resultDate(DateUtil.convertStringToDate(recentResult.verifiedOnDateTime!));
// filteredGraphValues = [filteredGraphValues.first];
navigationService.push(MaterialPageRoute(
builder: (_) =>
LabResultDetails(recentLabResult: recentResult)));
builder: (_) => LabResultDetails(recentLabResult: recentResult, testDescription: testDescription),
),
);
notifyListeners();
}
},
);
}
void checkIfGraphShouldBeDisplayed(LabResult recentResult){
shouldShowGraph = recentResult.checkIfGraphShouldBeDisplayed();
isGraphVisible = shouldShowGraph;
notifyListeners();
}
Future<void> getPatientSpecialResult(
PatientLabOrdersResponseModel laborder) async {
isSpecialResultsLoading = true;
labSpecialResult = "";
notifyListeners();
final result = await labRepo.getSpecialLabResult(
laborder,
Utils.isVidaPlusProject(int.parse(laborder.projectID ?? "0")),
);
result.fold(
(failure) async {
isSpecialResultsLoading = false;
notifyListeners();
// await errorHandlerService.handleError(failure: failure);
},
(apiResponse) {
isSpecialResultsLoading = false;
if (apiResponse.messageStatus == 2) {
} else if (apiResponse.messageStatus == 1) {
StringBuffer htmlbuffer = StringBuffer("");
apiResponse.data?.forEach((element) {
if(element.resultDataHTML != null && element.resultDataHTML?.isNotEmpty == true)
htmlbuffer.write("${element.resultDataHTML} <br/> <br/>");
});
labSpecialResult = htmlbuffer.toString();
notifyListeners();
}
notifyListeners();
},
);
}
@ -460,4 +543,26 @@ class LabViewModel extends ChangeNotifier {
return true;
}
}
String getSeverityText(String refernceValue) {
switch (refernceValue) {
case 'N':
return "normal";
case 'L':
case 'H':
return "monitor";
case 'CL':
case 'LCL':
case 'CH':
case 'HCH':
return "attention";
default:
return "normal";
}
}
alterGraphVisibility(){
isGraphVisible = !isGraphVisible;
notifyListeners();
}
}

@ -24,6 +24,8 @@ class LabResult {
String? referenceHigh;
String? criticalLow;
String? referenceLow;
num? resultTypeID;
String? packageShortDescription;
LabResult(
{this.description,
@ -78,6 +80,8 @@ class LabResult {
referenceHigh = json['ReferenceHigh'];
criticalLow = json['CriticalLow'];
referenceLow = json['ReferenceLow'];
packageShortDescription = json['PackageShortDescription'];
resultTypeID = json['ResultTypeID'];
}
Map<String, dynamic> toJson() {
@ -109,6 +113,21 @@ class LabResult {
return data;
}
bool checkIfGraphShouldBeDisplayed(){
if (resultTypeID == null) return false;
if (resultTypeID == 6) return false;
if (referanceRange == null || referanceRange == "" || referanceRange == "\n") return false;
bool isDigit = RegExp(r"\\d+").hasMatch("$resultValue");
if(isDigit) return true;
try {
num.parse(resultValue ?? "");
} catch (e) {
return false;
}
return true;
}
@override
String toString() {
return 'LabOrderResult(flag: $calculatedResultFlag, value: $resultValue, verifiedOn: $verifiedOnDateTime)';

@ -224,21 +224,26 @@ class PatientLabOrdersResponseModel {
class TestDetails {
String? description;
String? testDescriptionEn;
String? testDescriptionAr;
String? testCode;
String? testID;
String? createdOn;
PatientLabOrdersResponseModel? model;
TestDetails({this.description, this.testCode, this.testID, this.createdOn, this.model});
TestDetails({this.description, this.testDescriptionEn, this.testDescriptionAr, this.testCode, this.testID, this.createdOn, this.model});
TestDetails.fromJson(Map<String, dynamic> json) {
description = json['Description'];
testDescriptionEn = json['TestDescriptionEn'] ?? "";
testDescriptionAr = json['TestDescriptionAr'] ?? "";
testCode = json['TestCode'];
testID = json['TestID'];
createdOn = json['CreatedOn'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
final Map<String, dynamic> data = <String, dynamic>{};
data['Description'] = this.description;
data['TestCode'] = this.testCode;
data['TestID'] = this.testID;

@ -0,0 +1,32 @@
class PatientLabSpecialResult {
String? invoiceNo;
String? moduleID;
String? resultData;
String? resultDataHTML;
dynamic resultDataTxt;
PatientLabSpecialResult(
{this.invoiceNo,
this.moduleID,
this.resultData,
this.resultDataHTML,
this.resultDataTxt});
PatientLabSpecialResult.fromJson(Map<String, dynamic> json) {
invoiceNo = json['InvoiceNo'];
moduleID = json['ModuleID'];
resultData = json['ResultData'];
resultDataHTML = json['ResultDataHTML'];
resultDataTxt = json['ResultDataTxt'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['InvoiceNo'] = this.invoiceNo;
data['ModuleID'] = this.moduleID;
data['ResultData'] = this.resultData;
data['ResultDataHTML'] = this.resultDataHTML;
data['ResultDataTxt'] = this.resultDataTxt;
return data;
}
}

@ -8,6 +8,7 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/features/medical_file/models/patient_medical_response_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/models/patient_sickleave_response_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/models/patient_vaccine_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
import '../authentication/models/resp_models/authenticated_user_resp_model.dart';
@ -28,7 +29,11 @@ abstract class MedicalFileRepo {
Future<Either<Failure, GenericApiModel<List<FamilyFileResponseModelLists>>>> getAllPendingRecordsByResponseId({required Map<String, dynamic> request});
Future<Either<Failure, GenericApiModel<dynamic>>> addFamilyFile({required dynamic request});
Future<Either<Failure, GenericApiModel<List<dynamic>>>> addFamilyFile({required dynamic request});
Future<Either<Failure, GenericApiModel<List<PatientAppointmentHistoryResponseModel>>>> getPatientAppointmentsForMedicalReport();
Future<Either<Failure, GenericApiModel<dynamic>>> insertRequestForMedicalReport({required PatientAppointmentHistoryResponseModel appointmentHistoryResponseModel});
Future<Either<Failure, GenericApiModel<dynamic>>> removeFamilyFile({required int? id});
@ -454,4 +459,96 @@ class MedicalFileRepoImp implements MedicalFileRepo {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<List<PatientAppointmentHistoryResponseModel>>>> getPatientAppointmentsForMedicalReport() async {
Map<String, dynamic> mapDevice = {
"IsActiveAppointment": false,
"IsComingFromCOC": false,
"isForUpcomming": false,
"IsForMedicalReport": true,
"IsForArrived": false,
};
try {
GenericApiModel<List<PatientAppointmentHistoryResponseModel>>? apiResponse;
Failure? failure;
await apiClient.post(
GET_PATIENT_APPOINTMENT_HISTORY_ASYNC,
body: mapDevice,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['AppoimentAllHistoryResultList'];
final appointmentsList = list.map((item) => PatientAppointmentHistoryResponseModel.fromJson(item as Map<String, dynamic>)).toList().cast<PatientAppointmentHistoryResponseModel>();
apiResponse = GenericApiModel<List<PatientAppointmentHistoryResponseModel>>(
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<dynamic>>> insertRequestForMedicalReport({required PatientAppointmentHistoryResponseModel appointmentHistoryResponseModel}) async {
Map<String, dynamic> mapDevice = {
"ClinicID": appointmentHistoryResponseModel.clinicID,
"DoctorID": appointmentHistoryResponseModel.doctorID,
"SetupID": appointmentHistoryResponseModel.setupID,
"EncounterNo": appointmentHistoryResponseModel.appointmentNo,
"EncounterType": 1,
"IsActive": appointmentHistoryResponseModel.isActiveDoctor,
"ProjectID": appointmentHistoryResponseModel.projectID,
"Remarks": "",
"ProcedureId": "",
"RequestType": 1,
"Source": 2,
"Status": 1,
"CreatedBy": 102
};
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
INSERT_REQUEST_FOR_MEDICAL_REPORT,
body: mapDevice,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
apiResponse = GenericApiModel<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()));
}
}
}

@ -15,6 +15,7 @@ import 'package:hmg_patient_app_new/features/medical_file/models/family_file_res
import 'package:hmg_patient_app_new/features/medical_file/models/patient_medical_response_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/models/patient_sickleave_response_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/models/patient_vaccine_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
import 'package:hmg_patient_app_new/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';
@ -39,6 +40,9 @@ class MedicalFileViewModel extends ChangeNotifier {
List<PatientMedicalReportResponseModel> patientMedicalReportReadyList = [];
List<PatientMedicalReportResponseModel> patientMedicalReportCancelledList = [];
List<PatientAppointmentHistoryResponseModel> patientMedicalReportAppointmentHistoryList = [];
PatientAppointmentHistoryResponseModel? patientMedicalReportSelectedAppointment;
List<FamilyFileResponseModelLists> patientFamilyFiles = [];
List<FamilyFileResponseModelLists> pendingFamilyFiles = [];
@ -64,6 +68,7 @@ class MedicalFileViewModel extends ChangeNotifier {
MedicalFileViewModel({required this.medicalFileRepo, required this.errorHandlerService});
initMedicalFileProvider() {
patientMedicalReportAppointmentHistoryList.clear();
isPatientVaccineListLoading = true;
isPatientMedicalReportsListLoading = true;
notifyListeners();
@ -71,6 +76,7 @@ class MedicalFileViewModel extends ChangeNotifier {
void onMedicalReportTabChange(int index) {
selectedMedicalReportsTabIndex = index;
print("Selected Medical Report Tab Index: $selectedMedicalReportsTabIndex");
if (index == 0) {
patientMedicalReportList = patientMedicalReportRequestedList;
} else if (index == 1) {
@ -110,6 +116,7 @@ class MedicalFileViewModel extends ChangeNotifier {
setIsPatientMedicalReportsLoading(bool val) {
if (val) {
onMedicalReportTabChange(0);
patientMedicalReportList.clear();
patientMedicalReportPDFBase64 = "";
}
@ -117,6 +124,11 @@ class MedicalFileViewModel extends ChangeNotifier {
notifyListeners();
}
setSelectedMedicalReportAppointment(PatientAppointmentHistoryResponseModel? val) {
patientMedicalReportSelectedAppointment = val;
notifyListeners();
}
void onTabChanged(int index) {
selectedTabIndex = index;
notifyListeners();
@ -127,12 +139,17 @@ class MedicalFileViewModel extends ChangeNotifier {
final result = await medicalFileRepo.getPatientVaccinesList();
result.fold(
(failure) async => await errorHandlerService.handleError(
failure: failure,
onOkPressed: () {
onError!(failure.message);
},
),
// (failure) async => await errorHandlerService.handleError(
// failure: failure,
// onOkPressed: () {
// onError!(failure.message);
// },
// ),
(failure) async {
// onError!(failure.message);
isPatientVaccineListLoading = false;
notifyListeners();
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
@ -153,12 +170,16 @@ class MedicalFileViewModel extends ChangeNotifier {
final result = await medicalFileRepo.getPatientSickLeavesList();
result.fold(
(failure) async => await errorHandlerService.handleError(
failure: failure,
onOkPressed: () {
onError!(failure.message);
},
),
// (failure) async => await errorHandlerService.handleError(
// failure: failure,
// onOkPressed: () {
// onError!(failure.message);
// },
// ),
(failure) async {
isPatientSickLeaveListLoading = false;
notifyListeners();
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
@ -509,6 +530,50 @@ class MedicalFileViewModel extends ChangeNotifier {
});
}
Future<void> getPatientMedicalReportAppointmentsList({Function(dynamic)? onSuccess, Function(String)? onError}) async {
patientMedicalReportAppointmentHistoryList.clear();
notifyListeners();
final result = await medicalFileRepo.getPatientAppointmentsForMedicalReport();
result.fold(
(failure) async {
onError!(failure.message);
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
patientMedicalReportAppointmentHistoryList = apiResponse.data!;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
Future<void> insertRequestForMedicalReport({Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await medicalFileRepo.insertRequestForMedicalReport(appointmentHistoryResponseModel: patientMedicalReportSelectedAppointment!);
result.fold(
(failure) async {
onError!(failure.message);
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
Future<void> acceptRejectFileFromFamilyMembers({int? id, int? status}) async {
NavigationService navigationService = getIt.get<NavigationService>();
LoaderBottomSheet.showLoader();

@ -17,7 +17,8 @@ enum AppointmentViaRegionState {
enum RegionBottomSheetType{
FOR_REGION,
FOR_CLINIIC
REGION_FOR_DENTAL_AND_LASER,
FOR_CLINIIC,
}
class AppointmentViaRegionViewmodel extends ChangeNotifier {
@ -94,4 +95,19 @@ class AppointmentViaRegionViewmodel extends ChangeNotifier {
page: SelectDoctorPage(),
),);
}
void handleLastStepForClinicForDentalAndLaser() {
//todo handle the routing here
navigationService.pop();
}
void handleLastStepForDentalAndLaser() {
//todo handle the routing here
navigationService.pop();
navigationService.push(
CustomPageRoute(
page: SelectDoctorPage(),
),
);
}
}

@ -0,0 +1,17 @@
import 'package:hmg_patient_app_new/core/app_assets.dart';
enum AppointmentListingFilters{
WALKIN("walkin", AppAssets.walkin_appointment_icon),
BOOKED("booked", AppAssets.calendar),
CONFIRMED("confirmed", AppAssets.calendar),
ARRIVED("arrived", AppAssets.calendar),
LIVECARE("livecare", AppAssets.small_livecare_icon),
DATESELECTION("",AppAssets.calendar, trailingIcon: AppAssets.arrow_down);
final String labelText;
final String leadingIcon;
final String trailingIcon;
const AppointmentListingFilters(this.labelText, this.leadingIcon,
{this.trailingIcon = ""});
}

@ -0,0 +1,83 @@
class GetTamaraInstallmentsDetailsResponseModel {
String? name;
String? description;
MinLimit? minLimit;
MinLimit? maxLimit;
List<SupportedInstalments>? supportedInstalments;
GetTamaraInstallmentsDetailsResponseModel({this.name, this.description, this.minLimit, this.maxLimit, this.supportedInstalments});
GetTamaraInstallmentsDetailsResponseModel.fromJson(Map<String, dynamic> json) {
name = json['name'];
description = json['description'];
minLimit = json['minLimit'] != null ? new MinLimit.fromJson(json['minLimit']) : null;
maxLimit = json['maxLimit'] != null ? new MinLimit.fromJson(json['maxLimit']) : null;
if (json['supportedInstalments'] != null) {
supportedInstalments = <SupportedInstalments>[];
json['supportedInstalments'].forEach((v) {
supportedInstalments!.add(new SupportedInstalments.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['name'] = this.name;
data['description'] = this.description;
if (this.minLimit != null) {
data['minLimit'] = this.minLimit!.toJson();
}
if (this.maxLimit != null) {
data['maxLimit'] = this.maxLimit!.toJson();
}
if (this.supportedInstalments != null) {
data['supportedInstalments'] = this.supportedInstalments!.map((v) => v.toJson()).toList();
}
return data;
}
}
class MinLimit {
String? currency;
num? amount;
MinLimit({this.currency, this.amount});
MinLimit.fromJson(Map<String, dynamic> json) {
currency = json['currency'];
amount = json['amount'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['currency'] = this.currency;
data['amount'] = this.amount;
return data;
}
}
class SupportedInstalments {
int? instalments;
MinLimit? minLimit;
MinLimit? maxLimit;
SupportedInstalments({this.instalments, this.minLimit, this.maxLimit});
SupportedInstalments.fromJson(Map<String, dynamic> json) {
instalments = json['instalments'];
minLimit = json['minLimit'] != null ? new MinLimit.fromJson(json['minLimit']) : null;
maxLimit = json['maxLimit'] != null ? new MinLimit.fromJson(json['maxLimit']) : null;
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['instalments'] = this.instalments;
if (this.minLimit != null) {
data['minLimit'] = this.minLimit!.toJson();
}
if (this.maxLimit != null) {
data['maxLimit'] = this.maxLimit!.toJson();
}
return data;
}
}

@ -7,6 +7,7 @@ import 'package:hmg_patient_app_new/core/cache_consts.dart';
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart' show HospitalsModel;
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_share_response_model.dart';
@ -44,6 +45,8 @@ abstract class MyAppointmentsRepo {
Future<Either<Failure, GenericApiModel<List<PatientAppointmentHistoryResponseModel>>>> getPatientDoctorsList();
Future<Either<Failure, GenericApiModel<dynamic>>> insertLiveCareVIDARequest({required clientRequestID, required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel});
Future<Either<Failure, GenericApiModel<GetTamaraInstallmentsDetailsResponseModel>>> getTamaraInstallmentsDetails();
}
class MyAppointmentsRepoImp implements MyAppointmentsRepo {
@ -56,13 +59,10 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo {
Future<Either<Failure, GenericApiModel<List<PatientAppointmentHistoryResponseModel>>>> getPatientAppointments({required bool isActiveAppointment, required bool isArrivedAppointments}) async {
Map<String, dynamic> mapDevice = {
"IsActiveAppointment": isActiveAppointment,
"isDentalAllowedBackend": false,
"PatientTypeID": 1,
"IsComingFromCOC": false,
"PatientType": 1,
"isForUpcomming": false,
"IsForMedicalReport": false,
"IsForArrived": isArrivedAppointments,
"PatientOutSA": 0
};
try {
@ -546,4 +546,41 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<GetTamaraInstallmentsDetailsResponseModel>>> getTamaraInstallmentsDetails() async {
try {
GenericApiModel<GetTamaraInstallmentsDetailsResponseModel>? apiResponse;
Failure? failure;
await apiClient.get(
ApiConsts.GET_TAMARA_INSTALLMENTS_URL,
isExternal: true,
isAllowAny: true,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response;
final tamaraInstallmentsList = GetTamaraInstallmentsDetailsResponseModel.fromJson(list.first);
apiResponse = GenericApiModel<GetTamaraInstallmentsDetailsResponseModel>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: tamaraInstallmentsList,
);
} 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,14 +1,17 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/appointemnet_filters.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_share_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart';
import 'package:hmg_patient_app_new/features/my_appointments/utils/appointment_type.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import '../../core/utils/doctor_response_mapper.dart' show DoctorMapper;
class MyAppointmentsViewModel extends ChangeNotifier {
int selectedTabIndex = 0;
int previouslySelectedTab = -1;
MyAppointmentsRepo myAppointmentsRepo;
ErrorHandlerService errorHandlerService;
@ -21,7 +24,14 @@ class MyAppointmentsViewModel extends ChangeNotifier {
bool isAppointmentDataToBeLoaded = true;
List<AppointmentListingFilters> availableFilters = [];
List<AppointmentListingFilters>? selectedFilter = [];
bool isDateFilterSelected = false;
DateTime? start =null;
DateTime? end =null;
List<PatientAppointmentHistoryResponseModel> patientAppointmentsHistoryList = [];
List<PatientAppointmentHistoryResponseModel> filteredAppointmentList = [];
List<PatientAppointmentHistoryResponseModel> patientUpcomingAppointmentsHistoryList = [];
List<PatientAppointmentHistoryResponseModel> patientArrivedAppointmentsHistoryList = [];
@ -32,10 +42,16 @@ class MyAppointmentsViewModel extends ChangeNotifier {
PatientAppointmentShareResponseModel? patientAppointmentShareResponseModel;
GetTamaraInstallmentsDetailsResponseModel? getTamaraInstallmentsDetailsResponseModel;
bool isTamaraDetailsLoading = false;
MyAppointmentsViewModel({required this.myAppointmentsRepo, required this.errorHandlerService, required this.appState});
void onTabChange(int index) {
previouslySelectedTab = selectedTabIndex;
selectedTabIndex = index;
start = null;
end = null;
notifyListeners();
}
@ -50,6 +66,7 @@ class MyAppointmentsViewModel extends ChangeNotifier {
patientMyDoctorsList.clear();
isPatientMyDoctorsLoading = true;
}
isTamaraDetailsLoading = true;
isAppointmentPatientShareLoading = true;
notifyListeners();
}
@ -79,6 +96,11 @@ class MyAppointmentsViewModel extends ChangeNotifier {
notifyListeners();
}
setIsTamaraDetailsLoading(bool val) {
isTamaraDetailsLoading = val;
notifyListeners();
}
setAppointmentReminder(bool value, PatientAppointmentHistoryResponseModel item) {
int index = patientAppointmentsHistoryList.indexOf(item);
if (index != -1) {
@ -132,17 +154,57 @@ class MyAppointmentsViewModel extends ChangeNotifier {
patientAppointmentsHistoryList.addAll(patientUpcomingAppointmentsHistoryList);
patientAppointmentsHistoryList.addAll(patientArrivedAppointmentsHistoryList);
filteredAppointmentList.addAll(patientAppointmentsHistoryList);
print('Upcoming Appointments: ${patientUpcomingAppointmentsHistoryList.length}');
print('Arrived Appointments: ${patientArrivedAppointmentsHistoryList.length}');
print('All Appointments: ${patientAppointmentsHistoryList.length}');
getFiltersForSelectedAppointmentList(filteredAppointmentList);
}
void getFiltersForSelectedAppointmentList(
List<PatientAppointmentHistoryResponseModel> filteredAppointmentList) {
availableFilters.clear();
if (filteredAppointmentList.isEmpty == true) return;
availableFilters.add(AppointmentListingFilters.DATESELECTION);
if (filteredAppointmentList
.any((element) => element.isLiveCareAppointment == true)) {
availableFilters.add(AppointmentListingFilters.LIVECARE);
}
if (filteredAppointmentList
.any((element) => element.isLiveCareAppointment == false)) {
availableFilters.add(AppointmentListingFilters.WALKIN);
}
if (filteredAppointmentList
.any((element) => AppointmentType.isArrived(element) == true)) {
availableFilters.add(AppointmentListingFilters.ARRIVED);
}
if (filteredAppointmentList
.any((element) => AppointmentType.isBooked(element) == true)) {
availableFilters.add(AppointmentListingFilters.BOOKED);
}
if (filteredAppointmentList
.any((element) => AppointmentType.isConfirmed(element) == true)) {
availableFilters.add(AppointmentListingFilters.CONFIRMED);
}
notifyListeners();
}
Future<void> getPatientShareAppointment(int projectID, int clinicID, String appointmentNo, bool isLiveCareAppointment, {Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await myAppointmentsRepo.getPatientShareAppointment(projectID: projectID, clinicID: clinicID, appointmentNo: appointmentNo, isLiveCareAppointment: isLiveCareAppointment);
result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),
(failure) async {
await errorHandlerService.handleError(
failure: failure,
onOkPressed: () {
onError!(failure.message);
});
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
@ -337,4 +399,141 @@ class MyAppointmentsViewModel extends ChangeNotifier {
},
);
}
void updateListWRTTab(int index) {
isDateFilterSelected = false;
selectedFilter = [];
// if(previouslySelectedTab == selectedTabIndex ) return;
switch (index) {
case 0:
filteredAppointmentList.clear();
filteredAppointmentList.addAll(patientAppointmentsHistoryList);
break;
case 1:
filteredAppointmentList.clear();
filteredAppointmentList.addAll(patientUpcomingAppointmentsHistoryList);
break;
case 2:
filteredAppointmentList.clear();
filteredAppointmentList.addAll(patientArrivedAppointmentsHistoryList);
break;
}
getFiltersForSelectedAppointmentList(filteredAppointmentList);
notifyListeners();
}
void setSelectedFilter(AppointmentListingFilters availableFilter) {
if (selectedFilter?.contains(availableFilter) == true) {
selectedFilter?.remove(availableFilter);
notifyListeners();
return;
}
selectedFilter?.add(availableFilter) ;
notifyListeners();
}
void getSelectedDateRange(DateTime? start, DateTime? end) {
this.start = start;
this.end = end;
isDateFilterSelected = true;
List<PatientAppointmentHistoryResponseModel> sourceList = [];
if (selectedTabIndex == 0) {
sourceList = patientAppointmentsHistoryList;
} else if (selectedTabIndex == 1) {
sourceList = patientUpcomingAppointmentsHistoryList;
} else if (selectedTabIndex == 2) {
sourceList = patientArrivedAppointmentsHistoryList;
}
// if (isDateFilterSelected) sourceList = filteredAppointmentList;
if (start == null && end == null) {
isDateFilterSelected = false;
filteredAppointmentList.clear();
sourceList.forEach((element) {
if (isUnderFilter(element)) {
filteredAppointmentList.add(element);
}
});
} else {
filteredAppointmentList.clear();
sourceList.forEach((element) {
try {
var dateTime = DateUtil.convertStringToDate(element.appointmentDate).provideDateOnly();
if (start != null && end == null) {
if (dateTime.isAtSameMomentAs(start)) {
if (isUnderFilter(element)) {
filteredAppointmentList.add(element);
}
}
} else if (start != null && end != null) {
if ((dateTime.isAfter(start)) && ((dateTime.isBefore(end))||((dateTime.isAtSameMomentAs(end))))) {
if (isUnderFilter(element)) {
filteredAppointmentList.add(element);
}
}
}
} catch (e) {}
});
}
notifyListeners();
}
void filterTheListAsPerSelection() {
getSelectedDateRange(start, end);
}
bool isUnderFilter(PatientAppointmentHistoryResponseModel element) {
bool isUnderTheFilter = false;
if (selectedFilter == null || selectedFilter!.isEmpty) return true;
int count = 0;
for (var filter in selectedFilter ?? []) {
switch (filter) {
case AppointmentListingFilters.WALKIN:
if (element.isLiveCareAppointment == false) return true;
case AppointmentListingFilters.BOOKED:
if (AppointmentType.isBooked(element))return true;
case AppointmentListingFilters.CONFIRMED:
if (AppointmentType.isConfirmed(element))return true;
case AppointmentListingFilters.ARRIVED:
if (AppointmentType.isArrived(element))return true;
case AppointmentListingFilters.LIVECARE:
if (element.isLiveCareAppointment == true) return true;
case AppointmentListingFilters.DATESELECTION:
}
}
return false;
}
Future<void> getTamaraInstallmentsDetails({Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await myAppointmentsRepo.getTamaraInstallmentsDetails();
result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),
(apiResponse) {
getTamaraInstallmentsDetailsResponseModel = apiResponse.data!;
isTamaraDetailsLoading = false;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
// if (apiResponse.messageStatus == 2) {
// onError!(apiResponse.errorMessage!);
// // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
// } else if (apiResponse.messageStatus == 1) {
// getTamaraInstallmentsDetailsResponseModel = apiResponse.data!;
// notifyListeners();
// if (onSuccess != null) {
// onSuccess(apiResponse);
// }
// }
},
);
}
}

@ -18,6 +18,13 @@ abstract class PayfortRepo {
Future<Either<Failure, GenericApiModel<SdkTokenResponse>>> generateSdkSignatureFromAPI({required SdkTokenRequest tokenRequest});
Future<Either<Failure, GenericApiModel<PayfortCheckPaymentStatusResponseModel>>> checkPaymentStatus({required String transactionID});
Future<Either<Failure, GenericApiModel<dynamic>>> checkTamaraPaymentStatus({required String transactionID});
Future<Either<Failure, GenericApiModel<dynamic>>> markAppointmentAsTamaraPaid({required int projectID, required int appointmentNo});
Future<Either<Failure, GenericApiModel<dynamic>>> updateTamaraRequestStatus(
{required String responseMessage, required String status, required String clientRequestID, required String tamaraOrderID});
}
class PayfortRepoImp implements PayfortRepo {
@ -147,4 +154,100 @@ class PayfortRepoImp implements PayfortRepo {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel>> checkTamaraPaymentStatus({required String transactionID}) async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.get(
'${ApiConsts.GET_TAMARA_PAYMENT_STATUS}$transactionID',
isExternal: true,
isAllowAny: true,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
apiResponse = GenericApiModel<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>> updateTamaraRequestStatus({required String responseMessage, required String status, required String clientRequestID, required String tamaraOrderID}) async {
Map<String, dynamic> body = {
"Response_Message": responseMessage,
"ClientRequestID": clientRequestID,
"Status": status,
"FortID": tamaraOrderID, // Tamara order ID
"LanguageID": 1,
"Installments_Number": 3,
};
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(UPDATE_TAMARA_STATUS, body: body, onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
}, onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: response,
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
}, isAllowAny: true, isPaymentServices: true);
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>> markAppointmentAsTamaraPaid({required int projectID, required int appointmentNo}) async {
Map<String, dynamic> body = {"ProjectID": projectID, "AppointmentNo": appointmentNo, "LanguageID": 1};
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(MARK_APPOINTMENT_TAMARA_STATUS, body: body, onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
}, onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: response,
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
}, isAllowAny: true, isPaymentServices: true);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
}

@ -94,6 +94,40 @@ class PayfortViewModel extends ChangeNotifier {
);
}
Future<void> checkTamaraPaymentStatus({required String transactionID, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await payfortRepo.checkTamaraPaymentStatus(transactionID: transactionID);
result.fold(
(failure) async {
onError!(failure.message);
},
(apiResponse) {
print(apiResponse.data);
if (onSuccess != null) {
onSuccess(apiResponse);
}
// }
},
);
}
Future<void> updateTamaraRequestStatus(
{required String responseMessage, required String status, required String clientRequestID, required String tamaraOrderID, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await payfortRepo.updateTamaraRequestStatus(responseMessage: responseMessage, status: status, clientRequestID: clientRequestID, tamaraOrderID: tamaraOrderID);
result.fold(
(failure) async {
onError!(failure.message);
},
(apiResponse) {
print(apiResponse.data);
if (onSuccess != null) {
onSuccess(apiResponse);
}
},
);
}
Future<SdkTokenResponse?> _generateSdkResponse({
String? applePayAccessCode,
String? merchantIdentifier,
@ -199,4 +233,20 @@ class PayfortViewModel extends ChangeNotifier {
onFailed!(e.toString() as PayFortFailureResult);
}
}
Future<void> markAppointmentAsTamaraPaid({required int projectID, required int appointmentNo, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await payfortRepo.markAppointmentAsTamaraPaid(projectID: projectID, appointmentNo: appointmentNo);
result.fold(
(failure) async {
onError!(failure.message);
},
(apiResponse) {
print(apiResponse.data);
if (onSuccess != null) {
onSuccess(apiResponse);
}
},
);
}
}

@ -849,5 +849,29 @@ abstract class LocaleKeys {
static const selectCountry = 'selectCountry';
static const forLoginVerification = 'forLoginVerification';
static const searchHospital = 'searchHospital';
static const skip = 'skip';
static const getStarted = 'getStarted';
static const onboardingHeading1 = 'onboardingHeading1';
static const onboardingBody1 = 'onboardingBody1';
static const onboardingHeading2 = 'onboardingHeading2';
static const onboardingBody2 = 'onboardingBody2';
static const hmgHospitals = 'hmgHospitals';
static const hmcMedicalClinic = 'hmcMedicalClinic';
static const applyFilter = 'applyFilter';
static const facilityAndLocation = 'facilityAndLocation';
static const regionAndLocation = 'regionAndLocation';
static const clearAllFilters = 'clearAllFilters';
static const filters = 'filters';
static const searchClinic = 'searchClinic';
static const walkin = 'walkin';
static const normal = 'normal';
static const attention = 'attention';
static const monitor = 'monitor';
static const noSpecialResult = 'noSpecialResult';
static const setTheDateRange = 'setTheDateRange';
static const historyFlowchart = 'historyFlowchart';
static const to = 'to';
static const startDate = 'startDate';
static const endDate = 'endDate';
}

@ -10,10 +10,11 @@ import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/doctor_filter/doctor_filter_view_model.dart';
import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart';
import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart';
import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart';
import 'package:hmg_patient_app_new/features/lab/history/lab_history_viewmodel.dart';
import 'package:hmg_patient_app_new/features/lab/lab_range_view_model.dart';
import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart';
@ -26,6 +27,7 @@ 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';
import 'package:hmg_patient_app_new/theme/app_theme.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart' show DateRangeSelectorRangeViewModel;
import 'package:provider/provider.dart';
import 'package:provider/single_child_widget.dart';
@ -77,84 +79,53 @@ void main() async {
fallbackLocale: Locale('en', 'US'),
child: MultiProvider(providers: <SingleChildWidget>[
ChangeNotifierProvider<LabViewModel>(
create: (_) => LabViewModel(
labRepo: getIt(),
errorHandlerService: getIt(),
navigationService: getIt()),
create: (_) => getIt.get<LabViewModel>(),
),
ChangeNotifierProvider<RadiologyViewModel>(
create: (_) => RadiologyViewModel(
radiologyRepo: getIt(),
errorHandlerService: getIt(),
),
create: (_) => getIt.get<RadiologyViewModel>(),
),
ChangeNotifierProvider<PrescriptionsViewModel>(
create: (_) => PrescriptionsViewModel(
prescriptionsRepo: getIt(),
errorHandlerService: getIt(),
),
create: (_) => getIt.get<PrescriptionsViewModel>(),
),
ChangeNotifierProvider<InsuranceViewModel>(
create: (_) => InsuranceViewModel(
insuranceRepo: getIt(),
errorHandlerService: getIt(),
),
create: (_) => getIt.get<InsuranceViewModel>(),
),
ChangeNotifierProvider<MedicalFileViewModel>(
create: (_) => MedicalFileViewModel(
medicalFileRepo: getIt(),
errorHandlerService: getIt(),
),
create: (_) => getIt.get<MedicalFileViewModel>(),
),
ChangeNotifierProvider<ProfileSettingsViewModel>(
create: (_) => ProfileSettingsViewModel(),
create: (_) => getIt.get<ProfileSettingsViewModel>(),
),
ChangeNotifierProvider<MyAppointmentsViewModel>(
create: (_) => MyAppointmentsViewModel(
myAppointmentsRepo: getIt(),
errorHandlerService: getIt(),
appState: getIt(),
),
create: (_) => getIt.get<MyAppointmentsViewModel>(),
),
ChangeNotifierProvider<PayfortViewModel>(
create: (_) => PayfortViewModel(
payfortRepo: getIt(),
errorHandlerService: getIt(),
),
create: (_) => getIt.get<PayfortViewModel>(),
),
ChangeNotifierProvider<HabibWalletViewModel>(
create: (_) => HabibWalletViewModel(
habibWalletRepo: getIt(),
errorHandlerService: getIt(),
),
create: (_) => getIt.get<HabibWalletViewModel>(),
),
ChangeNotifierProvider<BookAppointmentsViewModel>(
create: (_) => BookAppointmentsViewModel(
bookAppointmentsRepo: getIt(),
errorHandlerService: getIt(),
navigationService: getIt(),
myAppointmentsViewModel: getIt(),
locationUtils: getIt(),
),
create: (_) => getIt.get<BookAppointmentsViewModel>(),
),
ChangeNotifierProvider<ImmediateLiveCareViewModel>(
create: (_) => getIt.get<ImmediateLiveCareViewModel>(),
),
ChangeNotifierProvider<AuthenticationViewModel>(
create: (_) => AuthenticationViewModel(
authenticationRepo: getIt(),
appState: getIt(),
dialogService: getIt(),
errorHandlerService: getIt(),
navigationService: getIt(),
cacheService: getIt(),
localAuthService: getIt(),
),
create: (_) => getIt.get<AuthenticationViewModel>(),
),
ChangeNotifierProvider<AppointmentViaRegionViewmodel>(
create: (_) => AppointmentViaRegionViewmodel(
navigationService: getIt(), appState: getIt())),
create: (_) => getIt.get<AppointmentViaRegionViewmodel>(),
),
ChangeNotifierProvider<LabHistoryViewModel>(
create: (_) => LabHistoryViewModel()),
ChangeNotifierProvider<LabRangeViewModel>(
create: (_) => LabRangeViewModel())
create: (_) => getIt.get<LabHistoryViewModel>(),
),
ChangeNotifierProvider<DateRangeSelectorRangeViewModel>(
create: (_) => getIt.get<DateRangeSelectorRangeViewModel>(),
),
ChangeNotifierProvider<DoctorFilterViewModel>(
create: (_) => getIt.get<DoctorFilterViewModel>(),
)
], child: MyApp()),
),
);

@ -419,7 +419,7 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"Total amount to pay".needTranslation.toText18(isBold: true),
"Amount before tax".needTranslation.toText18(isBold: true),
Utils.getPaymentAmountWithSymbol(widget.patientAppointmentHistoryResponseModel.patientShare!.toString().toText16(isBold: true), AppColors.blackColor, 13,
isSaudiCurrency: true),
],

@ -51,17 +51,30 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
String transID = "";
bool isShowTamara = false;
String tamaraPaymentStatus = "";
String tamaraOrderID = "";
@override
void initState() {
scheduleMicrotask(() {
payfortViewModel.initPayfortViewModel();
myAppointmentsViewModel.getTamaraInstallmentsDetails().then((val) {
if (myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax! >= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.minLimit!.amount! &&
myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax! <= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.maxLimit!.amount!) {
setState(() {
isShowTamara = true;
});
}
});
payfortViewModel.setIsApplePayConfigurationLoading(false);
myAppointmentsViewModel.getPatientShareAppointment(
widget.patientAppointmentHistoryResponseModel.projectID,
widget.patientAppointmentHistoryResponseModel.clinicID,
widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(),
widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false,
);
widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false, onError: (err) {
Navigator.of(context).pop();
Navigator.of(context).pop();
});
});
super.initState();
}
@ -106,7 +119,7 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
Transform.flip(
flipX: appState.isArabic() ? true : false,
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon,
icon: AppAssets.forward_arrow_icon_small,
iconColor: AppColors.blackColor,
width: 18.h,
height: 13.h,
@ -148,7 +161,7 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
Transform.flip(
flipX: appState.isArabic() ? true : false,
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon,
icon: AppAssets.forward_arrow_icon_small,
iconColor: AppColors.blackColor,
width: 18.h,
height: 13.h,
@ -162,11 +175,12 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
openPaymentURL("visa");
}),
SizedBox(height: 16.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: false,
isShowTamara
? Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: false,
),
child: Row(
mainAxisSize: MainAxisSize.max,
@ -184,19 +198,20 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
Transform.flip(
flipX: appState.isArabic() ? true : false,
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon,
iconColor: AppColors.blackColor,
width: 18.h,
height: 13.h,
fit: BoxFit.contain,
).toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading),
),
],
).paddingSymmetrical(16.h, 16.h),
).paddingSymmetrical(24.h, 0.h).onPress(() {
selectedPaymentMethod = "TAMARA";
openPaymentURL("tamara");
}),
icon: AppAssets.forward_arrow_icon_small,
iconColor: AppColors.blackColor,
width: 18.h,
height: 13.h,
fit: BoxFit.contain,
).toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading),
),
],
).paddingSymmetrical(16.h, 16.h),
).paddingSymmetrical(24.h, 0.h).onPress(() {
selectedPaymentMethod = "TAMARA";
openPaymentURL("tamara");
})
: SizedBox.shrink(),
],
),
),
@ -257,7 +272,7 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"Total amount to pay".needTranslation.toText14(isBold: true),
"Amount before tax".needTranslation.toText14(isBold: true),
Utils.getPaymentAmountWithSymbol(myAppointmentsVM.patientAppointmentShareResponseModel!.patientShare!.toString().toText16(isBold: true), AppColors.blackColor, 13,
isSaudiCurrency: true),
],
@ -293,7 +308,7 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
if (Utils.havePrivilege(103)) {
startApplePay();
} else {
openPaymentURL(selectedPaymentMethod);
openPaymentURL("ApplePay");
}
})
: SizedBox(height: 12.h),
@ -315,12 +330,12 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
if (selectedPaymentMethod == "tamara") {
if (Platform.isAndroid) {
Uri uri = new Uri.dataFromString(url);
// tamaraPaymentStatus = uri.queryParameters['status']!;
// tamaraOrderID = uri.queryParameters['AuthorizePaymentId']!;
tamaraPaymentStatus = uri.queryParameters['status']!;
tamaraOrderID = uri.queryParameters['AuthorizePaymentId']!;
} else {
Uri uri = new Uri.dataFromString(url);
// tamaraPaymentStatus = uri.queryParameters['paymentStatus']!;
// tamaraOrderID = uri.queryParameters['orderId']!;
tamaraPaymentStatus = uri.queryParameters['paymentStatus']!;
tamaraOrderID = uri.queryParameters['orderId']!;
}
}
@ -346,95 +361,155 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
}
onBrowserExit(bool isPaymentMade) async {
print("onBrowserExit Called!!!!");
if (selectedPaymentMethod == "TAMARA") {
// checkTamaraPaymentStatus(transID!, appo);
// if (tamaraPaymentStatus != null && tamaraPaymentStatus.toLowerCase() == "approved") {
// updateTamaraRequestStatus("success", "14", Utils.getAppointmentTransID(appo.projectID, appo.clinicID, appo.appointmentNo), tamaraOrderID, num.parse(selectedInstallments), appo);
// } else {
// updateTamaraRequestStatus("Failed", "00", Utils.getAppointmentTransID(appo.projectID, appo.clinicID, appo.appointmentNo), tamaraOrderID, num.parse(selectedInstallments), appo);
// }
} else {
checkPaymentStatus();
// checkPaymentStatus(appo);
}
checkPaymentStatus();
}
void checkPaymentStatus() async {
LoaderBottomSheet.showLoader();
await payfortViewModel.checkPaymentStatus(
transactionID: transID,
onSuccess: (apiResponse) async {
print(apiResponse.data);
if (payfortViewModel.payfortCheckPaymentStatusResponseModel!.responseMessage!.toLowerCase() == "success") {
await myAppointmentsViewModel.createAdvancePayment(
paymentMethodName: selectedPaymentMethod,
projectID: widget.patientAppointmentHistoryResponseModel.projectID,
clinicID: widget.patientAppointmentHistoryResponseModel.clinicID,
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(),
payedAmount: payfortViewModel.payfortCheckPaymentStatusResponseModel!.amount!,
paymentReference: payfortViewModel.payfortCheckPaymentStatusResponseModel!.fortId!,
patientID: appState.getAuthenticatedUser()!.patientId.toString(),
patientType: appState.getAuthenticatedUser()!.patientType!,
onSuccess: (value) async {
print(value);
await myAppointmentsViewModel.addAdvanceNumberRequest(
advanceNumber: Utils.isVidaPlusProject(widget.patientAppointmentHistoryResponseModel.projectID)
? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString()
: value.data['OnlineCheckInAppointments'][0]['AdvanceNumber'].toString(),
paymentReference: payfortViewModel.payfortCheckPaymentStatusResponseModel!.fortId!,
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(),
onSuccess: (value) async {
if (widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment!) {
//TODO: Implement LiveCare Check-In API Call
await myAppointmentsViewModel.insertLiveCareVIDARequest(
clientRequestID: transID,
patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel,
onSuccess: (apiResponse) {
Future.delayed(Duration(milliseconds: 500), () {
LoaderBottomSheet.hideLoader();
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
});
},
onError: (error) {});
} else {
await myAppointmentsViewModel.generateAppointmentQR(
clinicID: widget.patientAppointmentHistoryResponseModel.clinicID,
projectID: widget.patientAppointmentHistoryResponseModel.projectID,
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(),
isFollowUp: myAppointmentsViewModel.patientAppointmentShareResponseModel!.isFollowup!,
onSuccess: (apiResponse) {
Future.delayed(Duration(milliseconds: 500), () {
LoaderBottomSheet.hideLoader();
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
// Navigator.of(context).push(
// CustomPageRoute(page: MyAppointmentsPage()),
// );
});
});
}
});
});
} else {
LoaderBottomSheet.showLoader(loadingText: "Checking payment status, Please wait...".needTranslation);
if (selectedPaymentMethod == "TAMARA") {
await payfortViewModel.checkTamaraPaymentStatus(
transactionID: transID,
onSuccess: (apiResponse) async {
if (apiResponse.data["status"].toString().toLowerCase() == "success") {
tamaraOrderID = apiResponse.data["tamara_order_id"].toString();
await payfortViewModel.updateTamaraRequestStatus(responseMessage: "success", status: "14", clientRequestID: transID, tamaraOrderID: tamaraOrderID);
await payfortViewModel.markAppointmentAsTamaraPaid(
projectID: widget.patientAppointmentHistoryResponseModel.projectID, appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo);
await myAppointmentsViewModel.addAdvanceNumberRequest(
advanceNumber: "Tamara-Advance-0000",
paymentReference: tamaraOrderID,
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(),
onSuccess: (value) async {
if (widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment!) {
//TODO: Implement LiveCare Check-In API Call
await myAppointmentsViewModel.insertLiveCareVIDARequest(
clientRequestID: tamaraOrderID,
patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel,
onSuccess: (apiResponse) {
Future.delayed(Duration(milliseconds: 500), () {
LoaderBottomSheet.hideLoader();
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
});
},
onError: (error) {});
} else {
await myAppointmentsViewModel.generateAppointmentQR(
clinicID: widget.patientAppointmentHistoryResponseModel.clinicID,
projectID: widget.patientAppointmentHistoryResponseModel.projectID,
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(),
isFollowUp: myAppointmentsViewModel.patientAppointmentShareResponseModel!.isFollowup!,
onSuccess: (apiResponse) {
Future.delayed(Duration(milliseconds: 500), () {
LoaderBottomSheet.hideLoader();
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
});
});
}
});
} else {
await payfortViewModel.updateTamaraRequestStatus(responseMessage: "Failed", status: "00", clientRequestID: transID, tamaraOrderID: tamaraOrderID);
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: "Payment Failed! Please try again.".needTranslation),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
},
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: "Payment Failed! Please try again.".needTranslation),
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
});
});
} else {
await payfortViewModel.checkPaymentStatus(
transactionID: transID,
onSuccess: (apiResponse) async {
print(apiResponse.data);
if (payfortViewModel.payfortCheckPaymentStatusResponseModel!.responseMessage!.toLowerCase() == "success") {
await myAppointmentsViewModel.createAdvancePayment(
paymentMethodName: selectedPaymentMethod,
projectID: widget.patientAppointmentHistoryResponseModel.projectID,
clinicID: widget.patientAppointmentHistoryResponseModel.clinicID,
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(),
payedAmount: payfortViewModel.payfortCheckPaymentStatusResponseModel!.amount!,
paymentReference: payfortViewModel.payfortCheckPaymentStatusResponseModel!.fortId!,
patientID: appState.getAuthenticatedUser()!.patientId.toString(),
patientType: appState.getAuthenticatedUser()!.patientType!,
onSuccess: (value) async {
print(value);
await myAppointmentsViewModel.addAdvanceNumberRequest(
advanceNumber: Utils.isVidaPlusProject(widget.patientAppointmentHistoryResponseModel.projectID)
? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString()
: value.data['OnlineCheckInAppointments'][0]['AdvanceNumber'].toString(),
paymentReference: payfortViewModel.payfortCheckPaymentStatusResponseModel!.fortId!,
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(),
onSuccess: (value) async {
if (widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment!) {
//TODO: Implement LiveCare Check-In API Call
await myAppointmentsViewModel.insertLiveCareVIDARequest(
clientRequestID: transID,
patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel,
onSuccess: (apiResponse) {
Future.delayed(Duration(milliseconds: 500), () {
LoaderBottomSheet.hideLoader();
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
});
},
onError: (error) {});
} else {
await myAppointmentsViewModel.generateAppointmentQR(
clinicID: widget.patientAppointmentHistoryResponseModel.clinicID,
projectID: widget.patientAppointmentHistoryResponseModel.projectID,
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(),
isFollowUp: myAppointmentsViewModel.patientAppointmentShareResponseModel!.isFollowup!,
onSuccess: (apiResponse) {
Future.delayed(Duration(milliseconds: 500), () {
LoaderBottomSheet.hideLoader();
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
});
});
}
});
});
} else {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: "Payment Failed! Please try again.".needTranslation),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
});
}
}
openPaymentURL(String paymentMethod) {

@ -8,19 +8,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/my_appointments/models/appointemnet_filters.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/AppointmentFilter.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_card.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/date_range_calender.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.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/custom_tab_bar.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart';
import 'package:provider/provider.dart';
import '../../widgets/common_bottom_sheet.dart'
show showCommonBottomSheetWithoutHeight;
class MyAppointmentsPage extends StatefulWidget {
const MyAppointmentsPage({super.key});
@ -61,6 +68,8 @@ class _MyAppointmentsPageState extends State<MyAppointmentsPage> {
],
onTabChange: (index) {
myAppointmentsViewModel.onTabChange(index);
myAppointmentsViewModel.updateListWRTTab(index);
context.read<DateRangeSelectorRangeViewModel>().flush();
},
).paddingSymmetrical(24.h, 0.h),
Consumer<MyAppointmentsViewModel>(builder: (context, myAppointmentsVM, child) {
@ -74,218 +83,148 @@ class _MyAppointmentsPageState extends State<MyAppointmentsPage> {
}
Widget getSelectedTabData(int index, MyAppointmentsViewModel myAppointmentsVM) {
switch (index) {
case 0:
//All Appointments Tab Data
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Expandable list
ListView.separated(
padding: EdgeInsets.only(top: 24.h),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: myAppointmentsVM.isMyAppointmentsLoading
? 5
: myAppointmentsVM.patientAppointmentsHistoryList.isNotEmpty
? myAppointmentsVM.patientAppointmentsHistoryList.length
: 1,
itemBuilder: (context, index) {
return myAppointmentsVM.isMyAppointmentsLoading
? Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
child: AppointmentCard(
patientAppointmentHistoryResponseModel: PatientAppointmentHistoryResponseModel(),
myAppointmentsViewModel: myAppointmentsViewModel,
isLoading: true,
isFromHomePage: false,
),
).paddingSymmetrical(24.h, 0.h)
: myAppointmentsVM.patientAppointmentsHistoryList.isNotEmpty
? AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
child: AppointmentCard(
patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index],
myAppointmentsViewModel: myAppointmentsViewModel,
isLoading: false,
isFromHomePage: false,
),
).paddingSymmetrical(24.h, 0.h),
return getAppointList(
myAppointmentsVM, myAppointmentsVM.filteredAppointmentList);
}
Widget getAppointList(MyAppointmentsViewModel myAppointmentsVM,
List<PatientAppointmentHistoryResponseModel> filteredAppointmentList) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Visibility(
visible: myAppointmentsVM.availableFilters.isNotEmpty,
child: getAppointmentFilters(myAppointmentsVM)),
ListView.separated(
padding: EdgeInsets.only(top: 24.h),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: myAppointmentsVM.isMyAppointmentsLoading
? 5
: filteredAppointmentList.isNotEmpty
? filteredAppointmentList.length
: 1,
itemBuilder: (context, index) {
return myAppointmentsVM.isMyAppointmentsLoading
? Container(
decoration: RoundedRectangleBorder()
.toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: true),
child: AppointmentCard(
patientAppointmentHistoryResponseModel:
PatientAppointmentHistoryResponseModel(),
myAppointmentsViewModel: myAppointmentsViewModel,
isLoading: true,
isFromHomePage: false,
),
).paddingSymmetrical(24.h, 0.h)
: filteredAppointmentList.isNotEmpty
? AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
decoration: RoundedRectangleBorder()
.toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: true),
child: AppointmentCard(
patientAppointmentHistoryResponseModel:
filteredAppointmentList[index],
myAppointmentsViewModel:
myAppointmentsViewModel,
isLoading: false,
isFromHomePage: false,
),
),
)
: Utils.getNoDataWidget(
context,
noDataText: "You don't have any appointments yet.".needTranslation,
callToActionButton: CustomButton(
text: LocaleKeys.bookAppo.tr(context: context),
onPressed: () {
Navigator.of(context).push(
CustomPageRoute(
page: BookAppointmentPage(),
),
);
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
fontSize: 14,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40,
icon: AppAssets.add_icon,
iconColor: AppColors.primaryRedColor,
).paddingSymmetrical(48.h, 0.h),
);
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
),
],
);
case 1:
//Upcoming Appointments Tab Data
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Expandable list
ListView.separated(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: myAppointmentsVM.isMyAppointmentsLoading
? 5
: myAppointmentsVM.patientUpcomingAppointmentsHistoryList.isNotEmpty
? myAppointmentsVM.patientUpcomingAppointmentsHistoryList.length
: 1,
itemBuilder: (context, index) {
return myAppointmentsVM.isMyAppointmentsLoading
? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0.h)
: myAppointmentsVM.patientUpcomingAppointmentsHistoryList.isNotEmpty
? AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
margin: EdgeInsets.symmetric(vertical: 8.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
child: AppointmentCard(
patientAppointmentHistoryResponseModel: myAppointmentsVM.patientUpcomingAppointmentsHistoryList[index],
myAppointmentsViewModel: myAppointmentsViewModel,
),
).paddingSymmetrical(24.h, 0.h),
).paddingSymmetrical(24.h, 0.h),
),
),
)
: Utils.getNoDataWidget(
context,
noDataText: "You don't have any appointments yet."
.needTranslation,
callToActionButton: CustomButton(
text: LocaleKeys.bookAppo.tr(context: context),
onPressed: () {
Navigator.of(context).push(
CustomPageRoute(
page: BookAppointmentPage(),
),
),
)
: Utils.getNoDataWidget(
context,
noDataText: "You don't have any appointments yet.".needTranslation,
callToActionButton: CustomButton(
text: LocaleKeys.bookAppo.tr(context: context),
onPressed: () {
Navigator.of(context).push(
CustomPageRoute(
page: BookAppointmentPage(),
),
);
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
fontSize: 14,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40,
icon: AppAssets.add_icon,
iconColor: AppColors.primaryRedColor,
).paddingSymmetrical(48.h, 0.h),
);
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
),
],
);
case 2:
//Completed Appointments Tab Data
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
);
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
fontSize: 14,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40,
icon: AppAssets.add_icon,
iconColor: AppColors.primaryRedColor,
).paddingSymmetrical(48.h, 0.h),
);
},
separatorBuilder: (BuildContext cxt, int index) =>
SizedBox(height: 16.h),
),
SizedBox(height: 24.h),
],
);
}
Widget getAppointmentFilters(MyAppointmentsViewModel myAppointmentsVM) {
return SizedBox(
height: 56.h,
child: Row(
children: [
// Expandable list
ListView.separated(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: myAppointmentsVM.isMyAppointmentsLoading
? 5
: myAppointmentsVM.patientArrivedAppointmentsHistoryList.isNotEmpty
? myAppointmentsVM.patientArrivedAppointmentsHistoryList.length
: 1,
itemBuilder: (context, index) {
return myAppointmentsVM.isMyAppointmentsLoading
? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0.h)
: myAppointmentsVM.patientArrivedAppointmentsHistoryList.isNotEmpty
? AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
margin: EdgeInsets.symmetric(vertical: 8.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
child: AppointmentCard(
patientAppointmentHistoryResponseModel: myAppointmentsVM.patientArrivedAppointmentsHistoryList[index],
myAppointmentsViewModel: myAppointmentsViewModel,
),
).paddingSymmetrical(24.h, 0.h),
Expanded(
child: ListView.separated(
separatorBuilder: (_, index) => SizedBox(
width: 8.h,
),
scrollDirection: Axis.horizontal,
itemCount: myAppointmentsVM.availableFilters.length,
itemBuilder: (_, index) => AppointmentFilters(
selectedFilter: myAppointmentsVM.selectedFilter,
item: myAppointmentsVM.availableFilters[index],
onClicked: () {
if (myAppointmentsVM.availableFilters[index] ==
AppointmentListingFilters.DATESELECTION) {
showCommonBottomSheetWithoutHeight(
title: "Set The Date Range".needTranslation,
context,
child: DateRangeSelector(
onRangeSelected: (start, end) {
// if (start != null) {
myAppointmentsVM.getSelectedDateRange(
start, end);
// }
},
),
),
)
: Utils.getNoDataWidget(
context,
noDataText: "You don't have any appointments yet.".needTranslation,
callToActionButton: CustomButton(
text: LocaleKeys.bookAppo.tr(context: context),
onPressed: () {
Navigator.of(context).push(
CustomPageRoute(
page: BookAppointmentPage(),
),
);
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
fontSize: 14,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40,
icon: AppAssets.add_icon,
iconColor: AppColors.primaryRedColor,
).paddingSymmetrical(48.h, 0.h),
);
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
isFullScreen: false,
isCloseButtonVisible: true,
callBackFunc: () {},
);
} else {
myAppointmentsVM.setSelectedFilter(
myAppointmentsVM.availableFilters[index]);
myAppointmentsVM.filterTheListAsPerSelection();
}
},
)),
),
],
);
default:
return Container();
}
)).paddingOnly(top: 24.h, left: 24.h, right: 24.h);
}
}

@ -0,0 +1,48 @@
import 'package:easy_localization/easy_localization.dart' show tr, StringTranslateExtension;
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/appointemnet_filters.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_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:smooth_corner/smooth_corner.dart';
class AppointmentFilters extends StatelessWidget {
final AppointmentListingFilters item;
final List<AppointmentListingFilters>? selectedFilter;
final VoidCallback onClicked;
const AppointmentFilters(
{super.key,
required this.item,
required this.onClicked,
required this.selectedFilter});
@override
Widget build(BuildContext context) {
return AppCustomChipWidget(
backgroundColor: selectedFilter?.contains(item) == true?AppColors.chipSecondaryLightRedColor:AppColors.whiteColor,
icon: item.leadingIcon,
textColor: selectedFilter?.contains(item) == true
? AppColors.chipPrimaryRedBorderColor: AppColors.blackColor,
labelText: item.labelText.isNotEmpty?item.labelText.tr():"",
iconHasColor: true,
iconColor: selectedFilter?.contains(item) == true
? AppColors.chipPrimaryRedBorderColor:AppColors.blackColor,
iconSize: 16,
deleteIcon: item.trailingIcon,
labelPadding: EdgeInsetsDirectional.only(start: 8.h, end: 0.h),
padding: EdgeInsets.symmetric(vertical: 12.h, horizontal: 8.h),
deleteIconSize: Size(18.h, 18.h),
shape: SmoothRectangleBorder(
borderRadius: BorderRadius.circular(10 ),
smoothness: 10,
side: BorderSide(
color: selectedFilter?.contains(item) == true
? AppColors.chipPrimaryRedBorderColor
: AppColors.borderGrayColor,
width: 1),
)).onPress(onClicked);
}
}

@ -8,6 +8,7 @@ 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/medical_file/medical_file_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/utils/appointment_type.dart';
@ -21,12 +22,21 @@ import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart';
import 'package:smooth_corner/smooth_corner.dart';
class AppointmentCard extends StatefulWidget {
AppointmentCard({super.key, required this.patientAppointmentHistoryResponseModel, required this.myAppointmentsViewModel, this.isLoading = false, this.isFromHomePage = false});
AppointmentCard(
{super.key,
required this.patientAppointmentHistoryResponseModel,
required this.myAppointmentsViewModel,
this.isLoading = false,
this.isFromHomePage = false,
this.isFromMedicalReport = false,
this.medicalFileViewModel});
PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel;
MyAppointmentsViewModel myAppointmentsViewModel;
bool isLoading;
bool isFromHomePage;
bool isFromMedicalReport;
MedicalFileViewModel? medicalFileViewModel;
@override
State<AppointmentCard> createState() => _AppointmentCardState();
@ -157,9 +167,11 @@ class _AppointmentCardState extends State<AppointmentCard> {
labelText:
widget.isLoading ? "Cardiology" : DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate), false))
.toShimmer2(isShow: widget.isLoading),
AppCustomChipWidget(
icon: AppAssets.appointment_time_icon,
labelText: widget.isLoading
widget.isFromMedicalReport
? SizedBox.shrink()
: AppCustomChipWidget(
icon: AppAssets.appointment_time_icon,
labelText: widget.isLoading
? "Cardiology"
: DateUtil.formatDateToTimeLang(DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate), false))
.toShimmer2(isShow: widget.isLoading),
@ -171,75 +183,91 @@ class _AppointmentCardState extends State<AppointmentCard> {
],
),
SizedBox(height: 16.h),
Row(
children: [
Expanded(
flex: 6,
child: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)
? getArrivedAppointmentButton().toShimmer2(isShow: widget.isLoading)
: CustomButton(
text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction),
onPressed: () {
Navigator.of(context)
.push(CustomPageRoute(
page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel),
))
.then((val) {
widget.myAppointmentsViewModel.initAppointmentsViewModel();
widget.myAppointmentsViewModel.getPatientAppointments(true, false);
});
},
backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.15),
borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01),
textColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction),
fontSize: 14,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction),
iconColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction),
iconSize: 15.h,
).toShimmer2(isShow: widget.isLoading),
),
SizedBox(width: 8.h),
Expanded(
flex: 1,
child: Container(
widget.isFromMedicalReport
? CustomButton(
text: "Select appointment".needTranslation,
onPressed: () {
widget.medicalFileViewModel!.setSelectedMedicalReportAppointment(widget.patientAppointmentHistoryResponseModel);
Navigator.pop(context, false);
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 14,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
width: 40.h,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.textColor,
borderRadius: 10.h,
),
child: Padding(
padding: EdgeInsets.all(10.h),
child: Transform.flip(
flipX: appState.isArabic() ? true : false,
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon,
iconColor: AppColors.whiteColor,
width: 10.h,
height: 10.h,
fit: BoxFit.contain,
),
icon: AppAssets.checkmark_icon,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
)
: Row(
children: [
Expanded(
flex: 6,
child: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)
? getArrivedAppointmentButton().toShimmer2(isShow: widget.isLoading)
: CustomButton(
text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction),
onPressed: () {
Navigator.of(context)
.push(CustomPageRoute(
page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel),
))
.then((val) {
widget.myAppointmentsViewModel.initAppointmentsViewModel();
widget.myAppointmentsViewModel.getPatientAppointments(true, false);
});
},
backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.15),
borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01),
textColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction),
fontSize: 14,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction),
iconColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction),
iconSize: 15.h,
).toShimmer2(isShow: widget.isLoading),
),
),
).toShimmer2(isShow: widget.isLoading).onPress(() {
Navigator.of(context)
.push(
CustomPageRoute(
page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel),
SizedBox(width: 8.h),
Expanded(
flex: 1,
child: Container(
height: 40.h,
width: 40.h,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.textColor,
borderRadius: 10.h,
),
child: Transform.flip(
flipX: appState.isArabic() ? true : false,
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon,
iconColor: AppColors.whiteColor,
width: 40.h,
height: 40.h,
fit: BoxFit.cover,
),
),
).toShimmer2(isShow: widget.isLoading).onPress(() {
Navigator.of(context)
.push(
CustomPageRoute(
page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel),
),
)
.then((val) {
widget.myAppointmentsViewModel.initAppointmentsViewModel();
widget.myAppointmentsViewModel.getPatientAppointments(true, false);
});
}),
),
)
.then((val) {
widget.myAppointmentsViewModel.initAppointmentsViewModel();
widget.myAppointmentsViewModel.getPatientAppointments(true, false);
});
}),
),
],
),
],
),
],
),
),

@ -124,7 +124,7 @@ class AppointmentCheckinBottomSheet extends StatelessWidget {
Transform.flip(
flipX: appState.isArabic() ? true : false,
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon,
icon: AppAssets.forward_arrow_icon_small,
iconColor: AppColors.blackColor,
width: 18.h,
height: 13.h,

@ -48,7 +48,7 @@ class FacilitySelectionItem extends StatelessWidget {
Transform.flip(
flipX: appState.isArabic() ? true : false,
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon,
icon: AppAssets.forward_arrow_icon_small,
iconColor: AppColors.blackColor,
width: 18,
height: 13,

@ -99,11 +99,13 @@ class HospitalBottomSheetBody extends StatelessWidget {
if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_REGION) {
regionalViewModel.setBottomSheetState(AppointmentViaRegionState.CLINIC_SELECTION);
regionalViewModel.handleLastStepForRegion();
}else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_CLINIIC) {
} else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_CLINIIC) {
regionalViewModel.setBottomSheetState(AppointmentViaRegionState.DOCTOR_SELECTION);
regionalViewModel.handleLastStepForClinic();
} else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER) {
regionalViewModel.setBottomSheetState(AppointmentViaRegionState.DOCTOR_SELECTION);
regionalViewModel.handleLastStepForClinicForDentalAndLaser();
// regionalViewModel.handleLastStepForClinic();
}
});},
separatorBuilder: (_, __) => SizedBox(

@ -0,0 +1,66 @@
import 'package:easy_localization/easy_localization.dart'
show tr, StringTranslateExtension;
import 'package:flutter/material.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/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/doctor_filter/doctor_filter_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_bottom_sheet/type_selection_widget.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/theme/colors.dart' show AppColors;
import 'package:hmg_patient_app_new/widgets/input_widget.dart';
import 'package:provider/provider.dart';
class HospitalBottomSheetBodyForDoctorFilter extends StatelessWidget {
late BookAppointmentsViewModel appointmentsViewModel;
late AppointmentViaRegionViewmodel regionalViewModel;
final TextEditingController searchText = TextEditingController();
HospitalBottomSheetBodyForDoctorFilter({super.key});
@override
Widget build(BuildContext context) {
appointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context);
regionalViewModel = Provider.of<AppointmentViaRegionViewmodel>(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
LocaleKeys.selectHospital.tr(),
style: TextStyle(
fontSize: 21,
fontWeight: FontWeight.w600,
color: AppColors.blackColor,
),
),
SizedBox(height: 24.h),
SizedBox(
height: MediaQuery.sizeOf(context).height * .4,
child: ListView.separated(
itemBuilder: (_, index)
{
var hospital = appointmentsViewModel.searchedPatientDoctorAppointmentHospitalsList[index];
return HospitalListItem(
hospitalData: hospital,
isLocationEnabled: appointmentsViewModel.isLocationEnabled(),
).onPress(() {
regionalViewModel.setHospitalModel(hospital);
context.read<DoctorFilterViewModel>().setSelectedHospital(hospital);
Navigator.pop(context);
});},
separatorBuilder: (_, __) => SizedBox(
height: 16.h,
),
itemCount: appointmentsViewModel.searchedPatientDoctorAppointmentHospitalsList?.length ?? 0),
)
],
);
}
}

@ -41,10 +41,10 @@ class HospitalListItem extends StatelessWidget {
Transform.flip(
flipX: appState.isArabic() ? true : false,
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon,
icon: AppAssets.forward_arrow_icon_small,
iconColor: AppColors.blackColor,
width: 18,
height: 13,
width: 18.h,
height: 13.h,
fit: BoxFit.contain,
),
),

@ -45,7 +45,7 @@ class RegionListItem extends StatelessWidget {
Transform.flip(
flipX: appState.isArabic() ? true : false,
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon,
icon: AppAssets.forward_arrow_icon_small,
iconColor: AppColors.blackColor,
width: 18,
height: 13,

@ -24,7 +24,7 @@ class _RegionBottomSheetBodyState extends State<RegionBottomSheetBody> {
@override
void initState() {
scheduleMicrotask(() {
if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_REGION) {
if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_REGION || regionalViewModel.regionBottomSheetType == RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER ) {
myAppointmentsViewModel.getRegionMappedProjectList();
} else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_CLINIIC) {
myAppointmentsViewModel.getMappedDoctors();

@ -9,18 +9,24 @@ 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/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/doctor_list_api_response.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/faculity_selection/facility_type_selection_widget.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart' show RegionBottomSheetBody;
import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/immediate_livecare_pending_request_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/select_immediate_livecare_clinic_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/search_doctor_by_name.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/select_clinic_page.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.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/common_bottom_sheet.dart' show showCommonBottomSheetWithoutHeight;
import 'package:hmg_patient_app_new/widgets/custom_tab_bar.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:hmg_patient_app_new/widgets/transitions/fade_page.dart';
import 'package:provider/provider.dart';
@ -38,13 +44,17 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
late AppState appState;
late AppointmentViaRegionViewmodel regionalViewModel;
late BookAppointmentsViewModel bookAppointmentsViewModel;
late ImmediateLiveCareViewModel immediateLiveCareViewModel;
late final AuthenticationViewModel authVM;
@override
void initState() {
authVM = context.read<AuthenticationViewModel>();
scheduleMicrotask(() {
bookAppointmentsViewModel.selectedTabIndex = 0;
bookAppointmentsViewModel.initBookAppointmentViewModel();
bookAppointmentsViewModel.getLocation();
immediateLiveCareViewModel.initImmediateLiveCare();
});
super.initState();
}
@ -52,6 +62,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
@override
Widget build(BuildContext context) {
bookAppointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context, listen: false);
immediateLiveCareViewModel = Provider.of<ImmediateLiveCareViewModel>(context, listen: false);
appState = getIt.get<AppState>();
regionalViewModel = Provider.of<AppointmentViaRegionViewmodel>(context, listen: true);
return Scaffold(
@ -119,7 +130,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
],
),
Transform.flip(
flipX: appState.isArabic() ? true : false, child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h)),
flipX: appState.isArabic() ? true : false, child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
],
).onPress(() {
bookAppointmentsViewModel.setIsClinicsListLoading(true);
@ -152,7 +163,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
],
),
Transform.flip(
flipX: appState.isArabic() ? true : false, child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h)),
flipX: appState.isArabic() ? true : false, child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
],
).onPress(() {
bookAppointmentsViewModel.setIsDoctorSearchByNameStarted(false);
@ -183,7 +194,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
],
),
Transform.flip(
flipX: appState.isArabic() ? true : false, child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h)),
flipX: appState.isArabic() ? true : false, child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
],
).onPress(() {
bookAppointmentsViewModel.setProjectID(null);
@ -197,11 +208,12 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
).paddingSymmetrical(24.h, 0.h);
case 1:
//TODO: Get LiveCare type Select UI from Hussain
return Column(
children: [
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
return appState.isAuthenticated
? Column(
children: [
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: false,
),
@ -227,17 +239,28 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
],
),
Transform.flip(
flipX: appState.isArabic() ? true : false, child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h)),
flipX: appState.isArabic() ? true : false, child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
],
).onPress(() {
// bookAppointmentsViewModel.setIsClinicsListLoading(true);
// bookAppointmentsViewModel.setLoadSpecificClinic(false);
// bookAppointmentsViewModel.setProjectID(null);
// Navigator.of(context).push(
// CustomPageRoute(
// page: SelectClinicPage(),
// ),
// );
).onPress(() async {
//TODO Implement API to check for existing LiveCare Requests
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),
@ -259,7 +282,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
],
),
Transform.flip(
flipX: appState.isArabic() ? true : false, child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h)),
flipX: appState.isArabic() ? true : false, child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
],
).onPress(() {
bookAppointmentsViewModel.setIsClinicsListLoading(true);
@ -290,7 +313,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
],
),
Transform.flip(
flipX: appState.isArabic() ? true : false, child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h)),
flipX: appState.isArabic() ? true : false, child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
],
).onPress(() {
openRegionListBottomSheet(context, RegionBottomSheetType.FOR_REGION);
@ -300,7 +323,8 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
),
),
],
).paddingSymmetrical(24.h, 0.h);
).paddingSymmetrical(24.h, 0.h)
: getLiveCareNotLoggedInUI();
default:
SizedBox.shrink();
}
@ -357,4 +381,93 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
});
}
}
Widget getLiveCareNotLoggedInUI() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.immediate_service_icon, width: 58.h, height: 58.h),
SizedBox(width: 18.h),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"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),
],
),
),
],
),
SizedBox(height: 24.h),
Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.no_visit_icon, width: 58.h, height: 58.h),
SizedBox(width: 18.h),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"No visit required".needTranslation.toText18(color: AppColors.textColor, isBold: true),
LocaleKeys.livecarePoint5.tr(context: context).toText14(color: AppColors.greyTextColor, weight: FontWeight.w500),
],
),
),
],
),
SizedBox(height: 24.h),
Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.doctor_contact_icon, width: 58.h, height: 58.h),
SizedBox(width: 18.h),
Expanded(
child: Column(
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),
],
),
),
],
),
SizedBox(height: 24.h),
Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.free_med_delivery_icon, width: 58.h, height: 58.h),
SizedBox(width: 18.h),
Expanded(
child: Column(
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),
],
),
),
],
),
SizedBox(height: 36.h),
CustomButton(
text: "Login to use this service".needTranslation,
onPressed: () async {
await authVM.onLoginPressed();
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
fontSize: 16,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 50.h,
icon: AppAssets.login1,
iconColor: AppColors.whiteColor,
iconSize: 24.h,
),
],
).paddingSymmetrical(24.h, 0.h);
}
}

@ -0,0 +1,95 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/dental_chief_complaints_response_model.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/select_doctor_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/chief_complaint_card.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:provider/provider.dart';
class DentalChiefComplaintsPage extends StatefulWidget {
const DentalChiefComplaintsPage({super.key});
@override
State<DentalChiefComplaintsPage> createState() => _DentalChiefComplaintsPageState();
}
class _DentalChiefComplaintsPageState extends State<DentalChiefComplaintsPage> {
late AppState appState;
late BookAppointmentsViewModel bookAppointmentsViewModel;
@override
void initState() {
scheduleMicrotask(() {
bookAppointmentsViewModel.getDentalChiefComplaintsList();
});
super.initState();
}
@override
Widget build(BuildContext context) {
bookAppointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context, listen: false);
appState = getIt.get<AppState>();
return CollapsingListView(
title: "Dental Chief Complaints".needTranslation,
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 24.h),
child: Consumer<BookAppointmentsViewModel>(builder: (context, bookAppointmentsVM, child) {
return ListView.separated(
padding: EdgeInsets.only(top: 24.h),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: bookAppointmentsVM.isChiefComplaintsListLoading ? 5 : bookAppointmentsVM.dentalChiefComplaintsList.length,
itemBuilder: (context, index) {
return bookAppointmentsVM.isChiefComplaintsListLoading
? ChiefComplaintCard(
bookAppointmentsVM: bookAppointmentsVM,
dentalChiefComplaintsListResponseModel: DentalChiefComplaintsListResponseModel(),
isLoading: bookAppointmentsVM.isChiefComplaintsListLoading,
)
: AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
child: ChiefComplaintCard(
bookAppointmentsVM: bookAppointmentsVM,
dentalChiefComplaintsListResponseModel: bookAppointmentsVM.dentalChiefComplaintsList[index],
isLoading: bookAppointmentsVM.isChiefComplaintsListLoading,
).onPress(() {
bookAppointmentsVM.setSelectedChiefComplaintID(bookAppointmentsVM.dentalChiefComplaintsList[index].iD!);
bookAppointmentsViewModel.setIsDoctorsListLoading(true);
Navigator.of(context).push(
CustomPageRoute(
page: SelectDoctorPage(),
),
);
}),
),
),
),
);
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
);
}),
),
),
);
}
}

@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'
show BookAppointmentsViewModel;
import 'package:hmg_patient_app_new/features/doctor_filter/doctor_filter_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';
class RegionChips extends StatelessWidget {
const RegionChips({super.key});
@override
Widget build(BuildContext context) {
return Selector<DoctorFilterViewModel, List<String>>(
selector: (_, model) => model.searchedRegionList,
builder: (__, data, ___) =>
Selector<DoctorFilterViewModel, List<String>?>(
selector: (_, model) => model.selectedRegionForFilters,
builder: (context, selectRegion, ___) => Row(
children: [
Expanded(
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: data.length,
separatorBuilder: (_, __)=>SizedBox(width: 8.h,),
itemBuilder: (_, index) => AppCustomChipWidget(
labelText: data[index],
textColor: selectRegion?.any((selectedRegion)=>data[index] == selectedRegion) == true
? AppColors.primaryRedColor
: AppColors.textColor,
backgroundColor:
selectRegion?.any((selectedRegion)=>data[index] == selectedRegion) == true
? AppColors.primaryRedColor
.withOpacity(0.1)
: AppColors.whiteColor,
shape: RoundedRectangleBorder(
side: BorderSide(
color: selectRegion?.any((selectedRegion)=>data[index] == selectedRegion) == true
? AppColors
.primaryRedBorderColor
: AppColors
.chipBorderColorOpacity20,
width: 1,
),
borderRadius:
BorderRadius.circular(10)))
.onPress(() {
context
.read<DoctorFilterViewModel>()
.setSelectedRegion(data[index]);
})))
],
),
));
}
}

@ -0,0 +1,85 @@
import 'package:easy_localization/easy_localization.dart'
show tr, StringTranslateExtension;
import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.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/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/doctor_filter/doctor_filter_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_bottom_sheet/type_selection_widget.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_filter/clinic_item.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/clinic_card.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/theme/colors.dart' show AppColors;
import 'package:hmg_patient_app_new/widgets/input_widget.dart';
import 'package:provider/provider.dart';
import '../../../features/book_appointments/models/resp_models/get_clinic_list_response_model.dart' show GetClinicsListResponseModel;
class ClinicBottomSheet extends StatelessWidget {
late BookAppointmentsViewModel appointmentsViewModel;
late AppointmentViaRegionViewmodel regionalViewModel;
final TextEditingController searchText = TextEditingController();
ClinicBottomSheet({super.key});
@override
Widget build(BuildContext context) {
appointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context);
regionalViewModel = Provider.of<AppointmentViaRegionViewmodel>(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
LocaleKeys.selectClinic.tr(),
style: TextStyle(
fontSize: 21,
fontWeight: FontWeight.w600,
color: AppColors.blackColor,
),
),
SizedBox(height: 24.h),
SizedBox(
height: MediaQuery.sizeOf(context).height * .4,
child: ListView.separated(
itemBuilder: (_, index)
{
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
child: ClinicItem(
isArabic: appointmentsViewModel.isArabic(),
clinicName: appointmentsViewModel.searchedClinicList[index],
),
),
),
),
).onPress(() {
context.read<DoctorFilterViewModel>()
.setSelectedClinicForFilter(appointmentsViewModel.searchedClinicList[index]);
Navigator.pop(context);
});},
separatorBuilder: (_, __) => SizedBox(
height: 16.h,
),
itemCount: appointmentsViewModel.searchedClinicList.length ?? 0),
)
],
);
}
}

@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/theme/colors.dart' show AppColors;
class ClinicItem extends StatelessWidget {
final String clinicName;
final bool isArabic;
ClinicItem({super.key, required this.clinicName, required this.isArabic});
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(16.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: false,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Utils.buildSvgWithAssets(
icon: AppAssets.generic_clinic_icon,
width: 24.h,
height: 24.h,
fit: BoxFit.contain),
SizedBox(height: 16.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(child: clinicName.toText16(isBold: true)),
Transform.flip(
flipX: isArabic,
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon,
width: 15.h,
height: 15.h,
fit: BoxFit.contain,
iconColor: AppColors.textColor),
),
],
),
],
),
);
}
}

@ -0,0 +1,249 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/doctor_filter/doctor_filter_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body_for_doctor_filter.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_filter/RegionChips.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_filter/clinic_bottomsheet.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_filter/facility_Chips.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/input_widget.dart';
import 'package:provider/provider.dart';
import '../../../widgets/buttons/custom_button.dart';
class DoctorsFilters extends StatelessWidget{
TextEditingController hospitalController = TextEditingController();
TextEditingController clinicController = TextEditingController();
DoctorsFilters({super.key,});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
appBar: AppBar(
backgroundColor: AppColors.bgScaffoldColor,
automaticallyImplyLeading: false,
centerTitle: false,
title: Utils.buildSvgWithAssets(icon: AppAssets.ic_close, height: 32.h, width: 32.h).onPress((){
context.read<DoctorFilterViewModel>()
.clearSelection()
;
Navigator.pop(context);
})
),
body: Column(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
LocaleKeys.filters.tr(),
style:TextStyle(
fontFamily: 'Poppins',
fontWeight: FontWeight.w600,
fontSize: 27.fSize,
color: AppColors.textColor,
letterSpacing: -1
)
),
Text(
LocaleKeys.clearAllFilters.tr(),
style:TextStyle(
fontFamily: 'Poppins',
fontWeight: FontWeight.w500,
fontSize: 14.fSize,
color: AppColors.errorColor
)
).onPress((){
context.read<DoctorFilterViewModel>().clearSelection();
context.read<DoctorFilterViewModel>().updateApplyFilters(false);
// context.read<BookAppointmentsViewModel>().setSelections(
// context.read<DoctorFilterViewModel>().selectedFacilityForFilters,
// context.read<DoctorFilterViewModel>().selectedRegionForFilters,
// context.read<DoctorFilterViewModel>().selectedClinicForFilters,
// context.read<DoctorFilterViewModel>().selectedHospitalForFilters,
// context.read<DoctorFilterViewModel>().applyFilters);
context.read<BookAppointmentsViewModel>().updateList();
})
],
),
titleWidget(LocaleKeys.regionAndLocation.tr()),
SizedBox(
height: 42.h,
child: RegionChips()),
titleWidget(LocaleKeys.facilityAndLocation.tr()),
SizedBox(
height: 42.h,
child: FacilityChip()),
titleWidget(LocaleKeys.hospital.tr()),
TextInputWidget(
controller: TextEditingController()..text =context.watch<DoctorFilterViewModel>().selectedHospitalForFilters?.filterName??'',
labelText: LocaleKeys.hospital.tr(context: context),
hintText: LocaleKeys.searchHospital.tr(context: context),
isEnable: false,
prefix: null,
autoFocus: false,
isBorderAllowed: false,
keyboardType: TextInputType.text,
suffix:context.watch<DoctorFilterViewModel>().selectedHospitalForFilters != null
? GestureDetector(
onTap: () {
context.read<DoctorFilterViewModel>().setSelectedHospital(null);
},
child: Utils.buildSvgWithAssets(icon: AppAssets.ic_cross_circle, width: 24.h, height: 24.h, fit: BoxFit.scaleDown),
)
: null,
onChange: (value) {
// DoctorFilterViewModel.filterClinics(value!);
},
padding: EdgeInsets.symmetric(
vertical: ResponsiveExtension(8).h,
horizontal: ResponsiveExtension(10).h,
),
).onPress((){
openRegionListBottomSheet(context, RegionBottomSheetType.FOR_REGION);
}),
titleWidget(LocaleKeys.clinic.tr()),
TextInputWidget(
controller: TextEditingController()..text =context.watch<DoctorFilterViewModel>().selectedClinicForFilters ??'',
labelText: LocaleKeys.clinicName.tr(context: context),
hintText: LocaleKeys.searchClinic.tr().needTranslation,
isEnable: false,
prefix: null,
autoFocus: false,
isBorderAllowed: false,
keyboardType: TextInputType.text,
suffix:context.read<DoctorFilterViewModel>().selectedClinicForFilters?.isNotEmpty == true
? GestureDetector(
onTap: () {
context.read<DoctorFilterViewModel>().setSelectedClinicForFilter(null);
},
child: Utils.buildSvgWithAssets(icon: AppAssets.ic_cross_circle, width: 20.h, height: 20.h, fit: BoxFit.scaleDown),
)
: null,
onChange: (value) {
// DoctorFilterViewModel.filterClinics(value!);
},
padding: EdgeInsets.symmetric(
vertical: 8.h,
horizontal: 10.h,
),
).onPress((){
openClinicListBottomSheet(context,);
}),
],
).paddingSymmetrical(24.h, 0.h),
Spacer(),
DecoratedBox(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: Colors.white,
customBorder: BorderRadius.only(topLeft: Radius.circular(24.h), topRight: Radius.circular(24.h)) ,
),
child: CustomButton(
text: LocaleKeys.applyFilter.tr(),
onPressed: () {
context.read<DoctorFilterViewModel>().updateApplyFilters(true);
context.read<BookAppointmentsViewModel>().setSelections(
context.read<DoctorFilterViewModel>().selectedFacilityForFilters?.toList()??[],
context.read<DoctorFilterViewModel>().selectedRegionForFilters?.toList()??[],
context.read<DoctorFilterViewModel>().selectedClinicForFilters,
context.read<DoctorFilterViewModel>().selectedHospitalForFilters,
context.read<DoctorFilterViewModel>().applyFilters);
context.read<BookAppointmentsViewModel>().updateList();
Navigator.pop(context);
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: Colors.white,
fontSize: 16,
padding: EdgeInsets.zero,
fontWeight: FontWeight.w500,
borderRadius: 12,
icon: AppAssets.add_icon,
iconColor: AppColors.primaryRedColor,
).paddingAll(24.h),
),
],
),
);
}
Widget titleWidget(String title){
return Column(
children: [
SizedBox(height: 24.h,),
Text(
title,
style:TextStyle(
fontFamily: 'Poppins',
fontWeight: FontWeight.w600,
fontSize: 16.fSize,
color: AppColors.textColor,
letterSpacing:-1
)
),
SizedBox(height: 8.h,),
],
);
}
void openRegionListBottomSheet(BuildContext context, RegionBottomSheetType type) {
context.read<AppointmentViaRegionViewmodel>().flush();
context.read<AppointmentViaRegionViewmodel>().setBottomSheetType(type);
context.read<AppointmentViaRegionViewmodel>().setBottomSheetState(AppointmentViaRegionState.HOSPITAL_SELECTION);
// AppointmentViaRegionViewmodel? viewmodel = null;
showCommonBottomSheetWithoutHeight(context, title: "", titleWidget: Consumer<AppointmentViaRegionViewmodel>(builder: (_, data, __) => getTitle(data)), isDismissible: false,
child: Consumer<AppointmentViaRegionViewmodel>(builder: (_, data, __) {
return getRegionalSelectionWidget(data);
}), callBackFunc: () {});
}
Widget getRegionalSelectionWidget(AppointmentViaRegionViewmodel data) {
if (data.bottomSheetState == AppointmentViaRegionState.HOSPITAL_SELECTION) {
return HospitalBottomSheetBodyForDoctorFilter();
}
if (data.bottomSheetState == AppointmentViaRegionState.CLINIC_SELECTION) {
} else {
SizedBox.shrink();
}
return SizedBox.shrink();
}
getTitle(AppointmentViaRegionViewmodel data) {
return SizedBox.shrink();
}
void openClinicListBottomSheet(BuildContext context) {
showCommonBottomSheetWithoutHeight(context, title: "", titleWidget: Consumer<AppointmentViaRegionViewmodel>(builder: (_, data, __) => getTitle(data)), isDismissible: false,
child: Consumer<AppointmentViaRegionViewmodel>(builder: (_, data, __) {
return ClinicBottomSheet();
}), callBackFunc: () {});
}
}

@ -0,0 +1,65 @@
import 'package:easy_localization/easy_localization.dart' show tr, StringTranslateExtension;
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'
show BookAppointmentsViewModel;
import 'package:hmg_patient_app_new/features/doctor_filter/doctor_filter_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';
class FacilityChip extends StatelessWidget {
const FacilityChip({super.key});
@override
Widget build(BuildContext context) {
return Selector<DoctorFilterViewModel, List<String>>(
selector: (_, model) => model.facilityList,
builder: (__, data, ___) =>
Selector<DoctorFilterViewModel, List<String>?>(
selector: (_, model) => model.selectedFacilityForFilters,
builder: (context, selectRegion, ___) => Row(
children: [
Expanded(
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: data.length,
separatorBuilder: (_, __)=>SizedBox(width: 8.h,),
itemBuilder: (_, index) => AppCustomChipWidget(
icon: data[index].contains("hmg")?AppAssets.hmg: AppAssets.hmc,
iconHasColor: false,
iconSize: 18,
labelText: data[index].tr(),
textColor: selectRegion?.any((selectedRegion)=>data[index] == selectedRegion) == true
? AppColors.primaryRedColor
: AppColors.textColor,
backgroundColor:
data[index] == selectRegion
? AppColors.primaryRedColor
.withOpacity(0.1)
: AppColors.whiteColor,
shape: RoundedRectangleBorder(
side: BorderSide(
color: selectRegion?.any((selectedRegion)=>data[index] == selectedRegion) == true
? AppColors
.primaryRedBorderColor
: AppColors
.chipBorderColorOpacity20,
width: 1,
),
borderRadius:
BorderRadius.circular(10)))
.onPress(() {
context
.read<DoctorFilterViewModel>()
.setSelectedFacilityForFilter(data[index]);
})))
],
),
));
}
}

@ -0,0 +1,304 @@
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
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/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/widgets/select_livecare_call_type.dart';
import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.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/chip/app_custom_chip_widget.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart';
import 'package:smooth_corner/smooth_corner.dart';
class ImmediateLiveCarePaymentDetails extends StatelessWidget {
ImmediateLiveCarePaymentDetails({super.key});
late ImmediateLiveCareViewModel immediateLiveCareViewModel;
late AppState appState;
@override
Widget build(BuildContext context) {
immediateLiveCareViewModel = Provider.of<ImmediateLiveCareViewModel>(context, listen: false);
appState = getIt.get<AppState>();
return Scaffold(
backgroundColor: AppColors.scaffoldBgColor,
body: Column(
children: [
Expanded(
child: CollapsingListView(
title: "Review LiveCare Request".needTranslation,
child: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 24.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 24.h),
LocaleKeys.patientInfo.tr(context: context).toText16(isBold: true),
SizedBox(height: 16.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: false,
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Row(
children: [
Image.asset(
appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.male_img : AppAssets.femaleImg,
width: 52.h,
height: 52.h,
),
SizedBox(width: 8.h),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}".toText16(isBold: true),
SizedBox(height: 8.h),
AppCustomChipWidget(labelText: "${appState.getAuthenticatedUser()!.age} Years Old"),
],
),
],
),
),
),
SizedBox(height: 24.h),
"Clinic Information".needTranslation.toText16(isBold: true),
SizedBox(height: 16.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: false,
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.generic_clinic_icon, width: 32.h, height: 32.h, fit: BoxFit.contain),
SizedBox(width: 8.h),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
(appState.isArabic()
? immediateLiveCareViewModel.immediateLiveCareSelectedClinic.serviceNameN
: immediateLiveCareViewModel.immediateLiveCareSelectedClinic.serviceName)!
.toText16(isBold: true),
// SizedBox(height: 8.h),
// AppCustomChipWidget(labelText: "${appState.getAuthenticatedUser()!.age} Years Old"),
],
),
],
),
),
),
SizedBox(height: 24.h),
"Selected LiveCare Type".needTranslation.toText16(isBold: true),
SizedBox(height: 16.h),
Consumer<BookAppointmentsViewModel>(builder: (context, bookAppointmentsVM, child) {
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: false,
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.livecare_clinic_icon, width: 32.h, height: 32.h, fit: BoxFit.contain),
SizedBox(width: 8.h),
getLiveCareType(immediateLiveCareViewModel.liveCareSelectedCallType).toText16(isBold: true),
],
),
Utils.buildSvgWithAssets(icon: AppAssets.edit_icon, width: 24.h, height: 24.h, fit: BoxFit.contain),
],
),
),
).onPress(() {
showCommonBottomSheetWithoutHeight(context, child: SelectLiveCareCallType(immediateLiveCareViewModel: immediateLiveCareViewModel), callBackFunc: () async {
debugPrint("Selected Call Type: ${immediateLiveCareViewModel.liveCareSelectedCallType}");
}, title: "Select LiveCare call type".needTranslation, isCloseButtonVisible: true, isFullScreen: false);
});
}),
SizedBox(height: 24.h)
],
),
),
),
),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: false,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.isCash ?? true)
? Container(
height: 50.h,
decoration: ShapeDecoration(
color: AppColors.secondaryLightRedBorderColor,
shape: SmoothRectangleBorder(
borderRadius: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)),
smoothness: 1,
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"Insurance expired or inactive".needTranslation.toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500).paddingSymmetrical(24.h, 0.h),
CustomButton(
text: LocaleKeys.updateInsurance.tr(context: context),
onPressed: () {
Navigator.of(context).push(
CustomPageRoute(
page: InsuranceHomePage(),
),
);
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.secondaryLightRedBorderColor,
textColor: AppColors.whiteColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(15, 0, 15, 0),
height: 30.h,
).paddingSymmetrical(24.h, 0.h),
],
),
)
: const SizedBox(),
SizedBox(height: 24.h),
"Total amount to pay".needTranslation.toText18(isBold: true).paddingSymmetrical(24.h, 0.h),
SizedBox(height: 17.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"Amount before tax".needTranslation.toText14(isBold: true),
Utils.getPaymentAmountWithSymbol(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.amount!.toText16(isBold: true), AppColors.blackColor, 13,
isSaudiCurrency: immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar"),
],
).paddingSymmetrical(24.h, 0.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"VAT 15%".needTranslation.toText14(isBold: true, color: AppColors.greyTextColor),
Utils.getPaymentAmountWithSymbol(
immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.tax!.toText14(isBold: true, color: AppColors.greyTextColor), AppColors.greyTextColor, 13,
isSaudiCurrency: immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar"),
],
).paddingSymmetrical(24.h, 0.h),
SizedBox(height: 17.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(width: 150.h, child: Utils.getPaymentMethods()),
Utils.getPaymentAmountWithSymbol(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total!.toText24(isBold: true), AppColors.blackColor, 17,
isSaudiCurrency: immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar"),
],
).paddingSymmetrical(24.h, 0.h),
CustomButton(
text: LocaleKeys.payNow.tr(context: context),
onPressed: () async {
await askVideoCallPermission().then((val) {
if (val) {
Navigator.of(context).push(
CustomPageRoute(
page: ImmediateLiveCarePaymentPage(),
),
);
} else {
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.notice.tr(context: context),
context,
child: Utils.getWarningWidget(
loadingText:
"LiveCare requires Camera, Microphone & Location permissions to enable virtual consultation between patient & doctor, Please allow these to proceed.".needTranslation,
isShowActionButtons: true,
onCancelTap: () {
Navigator.pop(context);
},
onConfirmTap: () async {
openAppSettings();
}),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
});
},
backgroundColor: AppColors.infoColor,
borderColor: AppColors.infoColor,
textColor: AppColors.whiteColor,
fontSize: 16,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 50.h,
icon: AppAssets.appointment_pay_icon,
iconColor: AppColors.whiteColor,
iconSize: 18.h,
).paddingSymmetrical(24.h, 24.h),
],
),
),
],
),
);
}
Future<bool> askVideoCallPermission() async {
Map<Permission, PermissionStatus> statuses = await [
Permission.camera,
Permission.microphone,
].request();
if (statuses[Permission.camera] == PermissionStatus.granted && statuses[Permission.microphone] == PermissionStatus.granted) {
// Camera permission granted
return true;
} else {
return false;
}
// if (!(await Permission.camera.request().isGranted) || !(await Permission.microphone.request().isGranted)) {
// return false;
// }
}
String getLiveCareType(int callType) {
switch (callType) {
case 1:
return "Video Call".needTranslation;
case 2:
return "Audio Call".needTranslation;
case 3:
return "Phone Call".needTranslation;
default:
return "Video Call".needTranslation;
}
}
}

@ -0,0 +1,580 @@
import 'dart:async';
import 'dart:developer';
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart';
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/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/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart';
import 'package:hmg_patient_app_new/features/payfort/models/apple_pay_request_insert_model.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/appointments/my_appointments_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/immediate_livecare_pending_request_page.dart';
import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/in_app_browser/InAppBrowser.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:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart';
import 'package:smooth_corner/smooth_corner.dart';
class ImmediateLiveCarePaymentPage extends StatefulWidget {
ImmediateLiveCarePaymentPage({super.key});
@override
State<ImmediateLiveCarePaymentPage> createState() => _ImmediateLiveCarePaymentPageState();
}
class _ImmediateLiveCarePaymentPageState extends State<ImmediateLiveCarePaymentPage> {
late PayfortViewModel payfortViewModel;
late ImmediateLiveCareViewModel immediateLiveCareViewModel;
late MyAppointmentsViewModel myAppointmentsViewModel;
late AppState appState;
MyInAppBrowser? browser;
String selectedPaymentMethod = "";
String transID = "";
bool isShowTamara = false;
String tamaraPaymentStatus = "";
String tamaraOrderID = "";
@override
void initState() {
scheduleMicrotask(() {
payfortViewModel.initPayfortViewModel();
myAppointmentsViewModel.getTamaraInstallmentsDetails().then((val) {
if (num.parse(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total!) >= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.minLimit!.amount! &&
num.parse(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total!) <= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.maxLimit!.amount!) {
setState(() {
isShowTamara = true;
});
}
});
});
super.initState();
}
@override
Widget build(BuildContext context) {
appState = getIt.get<AppState>();
myAppointmentsViewModel = Provider.of<MyAppointmentsViewModel>(context, listen: false);
immediateLiveCareViewModel = Provider.of<ImmediateLiveCareViewModel>(context, listen: false);
payfortViewModel = Provider.of<PayfortViewModel>(context, listen: false);
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: Consumer<MyAppointmentsViewModel>(builder: (context, myAppointmentsVM, child) {
return Column(
children: [
Expanded(
child: CollapsingListView(
title: "LiveCare Payment".needTranslation,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 24.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: false,
),
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.asset(AppAssets.mada, width: 72.h, height: 25.h),
SizedBox(height: 16.h),
"Mada".needTranslation.toText16(isBold: true),
],
),
SizedBox(width: 8.h),
const Spacer(),
Transform.flip(
flipX: appState.isArabic() ? true : false,
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),
).paddingSymmetrical(24.h, 0.h).onPress(() {
selectedPaymentMethod = "MADA";
openPaymentURL("mada");
}),
SizedBox(height: 16.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: false,
),
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Image.asset(AppAssets.visa, width: 50.h, height: 50.h),
SizedBox(width: 8.h),
Image.asset(AppAssets.Mastercard, width: 40.h, height: 40.h),
],
),
SizedBox(height: 16.h),
"Visa or Mastercard".needTranslation.toText16(isBold: true),
],
),
SizedBox(width: 8.h),
const Spacer(),
Transform.flip(
flipX: appState.isArabic() ? true : false,
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),
).paddingSymmetrical(24.h, 0.h).onPress(() {
selectedPaymentMethod = "VISA";
openPaymentURL("visa");
}),
SizedBox(height: 16.h),
isShowTamara
? Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: false,
),
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.asset(AppAssets.tamara_en, width: 72.h, height: 25.h),
SizedBox(height: 16.h),
"Tamara".needTranslation.toText16(isBold: true),
],
),
SizedBox(width: 8.h),
const Spacer(),
Transform.flip(
flipX: appState.isArabic() ? true : false,
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),
).paddingSymmetrical(24.h, 0.h).onPress(() {
selectedPaymentMethod = "TAMARA";
openPaymentURL("tamara");
})
: SizedBox.shrink(),
],
),
),
),
),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: false,
),
child: Consumer<PayfortViewModel>(builder: (context, payfortVM, child) {
//TODO: Need to add loading state & animation for Apple Pay Configuration
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.isCash ?? true)
? Container(
height: 50.h,
decoration: ShapeDecoration(
color: AppColors.secondaryLightRedBorderColor,
shape: SmoothRectangleBorder(
borderRadius: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)),
smoothness: 1,
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"Insurance expired or inactive".needTranslation.toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500).paddingSymmetrical(24.h, 0.h),
CustomButton(
text: LocaleKeys.updateInsurance.tr(context: context),
onPressed: () {
Navigator.of(context).push(
CustomPageRoute(
page: InsuranceHomePage(),
),
);
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.secondaryLightRedBorderColor,
textColor: AppColors.whiteColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(15, 0, 15, 0),
height: 30.h,
).paddingSymmetrical(24.h, 0.h),
],
),
)
: const SizedBox(),
SizedBox(height: 24.h),
"Total amount to pay".needTranslation.toText18(isBold: true).paddingSymmetrical(24.h, 0.h),
SizedBox(height: 17.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"Amount before tax".needTranslation.toText14(isBold: true),
Utils.getPaymentAmountWithSymbol(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.amount!.toString().toText16(isBold: true), AppColors.blackColor, 13,
isSaudiCurrency: true),
],
).paddingSymmetrical(24.h, 0.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"VAT 15%".needTranslation.toText14(isBold: true, color: AppColors.greyTextColor),
Utils.getPaymentAmountWithSymbol(
immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.tax!.toString().toText14(isBold: true, color: AppColors.greyTextColor), AppColors.greyTextColor, 13,
isSaudiCurrency: true),
],
).paddingSymmetrical(24.h, 0.h),
SizedBox(height: 17.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"".needTranslation.toText14(isBold: true),
Utils.getPaymentAmountWithSymbol(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total!.toString().toText24(isBold: true), AppColors.blackColor, 17,
isSaudiCurrency: true),
],
).paddingSymmetrical(24.h, 0.h),
Platform.isIOS
? Utils.buildSvgWithAssets(
icon: AppAssets.apple_pay_button,
width: 200.h,
height: 80.h,
fit: BoxFit.contain,
).paddingSymmetrical(24.h, 0.h).onPress(() {
// payfortVM.setIsApplePayConfigurationLoading(true);
if (Utils.havePrivilege(103)) {
startApplePay();
} else {
openPaymentURL("ApplePay");
}
})
: SizedBox(height: 12.h),
SizedBox(height: 12.h),
],
);
}),
),
],
);
}),
);
}
onBrowserLoadStart(String url) {
print("onBrowserLoadStart");
print(url);
if (selectedPaymentMethod == "TAMARA") {
if (Platform.isAndroid) {
Uri uri = new Uri.dataFromString(url);
tamaraPaymentStatus = uri.queryParameters['status']!;
tamaraOrderID = uri.queryParameters['AuthorizePaymentId']!;
} else {
Uri uri = new Uri.dataFromString(url);
tamaraPaymentStatus = uri.queryParameters['paymentStatus']!;
tamaraOrderID = uri.queryParameters['orderId']!;
}
}
// if(selectedPaymentMethod != "TAMARA") {
MyInAppBrowser.successURLS.forEach((element) {
if (url.contains(element)) {
browser?.close();
MyInAppBrowser.isPaymentDone = true;
return;
}
});
// }
// if(selectedPaymentMethod != "TAMARA") {
MyInAppBrowser.errorURLS.forEach((element) {
if (url.contains(element)) {
browser?.close();
MyInAppBrowser.isPaymentDone = false;
return;
}
});
// }
}
onBrowserExit(bool isPaymentMade) async {
debugPrint("onBrowserExit Called!!!!");
checkPaymentStatus();
}
void checkPaymentStatus() async {
LoaderBottomSheet.showLoader(loadingText: "Checking payment status, Please wait...".needTranslation);
if (selectedPaymentMethod == "TAMARA") {
await payfortViewModel.checkTamaraPaymentStatus(
transactionID: transID,
onSuccess: (apiResponse) async {
if (apiResponse.data["status"].toString().toLowerCase() == "success") {
tamaraOrderID = apiResponse.data["tamara_order_id"].toString();
await payfortViewModel.updateTamaraRequestStatus(responseMessage: "success", status: "14", clientRequestID: transID, tamaraOrderID: tamaraOrderID);
await immediateLiveCareViewModel.addNewCallRequestForImmediateLiveCare(transID);
await immediateLiveCareViewModel.getPatientLiveCareHistory();
LoaderBottomSheet.hideLoader();
if (immediateLiveCareViewModel.patientHasPendingLiveCareRequest) {
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
Navigator.of(context).push(
CustomPageRoute(
page: ImmediateLiveCarePendingRequestPage(),
),
);
} else {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: "Unknown error occurred...".needTranslation),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
} else {
await payfortViewModel.updateTamaraRequestStatus(responseMessage: "Failed", status: "00", clientRequestID: transID, tamaraOrderID: tamaraOrderID);
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: "Payment Failed! Please try again.".needTranslation),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
},
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else {
await payfortViewModel.checkPaymentStatus(
transactionID: transID,
onSuccess: (apiResponse) async {
debugPrint(apiResponse.data.toString());
if (payfortViewModel.payfortCheckPaymentStatusResponseModel!.responseMessage!.toLowerCase() == "success") {
await immediateLiveCareViewModel.addNewCallRequestForImmediateLiveCare(transID);
await immediateLiveCareViewModel.getPatientLiveCareHistory();
LoaderBottomSheet.hideLoader();
if (immediateLiveCareViewModel.patientHasPendingLiveCareRequest) {
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
Navigator.of(context).push(
CustomPageRoute(
page: ImmediateLiveCarePendingRequestPage(),
),
);
} else {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: "Unknown error occurred...".needTranslation),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
} else {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: "Payment Failed! Please try again.".needTranslation),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
});
}
}
openPaymentURL(String paymentMethod) {
browser = MyInAppBrowser(onExitCallback: onBrowserExit, onLoadStartCallback: onBrowserLoadStart, context: context);
transID = Utils.getAppointmentTransID(
immediateLiveCareViewModel.immediateLiveCareSelectedClinic.serviceID!,
ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? 15 : 12,
DateTime.now().millisecondsSinceEpoch,
);
//TODO: Need to pass dynamic params to the payment request instead of static values
browser?.openPaymentBrowser(
num.parse(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total!),
"LiveCare Payment",
transID,
"12",
"CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com",
selectedPaymentMethod,
appState.getAuthenticatedUser()!.patientType.toString(),
"${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}",
appState.getAuthenticatedUser()!.patientId.toString(),
appState.getAuthenticatedUser()!,
browser!,
false,
"4",
immediateLiveCareViewModel.immediateLiveCareSelectedClinic.serviceID.toString(),
context,
"3");
}
startApplePay() async {
LoaderBottomSheet.showLoader(loadingText: "Fetching Apple Pay details, Please wait...".needTranslation);
transID = Utils.getAppointmentTransID(
immediateLiveCareViewModel.immediateLiveCareSelectedClinic.serviceID!,
ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? 15 : 12,
DateTime.now().millisecondsSinceEpoch,
);
ApplePayInsertRequest applePayInsertRequest = ApplePayInsertRequest();
await payfortViewModel.getPayfortConfigurations(
serviceId: ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum(), projectId: ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? 15 : 12, integrationId: 2);
applePayInsertRequest.clientRequestID = transID;
applePayInsertRequest.clinicID = immediateLiveCareViewModel.immediateLiveCareSelectedClinic.serviceID!;
// TODO: Need to pass dynamic currency coming from the API
applePayInsertRequest.currency = appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED";
applePayInsertRequest.customerEmail = "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com";
applePayInsertRequest.customerID = appState.getAuthenticatedUser()!.patientId.toString();
applePayInsertRequest.customerName = "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}";
applePayInsertRequest.deviceToken = await Utils.getStringFromPrefs(CacheConst.pushToken);
applePayInsertRequest.voipToken = await Utils.getStringFromPrefs(CacheConst.voipToken);
applePayInsertRequest.doctorID = 0;
applePayInsertRequest.projectID = "12";
applePayInsertRequest.serviceID = ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum().toString();
applePayInsertRequest.channelID = 3;
applePayInsertRequest.patientID = appState.getAuthenticatedUser()!.patientId.toString();
applePayInsertRequest.patientTypeID = appState.getAuthenticatedUser()!.patientType;
applePayInsertRequest.patientOutSA = appState.getAuthenticatedUser()!.outSa;
applePayInsertRequest.appointmentDate = DateUtil.convertDateToString(DateTime.now());
applePayInsertRequest.appointmentNo = 0;
applePayInsertRequest.orderDescription = "LiveCare Payment";
applePayInsertRequest.liveServiceID = immediateLiveCareViewModel.immediateLiveCareSelectedClinic.serviceID!.toString();
applePayInsertRequest.latitude = "0.0";
applePayInsertRequest.longitude = "0.0";
applePayInsertRequest.amount = immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total.toString();
applePayInsertRequest.isSchedule = "0";
applePayInsertRequest.language = appState.isArabic() ? 'ar' : 'en';
applePayInsertRequest.languageID = appState.isArabic() ? 1 : 2;
applePayInsertRequest.userName = appState.getAuthenticatedUser()!.patientId;
applePayInsertRequest.responseContinueURL = "http://hmg.com/Documents/success.html";
applePayInsertRequest.backClickUrl = "http://hmg.com/Documents/success.html";
applePayInsertRequest.paymentOption = "ApplePay";
applePayInsertRequest.isMobSDK = true;
applePayInsertRequest.merchantReference = transID;
applePayInsertRequest.merchantIdentifier = payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier;
applePayInsertRequest.commandType = "PURCHASE";
applePayInsertRequest.signature = payfortViewModel.payfortProjectDetailsRespModel!.signature;
applePayInsertRequest.accessCode = payfortViewModel.payfortProjectDetailsRespModel!.accessCode;
applePayInsertRequest.shaRequestPhrase = payfortViewModel.payfortProjectDetailsRespModel!.shaRequest;
applePayInsertRequest.shaResponsePhrase = payfortViewModel.payfortProjectDetailsRespModel!.shaResponse;
applePayInsertRequest.returnURL = "";
//TODO: Need to pass dynamic params to the Apple Pay instead of static values
await payfortViewModel.applePayRequestInsert(applePayInsertRequest: applePayInsertRequest).then((value) {
payfortViewModel.paymentWithApplePay(
customerName: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}",
// customerEmail: projectViewModel.authenticatedUserObject.user.emailAddress,
customerEmail: "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com",
orderDescription: "LiveCare Payment",
orderAmount: double.parse(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total!),
merchantReference: transID,
merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier,
applePayAccessCode: payfortViewModel.payfortProjectDetailsRespModel!.accessCode,
applePayShaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel!.shaRequest,
currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED",
onFailed: (failureResult) async {
log("failureResult: ${failureResult.message.toString()}");
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: failureResult.message.toString()),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
},
onSucceeded: (successResult) async {
LoaderBottomSheet.hideLoader();
log("successResult: ${successResult.responseMessage.toString()}");
selectedPaymentMethod = successResult.paymentOption ?? "VISA";
checkPaymentStatus();
},
// projectId: appo.projectID,
// serviceTypeEnum: ServiceTypeEnum.appointmentPayment,
);
});
}
}

@ -0,0 +1,305 @@
import 'dart:async';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'package:lottie/lottie.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
class ImmediateLiveCarePendingRequestPage extends StatefulWidget {
ImmediateLiveCarePendingRequestPage({super.key});
@override
State<ImmediateLiveCarePendingRequestPage> createState() => _ImmediateLiveCarePendingRequestPageState();
}
class _ImmediateLiveCarePendingRequestPageState extends State<ImmediateLiveCarePendingRequestPage> {
late ImmediateLiveCareViewModel immediateLiveCareViewModel;
late AppState appState;
static Duration countdownDuration = Duration(minutes: 1, seconds: 0);
ValueNotifier<Duration> durationNotifier = ValueNotifier<Duration>(countdownDuration);
Timer? timer;
@override
void initState() {
super.initState();
scheduleMicrotask(() {
countdownDuration = Duration(minutes: immediateLiveCareViewModel.patientLiveCareHistoryList[0].watingtimeInteger!, seconds: 0);
durationNotifier = ValueNotifier<Duration>(countdownDuration);
startTimer();
});
}
@override
void dispose() {
timer?.cancel();
durationNotifier.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
immediateLiveCareViewModel = Provider.of<ImmediateLiveCareViewModel>(context, listen: false);
appState = getIt.get<AppState>();
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: Consumer<ImmediateLiveCareViewModel>(builder: (context, immediateLiveCareVM, child) {
return Column(
children: [
Expanded(
child: CollapsingListView(
title: "LiveCare Pending Request".needTranslation,
child: Padding(
padding: EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: false,
side: BorderSide(color: AppColors.ratingColorYellow, width: 3.h),
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Expected waiting time: ".toText16(isBold: true),
SizedBox(height: 8.h),
ValueListenableBuilder<Duration>(
valueListenable: durationNotifier,
builder: (context, duration, child) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
buildTime(duration),
],
);
},
),
SizedBox(height: 8.h),
],
),
),
),
SizedBox(height: 16.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: false,
side: BorderSide(color: AppColors.ratingColorYellow, width: 3.h),
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
AppCustomChipWidget(
labelText: immediateLiveCareViewModel.patientLiveCareHistoryList[0].stringCallStatus,
backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.20),
textColor: AppColors.alertColor,
),
Utils.buildSvgWithAssets(icon: AppAssets.waiting_icon, width: 24.h, height: 24.h),
// Lottie.asset(AppAnimations.pending_loading_animation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 80.h, height: 80.h, fit: BoxFit.cover),
],
),
SizedBox(height: 8.h),
"Hala ${appState.getAuthenticatedUser()!.firstName}!!!".needTranslation.toText16(isBold: true),
SizedBox(height: 8.h),
AppCustomChipWidget(
icon: AppAssets.appointment_calendar_icon,
labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(immediateLiveCareViewModel.patientLiveCareHistoryList[0].arrivalTime), false)),
SizedBox(height: 8.h),
"Your turn is after ${immediateLiveCareViewModel.patientLiveCareHistoryList[0].patCount} patients.".toText16(isBold: true),
SizedBox(height: 8.h),
],
),
),
)
],
),
),
),
),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: true,
),
child: CustomButton(
text: "Call LiveCare Support".needTranslation,
onPressed: () async {
launchUrl(Uri.parse("tel://" + "011 525 9553"));
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
fontSize: 16,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 50.h,
icon: AppAssets.call_fill,
iconColor: AppColors.whiteColor,
iconSize: 21.h,
).paddingSymmetrical(24.h, 24.h),
),
],
);
}),
);
}
void startTimer() {
timer = Timer.periodic(const Duration(seconds: 1), (_) => addTime());
setState(() {});
}
void addTime() {
final seconds = durationNotifier.value.inSeconds - 1;
if (seconds < 0) {
timer?.cancel();
// Handle end of timer here
// showEndMessage();
} else {
durationNotifier.value = Duration(seconds: seconds);
}
}
Future<bool> _onWillPop() async {
timer?.cancel();
Navigator.of(context).pop();
return true;
}
Widget buildTime(Duration duration) {
String twoDigits(int n) => n.toString().padLeft(2, '0');
final hours = twoDigits(duration.inHours);
final minutes = twoDigits(duration.inMinutes.remainder(60));
final seconds = twoDigits(duration.inSeconds.remainder(60));
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
buildTimeColumn(hours, "Hours".needTranslation),
buildTimeColumn(minutes, "Mins".needTranslation),
buildTimeColumn(seconds, "Secs".needTranslation, isLast: true),
],
);
}
Widget buildTimeColumn(String time, String label, {bool isLast = false}) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
children: [
buildDigit(time[0]),
buildDigit(time[1]),
if (!isLast) buildTimeSeparator(),
],
),
buildLabel(label),
],
);
}
Widget buildDigit(String digit) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
// margin: const EdgeInsets.symmetric(horizontal: 2),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: ClipRect(
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 600),
switchInCurve: Curves.easeOutExpo,
switchOutCurve: Curves.easeInExpo,
transitionBuilder: (Widget child, Animation<double> animation) {
return Stack(
children: <Widget>[
SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, -1),
end: const Offset(0, 1),
).animate(CurvedAnimation(
parent: animation,
curve: Curves.easeOutCubic,
)),
child: FadeTransition(
opacity: animation,
child: child,
),
),
SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, -1),
end: const Offset(0, 0),
).animate(CurvedAnimation(
parent: animation,
curve: Curves.bounceIn,
)),
child: FadeTransition(
opacity: animation,
child: child,
),
),
],
);
},
child: Text(
digit,
key: ValueKey<String>(digit),
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black,
fontSize: 20.fSize,
),
),
),
),
);
}
Widget buildLabel(String label) {
return label.toText14(isBold: true);
}
Widget buildTimeSeparator() {
return const Padding(
padding: EdgeInsets.symmetric(horizontal: 2.0),
child: Text(
":",
style: TextStyle(
color: Colors.black,
fontSize: 20,
),
),
);
}
}

@ -0,0 +1,173 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_livecare_clinics_response_model.dart';
import 'package:hmg_patient_app_new/features/immediate_livecare/models/resp_models/get_livecare_immediate_clinics_response_model.dart';
import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/widgets/select_livecare_call_type.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/clinic_card.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/widgets/livecare_clinic_card.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:provider/provider.dart';
class SelectImmediateLiveCareClinicPage extends StatefulWidget {
const SelectImmediateLiveCareClinicPage({super.key});
@override
State<SelectImmediateLiveCareClinicPage> createState() => _SelectImmediateLiveCareClinicPageState();
}
class _SelectImmediateLiveCareClinicPageState extends State<SelectImmediateLiveCareClinicPage> {
TextEditingController searchEditingController = TextEditingController();
FocusNode textFocusNode = FocusNode();
late AppState appState;
late ImmediateLiveCareViewModel immediateLiveCareViewModel;
@override
void initState() {
scheduleMicrotask(() {
immediateLiveCareViewModel.getLiveCareImmediateClinicsList();
immediateLiveCareViewModel.setLiveCareSelectedCallType(0);
});
super.initState();
}
@override
void dispose() {
textFocusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
immediateLiveCareViewModel = Provider.of<ImmediateLiveCareViewModel>(context, listen: false);
appState = getIt.get<AppState>();
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView(
title: "Select LiveCare Clinic".needTranslation,
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(24.h),
child: Consumer<ImmediateLiveCareViewModel>(builder: (context, immediateLiveCareVM, child) {
return Column(
children: [
// SizedBox(height: 16.h),
// TextInputWidget(
// labelText: LocaleKeys.search.tr(context: context),
// hintText: LocaleKeys.clinicName.tr(context: context),
// controller: searchEditingController,
// isEnable: true,
// prefix: null,
// autoFocus: false,
// isBorderAllowed: false,
// keyboardType: TextInputType.text,
// focusNode: textFocusNode,
// suffix: searchEditingController.text.isNotEmpty
// ? GestureDetector(
// onTap: () {
// searchEditingController.clear();
// bookAppointmentsViewModel.filterClinics("");
// textFocusNode.unfocus();
// },
// child: Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown),
// )
// : null,
// onChange: (value) {
// bookAppointmentsViewModel.filterClinics(value!);
// },
// padding: EdgeInsets.symmetric(
// vertical: ResponsiveExtension(10).h,
// horizontal: ResponsiveExtension(15).h,
// ),
// ),
ListView.separated(
padding: EdgeInsets.only(top: 16.h),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: immediateLiveCareVM.isImmediateLiveCareClinicsLoading ? 5 : immediateLiveCareVM.immediateLiveCareClinicsList.length,
itemBuilder: (context, index) {
return immediateLiveCareVM.isImmediateLiveCareClinicsLoading
? ClinicCard(
bookAppointmentsVM: getIt.get<BookAppointmentsViewModel>(),
liveCareClinicsResponseModel: GetLiveCareClinicsResponseModel(),
clinicsListResponseModel: GetClinicsListResponseModel(),
isLoading: immediateLiveCareVM.isImmediateLiveCareClinicsLoading,
)
: AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
child: LiveCareClinicCard(
immediateLiveCareViewModel: immediateLiveCareVM,
liveCareClinicListResponseModel: immediateLiveCareVM.immediateLiveCareClinicsList[index],
isLoading: immediateLiveCareVM.isImmediateLiveCareClinicsLoading,
).onPress(() {
onImmediateLiveCareClinicSelected(immediateLiveCareVM.immediateLiveCareClinicsList[index]);
}),
),
),
),
);
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
),
],
);
}),
),
),
),
);
}
onImmediateLiveCareClinicSelected(GetLiveCareClinicListResponseModel liveCareClinic) {
//TODO: add implementation to show clinic schedule
if (liveCareClinic.isOnline == 1) {
showCommonBottomSheetWithoutHeight(context, child: SelectLiveCareCallType(immediateLiveCareViewModel: immediateLiveCareViewModel), callBackFunc: () async {
if (immediateLiveCareViewModel.liveCareSelectedCallType != 0) {
immediateLiveCareViewModel.setImmediateLiveCareSelectedClinic(liveCareClinic);
LoaderBottomSheet.showLoader(loadingText: "Fetching fees, Please wait...".needTranslation);
await immediateLiveCareViewModel.getLiveCareImmediateAppointmentFees(onSuccess: (val) {
LoaderBottomSheet.hideLoader();
Navigator.of(context).push(
CustomPageRoute(
page: ImmediateLiveCarePaymentDetails(),
),
);
}, onError: (err) {
LoaderBottomSheet.hideLoader();
});
}
}, title: "Select LiveCare call type".needTranslation, isCloseButtonVisible: true, isFullScreen: false);
} else {
showCommonBottomSheetWithoutHeight(context,
child: Utils.getErrorWidget(
loadingText: "The selected clinic is only available between ${liveCareClinic.shiftTimings!.first.startTime} & ${liveCareClinic.shiftTimings!.first.endTime} hours.".needTranslation),
callBackFunc: () {},
title: "",
isCloseButtonVisible: true,
isFullScreen: false);
}
}
}

@ -0,0 +1,67 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/immediate_livecare/models/resp_models/get_livecare_immediate_clinics_response_model.dart';
import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
class LiveCareClinicCard extends StatelessWidget {
LiveCareClinicCard({super.key, required this.liveCareClinicListResponseModel, required this.isLoading, required this.immediateLiveCareViewModel});
GetLiveCareClinicListResponseModel liveCareClinicListResponseModel;
bool isLoading;
ImmediateLiveCareViewModel immediateLiveCareViewModel;
@override
Widget build(BuildContext context) {
AppState appState = getIt.get<AppState>();
return Container(
padding: EdgeInsets.all(16.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: false,
),
child: Column(
children: [
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Utils.buildSvgWithAssets(icon: AppAssets.generic_clinic_icon, width: 24.h, height: 24.h, fit: BoxFit.contain).toShimmer2(isShow: isLoading),
Column(
children: [
Utils.buildSvgWithAssets(
icon: AppAssets.livecare_online_icon,
width: 16.h,
height: 16.h,
fit: BoxFit.contain,
iconColor: liveCareClinicListResponseModel.isOnline == 1 ? AppColors.successColor : AppColors.primaryRedColor)
.toShimmer2(isShow: isLoading),
SizedBox(height: 4.h),
liveCareClinicListResponseModel.isOnline == 1
? LocaleKeys.online.tr(context: context).toText10(isBold: true, color: AppColors.successColor).toShimmer2(isShow: isLoading)
: "Offline".toText10(isBold: true, color: AppColors.primaryRedColor).toShimmer2(isShow: isLoading),
],
),
]),
SizedBox(height: 8.h),
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Expanded(
child: (isLoading ? "Cardiology" : (appState.isArabic() ? liveCareClinicListResponseModel.serviceNameN : liveCareClinicListResponseModel.serviceName))!
.toText16(isBold: true)
.toShimmer2(isShow: isLoading)),
Transform.flip(
flipX: appState.isArabic() ? true : false,
child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, width: 40.h, height: 40.h, fit: BoxFit.contain, iconColor: AppColors.textColor).toShimmer2(isShow: isLoading)),
]),
],
),
);
}
}

@ -0,0 +1,66 @@
import 'package:flutter/cupertino.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/widgets/medical_file_card.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
class SelectLiveCareCallType extends StatelessWidget {
SelectLiveCareCallType({super.key, required this.immediateLiveCareViewModel});
ImmediateLiveCareViewModel immediateLiveCareViewModel;
@override
Widget build(BuildContext context) {
//TODO: Replace with actual icons
return GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
mainAxisExtent: 130,
),
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
shrinkWrap: true,
children: [
MedicalFileCard(
label: "Video Call".needTranslation,
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.eye_result_icon,
isLargeText: true,
iconSize: 36.h,
).onPress(() {
Navigator.of(context).pop();
immediateLiveCareViewModel.setLiveCareSelectedCallType(1);
}),
MedicalFileCard(
label: "Audio Call".needTranslation,
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.allergy_info_icon,
isLargeText: true,
iconSize: 36.h,
).onPress(() {
Navigator.of(context).pop();
immediateLiveCareViewModel.setLiveCareSelectedCallType(2);
}),
MedicalFileCard(
label: "Phone Call".needTranslation,
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.vaccine_info_icon,
isLargeText: true,
iconSize: 36.h,
).onPress(() {
Navigator.of(context).pop();
immediateLiveCareViewModel.setLiveCareSelectedCallType(3);
}),
],
);
}
}

@ -144,6 +144,7 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
),
SizedBox(width: 8.h),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}".toText16(isBold: true),
SizedBox(height: 8.h),
@ -228,6 +229,7 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
});
});
} else {
//TODO: Add patient Derma package check API Here
await bookAppointmentsViewModel.insertSpecificAppointment(onError: (err) {
print(err.data["ErrorEndUserMessage"]);
LoadingUtils.hideFullScreenLoader();

@ -9,7 +9,9 @@ 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/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/doctor_filter/doctor_filter_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_filter/doctors_filter.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_profile_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/doctor_card.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
@ -33,16 +35,12 @@ class SearchDoctorByName extends StatefulWidget {
class _SearchDoctorByNameState extends State<SearchDoctorByName> {
TextEditingController searchEditingController = TextEditingController();
FocusNode textFocusNode = FocusNode();
late AppState appState;
late BookAppointmentsViewModel bookAppointmentsViewModel;
@override
Widget build(BuildContext context) {
bookAppointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context, listen: false);
appState = getIt.get<AppState>();
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: Column(
@ -56,33 +54,85 @@ class _SearchDoctorByNameState extends State<SearchDoctorByName> {
child: Column(
children: [
SizedBox(height: 16.h),
TextInputWidget(
labelText: LocaleKeys.search.tr(context: context),
hintText: LocaleKeys.doctorName.tr(context: context),
controller: searchEditingController,
isEnable: true,
prefix: null,
autoFocus: false,
isBorderAllowed: false,
keyboardType: TextInputType.text,
focusNode: textFocusNode,
suffix: searchEditingController.text.isNotEmpty
? GestureDetector(
onTap: () {
searchEditingController.clear();
// bookAppointmentsViewModel.filterClinics("");
textFocusNode.unfocus();
},
child: Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown),
)
: null,
onChange: (value) {
// bookAppointmentsViewModel.filterClinics(value!);
},
padding: EdgeInsets.symmetric(
vertical: ResponsiveExtension(10).h,
horizontal: ResponsiveExtension(15).h,
),
Row(
spacing: 8.h,
children: [
Expanded(
child: TextInputWidget(
labelText: LocaleKeys.search.tr(context: context),
hintText: LocaleKeys.doctorName.tr(context: context),
controller: searchEditingController,
isEnable: true,
prefix: null,
autoFocus: false,
isBorderAllowed: false,
keyboardType: TextInputType.text,
focusNode: textFocusNode,
suffix: searchEditingController.text.isNotEmpty
? GestureDetector(
onTap: () {
searchEditingController.clear();
// bookAppointmentsViewModel.filterClinics("");
textFocusNode.unfocus();
},
child: Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown),
)
: null,
onChange: (value) {
// bookAppointmentsViewModel.filterClinics(value!);
},
padding: EdgeInsets.symmetric(
vertical: ResponsiveExtension(10).h,
horizontal: ResponsiveExtension(15).h,
),
),
),
Visibility(
visible: context.watch<BookAppointmentsViewModel>().doctorsList.isNotEmpty,
child: SizedBox(
height: 56.h,
width: 56.h,
child: DecoratedBox(decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 10.h,
hasShadow: false,
),
child: Utils.buildSvgWithAssets(icon: AppAssets.ic_filters,
height: 24.h,
width: 24.h, ).paddingAll(16.h).onPress((){
context.read<DoctorFilterViewModel>()
..clearSelection()
..clearSearchFilters()
..getFiltersFromDoctorList(
bookAppointmentsViewModel.doctorsList
)..setSelections(
bookAppointmentsViewModel.selectedFacilityForFilters?.toList(),
bookAppointmentsViewModel.selectedRegionForFilters?.toList(),
bookAppointmentsViewModel.selectedClinicForFilters,
bookAppointmentsViewModel.selectedHospitalForFilters,
bookAppointmentsViewModel.applyFilters) ;
Navigator.of(context).push(
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => DoctorsFilters(), // Replace YourNewPage with your actual page widget
transitionsBuilder: (context, animation, secondaryAnimation, child) {
const begin = Offset(0.0, 1.0); // Start from the bottom (y=1.0)
const end = Offset.zero; // End at the original position (y=0.0)
final tween = Tween(begin: begin, end: end);
final offsetAnimation = animation.drive(tween);
return SlideTransition(
position: offsetAnimation,
child: child,
);
},
transitionDuration: Duration(milliseconds: 200), // Adjust duration as needed
),
);
}),
),
),
)
],
),
SizedBox(height: 16.h),
Consumer<BookAppointmentsViewModel>(builder: (context, bookAppointmentsVM, child) {
@ -94,7 +144,7 @@ class _SearchDoctorByNameState extends State<SearchDoctorByName> {
padding: EdgeInsets.only(top: 24.h),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: bookAppointmentsVM.isDoctorsListLoading ? 5 : bookAppointmentsVM.doctorsList.length,
itemCount: bookAppointmentsVM.isDoctorsListLoading ? 5 : bookAppointmentsVM.filteredDoctorList.length,
itemBuilder: (context, index) {
return bookAppointmentsVM.isDoctorsListLoading
? DoctorCard(
@ -113,11 +163,11 @@ class _SearchDoctorByNameState extends State<SearchDoctorByName> {
curve: Curves.easeInOut,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
child: DoctorCard(
doctorsListResponseModel: bookAppointmentsVM.doctorsList[index],
doctorsListResponseModel: bookAppointmentsVM.filteredDoctorList[index],
isLoading: false,
bookAppointmentsViewModel: bookAppointmentsViewModel,
).onPress(() async {
bookAppointmentsVM.setSelectedDoctor(bookAppointmentsVM.doctorsList[index]);
bookAppointmentsVM.setSelectedDoctor(bookAppointmentsVM.filteredDoctorList[index]);
// bookAppointmentsVM.setSelectedDoctor(DoctorsListResponseModel());
LoaderBottomSheet.showLoader();
await bookAppointmentsVM.getDoctorProfile(onSuccess: (dynamic respData) {
@ -166,7 +216,11 @@ class _SearchDoctorByNameState extends State<SearchDoctorByName> {
text: LocaleKeys.search.tr(context: context),
onPressed: () async {
textFocusNode.unfocus();
print("the value is empty ${searchEditingController.text.isNotEmpty}");
if (searchEditingController.text.isNotEmpty) {
bookAppointmentsViewModel.updateApplyFilters(false);
bookAppointmentsViewModel.clearSelection();
bookAppointmentsViewModel.updateList();
bookAppointmentsViewModel.setIsDoctorSearchByNameStarted(true);
bookAppointmentsViewModel.setIsDoctorsListLoading(true);
// LoaderBottomSheet.showLoader();
@ -210,4 +264,9 @@ class _SearchDoctorByNameState extends State<SearchDoctorByName> {
),
);
}
@override
void dispose() {
bookAppointmentsViewModel.doctorsList.clear();
super.dispose();
}
}

@ -18,13 +18,17 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/faculity_selection/facility_type_selection_widget.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/dental_chief_complaints_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/select_doctor_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/select_livecare_clinic_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/clinic_card.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.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/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/input_widget.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart';
import 'package:provider/provider.dart';
@ -206,24 +210,35 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
Navigator.of(context).push(
CustomPageRoute(
page: SelectLivecareClinicPage(onNegativeClicked: (){
handleDoctorScreen();
handleDoctorScreen(clinic);
},),
),
);
} else {
handleDoctorScreen();
handleDoctorScreen(clinic);
}
}
void handleDoctorScreen() {
//17 and 235
void handleDoctorScreen(GetClinicsListResponseModel clinic) async {
if (widget.isFromRegionFlow) {
Navigator.of(context).push(
CustomPageRoute(
page: SelectDoctorPage(),
),
);
//Dental Clinic Flow
if (clinic.clinicID == 17 && appState.isAuthenticated) {
initDentalAppointmentBookingFlow(int.parse(bookAppointmentsViewModel.currentlySelectedHospitalFromRegionFlow ?? "0"));
} else {
bookAppointmentsViewModel.setIsChiefComplaintsListLoading(true);
Navigator.of(context).push(
CustomPageRoute(
page: DentalChiefComplaintsPage(),
),
);
}
} else {
openRegionListBottomSheet(context, RegionBottomSheetType.FOR_CLINIIC);
var bottomSheetType = RegionBottomSheetType.FOR_CLINIIC;
if (clinic.clinicID == 17 || clinic.clinicID == 235) {
bottomSheetType = RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER;
}
openRegionListBottomSheet(context, bottomSheetType);
}
}
@ -236,7 +251,18 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
showCommonBottomSheetWithoutHeight(context, title: "", titleWidget: Consumer<AppointmentViaRegionViewmodel>(builder: (_, data, __) => getTitle(data)), isDismissible: false,
child: Consumer<AppointmentViaRegionViewmodel>(builder: (_, data, __) {
return getRegionalSelectionWidget(data);
}), callBackFunc: () {});
}), callBackFunc: () {
if (type == RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER && appState.isAuthenticated) {
initDentalAppointmentBookingFlow(regionalViewModel.selectedHospital?.hospitalList.first.iD);
} else {
bookAppointmentsViewModel.setIsChiefComplaintsListLoading(true);
Navigator.of(context).push(
CustomPageRoute(
page: DentalChiefComplaintsPage(),
),
);
}
});
}
Widget getRegionalSelectionWidget(AppointmentViaRegionViewmodel data) {
@ -252,8 +278,16 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
if (data.bottomSheetState == AppointmentViaRegionState.HOSPITAL_SELECTION) {
return HospitalBottomSheetBody();
}
if(data.bottomSheetState == AppointmentViaRegionState.DOCTOR_SELECTION){
bookAppointmentsViewModel.setProjectID(regionalViewModel.selectedHospital?.patientDoctorAppointmentList?.first.projectID.toString());
if (data.bottomSheetState == AppointmentViaRegionState.DOCTOR_SELECTION) {
//if the region screen is opened for the dental clinic then the project id will be in the hospital list as the list is formed form the get project api
var id = "";
if (data.regionBottomSheetType == RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER) {
id = regionalViewModel.selectedHospital?.hospitalList.first.iD?.toString() ?? "";
} else {
id = regionalViewModel.selectedHospital?.patientDoctorAppointmentList?.first.projectID?.toString() ?? "";
}
bookAppointmentsViewModel.setProjectID(id);
return SizedBox.shrink();
}
else {
return SizedBox.shrink();
@ -277,4 +311,128 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
});
}
}
void initDentalAppointmentBookingFlow(int projectID) async {
bookAppointmentsViewModel.setProjectID(projectID.toString());
LoaderBottomSheet.showLoader(loadingText: "Checking for an existing dental plan, Please wait...".needTranslation);
await bookAppointmentsViewModel.getPatientDentalEstimation(projectID: projectID).then((value) {
LoaderBottomSheet.hideLoader();
if (bookAppointmentsViewModel.patientDentalPlanEstimationList.isNotEmpty) {
showCommonBottomSheetWithoutHeight(
// title: LocaleKeys.notice.tr(context: context),
title: "Dental treatment plan".needTranslation,
context,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"You have an existing treatment plan: ".needTranslation.toText14(weight: FontWeight.w500),
SizedBox(height: 8.h),
Container(
width: double.infinity,
padding: EdgeInsets.all(16.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: true,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ListView.separated(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: bookAppointmentsViewModel.patientDentalPlanEstimationList.length,
separatorBuilder: (_, __) {
return Column(
children: [
SizedBox(height: 8.h),
Divider(height: 1, color: AppColors.greyColor),
SizedBox(height: 8.h),
],
);
},
itemBuilder: (context, index) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
bookAppointmentsViewModel.patientDentalPlanEstimationList[index].procedureName!.toText12(isBold: true),
AppCustomChipWidget(icon: AppAssets.appointment_time_icon, labelText: "${bookAppointmentsViewModel.totalTimeNeededForDentalProcedure} Mins".needTranslation),
],
);
},
),
SizedBox(
height: 16.h,
),
Divider(height: 1, color: AppColors.greyColor),
SizedBox(
height: 8.h,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"Total time required".needTranslation.toText14(isBold: true),
AppCustomChipWidget(icon: AppAssets.appointment_time_icon, labelText: "30 Mins".needTranslation),
],
)
],
),
),
SizedBox(height: 16.h),
"Would you like to continue it?".needTranslation.toText14(weight: FontWeight.w500),
SizedBox(height: 16.h),
Row(
children: [
Expanded(
child: CustomButton(
text: LocaleKeys.cancel.tr(),
onPressed: () {
bookAppointmentsViewModel.setIsContinueDentalPlan(false);
bookAppointmentsViewModel.setIsChiefComplaintsListLoading(true);
Navigator.of(context).pop();
Navigator.of(context).push(
CustomPageRoute(
page: DentalChiefComplaintsPage(),
),
);
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
icon: AppAssets.cancel,
iconColor: AppColors.whiteColor,
),
),
SizedBox(width: 8.h),
Expanded(
child: CustomButton(
text: LocaleKeys.confirm.tr(),
onPressed: () async {
bookAppointmentsViewModel.setIsContinueDentalPlan(true);
Navigator.of(context).pop();
Navigator.of(context).push(
CustomPageRoute(
page: SelectDoctorPage(),
),
);
},
backgroundColor: AppColors.bgGreenColor,
borderColor: AppColors.bgGreenColor,
textColor: Colors.white,
icon: AppAssets.confirm,
),
),
],
)
],
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
} else {
// Navigate to Chief Complaint Screen
}
});
}
}

@ -21,7 +21,6 @@ import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/input_widget.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart';
import 'package:provider/provider.dart';
class SelectDoctorPage extends StatefulWidget {
@ -45,7 +44,11 @@ class _SelectDoctorPageState extends State<SelectDoctorPage> {
if (bookAppointmentsViewModel.isLiveCareSchedule) {
bookAppointmentsViewModel.getLiveCareDoctorsList();
} else {
bookAppointmentsViewModel.getDoctorsList();
if (bookAppointmentsViewModel.selectedClinic.clinicID == 17) {
bookAppointmentsViewModel.getDentalChiefComplaintDoctorsList();
} else {
bookAppointmentsViewModel.getDoctorsList();
}
}
});
super.initState();
@ -66,42 +69,51 @@ class _SelectDoctorPageState extends State<SelectDoctorPage> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// TODO: Implement doctor filter functionality
SizedBox(height: 16.h),
TextInputWidget(
labelText: LocaleKeys.search.tr(context: context),
hintText: LocaleKeys.doctorName.tr(context: context),
controller: searchEditingController,
isEnable: true,
prefix: null,
autoFocus: false,
isBorderAllowed: false,
keyboardType: TextInputType.text,
focusNode: textFocusNode,
suffix: searchEditingController.text.isNotEmpty
? GestureDetector(
onTap: () {
searchEditingController.clear();
bookAppointmentsViewModel.filterClinics("");
textFocusNode.unfocus();
},
child: Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown),
)
: null,
onChange: (value) {
bookAppointmentsViewModel.filterClinics(value!);
},
padding: EdgeInsets.symmetric(
vertical: ResponsiveExtension(10).h,
horizontal: ResponsiveExtension(15).h,
),
Row(
spacing: 8.h,
children: [
Expanded(
child: TextInputWidget(
labelText: LocaleKeys.search.tr(context: context),
hintText: LocaleKeys.doctorName.tr(context: context),
controller: searchEditingController,
isEnable: true,
prefix: null,
autoFocus: false,
isBorderAllowed: false,
keyboardType: TextInputType.text,
focusNode: textFocusNode,
suffix: searchEditingController.text.isNotEmpty
? GestureDetector(
onTap: () {
searchEditingController.clear();
bookAppointmentsViewModel.filterClinics("");
textFocusNode.unfocus();
},
child: Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown),
)
: null,
onChange: (value) {
bookAppointmentsViewModel.filterClinics(value!);
},
padding: EdgeInsets.symmetric(
vertical: ResponsiveExtension(10).h,
horizontal: ResponsiveExtension(15).h,
),
),
),
],
),
ListView.separated(
padding: EdgeInsets.only(top: 24.h),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount:
bookAppointmentsVM.isDoctorsListLoading ? 5 : (bookAppointmentsVM.isLiveCareSchedule ? bookAppointmentsVM.liveCareDoctorsList.length : bookAppointmentsVM.doctorsList.length),
itemCount: bookAppointmentsVM.isDoctorsListLoading
? 5
: (bookAppointmentsVM.isLiveCareSchedule
? (bookAppointmentsVM.liveCareDoctorsList.isNotEmpty ? bookAppointmentsVM.liveCareDoctorsList.length : 1)
: (bookAppointmentsVM.doctorsList.isNotEmpty ? bookAppointmentsVM.doctorsList.length : 1)),
itemBuilder: (context, index) {
return bookAppointmentsVM.isDoctorsListLoading
? DoctorCard(
@ -109,47 +121,49 @@ class _SelectDoctorPageState extends State<SelectDoctorPage> {
isLoading: true,
bookAppointmentsViewModel: bookAppointmentsViewModel,
)
: AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
child: DoctorCard(
doctorsListResponseModel: bookAppointmentsVM.isLiveCareSchedule ? bookAppointmentsVM.liveCareDoctorsList[index] : bookAppointmentsVM.doctorsList[index],
isLoading: false,
bookAppointmentsViewModel: bookAppointmentsViewModel,
).onPress(() async {
bookAppointmentsVM
.setSelectedDoctor(bookAppointmentsVM.isLiveCareSchedule ? bookAppointmentsVM.liveCareDoctorsList[index] : bookAppointmentsVM.doctorsList[index]);
// bookAppointmentsVM.setSelectedDoctor(DoctorsListResponseModel());
LoaderBottomSheet.showLoader();
await bookAppointmentsVM.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,
);
});
}),
: checkIsDoctorsListEmpty()
? Utils.getNoDataWidget(context, noDataText: "No Doctor found for selected criteria...".needTranslation)
: AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
child: DoctorCard(
doctorsListResponseModel: bookAppointmentsVM.isLiveCareSchedule ? bookAppointmentsVM.liveCareDoctorsList[index] : bookAppointmentsVM.doctorsList[index],
isLoading: false,
bookAppointmentsViewModel: bookAppointmentsViewModel,
).onPress(() async {
bookAppointmentsVM
.setSelectedDoctor(bookAppointmentsVM.isLiveCareSchedule ? bookAppointmentsVM.liveCareDoctorsList[index] : bookAppointmentsVM.doctorsList[index]);
// bookAppointmentsVM.setSelectedDoctor(DoctorsListResponseModel());
LoaderBottomSheet.showLoader();
await bookAppointmentsVM.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(height: 16.h),
),
@ -162,4 +176,12 @@ class _SelectDoctorPageState extends State<SelectDoctorPage> {
),
);
}
bool checkIsDoctorsListEmpty() {
if (bookAppointmentsViewModel.isLiveCareSchedule) {
return bookAppointmentsViewModel.liveCareDoctorsList.isEmpty;
} else {
return bookAppointmentsViewModel.doctorsList.isEmpty;
}
}
}

@ -6,12 +6,9 @@ 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/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/select_doctor_page.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart';
class SelectLivecareClinicPage extends StatelessWidget {

@ -0,0 +1,38 @@
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/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/dental_chief_complaints_response_model.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
class ChiefComplaintCard extends StatelessWidget {
ChiefComplaintCard({super.key, required this.isLoading, required this.bookAppointmentsVM, required this.dentalChiefComplaintsListResponseModel});
bool isLoading;
BookAppointmentsViewModel bookAppointmentsVM;
DentalChiefComplaintsListResponseModel dentalChiefComplaintsListResponseModel;
@override
Widget build(BuildContext context) {
AppState appState = getIt.get<AppState>();
return Container(
padding: EdgeInsets.all(16.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: false,
),
child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Expanded(child: (isLoading ? "Cardiology" : dentalChiefComplaintsListResponseModel.name)!.toText16(isBold: true).toShimmer2(isShow: isLoading)),
Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, width: 40.h, height: 40.h, fit: BoxFit.contain, iconColor: AppColors.textColor).toShimmer2(isShow: isLoading)),
]),
);
}
}

@ -36,7 +36,8 @@ class ClinicCard extends StatelessWidget {
(clinicsListResponseModel.isLiveCareClinicAndOnline ?? true)
? Utils.buildSvgWithAssets(icon: AppAssets.livecare_clinic_icon, width: 32.h, height: 32.h, fit: BoxFit.contain).toShimmer2(isShow: isLoading)
: SizedBox.shrink(),
]),
],
),
SizedBox(height: 16.h),
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Expanded(
@ -44,8 +45,8 @@ class ClinicCard extends StatelessWidget {
.toText16(isBold: true)
.toShimmer2(isShow: isLoading)),
Transform.flip(
flipX: appState.isArabic() ? true : false,
child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, width: 15.h, height: 15.h, fit: BoxFit.contain, iconColor: AppColors.textColor).toShimmer2(isShow: isLoading)),
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, width: 40.h, height: 40.h, fit: BoxFit.contain, iconColor: AppColors.textColor).toShimmer2(isShow: isLoading)),
]),
],
),

@ -16,6 +16,8 @@ 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/habib_wallet/habib_wallet_view_model.dart';
import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart';
import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
@ -25,24 +27,26 @@ import 'package:hmg_patient_app_new/presentation/appointments/my_appointments_pa
import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_card.dart';
import 'package:hmg_patient_app_new/presentation/authentication/quick_login.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/immediate_livecare_pending_request_page.dart';
import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dart';
import 'package:hmg_patient_app_new/presentation/home/widgets/habib_wallet_card.dart';
import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart';
import 'package:hmg_patient_app_new/presentation/home/widgets/small_service_card.dart';
import 'package:hmg_patient_app_new/presentation/home/widgets/welcome_widget.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_results/lab_result_calender.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart';
import 'package:hmg_patient_app_new/presentation/profile_settings/profile_settings.dart';
import 'package:hmg_patient_app_new/services/cache_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/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart' show CustomTabBar;
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:hmg_patient_app_new/widgets/routes/spring_page_route_builder.dart';
import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart';
import 'package:lottie/lottie.dart';
import 'package:provider/provider.dart';
class LandingPage extends StatefulWidget {
@ -62,6 +66,9 @@ class _LandingPageState extends State<LandingPage> {
late PrescriptionsViewModel prescriptionsViewModel;
final CacheService cacheService = GetIt.instance<CacheService>();
late InsuranceViewModel insuranceViewModel;
late ImmediateLiveCareViewModel immediateLiveCareViewModel;
final SwiperController _controller = SwiperController();
@override
@ -82,7 +89,9 @@ class _LandingPageState extends State<LandingPage> {
myAppointmentsViewModel.getPatientAppointments(true, false);
myAppointmentsViewModel.getPatientMyDoctors();
prescriptionsViewModel.initPrescriptionsViewModel();
insuranceViewModel.initInsuranceProvider();
immediateLiveCareViewModel.initImmediateLiveCare();
immediateLiveCareViewModel.getPatientLiveCareHistory();
}
});
super.initState();
@ -94,6 +103,8 @@ class _LandingPageState extends State<LandingPage> {
NavigationService navigationService = getIt.get<NavigationService>();
myAppointmentsViewModel = Provider.of<MyAppointmentsViewModel>(context, listen: false);
prescriptionsViewModel = Provider.of<PrescriptionsViewModel>(context, listen: false);
insuranceViewModel = Provider.of<InsuranceViewModel>(context, listen: false);
immediateLiveCareViewModel = Provider.of<ImmediateLiveCareViewModel>(context, listen: false);
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: SingleChildScrollView(
@ -121,11 +132,11 @@ class _LandingPageState extends State<LandingPage> {
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
fontSize: 16,
fontSize: 14,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 50,
padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0),
height: 40.h,
),
Row(
mainAxisSize: MainAxisSize.min,
@ -271,14 +282,72 @@ class _LandingPageState extends State<LandingPage> {
),
).paddingSymmetrical(24.h, 0.h);
}),
SizedBox(height: 12.h),
Consumer<ImmediateLiveCareViewModel>(builder: (context, immediateLiveCareVM, child) {
return immediateLiveCareVM.patientHasPendingLiveCareRequest
? Column(
children: [
SizedBox(height: 12.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: true,
side: BorderSide(color: AppColors.ratingColorYellow, width: 3.h),
),
width: double.infinity,
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
AppCustomChipWidget(
labelText: immediateLiveCareViewModel.patientLiveCareHistoryList[0].stringCallStatus,
backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.20),
textColor: AppColors.alertColor,
),
Utils.buildSvgWithAssets(icon: AppAssets.waiting_icon, width: 24.h, height: 24.h),
// Lottie.asset(AppAnimations.pending_loading_animation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 40.h, height: 40.h, fit: BoxFit.contain),
],
),
SizedBox(height: 8.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"You have a pending LiveCare request".needTranslation.toText12(isBold: true),
Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon_small,
iconColor: AppColors.blackColor,
width: 20.h,
height: 15.h,
fit: BoxFit.contain,
),
],
),
],
),
),
).paddingSymmetrical(24.h, 0.h).onPress(() {
Navigator.of(context).push(
CustomPageRoute(
page: ImmediateLiveCarePendingRequestPage(),
),
);
}),
SizedBox(height: 12.h),
],
)
: SizedBox(height: 12.h);
}),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"Quick Links".toText16(isBold: true),
"Quick Links".needTranslation.toText16(isBold: true),
Row(
children: [
"View medical file".toText12(color: AppColors.primaryRedColor),
"View medical file".needTranslation.toText12(color: AppColors.primaryRedColor),
SizedBox(width: 2.h),
Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h),
],

@ -3,12 +3,17 @@ import 'dart:async';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/insurance/widgets/insurance_update_details_card.dart';
import 'package:hmg_patient_app_new/presentation/insurance/widgets/patient_insurance_card.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
@ -30,6 +35,8 @@ class InsuranceHomePage extends StatefulWidget {
class _InsuranceHomePageState extends State<InsuranceHomePage> {
late InsuranceViewModel insuranceViewModel;
late AppState appState;
@override
void initState() {
scheduleMicrotask(() {
@ -40,6 +47,7 @@ class _InsuranceHomePageState extends State<InsuranceHomePage> {
@override
Widget build(BuildContext context) {
appState = getIt.get<AppState>();
insuranceViewModel = Provider.of<InsuranceViewModel>(context, listen: false);
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
@ -48,44 +56,55 @@ class _InsuranceHomePageState extends State<InsuranceHomePage> {
history: () {
insuranceViewModel.setIsInsuranceHistoryLoading(true);
insuranceViewModel.getPatientInsuranceCardHistory();
showCommonBottomSheet(context,
child: InsuranceHistory(), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.65, isCloseButtonVisible: false, isFullScreen: false);
showCommonBottomSheetWithoutHeight(context, child: InsuranceHistory(), callBackFunc: () {}, title: "", isCloseButtonVisible: false, isFullScreen: false);
},
child: SingleChildScrollView(
child: Consumer<InsuranceViewModel>(builder: (context, insuranceVM, child) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// "${LocaleKeys.insurance.tr(context: context)} ${LocaleKeys.updateInsurance.tr(context: context)}".toText24(isBold: true),
// CustomButton(
// icon: AppAssets.insurance_history_icon,
// iconColor: AppColors.primaryRedColor,
// iconSize: 21.h,
// text: LocaleKeys.history.tr(context: context),
// onPressed: () {
// },
// backgroundColor: AppColors.primaryRedColor.withOpacity(0.1),
// borderColor: AppColors.primaryRedColor.withOpacity(0.0),
// textColor: AppColors.primaryRedColor,
// fontSize: 14,
// fontWeight: FontWeight.w600,
// borderRadius: 12,
// padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
// height: 40.h,
// ),
// ],
// ).paddingSymmetrical(24.h, 24.h),
insuranceVM.isInsuranceLoading
? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0)
: Padding(
padding: EdgeInsets.only(top: 24.h),
child: PatientInsuranceCard(
insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first,
isInsuranceExpired: DateTime.now().isAfter(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo))),
),
? LabResultItemView(
onTap: () {},
labOrder: null,
index: 0,
isLoading: true,
).paddingSymmetrical(24.h, 24.h)
: insuranceVM.patientInsuranceList.isNotEmpty
? Padding(
padding: EdgeInsets.only(top: 24.h),
child: PatientInsuranceCard(
insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first,
isInsuranceExpired: DateTime.now().isAfter(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo))),
)
: Padding(
padding: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.12),
child: Utils.getNoDataWidget(
context,
noDataText: "You don't have insurance registered with HMG.".needTranslation,
callToActionButton: CustomButton(
icon: AppAssets.update_insurance_card_icon,
iconColor: AppColors.successColor,
iconSize: 15.h,
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);
},
backgroundColor: AppColors.bgGreenColor.withOpacity(0.20),
borderColor: AppColors.bgGreenColor.withOpacity(0.0),
textColor: AppColors.bgGreenColor,
fontSize: 14,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
).paddingSymmetrical(64.h, 0.h),
),
),
],
);
}),

@ -8,6 +8,7 @@ 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/insurance/insurance_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart';
import 'package:hmg_patient_app_new/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';
@ -25,6 +26,7 @@ class InsuranceHistory extends StatelessWidget {
return Consumer<InsuranceViewModel>(builder: (context, insuranceVM, child) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -36,72 +38,85 @@ class InsuranceHistory extends StatelessWidget {
],
).paddingSymmetrical(24.h, 24.h),
insuranceVM.isInsuranceHistoryLoading
? const MoviesShimmerWidget().paddingSymmetrical(24.h, 24.h)
: ListView.separated(
itemCount: insuranceVM.patientInsuranceCardHistoryList.length,
shrinkWrap: true,
padding: const EdgeInsets.only(left: 0, right: 8),
itemBuilder: (context, index) {
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 1000),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: Container(
// height: 120.h,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
CustomButton(
text: insuranceVM.patientInsuranceCardHistoryList[index].statusDescription!,
onPressed: () {},
backgroundColor: AppColors.primaryRedColor.withOpacity(0.1),
borderColor: AppColors.primaryRedColor.withOpacity(0.0),
textColor: AppColors.primaryRedColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 30.h,
),
],
? LabResultItemView(
onTap: () {},
labOrder: null,
index: 0,
isLoading: true,
).paddingSymmetrical(24.h, 24.h)
: insuranceVM.patientInsuranceCardHistoryList.isNotEmpty
? ListView.separated(
itemCount: insuranceVM.patientInsuranceCardHistoryList.length,
shrinkWrap: true,
padding: const EdgeInsets.only(left: 0, right: 8),
itemBuilder: (context, index) {
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 1000),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: Container(
// height: 120.h,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24,
),
SizedBox(height: 8.h),
// "Haroon Amjad".toText16(weight: FontWeight.w600),
SizedBox(height: 8.h),
Row(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Wrap(
direction: Axis.horizontal,
spacing: 4.h,
runSpacing: 4.h,
Row(
children: [
AppCustomChipWidget(
labelText: "File No.: ${insuranceVM.patientInsuranceCardHistoryList[index].patientID}",
CustomButton(
text: insuranceVM.patientInsuranceCardHistoryList[index].statusDescription!,
onPressed: () {},
backgroundColor: AppColors.primaryRedColor.withOpacity(0.1),
borderColor: AppColors.primaryRedColor.withOpacity(0.0),
textColor: AppColors.primaryRedColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 30.h,
),
AppCustomChipWidget(
labelText: insuranceVM.patientInsuranceCardHistoryList[index].createdOn!,
],
),
SizedBox(height: 8.h),
// "Haroon Amjad".toText16(weight: FontWeight.w600),
SizedBox(height: 8.h),
Row(
children: [
Wrap(
direction: Axis.horizontal,
spacing: 4.h,
runSpacing: 4.h,
children: [
AppCustomChipWidget(
labelText: "File No.: ${insuranceVM.patientInsuranceCardHistoryList[index].patientID}",
),
AppCustomChipWidget(
labelText: insuranceVM.patientInsuranceCardHistoryList[index].createdOn!,
),
],
),
],
),
],
),
],
).paddingSymmetrical(16.h, 16.h),
).paddingSymmetrical(24.h, 0.h),
),
),
);
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
),
).paddingSymmetrical(16.h, 16.h),
).paddingSymmetrical(24.h, 0.h),
),
),
);
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
)
: Utils.getNoDataWidget(
context,
noDataText: "No insurance update requests found.".needTranslation,
// isSmallWidget: true,
// width: 62,
// height: 62,
),
],
);
});

@ -8,6 +8,7 @@ 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/insurance/insurance_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart';
import 'package:hmg_patient_app_new/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';
@ -23,6 +24,7 @@ class PatientInsuranceCardUpdateCard extends StatelessWidget {
Widget build(BuildContext context) {
insuranceViewModel = Provider.of<InsuranceViewModel>(context);
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -34,57 +36,63 @@ class PatientInsuranceCardUpdateCard extends StatelessWidget {
],
).paddingSymmetrical(24.h, 24.h),
insuranceViewModel.isInsuranceUpdateDetailsLoading
? const MoviesShimmerWidget().paddingSymmetrical(24.h, 24.h)
: Container(
// height: 120.h,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Haroon Amjad".toText16(weight: FontWeight.w600),
"Policy: ${insuranceViewModel.patientInsuranceUpdateResponseModel!.policyNumber}".toText12(isBold: true, color: AppColors.lightGrayColor),
SizedBox(height: 8.h),
Row(
children: [
insuranceViewModel.patientInsuranceUpdateResponseModel!.companyName!.toText12(isBold: true),
SizedBox(
width: 6.h,
),
Container(
padding: EdgeInsets.symmetric(horizontal: 6.h, vertical: 3.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.infoColor,
borderRadius: 50,
),
child: insuranceViewModel.patientInsuranceUpdateResponseModel!.subCategory!.toText8(isBold: true, color: AppColors.whiteColor),
),
],
? LabResultItemView(
onTap: () {},
labOrder: null,
index: 0,
isLoading: true,
).paddingSymmetrical(24.h, 24.h)
: insuranceViewModel.patientInsuranceUpdateResponseModel != null
? Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24,
),
SizedBox(height: 8.h),
Row(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Wrap(
direction: Axis.horizontal,
spacing: 4.h,
runSpacing: 4.h,
"Haroon Amjad".toText16(weight: FontWeight.w600),
"Policy: ${insuranceViewModel.patientInsuranceUpdateResponseModel!.policyNumber}".toText12(isBold: true, color: AppColors.lightGrayColor),
SizedBox(height: 8.h),
Row(
children: [
AppCustomChipWidget(
icon: AppAssets.doctor_calendar_icon,
labelText: "${LocaleKeys.expiryOn.tr(context: context)} ${insuranceViewModel.patientInsuranceUpdateResponseModel!.effectiveTo}",
insuranceViewModel.patientInsuranceUpdateResponseModel!.companyName!.toText12(isBold: true),
SizedBox(
width: 6.h,
),
Container(
padding: EdgeInsets.symmetric(horizontal: 6.h, vertical: 3.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.infoColor,
borderRadius: 50,
),
child: insuranceViewModel.patientInsuranceUpdateResponseModel!.subCategory!.toText8(isBold: true, color: AppColors.whiteColor),
),
AppCustomChipWidget(
labelText: "Member ID: ${insuranceViewModel.patientInsuranceUpdateResponseModel!.memberID!}",
],
),
SizedBox(height: 8.h),
Row(
children: [
Wrap(
direction: Axis.horizontal,
spacing: 4.h,
runSpacing: 4.h,
children: [
AppCustomChipWidget(
icon: AppAssets.doctor_calendar_icon,
labelText: "${LocaleKeys.expiryOn.tr(context: context)} ${insuranceViewModel.patientInsuranceUpdateResponseModel!.effectiveTo}",
),
AppCustomChipWidget(
labelText: "Member ID: ${insuranceViewModel.patientInsuranceUpdateResponseModel!.memberID!}",
),
],
),
],
),
],
),
],
).paddingSymmetrical(16.h, 16.h),
).paddingSymmetrical(24.h, 0.h),
).paddingSymmetrical(16.h, 16.h),
).paddingSymmetrical(24.h, 0.h)
: Utils.getNoDataWidget(context, noDataText: "No insurance data found...".needTranslation),
SizedBox(
height: 24.h,
),
@ -92,9 +100,9 @@ class PatientInsuranceCardUpdateCard extends StatelessWidget {
icon: AppAssets.insurance_active_icon,
iconColor: AppColors.whiteColor,
iconSize: 20.h,
text: "Update Insurance",
text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}",
onPressed: () {},
backgroundColor: AppColors.successColor,
backgroundColor: insuranceViewModel.patientInsuranceUpdateResponseModel != null ? AppColors.successColor : AppColors.lightGrayBGColor,
borderColor: AppColors.successColor.withOpacity(0.01),
textColor: AppColors.whiteColor,
fontSize: 16,
@ -103,9 +111,6 @@ class PatientInsuranceCardUpdateCard extends StatelessWidget {
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 56.h,
).paddingSymmetrical(24.h, 0.h),
SizedBox(
height: 24.h,
),
],
);
}

@ -55,7 +55,7 @@ class PatientInsuranceCard extends StatelessWidget {
icon: isInsuranceExpired ? AppAssets.cancel_circle_icon : AppAssets.insurance_active_icon,
iconColor: isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor,
iconSize: 13.h,
text: isInsuranceExpired ? "Insurance Expired" : "Insurance Active",
text: isInsuranceExpired ? "Insurance Expired".needTranslation : "Insurance Active".needTranslation,
onPressed: () {},
backgroundColor: isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.15) : AppColors.successColor.withOpacity(0.15),
borderColor: isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.01) : AppColors.successColor.withOpacity(0.01),
@ -94,11 +94,11 @@ class PatientInsuranceCard extends StatelessWidget {
insuranceViewModel.setIsInsuranceUpdateDetailsLoading(true);
insuranceViewModel.getPatientInsuranceDetailsForUpdate(
appState.getAuthenticatedUser()!.patientId.toString(), appState.getAuthenticatedUser()!.patientIdentificationNo.toString());
showCommonBottomSheet(context,
showCommonBottomSheetWithoutHeight(context,
child: PatientInsuranceCardUpdateCard(),
callBackFunc: (str) {},
callBackFunc: () {},
title: "",
height: ResponsiveExtension.screenHeight * 0.42,
// height: ResponsiveExtension.screenHeight * 0.42,
isCloseButtonVisible: false,
isFullScreen: false);
},

@ -2,6 +2,7 @@ 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/app_state.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/extensions/string_extensions.dart';
@ -19,8 +20,9 @@ class LabOrderByTest extends StatelessWidget {
final TestDetails? tests;
final bool isLoading;
final bool isExpanded;
final AppState appState;
const LabOrderByTest({super.key, required this.onTap, this.tests, required this.index, this.isLoading = false, this.isExpanded = false});
const LabOrderByTest({super.key, required this.onTap, required this.appState, this.tests, required this.index, this.isLoading = false, this.isExpanded = false});
@override
build(BuildContext context) {
@ -35,41 +37,42 @@ class LabOrderByTest extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// ...labOrder!.testDetails!.map((detail) {
Padding(
padding: EdgeInsets.only(bottom: 8.h),
child: '${tests!.description}'.toText14(weight: FontWeight.w500),
),
SizedBox(height: 12.h),
'${tests!.description}'.toText16(isBold: true),
SizedBox(height: 4.h),
(appState.isArabic() ? tests!.testDescriptionAr : tests!.testDescriptionEn)!.toText12(fontWeight: FontWeight.w500),
SizedBox(height: 8.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
AppCustomChipWidget(
richText: '${"Last Tested:".needTranslation} ${ DateUtil.formatDateToDate(DateUtil.convertStringToDate(tests!.createdOn), false)}'.toText12(isBold: true),
// chipType: ChipTypeEnum.lightBg,
richText: '${"Last Tested:".needTranslation} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(tests!.createdOn), false)}'.toText12(fontWeight: FontWeight.w500),
backgroundColor: AppColors.greyLightColor,
textColor: AppColors.textColor,
// borderRadius: 5,
),
CustomButton(
icon: AppAssets.view_report_icon,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
text: LocaleKeys.viewReport.tr(context: context),
onPressed: () {
],
),
SizedBox(height: 16.h),
Row(
children: [
Expanded(child: Container()),
Expanded(
child: CustomButton(
icon: AppAssets.view_report_icon,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
text: LocaleKeys.viewReport.tr(context: context),
onPressed: () {
onTap();
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 14,
fontWeight: FontWeight.bold,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 14,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
),
],
),

File diff suppressed because one or more lines are too long

@ -3,6 +3,7 @@ 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/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart';
@ -49,7 +50,7 @@ class LabResultItemView extends StatelessWidget {
backgroundColor: getLabOrderStatusColor(labOrder?.status ?? 0).withOpacity(0.15),
textColor: getLabOrderStatusColor(labOrder?.status ?? 0),
).toShimmer2(isShow: isLoading, width: 100),
if (!isLoading) Icon(isExpanded ? Icons.expand_less : Icons.expand_more),
// if (!isLoading) Icon(isExpanded ? Icons.expand_less : Icons.expand_more),
],
),
SizedBox(height: 8.h),
@ -59,7 +60,7 @@ class LabResultItemView extends StatelessWidget {
isLoading ? "" : labOrder!.doctorImageURL!,
width: 24.h,
height: 24.h,
fit: BoxFit.fill,
fit: BoxFit.cover,
errorBuilder: (cxt, child, tr) {
return SizedBox(height: 24, width: 24);
},
@ -69,71 +70,85 @@ class LabResultItemView extends StatelessWidget {
],
),
SizedBox(height: 8.h),
Wrap(
spacing: 8.h,
runSpacing: 0.h,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
AppCustomChipWidget(labelText: isLoading ? "null" : DateUtil.formatDateToDate(DateUtil.convertStringToDate(labOrder!.createdOn), false)).toShimmer2(isShow: isLoading, width: 70),
AppCustomChipWidget(labelText: isLoading ? "null" : labOrder!.clinicDescription!).toShimmer2(isShow: isLoading, width: 100),
Wrap(
spacing: 8.h,
runSpacing: 0.h,
children: [
AppCustomChipWidget(labelText: isLoading ? "null" : DateUtil.formatDateToDate(DateUtil.convertStringToDate(labOrder!.createdOn), false)).toShimmer2(isShow: isLoading, width: 70),
AppCustomChipWidget(labelText: isLoading ? "null" : labOrder!.clinicDescription!).toShimmer2(isShow: isLoading, width: 100),
],
),
isLoading
? SizedBox.shrink()
: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon_small,
iconColor: AppColors.textColor,
width: 20.h,
height: 14.h,
fit: BoxFit.contain,
),
],
),
],
),
),
AnimatedSwitcher(
duration: Duration(milliseconds: 300),
switchInCurve: Curves.easeIn,
switchOutCurve: Curves.easeOut,
transitionBuilder: (Widget child, Animation<double> animation) {
return FadeTransition(
opacity: animation,
child: SizeTransition(
sizeFactor: animation,
axisAlignment: 0.0,
child: child,
),
);
},
child: isExpanded
? Container(
key: ValueKey<int>(index),
padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...labOrder!.testDetails!.map((detail) {
return Padding(
padding: EdgeInsets.only(bottom: 8.h),
child: '${detail.description}'.toText14(weight: FontWeight.w500),
);
}).toList(),
SizedBox(height: 16.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(),
CustomButton(
icon: AppAssets.view_report_icon,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
text: LocaleKeys.viewReport.tr(context: context),
onPressed: () {},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 14,
fontWeight: FontWeight.bold,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
],
),
],
),
)
: SizedBox.shrink(key: ValueKey<int>(-index)),
),
// AnimatedSwitcher(
// duration: Duration(milliseconds: 300),
// switchInCurve: Curves.easeIn,
// switchOutCurve: Curves.easeOut,
// transitionBuilder: (Widget child, Animation<double> animation) {
// return FadeTransition(
// opacity: animation,
// child: SizeTransition(
// sizeFactor: animation,
// axisAlignment: 0.0,
// child: child,
// ),
// );
// },
// child: isExpanded
// ? Container(
// key: ValueKey<int>(index),
// padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h),
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// ...labOrder!.testDetails!.map((detail) {
// return Padding(
// padding: EdgeInsets.only(bottom: 8.h),
// child: '${detail.description}'.toText14(weight: FontWeight.w500),
// );
// }).toList(),
// SizedBox(height: 16.h),
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// SizedBox(),
// CustomButton(
// icon: AppAssets.view_report_icon,
// iconColor: AppColors.primaryRedColor,
// iconSize: 16.h,
// text: LocaleKeys.viewReport.tr(context: context),
// onPressed: () {},
// backgroundColor: AppColors.secondaryLightRedColor,
// borderColor: AppColors.secondaryLightRedColor,
// textColor: AppColors.primaryRedColor,
// fontSize: 14,
// fontWeight: FontWeight.bold,
// borderRadius: 12,
// padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
// height: 40.h,
// ),
// ],
// ),
// ],
// ),
// )
// : SizedBox.shrink(key: ValueKey<int>(-index)),
// ),
],
),
),

@ -0,0 +1,69 @@
import 'package:easy_localization/easy_localization.dart'
show tr, StringTranslateExtension;
import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.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/features/lab/lab_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_hospital/LabResultList.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_hospital/lab_order_specialResult.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:provider/provider.dart';
class LabResultByHospitals extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CollapsingListView(
title: LocaleKeys.labResults.tr(),
child: SingleChildScrollView(
child: Column(
spacing: 8.h,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Selector<LabViewModel, bool>(
selector: (_, model) => model.isLabResultByHospitalLoading,
builder: (_, isLoading, __) {
if (isLoading) {
return Column(
children: [
LabResultItemView(
onTap: () {},
labOrder: null,
index: 0,
isLoading: true,
),
LabResultItemView(
onTap: () {},
labOrder: null,
index: 0,
isLoading: true,
),
LabResultItemView(
onTap: () {},
labOrder: null,
index: 0,
isLoading: true,
),
LabResultItemView(
onTap: () {},
labOrder: null,
index: 0,
isLoading: true,
),
],
);
} else {
return LabResultList();
}
},
),
LabOrderSpecialResult()
],
).paddingAll(24.h),
));
}
}

@ -0,0 +1,40 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart';
import 'package:hmg_patient_app_new/features/lab/models/resp_models/lab_result.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_hospital/lab_order_result_item.dart';
import 'package:provider/provider.dart' show Selector, Provider, ReadContext;
class LabResultList extends StatelessWidget {
late LabViewModel model;
@override
Widget build(BuildContext context) {
model = Provider.of<LabViewModel>(context);
return Selector<LabViewModel, List<LabResult>>(
selector: (_, model) => model.mainLabResultsByHospitals,
builder: (__, list, ___) {
if (list.isEmpty && context.read<LabViewModel>().labSpecialResult.isEmpty) {
return Utils.getNoDataWidget(context,
noDataText: "You don't have any lab results yet."
.needTranslation);
} else {
return ListView.builder(
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
shrinkWrap: true,itemCount: list.length,itemBuilder: (____, index) {
var labItem = list[index];
return LabOrderResultItem(onTap: () {
model.getPatientLabResult(model.currentlySelectedPatientOrder!, labItem.description ?? "", labItem.packageShortDescription!);
},
tests: labItem,
index: index,
iconColor: model.getColor(labItem.calculatedResultFlag ?? "N"),
severityText: model.getSeverityText(labItem.calculatedResultFlag ?? "N"));
});
}
},
);
}
}

@ -0,0 +1,132 @@
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/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart' show LabViewModel;
import 'package:hmg_patient_app_new/features/lab/models/resp_models/lab_result.dart';
import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart';
import 'package:provider/provider.dart';
class LabOrderResultItem extends StatelessWidget {
final VoidCallback onTap;
final int index;
final LabResult? tests;
final String severityText;
final bool isLoading;
final bool isExpanded;
final Color iconColor;
const LabOrderResultItem({super.key, required this.onTap, this.tests, required this.index, this.isLoading = false, this.isExpanded = false,required this.iconColor, required this.severityText});
@override
build(BuildContext context) {
return AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
margin: EdgeInsets.symmetric(vertical: 8.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true),
child: Container(
key: ValueKey<int>(index),
padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// ...labOrder!.testDetails!.map((detail) {
Padding(
padding: EdgeInsets.only(bottom: 8.h),
child: '${tests!.description}'.toText14(weight: FontWeight.w500),
),
'${tests!.packageShortDescription}'.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight),
SizedBox(height: 12.h),
Row(
mainAxisSize: MainAxisSize.max,
children: [
Flexible(
child: Text(
tests?.resultValue ?? "",
style: TextStyle(
fontSize: 24.fSize,
fontWeight: FontWeight.w600,
fontFamily: 'Poppins',
color: context.read<LabViewModel>().getColor(
tests?.calculatedResultFlag ?? "",
),
letterSpacing: -2,
),
overflow: TextOverflow.ellipsis, // prevent overflow
maxLines: 1,
softWrap: false,
),
),
SizedBox(width: 4.h,),
Expanded(
flex: 2,
child: Visibility(
visible: tests?.referanceRange != null,
child: Text(
"(Reference range ${tests?.referanceRange})".needTranslation,
style: TextStyle(
fontSize: 12.fSize,
fontWeight: FontWeight.w500,
fontFamily: 'Poppins',
color: AppColors.greyTextColor,
),
// overflow: TextOverflow.ellipsis,
// maxLines: 2,
softWrap: true,
),
),
),
],
),
SizedBox(height: 12.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
spacing: 6.h,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
severityText.tr().toText10(weight: FontWeight.w500, color: AppColors.greyTextColor),
Utils.buildSvgWithAssets(
icon: AppAssets.lab_result_indicator,
width: 21,
height: 23,
iconColor: iconColor
),
],
),
CustomButton(
icon: AppAssets.view_report_icon,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
text: LocaleKeys.viewReport.tr(context: context),
onPressed: () {
onTap();
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 14,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
],
),
],
),
));
}
}

@ -0,0 +1,120 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.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/features/lab/lab_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:provider/provider.dart';
class LabOrderSpecialResult extends StatelessWidget {
const LabOrderSpecialResult({super.key});
@override
Widget build(BuildContext context) {
return Selector<LabViewModel, bool>(
selector: (_, model) => model.isSpecialResultsLoading,
builder: (_, isLoading, __) {
return Selector<LabViewModel, String>(
selector: (_, model) => model.labSpecialResult,
builder: (_, data, __) {
if(isLoading){
return Container(
margin: EdgeInsets.symmetric(vertical: 8.h),
padding: EdgeInsets.symmetric(
horizontal: 16.h, vertical: 16.h),
width: MediaQuery.sizeOf(context).width - 24,
decoration: RoundedRectangleBorder()
.toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: true),
child:Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 12.h,
children: [
"loading".toText14().toShimmer2(isShow: isLoading),
"loading".toText14().toShimmer2(isShow: isLoading),
],
)
);
}
if(data.isNotEmpty ) {
return AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
margin: EdgeInsets.symmetric(vertical: 8.h),
decoration: RoundedRectangleBorder()
.toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: true),
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 16.h, vertical: 16.h),
width: MediaQuery.sizeOf(context).width - 24,
child: Column(
spacing: 8.h,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// ...labOrder!.testDetails!.map((detail) {
LocaleKeys.specialResult
.tr()
.toText14(weight: FontWeight.w500)
.toShimmer2(isShow: isLoading),
data.isEmpty
? LocaleKeys.noSpecialResult
.tr()
.toText12(
fontWeight: FontWeight.w500,
color: AppColors.textColorLight)
.toShimmer2(isShow: isLoading)
: HtmlWidget(data).toShimmer2(isShow: isLoading)
//
],
),
));
} return SizedBox.shrink();
});
});
}
}
/*Text(
"Special Results",
style: TextStyle(
fontSize: 18.fSize,
fontWeight: FontWeight.w600,
color: AppColors.blackColor),
),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: true
),
padding: EdgeInsets.all(16.h),
width: MediaQuery.sizeOf(context).width-24,
child: Selector<LabViewModel, bool>(
selector: (_, model) =>
model.isLabResultByHospitalLoading,
builder: (_, isLoading, __) {
return Selector<LabViewModel, String>(
selector: (_, model) => model.labSpecialResult,
builder: (_, data, __) {
return (data.isEmpty)
? Text("No result available".needTranslation,
style: TextStyle(
fontSize: 12.fSize,
fontWeight: FontWeight.w500,
color: AppColors.textColorLight))
.toShimmer2(isShow: isLoading)
: HtmlWidget(data)
.toShimmer2(isShow: isLoading);
});
}))*/

@ -6,42 +6,47 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/lab/lab_range_view_model.dart' show LabRangeViewModel;
import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart';
import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart';
import 'package:hmg_patient_app_new/features/lab/models/resp_models/lab_result.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_results/lab_result_calender.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/date_range_calender.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_results/lab_result_list_item.dart';
import 'package:hmg_patient_app_new/theme/colors.dart' show AppColors;
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/graph/custom_graph.dart';
import 'package:provider/provider.dart' show Consumer, Provider;
import 'package:provider/provider.dart' show Consumer, Provider, ReadContext;
import '../../../widgets/common_bottom_sheet.dart'
show showCommonBottomSheetWithoutHeight;
class LabResultDetails extends StatelessWidget {
final LabResult recentLabResult;
final String? testDescription;
// final List<DataPoint> graphPoint;
late LabViewModel model;
LabResultDetails({super.key, required this.recentLabResult});
const LabResultDetails(
{super.key,
required this.recentLabResult,
required this.testDescription});
@override
Widget build(BuildContext context) {
model = Provider.of<LabViewModel>(context, listen: false);
return CollapsingListView(
title: 'Lab Result Details'.needTranslation,
child: SingleChildScrollView(
child: Column(
spacing: 16.h,
children: [LabNameAndStatus, LabGraph(context)],
children: [
LabNameAndStatus(context),
getLabDescription(context),
LabGraph(context)
],
).paddingAll(24.h),
),
);
}
Widget get LabNameAndStatus => Container(
Widget LabNameAndStatus(BuildContext context) => Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
@ -78,11 +83,12 @@ class LabResultDetails extends StatelessWidget {
//todo change the text color according to the provided test values
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Row(
spacing: 4.h,
mainAxisSize: MainAxisSize.max,
children: [
Flexible(
child: Text(
@ -91,8 +97,8 @@ class LabResultDetails extends StatelessWidget {
fontSize: 24.fSize,
fontWeight: FontWeight.w600,
fontFamily: 'Poppins',
color: model.getColor(
recentLabResult.calculatedResultFlag ?? "",
color: context.read<LabViewModel>().getColor(
recentLabResult.calculatedResultFlag ?? "",
),
letterSpacing: -2,
),
@ -101,30 +107,37 @@ class LabResultDetails extends StatelessWidget {
softWrap: false,
),
),
Visibility(
visible: recentLabResult.referanceRange != null,
child: Text(
"(Reference range ${recentLabResult.referanceRange})".needTranslation,
style: TextStyle(
fontSize: 12.fSize,
fontWeight: FontWeight.w500,
fontFamily: 'Poppins',
color: AppColors.greyTextColor,
SizedBox(width: 4.h,),
Expanded(
flex: 2,
child: Visibility(
visible: recentLabResult.referanceRange != null,
child: Text(
"(Reference range ${recentLabResult.referanceRange})".needTranslation,
style: TextStyle(
fontSize: 12.fSize,
fontWeight: FontWeight.w500,
fontFamily: 'Poppins',
color: AppColors.greyTextColor,
),
// overflow: TextOverflow.ellipsis,
// maxLines: 2,
softWrap: true,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
softWrap: false,
),
),
],
),
),
Utils.buildSvgWithAssets(
icon: AppAssets.lab_result_indicator,
SizedBox(
width: 21,
height: 23,
iconColor: model.getColor(
recentLabResult.calculatedResultFlag ?? "",
child: Utils.buildSvgWithAssets(
icon: AppAssets.lab_result_indicator,
width: 21,
height: 23,
iconColor: context.read<LabViewModel>().getColor(
recentLabResult.calculatedResultFlag ?? "",
),
),
),
],
@ -133,73 +146,80 @@ class LabResultDetails extends StatelessWidget {
],
));
Widget LabGraph(BuildContext context) => Consumer<LabRangeViewModel>(
builder: (_, model, ___) => Consumer<LabViewModel>(
builder: (_, labmodel, ___) => Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: true,
),
height: 260.h,
padding: EdgeInsets.all(16.h),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
Widget LabGraph(BuildContext context) => Consumer<LabViewModel>(
builder: (_, labmodel, ___) => Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: true,
),
height: labmodel.isGraphVisible
? 260.h
: (labmodel.filteredGraphValues.length < 3)
? (labmodel.filteredGraphValues.length * 64) + 80.h
: 260.h,
padding: EdgeInsets.all(16.h),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
//title and filter icon
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
//title and filter icon
Text(
labmodel.isGraphVisible
? LocaleKeys.historyFlowchart.tr()
: LocaleKeys.history.tr(),
style: TextStyle(
fontSize: 16,
fontFamily: 'Poppins',
fontWeight: FontWeight.w600,
color: AppColors.textColor,
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
spacing: 16.h,
children: [
Text(
model.isGraphVisible?"History FlowChart".needTranslation: "History".needTranslation,
style: TextStyle(
fontSize: 16,
fontFamily: 'Poppins',
fontWeight: FontWeight.w600,
color: AppColors.textColor,
),
),
Row(
spacing: 16.h,
children: [
//todo handle when the graph icon is being displayed
Utils.buildSvgWithAssets(
icon: model.isGraphVisible?AppAssets.ic_list:AppAssets.ic_graph,
width: 24,
height: 24)
.onPress(() {
model.alterGraphVisibility();
}),
Utils.buildSvgWithAssets(
icon: AppAssets.ic_date_filter,
width: 24,
height: 24)
.onPress(() {
showCommonBottomSheetWithoutHeight(
title: "Set The Date Range".needTranslation,
context,
child: LabResultCalender(
onRangeSelected: (start, end) {
// if (start != null) {
labmodel.getSelectedDateRange(start, end);
// }
},
),
isFullScreen: false,
isCloseButtonVisible: true,
callBackFunc: () {},
);
}),
],
)
//todo handle when the graph icon is being displayed
Utils.buildSvgWithAssets(
icon: labmodel.isGraphVisible
? AppAssets.ic_list
: AppAssets.ic_graph,
width: 24.h,
height: 24.h)
.onPress(() {
if (labmodel.shouldShowGraph) {
labmodel.alterGraphVisibility();
}
}),
Utils.buildSvgWithAssets(
icon: AppAssets.ic_date_filter,
width: 24,
height: 24)
.onPress(() {
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.setTheDateRange.tr(),
context,
child: DateRangeSelector(
onRangeSelected: (start, end) {
// if (start != null) {
labmodel.getSelectedDateRange(start, end);
// }
},
),
isFullScreen: false,
isCloseButtonVisible: true,
callBackFunc: () {},
);
}),
],
).paddingOnly(bottom: model.isGraphVisible? 16.h :24.h),
historyBody(model, labmodel)
)
],
)),
));
).paddingOnly(bottom: labmodel.isGraphVisible ? 16.h : 24.h),
historyBody(labmodel)
],
)),
);
Widget leftLabels(String value) {
return Text(
@ -227,17 +247,16 @@ class LabResultDetails extends StatelessWidget {
);
}
Widget historyBody(LabRangeViewModel model, LabViewModel labmodel) {
if(model.isGraphVisible){
Widget historyBody(LabViewModel labmodel) {
if (labmodel.isGraphVisible && labmodel.shouldShowGraph) {
var graphColor = labmodel.getColor(recentLabResult.calculatedResultFlag??"N");
return CustomGraph(
dataPoints: labmodel.filteredGraphValues,
// maxY: 100,
makeGraphBasedOnActualValue: true,
leftLabelReservedSize: 40,
leftLabelInterval: getInterval(labmodel),
maxY: (labmodel.maxY)+(getInterval(labmodel)??0)/2,
maxX: labmodel.filteredGraphValues.length.toDouble()-.75,
leftLabelFormatter: (value) {
return leftLabels(value.toStringAsFixed(2).tr());
// switch (value.toInt()) {
@ -257,7 +276,7 @@ class LabResultDetails extends StatelessWidget {
// }
},
graphColor:graphColor ,
graphShadowColor: graphColor.withOpacity(.4),
graphShadowColor: graphColor.withOpacity(.1),
graphGridColor: graphColor.withOpacity(.4),
bottomLabelFormatter: (value, data) {
if(data.isEmpty) return SizedBox.shrink();
@ -276,13 +295,13 @@ class LabResultDetails extends StatelessWidget {
scrollDirection: Axis.horizontal,
height: 180.h);
}else {
return labHistoryList(model, labmodel);
return labHistoryList(labmodel);
}
}
Widget labHistoryList(LabRangeViewModel model, LabViewModel labmodel) {
Widget labHistoryList(LabViewModel labmodel) {
return SizedBox(
height: 180.h,
height: labmodel.filteredGraphValues.length<3?labmodel.filteredGraphValues.length*64:180.h,
child: ListView.separated(
padding: EdgeInsets.zero,
itemCount: labmodel.filteredGraphValues.length,itemBuilder: (context, index){
@ -304,6 +323,7 @@ class LabResultDetails extends StatelessWidget {
double? getInterval(LabViewModel labmodel) {
var maxX = labmodel.maxY;
if(maxX<1) return .5;
if(maxX >1 && maxX < 5) return 1;
if(maxX >5 && maxX < 10) return 5;
if(maxX >10 && maxX < 50) return 10;
@ -311,4 +331,27 @@ class LabResultDetails extends StatelessWidget {
if(maxX >100 && maxX < 200) return 30;
return 50;
}
Widget getLabDescription(BuildContext context) {
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: true,
),
height: 98.h,
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8.h,
children: [
"What is this result?"
.needTranslation
.toText16(weight: FontWeight.w600, color: AppColors.textColor),
testDescription?.toText12(
fontWeight: FontWeight.w500, color: AppColors.textColorLight) ??
SizedBox.shrink()
],
));
}
}

@ -28,10 +28,12 @@ import 'package:hmg_patient_app_new/presentation/appointments/my_doctors_page.da
import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart';
import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart';
import 'package:hmg_patient_app_new/presentation/insurance/widgets/insurance_update_details_card.dart';
import 'package:hmg_patient_app_new/presentation/insurance/widgets/patient_insurance_card.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/medical_reports_page.dart';
import 'package:hmg_patient_app_new/presentation/medical_report/medical_reports_page.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/patient_sickleaves_list_page.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/vaccine_list_page.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/widgets/lab_rad_card.dart';
@ -50,7 +52,6 @@ import 'package:hmg_patient_app_new/widgets/input_widget.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart';
import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart';
import 'package:provider/provider.dart';
import '../prescriptions/prescription_detail_page.dart';
@ -529,7 +530,14 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
),
),
).paddingSymmetrical(24.h, 0.h)
: Utils.getNoDataWidget(context, noDataText: "You don't have any prescriptions yet.".needTranslation, isSmallWidget: true, width: 62, height: 62).paddingSymmetrical(24.h, 0.h);
: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: true,
),
child: Utils.getNoDataWidget(context, noDataText: "You don't have any prescriptions yet.".needTranslation, isSmallWidget: true, width: 62, height: 62))
.paddingSymmetrical(24.h, 0.h);
}),
SizedBox(height: 24.h),
//My Doctor Section
@ -578,7 +586,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
"https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png",
width: 64.h,
height: 64.h,
fit: BoxFit.fill,
fit: BoxFit.cover,
).circle(100).toShimmer2(isShow: true, radius: 50.h),
SizedBox(height: 8.h),
Expanded(
@ -603,7 +611,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
myAppointmentsVM.patientMyDoctorsList[index].doctorImageURL!,
width: 64.h,
height: 64.h,
fit: BoxFit.fill,
fit: BoxFit.cover,
).circle(100).toShimmer2(isShow: false, radius: 50.h),
SizedBox(height: 8.h),
Expanded(
@ -618,8 +626,14 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
),
),
)
: Utils.getNoDataWidget(context, noDataText: "You don't have any completed visits yet.".needTranslation, isSmallWidget: true, width: 62, height: 62)
.paddingSymmetrical(24.h, 0.h);
: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: true,
),
child: Utils.getNoDataWidget(context, noDataText: "You don't have any completed visits yet.".needTranslation, isSmallWidget: true, width: 62, height: 62),
).paddingSymmetrical(24.h, 0.h);
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 8.h),
),
@ -629,7 +643,12 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
"Others".needTranslation.toText18(isBold: true).paddingSymmetrical(24.h, 0.h),
SizedBox(height: 16.h),
GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, crossAxisSpacing: 13, mainAxisSpacing: 13),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
mainAxisExtent: 130,
),
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
shrinkWrap: true,
@ -640,15 +659,15 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.eye_result_icon,
isLargeText: true,
iconSize: 40.h,
iconSize: 36.h,
),
MedicalFileCard(
label: "Allergy Info".needTranslation,
label: "Allergy Info".needTranslation,
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.allergy_info_icon,
isLargeText: true,
iconSize: 40.h,
iconSize: 36.h,
),
MedicalFileCard(
label: "Vaccine Info".needTranslation,
@ -656,7 +675,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.vaccine_info_icon,
isLargeText: true,
iconSize: 40.h,
iconSize: 36.h,
).onPress(() {
Navigator.of(context).push(
CustomPageRoute(
@ -675,7 +694,12 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
children: [
Consumer<InsuranceViewModel>(builder: (context, insuranceVM, child) {
return insuranceVM.isInsuranceLoading
? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0.0)
? LabResultItemView(
onTap: () {},
labOrder: null,
index: index,
isLoading: true,
).paddingSymmetrical(24.h, 0.0)
: insuranceVM.patientInsuranceList.isNotEmpty
? PatientInsuranceCard(
insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first,
@ -683,11 +707,49 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo),
),
)
: SizedBox.shrink();
: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: true,
),
child: Utils.getNoDataWidget(
context,
noDataText: "You don't have insurance registered with HMG.".needTranslation,
isSmallWidget: true,
width: 62,
height: 62,
callToActionButton: CustomButton(
icon: AppAssets.update_insurance_card_icon,
iconColor: AppColors.successColor,
iconSize: 15.h,
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);
},
backgroundColor: AppColors.bgGreenColor.withOpacity(0.20),
borderColor: AppColors.bgGreenColor.withOpacity(0.0),
textColor: AppColors.bgGreenColor,
fontSize: 14,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
).paddingOnly(left: 12.h, right: 12.h, bottom: 12.h),
),
).paddingSymmetrical(24.h, 0.h);
}),
SizedBox(height: 10.h),
GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, crossAxisSpacing: 13, mainAxisSpacing: 13),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
mainAxisExtent: 140,
),
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.only(top: 12),
shrinkWrap: true,
@ -697,7 +759,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.eye_result_icon,
isLargeText: false,
isLargeText: true,
iconSize: 36.h)
.onPress(() {
Navigator.of(context).push(
@ -711,21 +773,21 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.eye_result_icon,
isLargeText: false,
isLargeText: true,
iconSize: 36.h),
MedicalFileCard(
label: "My Invoices List".needTranslation,
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.eye_result_icon,
isLargeText: false,
isLargeText: true,
iconSize: 36.h),
MedicalFileCard(
label: "Ancillary Orders List".needTranslation,
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.eye_result_icon,
isLargeText: false,
isLargeText: true,
iconSize: 36.h),
],
).paddingSymmetrical(24.h, 0.0),
@ -747,11 +809,22 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
patientSickLeavesResponseModel: medicalFileVM.patientSickLeaveList.first,
isLoading: false,
).paddingSymmetrical(24.h, 0.0)
: SizedBox.shrink();
: Utils.getNoDataWidget(
context,
noDataText: "You don't have any sick leaves yet.".needTranslation,
isSmallWidget: true,
width: 62,
height: 62,
);
}),
SizedBox(height: 16.h),
GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, crossAxisSpacing: 13, mainAxisSpacing: 13),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
mainAxisExtent: 140,
),
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
shrinkWrap: true,
@ -761,16 +834,16 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.eye_result_icon,
isLargeText: false,
iconSize: 40.h,
isLargeText: true,
iconSize: 36.h,
),
MedicalFileCard(
label: "Medical Reports".needTranslation,
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.allergy_info_icon,
isLargeText: false,
iconSize: 40.h,
isLargeText: true,
iconSize: 36.h,
).onPress(() {
medicalFileViewModel.setIsPatientMedicalReportsLoading(true);
medicalFileViewModel.getPatientMedicalReportList();
@ -785,8 +858,8 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.vaccine_info_icon,
isLargeText: false,
iconSize: 40.h,
isLargeText: true,
iconSize: 36.h,
).onPress(() {
Navigator.of(context).push(
CustomPageRoute(

@ -1,94 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/models/patient_medical_response_model.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/widgets/patient_medical_report_card.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart';
import 'package:provider/provider.dart';
class MedicalReportsPage extends StatefulWidget {
const MedicalReportsPage({super.key});
@override
State<MedicalReportsPage> createState() => _MedicalReportsPageState();
}
class _MedicalReportsPageState extends State<MedicalReportsPage> {
late MedicalFileViewModel medicalFileViewModel;
@override
Widget build(BuildContext context) {
medicalFileViewModel = Provider.of<MedicalFileViewModel>(context, listen: false);
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView(
title: "Medical Reports".needTranslation,
child: SingleChildScrollView(
child: Column(
children: [
SizedBox(height: 16.h),
CustomTabBar(
activeTextColor: Color(0xffED1C2B),
activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1),
tabs: [
CustomTabBarModel(null, "Requested".needTranslation),
CustomTabBarModel(null, "Ready".needTranslation),
CustomTabBarModel(null, "Cancelled".needTranslation),
],
onTabChange: (index) {
medicalFileViewModel.onMedicalReportTabChange(index);
},
).paddingSymmetrical(24.h, 0.h),
Consumer<MedicalFileViewModel>(builder: (context, medicalFileVM, child) {
return ListView.separated(
padding: EdgeInsets.only(top: 24.h),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: medicalFileViewModel.isPatientMedicalReportsListLoading ? 3 : medicalFileViewModel.patientMedicalReportList.length,
// medicalFileViewModel.patientMedicalReportList.isNotEmpty
// ? medicalFileViewModel.patientMedicalReportList.length
// : 1,
itemBuilder: (context, index) {
return medicalFileViewModel.isPatientMedicalReportsListLoading
? PatientMedicalReportCard(
patientMedicalReportResponseModel: PatientMedicalReportResponseModel(),
medicalFileViewModel: medicalFileVM,
isLoading: true,
).paddingSymmetrical(24.h, 0.h)
: AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
child: PatientMedicalReportCard(
patientMedicalReportResponseModel: medicalFileVM.patientMedicalReportList[index],
medicalFileViewModel: medicalFileVM,
isLoading: false,
),
).paddingSymmetrical(24.h, 0.h),
),
),
);
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
);
}),
SizedBox(height: 24.h),
],
),
),
),
);
}
}

@ -30,10 +30,7 @@ class _PatientSickleavesListPageState extends State<PatientSickleavesListPage> {
void initState() {
scheduleMicrotask(() {
medicalFileViewModel.setIsPatientSickLeaveListLoading(true);
medicalFileViewModel.getPatientSickLeaveList(onError: (error) {
Navigator.of(context).pop();
Navigator.of(context).pop();
});
medicalFileViewModel.getPatientSickLeaveList();
});
super.initState();
}
@ -75,6 +72,7 @@ class _PatientSickleavesListPageState extends State<PatientSickleavesListPage> {
child: PatientSickLeaveCard(
patientSickLeavesResponseModel: medicalFileVM.patientSickLeaveList.first,
isLoading: false,
isSickLeaveListPage: true,
).paddingSymmetrical(24.h, 0.0),
),
),

@ -5,6 +5,7 @@ import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart';
@ -28,10 +29,7 @@ class _VaccineListPageState extends State<VaccineListPage> {
void initState() {
scheduleMicrotask(() {
medicalFileViewModel.setIsPatientVaccineListLoading(true);
medicalFileViewModel.getPatientVaccinesList(onError: (error) {
Navigator.of(context).pop();
Navigator.of(context).pop();
});
medicalFileViewModel.getPatientVaccinesList();
});
super.initState();
}
@ -51,7 +49,11 @@ class _VaccineListPageState extends State<VaccineListPage> {
SizedBox(height: 16.h),
ListView.separated(
scrollDirection: Axis.vertical,
itemCount: medicalFileVM.isPatientVaccineListLoading ? 5 : medicalFileVM.patientVaccineList.length,
itemCount: medicalFileVM.isPatientVaccineListLoading
? 5
: medicalFileVM.patientVaccineList.isNotEmpty
? medicalFileVM.patientVaccineList.length
: 1,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.only(left: 24.h, right: 24.h),
@ -102,8 +104,9 @@ class _VaccineListPageState extends State<VaccineListPage> {
),
),
)
: AnimationConfiguration.staggeredList(
position: index,
: medicalFileVM.patientVaccineList.isNotEmpty
? AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 1000),
child: SlideAnimation(
verticalOffset: 100.0,
@ -166,7 +169,8 @@ class _VaccineListPageState extends State<VaccineListPage> {
),
),
),
);
)
: Utils.getNoDataWidget(context, noDataText: "No vaccines data found...".needTranslation);
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
),

@ -54,7 +54,7 @@ class LabRadCard extends StatelessWidget {
SizedBox.shrink(),
Transform.flip(
flipX: appState.isArabic() ? true : false,
child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, width: 15.h, height: 15.h, fit: BoxFit.contain, iconColor: AppColors.textColor)
child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon_small, width: 15.h, height: 15.h, fit: BoxFit.contain, iconColor: AppColors.textColor)
.toShimmer2(isShow: false, radius: 12.h),
),
],

@ -121,11 +121,12 @@ class MedicalFileAppointmentCard extends StatelessWidget {
child: Padding(
padding: EdgeInsets.all(10.h),
child: Transform.flip(
flipX: appState.isArabic() ? true : false,
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon,
width: 10.h,
height: 10.h,
iconColor: AppColors.whiteColor,
icon: AppAssets.forward_arrow_icon_small,
width: 40.h,
height: 40.h,
fit: BoxFit.contain,
),
),

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

Loading…
Cancel
Save