Merge branch 'master' into faiz_dev

# Conflicts:
#	ios/Podfile.lock
faiz_dev
faizatflutter 4 days ago
commit 8c3121fd38

@ -34,7 +34,6 @@ android {
defaultConfig {
applicationId = "com.cloudsolutions.HMGPatientApp"
// minSdk = 24
// minSdk = 26
minSdk = 29
targetSdk = 35
compileSdk = 36

@ -41,15 +41,20 @@
tools:node="remove" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" tools:node="remove" />
<!-- Added by open_filex -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" tools:node="remove" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" tools:node="remove" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" tools:node="remove" />
<!-- Gallery/Photo permissions for Android 13+ (API 33+) -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<!-- Gallery/Photo permissions for Android 12 and below -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
<uses-permission
android:name="android.permission.ACCESS_BACKGROUND_LOCATION"
tools:node="remove" /> <!-- <uses-permission android:name="android.permission.INTERNET" /> -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" tools:node="remove" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
@ -88,11 +93,8 @@
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" tools:node="remove"/>
<uses-permission android:name="android.permission.VIDEO_CAPTURE" />
<uses-permission android:name="android.permission.AUDIO_CAPTURE" />

@ -45,6 +45,7 @@
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" tools:node="remove" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" tools:node="remove" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" tools:node="remove" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" tools:node="remove" />
<uses-permission
android:name="android.permission.ACCESS_BACKGROUND_LOCATION"
@ -93,7 +94,6 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.VIDEO_CAPTURE" />
<uses-permission android:name="android.permission.AUDIO_CAPTURE" />

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 202 KiB

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 KiB

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 KiB

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

After

Width:  |  Height:  |  Size: 228 KiB

@ -0,0 +1,6 @@
<svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="56" height="56" rx="28" fill="white"/>
<rect width="56" height="56" rx="28" fill="white"/>
<rect x="0.5" y="0.5" width="55" height="55" rx="27.5" stroke="#2E3039" stroke-opacity="0.13" stroke-dasharray="2 2"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M28 18.25C28.4142 18.25 28.75 18.5858 28.75 19V26.25H36C36.4142 26.25 36.75 26.5858 36.75 27C36.75 27.4142 36.4142 27.75 36 27.75H28.75V35C28.75 35.4142 28.4142 35.75 28 35.75C27.5858 35.75 27.25 35.4142 27.25 35V27.75H20C19.5858 27.75 19.25 27.4142 19.25 27C19.25 26.5858 19.5858 26.25 20 26.25H27.25V19C27.25 18.5858 27.5858 18.25 28 18.25Z" fill="#161616" stroke="#2E3039" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 835 B

@ -0,0 +1,6 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" rx="10" fill="#EFEFF0"/>
<path d="M18.9994 9.25C16.0999 9.25 13.7494 11.6005 13.7494 14.5C13.7494 17.3995 16.0999 19.75 18.9994 19.75C21.8989 19.75 24.2494 17.3995 24.2494 14.5C24.2494 11.6005 21.8989 9.25 18.9994 9.25Z" fill="#8F9AA3"/>
<path d="M22.4393 22.3825L22.4269 22.3899C22.3982 22.4063 22.3715 22.4164 22.3391 22.4232L22.3222 22.4264C21.5275 22.5747 20.3099 23.081 19.8772 24.4469C19.436 25.84 20.178 26.9751 20.7397 27.5414L20.9312 27.7346C21.045 27.8493 21.1019 27.9066 21.1231 27.9808C21.1442 28.055 21.1262 28.1337 21.09 28.2912L21.0601 28.4219C20.9887 28.7329 20.9096 29.2541 20.9862 29.8241C21.0509 30.3047 21.0832 30.545 20.9935 30.6475C20.9039 30.75 20.7005 30.75 20.2937 30.75L14.5901 30.75C12.7718 30.75 11.366 29.8556 10.1945 28.7396C9.51551 28.0927 9.17705 27.3646 9.26185 26.5888C9.3416 25.8592 9.78025 25.2637 10.2419 24.8118C10.9801 24.0893 12.0577 23.4813 12.7705 23.0792C12.9331 22.9875 13.0768 22.9064 13.1931 22.8372C14.4979 22.0602 15.93 21.5691 17.3945 21.3625C18.4583 21.2125 19.5401 21.2125 20.6039 21.3625C21.0835 21.4302 21.8204 21.628 22.365 21.7855C22.4184 21.801 22.4451 21.8087 22.4641 21.8172C22.6658 21.9073 22.7022 22.1856 22.5303 22.3244C22.5142 22.3375 22.4892 22.3525 22.4393 22.3825Z" fill="#8F9AA3"/>
<path d="M27.3627 22.245C27.12 21.7533 26.67 21.25 25.9982 21.25C25.3273 21.25 24.8762 21.7522 24.6315 22.2431L23.9345 23.6487L23.9282 23.6547C23.9233 23.6592 23.9176 23.6639 23.9113 23.6687C23.9049 23.6734 23.8987 23.6775 23.893 23.681L23.8859 23.6851L22.6351 23.8946C22.0941 23.9855 21.5068 24.2757 21.3091 24.8998C21.1123 25.5211 21.4213 26.0967 21.8065 26.4851L22.7841 27.4708C22.7875 27.4778 22.792 27.4888 22.7959 27.5027C22.8003 27.5181 22.8024 27.5314 22.8031 27.5401L22.524 28.7572C22.397 29.3106 22.3883 30.0742 22.9815 30.5106C23.5783 30.9496 24.3047 30.7069 24.7916 30.4156L25.9668 29.7142C26.0142 29.6859 26.0283 29.702 26.0294 29.7135L27.2061 30.4159C27.6913 30.7048 28.4194 30.9516 29.0171 30.5123C29.6116 30.0753 29.5995 29.3093 29.4735 28.7579L29.1942 27.5401C29.1949 27.5314 29.197 27.5181 29.2014 27.5027C29.2053 27.4888 29.2098 27.4778 29.2132 27.4708L30.1889 26.487L30.1899 26.486C30.5775 26.0974 30.8877 25.521 30.6893 24.8985C30.4905 24.275 29.9028 23.9855 29.3626 23.8947L28.1075 23.6844L28.0992 23.6798C28.0935 23.6763 28.0874 23.6722 28.0811 23.6676C28.0789 23.6659 28.0766 23.6642 28.0745 23.6625C28.0709 23.6596 28.0675 23.6567 28.0645 23.654L28.0587 23.6484L27.3632 22.2459L27.3627 22.245Z" fill="#8F9AA3"/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

@ -0,0 +1,9 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" rx="10" fill="#2E3039"/>
<path d="M16 20C15.4477 20 15 20.4477 15 21C15 21.5523 15.4477 22 16 22H16.009C16.5613 22 17.009 21.5523 17.009 21C17.009 20.4477 16.5613 20 16.009 20H16Z" fill="white"/>
<path d="M19.9955 20C19.4432 20 18.9955 20.4477 18.9955 21C18.9955 21.5523 19.4432 22 19.9955 22H20.0045C20.5568 22 21.0045 21.5523 21.0045 21C21.0045 20.4477 20.5568 20 20.0045 20H19.9955Z" fill="white"/>
<path d="M23.991 20C23.4387 20 22.991 20.4477 22.991 21C22.991 21.5523 23.4387 22 23.991 22H24C24.5523 22 25 21.5523 25 21C25 20.4477 24.5523 20 24 20H23.991Z" fill="white"/>
<path d="M16 24C15.4477 24 15 24.4477 15 25C15 25.5523 15.4477 26 16 26H16.009C16.5613 26 17.009 25.5523 17.009 25C17.009 24.4477 16.5613 24 16.009 24H16Z" fill="white"/>
<path d="M19.9955 24C19.4432 24 18.9955 24.4477 18.9955 25C18.9955 25.5523 19.4432 26 19.9955 26H20.0045C20.5568 26 21.0045 25.5523 21.0045 25C21.0045 24.4477 20.5568 24 20.0045 24H19.9955Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.75 10C14.75 9.58579 14.4142 9.25 14 9.25C13.5858 9.25 13.25 9.58579 13.25 10V10.6348C12.4452 10.8629 11.7665 11.2336 11.2015 11.8444C10.4236 12.6853 10.0778 13.7479 9.91225 15.0795C9.74998 16.3843 9.74999 18.0565 9.75 20.19V20.81C9.74999 22.9435 9.74998 24.6157 9.91225 25.9205C10.0778 27.2521 10.4236 28.3147 11.2015 29.1556C11.9871 30.0049 12.9928 30.3901 14.2513 30.573C15.4693 30.75 17.0255 30.75 18.9892 30.75H21.0108C22.9745 30.75 24.5307 30.75 25.7487 30.573C27.0072 30.3901 28.0129 30.0049 28.7985 29.1556C29.5764 28.3147 29.9222 27.2521 30.0878 25.9205C30.25 24.6157 30.25 22.9435 30.25 20.8099V20.1901C30.25 18.0565 30.25 16.3843 30.0878 15.0795C29.9222 13.7479 29.5764 12.6853 28.7985 11.8444C28.2335 11.2336 27.5548 10.8629 26.75 10.6348V10C26.75 9.58579 26.4142 9.25 26 9.25C25.5858 9.25 25.25 9.58579 25.25 10V10.3659C24.1139 10.25 22.716 10.25 21.0108 10.25L18.9892 10.25C17.284 10.25 15.8861 10.25 14.75 10.3659V10ZM13.2786 12.2058C13.3681 12.52 13.6572 12.75 14 12.75C14.4142 12.75 14.75 12.4142 14.75 12V11.8746C15.811 11.7516 17.1824 11.75 19.05 11.75L20.95 11.75C22.8176 11.75 24.189 11.7516 25.25 11.8746V12C25.25 12.4142 25.5858 12.75 26 12.75C26.3428 12.75 26.6319 12.52 26.7214 12.2058C27.1287 12.3679 27.438 12.5826 27.6973 12.8629C28.1704 13.3744 28.4505 14.0759 28.5982 15.2564C28.5661 15.2522 28.5333 15.25 28.5 15.25L11.5 15.25C11.4667 15.25 11.4339 15.2522 11.4018 15.2564C11.5495 14.0759 11.8296 13.3744 12.3027 12.8629C12.562 12.5826 12.8713 12.3679 13.2786 12.2058ZM11.2901 16.7202C11.2506 17.6832 11.25 18.8363 11.25 20.2432V20.7568C11.25 22.9551 11.2514 24.5339 11.4008 25.7354C11.5482 26.9208 11.8285 27.6244 12.3027 28.1371C12.7692 28.6414 13.3973 28.9331 14.467 29.0886C15.5648 29.2481 17.0119 29.25 19.05 29.25H20.95C22.9881 29.25 24.4352 29.2481 25.533 29.0886C26.6027 28.9331 27.2308 28.6414 27.6973 28.1371C28.1715 27.6244 28.4518 26.9208 28.5992 25.7354C28.7486 24.5339 28.75 22.9551 28.75 20.7568V20.2432C28.75 18.8363 28.7494 17.6832 28.7099 16.7202C28.6433 16.7396 28.5728 16.75 28.5 16.75L11.5 16.75C11.4272 16.75 11.3567 16.7396 11.2901 16.7202Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

@ -1,11 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30.996" height="28.414" viewBox="0 0 30.996 28.414">
<g id="image-gallery" transform="translate(-0.001 -2)">
<g id="Group_8635" data-name="Group 8635" transform="translate(0.001 5.875)">
<g id="Group_8634" data-name="Group 8634" transform="translate(0 5.078)">
<path id="Path_5018" data-name="Path 5018" d="M8.072,22.581A4.836,4.836,0,0,1,3.5,19.286l-.045-.149a4.711,4.711,0,0,1-.223-1.4V8.932L.1,19.391a2.933,2.933,0,0,0,2.056,3.558L22.123,28.3a2.962,2.962,0,0,0,.744.1,2.88,2.88,0,0,0,2.791-2.112l1.164-3.7Z" transform="translate(-0.001 -8.932)" fill="#2bb8a6"/>
</g>
<path id="Path_5019" data-name="Path 5019" d="M9.583,10.166A2.583,2.583,0,1,0,7,7.583,2.586,2.586,0,0,0,9.583,10.166Z" transform="translate(2.04 -5)" fill="#2bb8a6"/>
</g>
<path id="Path_5020" data-name="Path 5020" d="M26.6,2H7.229A3.233,3.233,0,0,0,4,5.229V19.436a3.233,3.233,0,0,0,3.229,3.229H26.6a3.233,3.233,0,0,0,3.229-3.229V5.229A3.233,3.233,0,0,0,26.6,2ZM7.229,4.583H26.6a.646.646,0,0,1,.646.646V14.4l-4.08-4.761a2.313,2.313,0,0,0-1.732-.794,2.258,2.258,0,0,0-1.726.815l-4.8,5.758-1.563-1.559a2.267,2.267,0,0,0-3.2,0L6.583,17.42V5.229a.646.646,0,0,1,.646-.646Z" transform="translate(1.166)" fill="#2bb8a6"/>
</g>
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" rx="10" fill="#EFEFF0"/>
<path d="M21.2444 13.5C20.5571 13.5 20 14.0596 20 14.75C20 15.4404 20.5571 16 21.2444 16C21.9268 16 22.5 15.4401 22.5 14.75C22.5 14.0599 21.9268 13.5 21.2444 13.5Z" fill="#8F9AA3"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.0732 9H17.9268C16.1034 8.99997 14.6336 8.99995 13.4777 9.15537C12.2775 9.31672 11.267 9.66191 10.4645 10.4645C9.66191 11.267 9.31672 12.2775 9.15537 13.4777C8.99995 14.6336 8.99997 16.1034 9 17.9268V18.0732C8.99997 19.8966 8.99995 21.3664 9.15537 22.5223C9.31672 23.7225 9.66191 24.733 10.4645 25.5355C11.1805 26.2516 12.062 26.6035 13.0961 26.7857C13.2742 27.8673 13.6274 28.7857 14.3689 29.5272C15.1752 30.3335 16.1906 30.6805 17.3977 30.8428C18.5606 30.9992 20.0394 30.9991 21.8748 30.9991H22.0212C23.8567 30.9991 25.3355 30.9992 26.4984 30.8428C27.7055 30.6805 28.7209 30.3335 29.5272 29.5272C30.3335 28.7209 30.6805 27.7055 30.8428 26.4984C30.9992 25.3355 30.9991 23.8567 30.9991 22.0212V21.8748C30.9991 20.0394 30.9992 18.5606 30.8428 17.3977C30.6805 16.1906 30.3335 15.1752 29.5272 14.3689C28.7857 13.6274 27.8673 13.2742 26.7857 13.0961C26.6035 12.062 26.2516 11.1805 25.5355 10.4645C24.733 9.66191 23.7225 9.31672 22.5223 9.15537C21.3664 8.99995 19.8966 8.99997 18.0732 9ZM26.9723 15.1769C27 15.9862 27 16.9005 27 17.9268V18.0732C27 19.8966 27.0001 21.3664 26.8446 22.5223C26.6833 23.7225 26.3381 24.733 25.5355 25.5355C24.733 26.3381 23.7225 26.6833 22.5223 26.8446C21.3664 27.0001 19.8966 27 18.0732 27H17.9268C16.9005 27 15.9862 27 15.1769 26.9723C15.3177 27.5048 15.5173 27.8471 15.7831 28.1129C16.1559 28.4857 16.6789 28.7282 17.6642 28.8606C18.6783 28.997 20.0221 28.9991 21.948 28.9991C23.874 28.9991 25.2178 28.997 26.2319 28.8606C27.2172 28.7282 27.7402 28.4857 28.1129 28.1129C28.4857 27.7402 28.7282 27.2172 28.8606 26.2319C28.997 25.2178 28.9991 23.874 28.9991 21.948C28.9991 20.0221 28.997 18.6783 28.8606 17.6642C28.7282 16.6789 28.4857 16.1559 28.1129 15.7831C27.8471 15.5173 27.5048 15.3177 26.9723 15.1769ZM11.8787 11.8787C12.2477 11.5097 12.7658 11.2691 13.7442 11.1375C14.7513 11.0021 16.0861 11 18 11C19.9139 11 21.2487 11.0021 22.2558 11.1375C23.2342 11.2691 23.7523 11.5097 24.1213 11.8787C24.4903 12.2477 24.7309 12.7658 24.8625 13.7442C24.9979 14.7513 25 16.0861 25 18C25 19.9139 24.9979 21.2487 24.8625 22.2558C24.7309 23.2342 24.4903 23.7523 24.1213 24.1213C23.9227 24.3199 23.6809 24.4813 23.3474 24.6086C22.5834 22.9592 21.3002 21.5131 19.6363 20.4162C17.4301 18.9618 14.6803 18.2023 11.8763 18.2523C11.5839 18.2518 11.2916 18.2596 11 18.2755L11 18C11 16.0861 11.0021 14.7513 11.1375 13.7442C11.2691 12.7658 11.5097 12.2477 11.8787 11.8787Z" fill="#8F9AA3"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

@ -0,0 +1,5 @@
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.875 8.75C11.875 8.05965 12.4346 7.5 13.125 7.5L21.875 7.5C22.5654 7.5 23.125 8.05965 23.125 8.75C23.125 9.44036 22.5654 10 21.875 10L13.125 10C12.4346 10 11.875 9.44036 11.875 8.75Z" fill="#2E3039"/>
<path d="M13.125 12.5C12.4346 12.5 11.875 13.0596 11.875 13.75C11.875 14.4404 12.4346 15 13.125 15H17.5C18.1904 15 18.75 14.4404 18.75 13.75C18.75 13.0596 18.1904 12.5 17.5 12.5H13.125Z" fill="#2E3039"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M24.0703 1.70757C22.9908 1.56243 21.6268 1.56246 19.9726 1.5625L14.9137 1.5625C13.2596 1.56246 11.8955 1.56243 10.8159 1.70757C9.6825 1.85996 8.6815 2.19219 7.88001 2.99369C7.07851 3.79518 6.74627 4.79619 6.59389 5.92962C6.44874 7.00921 6.44878 8.37325 6.44882 10.0274L6.44882 19.1364C6.44882 19.4899 6.44882 19.6667 6.33899 19.7765C6.22915 19.8864 6.05237 19.8864 5.69881 19.8864L5.1043 19.8864C4.5742 19.8862 4.03689 19.886 3.59055 19.9603C3.05657 20.0492 2.49845 20.266 2.06822 20.803C1.66663 21.3043 1.54538 21.8484 1.56439 22.3879C1.57941 22.814 1.6877 23.2915 1.78692 23.7289C2.03287 24.816 2.48165 26.0118 3.20368 26.8876C3.95427 27.798 5.02349 28.4375 6.40497 28.4375L23.5434 28.4375C25.2852 28.4375 26.5975 27.7012 27.4152 26.4548C28.1809 25.2875 28.4375 23.7913 28.4375 22.3296V10.0275C28.4375 8.37327 28.4375 7.00921 28.2924 5.92962C28.14 4.79619 27.8078 3.79518 27.0063 2.99369C26.2048 2.19219 25.2038 1.85996 24.0703 1.70757ZM9.64221 19.8864C9.28866 19.8864 9.11188 19.8864 9.00205 19.7766C8.89221 19.6668 8.89221 19.49 8.89221 19.1364L8.89221 10.1137C8.89221 8.35157 8.89481 7.15287 9.01549 6.25522C9.13145 5.39271 9.33591 4.99322 9.6078 4.72133C9.87969 4.44944 10.2792 4.24498 11.1417 4.12902C12.0393 4.00833 13.238 4.00574 15.0002 4.00574L19.8865 4.00574C21.6487 4.00574 22.8474 4.00833 23.745 4.12902C24.6075 4.24498 25.007 4.44944 25.2789 4.72133C25.5508 4.99322 25.7552 5.39271 25.8712 6.25522C25.9919 7.15287 25.9945 8.35157 25.9945 10.1137V22.3296C25.9945 23.5665 25.7669 24.5135 25.3725 25.1147C25.03 25.6368 24.506 25.9944 23.5436 25.9944C23.0042 25.9944 22.5905 25.7739 22.2274 25.3334C21.8357 24.8583 21.5233 24.1393 21.325 23.2628C21.1929 22.6787 21.0635 22.1067 20.9328 21.7044C20.8046 21.3095 20.5948 20.7891 20.1326 20.4205C19.6995 20.0749 19.209 19.9703 18.8049 19.9269C18.428 19.8863 17.9679 19.8864 17.479 19.8864L9.64221 19.8864Z" fill="#2E3039"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

@ -1,3 +1,4 @@
<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="M28.835 8.71465C29 10.2375 29 12.157 29 14.6057L29 17.393V17.3931C29 19.8418 29 21.7612 28.835 23.284C28.6667 24.8381 28.3169 26.0973 27.5358 27.1724C27.0629 27.8232 26.4905 28.3956 25.8397 28.8685C24.7646 29.6496 23.5054 29.9993 21.9514 30.1677C20.4285 30.3327 18.5091 30.3327 16.0604 30.3327H16.0603H15.9397C13.491 30.3327 11.5715 30.3327 10.0486 30.1677C8.49458 29.9993 7.23541 29.6496 6.16031 28.8685C5.50945 28.3956 4.93708 27.8232 4.4642 27.1724C3.6831 26.0973 3.33334 24.8381 3.16497 23.284C2.99998 21.7612 2.99999 19.8418 3 17.3931V17.393L3 14.6056V14.6056C2.99999 12.1569 2.99998 10.2375 3.16497 8.71465C3.33334 7.1606 3.6831 5.90143 4.4642 4.82633C4.93708 4.17547 5.50945 3.6031 6.16032 3.13022C7.23541 2.34911 8.49459 1.99936 10.0486 1.83099C11.5715 1.666 13.4909 1.666 15.9395 1.66602H15.9396H16.0604H16.0605C18.5091 1.66601 20.4285 1.666 21.9514 1.83099C23.5054 1.99936 24.7646 2.34911 25.8397 3.13022C26.4905 3.6031 27.0629 4.17547 27.5358 4.82633C28.3169 5.90143 28.6667 7.1606 28.835 8.71465ZM16 5.66602C16.7364 5.66602 17.3333 6.26297 17.3333 6.99935V9.24299C17.6276 9.59842 17.8786 9.66602 18 9.66602C18.1485 9.66602 18.4909 9.56487 18.8693 8.95942C19.2595 8.33494 20.0821 8.14507 20.7066 8.53531C21.3311 8.92555 21.521 9.74814 21.1307 10.3726C20.4782 11.4167 19.4088 12.3327 18 12.3327C17.7685 12.3327 17.5462 12.3079 17.3333 12.262L17.3333 13.0409C17.8948 13.4337 18.584 13.666 19.3333 13.666C20.2769 13.666 21.1252 13.2976 21.7432 12.7042C22.2744 12.1942 23.1185 12.2114 23.6285 12.7426C24.1385 13.2738 24.1213 14.1178 23.5901 14.6278C22.49 15.6841 20.9852 16.3327 19.3333 16.3327C18.6338 16.3327 17.9606 16.2163 17.3333 16.002V18.0531C18.0392 17.9934 18.7108 17.8873 19.335 17.7423C21.0296 17.3485 23 18.5587 23 20.5393C23 21.5265 22.4537 22.5524 21.4063 22.9419C20.935 23.1172 20.4345 23.269 19.911 23.3954C19.3839 23.5228 18.9627 23.8554 18.7618 24.3012L18.6199 24.6159C18.1546 25.6483 17.1409 26.3327 16 26.3327C14.8591 26.3327 13.8454 25.6483 13.3801 24.6159L13.2382 24.3012C13.0373 23.8554 12.6161 23.5228 12.089 23.3954C11.5655 23.269 11.065 23.1172 10.5937 22.9419C9.54632 22.5524 9 21.5265 9 20.5393C9 18.5587 10.9704 17.3485 12.665 17.7423C13.2892 17.8873 13.9608 17.9934 14.6667 18.0531V16.002C14.0394 16.2164 13.3662 16.3327 12.6667 16.3327C11.0148 16.3327 9.51001 15.6841 8.4099 14.6278C7.87872 14.1178 7.86154 13.2738 8.37154 12.7426C8.88155 12.2114 9.72559 12.1942 10.2568 12.7042C10.8748 13.2976 11.7231 13.666 12.6667 13.666C13.416 13.666 14.1052 13.4337 14.6667 13.0409V12.262C14.4538 12.3079 14.2315 12.3327 14 12.3327C12.5912 12.3327 11.5218 11.4167 10.8693 10.3726C10.4791 9.74814 10.6689 8.92555 11.2934 8.53531C11.9179 8.14507 12.7405 8.33494 13.1307 8.95942C13.5091 9.56487 13.8515 9.66602 14 9.66602C14.1214 9.66602 14.3724 9.59842 14.6667 9.24299L14.6667 6.99935C14.6667 6.26297 15.2636 5.66602 16 5.66602Z" fill="#2E3039"/>
<svg width="26" height="29" viewBox="0 0 26 29" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13 4C13.5523 4 14 4.44772 14 5V7.35861C14.3647 7.84353 14.7315 7.99997 15.0001 7.99997C15.3062 7.99997 15.7395 7.79698 16.1521 7.13669C16.4448 6.66833 17.0617 6.52593 17.5301 6.81861C17.9984 7.11129 18.1408 7.72823 17.8482 8.19659C17.2299 9.18588 16.2514 9.99997 15.0001 9.99997C14.644 9.99997 14.3099 9.93402 14 9.81685V11.2101C14.6402 11.7041 15.4494 12 16.3333 12C17.3655 12 18.2958 11.5965 18.9741 10.9453C19.3725 10.5628 20.0055 10.5757 20.388 10.9741C20.7705 11.3725 20.7576 12.0055 20.3592 12.388C19.3194 13.3864 17.8967 14 16.3333 14C15.5039 14 14.7141 13.8273 14 13.516V16.4116C14.8282 16.3596 15.6132 16.244 16.335 16.0762C18.0296 15.6825 20 16.8927 20 18.8733C20 19.8605 19.4537 20.8864 18.4063 21.2759C17.935 21.4511 17.4345 21.603 16.911 21.7294C16.3839 21.8567 15.9627 22.1893 15.7618 22.6351L15.6199 22.9499C15.1546 23.9822 14.1409 24.6667 13 24.6667C11.8591 24.6667 10.8454 23.9822 10.3801 22.9499L10.2382 22.6351C10.0373 22.1893 9.61608 21.8567 9.08902 21.7294C8.56548 21.603 8.06504 21.4511 7.59375 21.2759C6.54632 20.8864 6 19.8605 6 18.8733C6 16.8927 7.97038 15.6825 9.66501 16.0762C10.3868 16.244 11.1718 16.3596 12 16.4116V13.516C11.2859 13.8273 10.4961 14 9.66667 14C8.1033 14 6.6806 13.3864 5.64076 12.388C5.24237 12.0055 5.22949 11.3725 5.61199 10.9741C5.99449 10.5757 6.62753 10.5628 7.02591 10.9453C7.70416 11.5965 8.63452 12 9.66667 12C10.5506 12 11.3598 11.7041 12 11.2101V9.81695C11.6901 9.93406 11.3562 9.99997 11.0001 9.99997C9.74886 9.99997 8.77033 9.18588 8.1521 8.19659C7.85942 7.72823 8.00183 7.11129 8.47018 6.81861C8.93854 6.52593 9.55548 6.66833 9.84816 7.13669C10.2608 7.79698 10.6941 7.99997 11.0001 7.99997C11.2687 7.99997 11.6354 7.84361 12 7.35897V5C12 4.44772 12.4477 4 13 4Z" fill="#2E3039"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M26 15.727V12.9397C26 10.491 26 8.57148 25.835 7.04863C25.6667 5.49458 25.3169 4.23541 24.5358 3.16031C24.0629 2.50945 23.4905 1.93708 22.8397 1.4642C21.7646 0.683098 20.5054 0.333341 18.9514 0.16497C17.4285 -1.87904e-05 15.5091 -1.04558e-05 13.0604 1.75524e-07H12.9396C10.491 -1.06751e-05 8.57146 -1.91778e-05 7.04863 0.16497C5.49459 0.33334 4.23541 0.683097 3.16032 1.4642C2.50945 1.93708 1.93708 2.50945 1.4642 3.16031C0.683099 4.23541 0.333342 5.49458 0.164971 7.04863C-1.6585e-05 8.57145 -9.22962e-06 10.4909 1.48706e-07 12.9395V15.727C-9.56553e-06 18.1757 -1.7181e-05 20.0952 0.164971 21.618C0.333341 23.1721 0.683098 24.4313 1.4642 25.5064C1.93708 26.1572 2.50945 26.7296 3.16031 27.2025C4.23541 27.9836 5.49458 28.3333 7.04863 28.5017C8.57148 28.6667 10.491 28.6667 12.9397 28.6667H13.0603C15.509 28.6667 17.4285 28.6667 18.9514 28.5017C20.5054 28.3333 21.7646 27.9836 22.8397 27.2025C23.4905 26.7296 24.0629 26.1572 24.5358 25.5064C25.3169 24.4313 25.6667 23.1721 25.835 21.618C26 20.0952 26 18.1757 26 15.727ZM23.8467 21.4026C23.6967 22.7865 23.4099 23.6534 22.9178 24.3308C22.5682 24.8119 22.1452 25.2349 21.6641 25.5844C20.9868 26.0765 20.1198 26.3634 18.7359 26.5133C17.3333 26.6653 15.5221 26.6667 13 26.6667C10.4779 26.6667 8.66666 26.6653 7.26406 26.5133C5.88018 26.3634 5.01322 26.0765 4.33588 25.5844C3.85481 25.2349 3.43176 24.8119 3.08224 24.3308C2.59012 23.6534 2.30327 22.7865 2.15334 21.4026C2.00137 20 2 18.1888 2 15.6667V13C2 10.4779 2.00137 8.66665 2.15334 7.26406C2.30327 5.88017 2.59012 5.01322 3.08224 4.33588C3.43176 3.85481 3.85481 3.43175 4.33589 3.08224C5.01322 2.59012 5.88018 2.30327 7.26406 2.15333C8.66666 2.00137 10.4779 2 13 2C15.5221 2 17.3333 2.00137 18.7359 2.15334C20.1198 2.30327 20.9868 2.59012 21.6641 3.08224C22.1452 3.43175 22.5682 3.85481 22.9178 4.33588C23.4099 5.01322 23.6967 5.88018 23.8467 7.26406C23.9986 8.66665 24 10.4779 24 13V15.6667C24 18.1888 23.9986 20 23.8467 21.4026Z" fill="#2E3039"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

@ -0,0 +1,5 @@
<svg width="79" height="79" viewBox="0 0 79 79" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M22.2188 6.58398C22.2188 5.22053 21.1135 4.11523 19.7501 4.11523C18.3866 4.11523 17.2813 5.22053 17.2813 6.58398V8.6735C14.6323 9.4243 12.3981 10.6446 10.5385 12.655C7.97788 15.4232 6.83967 18.9209 6.29457 23.304C5.76043 27.599 5.76046 33.1032 5.7605 40.1262V42.1668C5.76046 49.1898 5.76043 54.694 6.29457 58.9889C6.83967 63.3721 7.97788 66.8697 10.5385 69.638C13.1244 72.4336 16.4346 73.7013 20.5772 74.3034C24.5866 74.8862 29.7092 74.8861 36.173 74.8861H36.373C37.7365 74.8861 38.8418 73.7808 38.8418 72.4173C38.8418 71.0539 37.7365 69.9486 36.373 69.9486C29.6642 69.9486 24.9007 69.9425 21.2874 69.4173C17.7661 68.9055 15.6987 67.9453 14.1631 66.2852C12.6022 64.5977 11.6796 62.2818 11.1943 58.3796C10.7025 54.4248 10.698 49.2277 10.698 41.9916V40.3013C10.698 35.7179 10.6998 31.9525 10.8261 28.8027L68.1739 28.8027C68.2766 31.3663 68.2977 34.3477 68.3013 37.8574V42.7923C68.3013 44.1558 69.4066 45.2611 70.7701 45.2611C72.1335 45.2611 73.2388 44.1558 73.2388 42.7923V37.8522C73.2322 31.5322 73.1767 26.5141 72.6033 22.541C72.0218 18.5124 70.8717 15.2605 68.4617 12.655C66.602 10.6446 64.3678 9.4243 61.7188 8.6735V6.58398C61.7188 5.22053 60.6135 4.11523 59.2501 4.11523C57.8866 4.11523 56.7813 5.22053 56.7813 6.58398V7.78839C53.0418 7.40681 48.4402 7.40685 42.8272 7.4069L36.173 7.4069C30.56 7.40685 25.9584 7.40681 22.2188 7.78839V6.58398ZM67.7999 23.8652C67.7735 23.6553 67.7457 23.449 67.7164 23.2463C67.2109 19.7436 66.3072 17.5971 64.837 16.0078C63.9834 15.0849 62.9654 14.3784 61.6247 13.8449C61.3302 14.8789 60.3786 15.6361 59.2501 15.6361C57.8866 15.6361 56.7813 14.5308 56.7813 13.1673V12.7545C53.2889 12.3495 48.7748 12.3444 42.6272 12.3444L36.373 12.3444C30.2254 12.3444 25.7113 12.3495 22.2188 12.7545V13.1673C22.2188 14.5308 21.1135 15.6361 19.7501 15.6361C18.6216 15.6361 17.67 14.8789 17.3755 13.8449C16.0348 14.3784 15.0168 15.0849 14.1631 16.0078C12.6086 17.6883 11.6872 19.9921 11.2003 23.8652L67.7999 23.8652Z" fill="#ED1C2B"/>
<path d="M52.7912 47.655C53.7415 48.6327 53.7193 50.1956 52.7416 51.1459C52.2097 51.6629 51.3634 52.3286 50.5578 52.9614L50.3703 53.1086C49.5615 53.7436 48.6965 54.4226 47.8781 55.1161L47.8546 55.1361L69.1251 55.1361C70.4885 55.1361 71.5938 56.2414 71.5938 57.6048C71.5938 58.9683 70.4885 60.0736 69.1251 60.0736L47.8546 60.0736L47.8781 60.0935C48.6965 60.787 49.5615 61.4661 50.3703 62.1011L50.5578 62.2483C51.3634 62.881 52.2097 63.5467 52.7416 64.0637C53.7193 65.014 53.7415 66.577 52.7912 67.5547C51.8409 68.5324 50.2779 68.5546 49.3002 67.6043C48.9998 67.3123 48.4014 66.833 47.5079 66.1312L47.3107 65.9764C46.5137 65.3508 45.5785 64.6167 44.6861 63.8605C43.7297 63.0501 42.7363 62.143 41.964 61.2362C41.577 60.7818 41.1953 60.2696 40.9004 59.7187C40.616 59.1872 40.323 58.455 40.323 57.6048C40.323 56.7546 40.616 56.0224 40.9004 55.491C41.1953 54.94 41.577 54.4278 41.964 53.9734C42.7363 53.0666 43.7297 52.1595 44.6861 51.3491C45.5786 50.5929 46.5136 49.859 47.3107 49.2333L47.5079 49.0784C48.4014 48.3767 48.9998 47.8973 49.3002 47.6054C50.2779 46.655 51.8409 46.6773 52.7912 47.655Z" fill="#ED1C2B"/>
<path d="M52.7912 47.6543C53.7415 48.632 53.7193 50.195 52.7416 51.1453C52.2097 51.6623 51.3634 52.328 50.5578 52.9607L50.3703 53.1079C49.5615 53.7429 48.6965 54.422 47.8781 55.1155L47.8546 55.1354L69.1251 55.1354C70.4885 55.1354 71.5938 56.2407 71.5938 57.6042C71.5938 58.9676 70.4885 60.0729 69.1251 60.0729L47.8546 60.0729L47.8781 60.0929C48.6965 60.7864 49.5615 61.4654 50.3703 62.1004L50.5578 62.2476C51.3634 62.8804 52.2097 63.546 52.7416 64.063C53.7193 65.0134 53.7415 66.5763 52.7912 67.554C51.8409 68.5317 50.2779 68.5539 49.3002 67.6036C48.9998 67.3117 48.4014 66.8323 47.5079 66.1306L47.3107 65.9757C46.5137 65.3501 45.5785 64.616 44.6861 63.8599C43.7297 63.0494 42.7363 62.1423 41.964 61.2356C41.577 60.7812 41.1953 60.269 40.9004 59.718C40.616 59.1866 40.323 58.4544 40.323 57.6042C40.323 56.754 40.616 56.0217 40.9004 55.4903C41.1953 54.9393 41.577 54.4272 41.964 53.9728C42.7363 53.066 43.7297 52.1589 44.6861 51.3485C45.5786 50.5923 46.5136 49.8583 47.3107 49.2326L47.5079 49.0778C48.4014 48.376 48.9998 47.8967 49.3002 47.6047C50.2779 46.6544 51.8409 46.6766 52.7912 47.6543Z" fill="#2E3039"/>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

@ -1,7 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="31.908" height="29.134" viewBox="0 0 31.908 29.134">
<g id="Group_8579" data-name="Group 8579" transform="translate(0 -100)">
<path id="Path_4556" data-name="Path 4556" d="M88.972,88.108h-4.4l-2.539-2.775H72.324v4.162H68.162v4.162H64V107.53a2.783,2.783,0,0,0,2.775,2.775h22.2a2.783,2.783,0,0,0,2.775-2.775V90.882A2.783,2.783,0,0,0,88.972,88.108Zm-11.1,18.035a6.937,6.937,0,1,1,6.937-6.937A6.939,6.939,0,0,1,77.873,106.143Z" transform="translate(-59.838 18.829)" fill="#2bb8a6"/>
<path id="Path_4557" data-name="Path 4557" d="M4.162,32.432H6.937V28.27H11.1V25.495H6.937V21.333H4.162v4.162H0V28.27H4.162Z" transform="translate(0 78.667)" fill="#2bb8a6"/>
<path id="Path_4558" data-name="Path 4558" d="M213.505,230.4a4.435,4.435,0,0,0-4.439,4.439h0a4.439,4.439,0,1,0,4.439-4.439Z" transform="translate(-195.47 -116.804)" fill="#2bb8a6"/>
</g>
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" rx="10" fill="#EFEFF0"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.9686 9.25H20.0314H20.0314C20.6355 9.24999 21.1356 9.24998 21.5485 9.28515C21.9812 9.32201 22.3723 9.40069 22.7494 9.58742C23.4214 9.92009 23.8461 10.4488 24.1443 10.979C24.3755 11.39 24.555 11.8518 24.7071 12.2432C24.743 12.3355 24.7774 12.424 24.8105 12.5069L24.9569 12.8728C25.0299 13.0554 25.0664 13.1466 25.1425 13.1982C25.2185 13.2498 25.317 13.2499 25.5141 13.2502C26.3986 13.2516 27.0637 13.2617 27.5921 13.3345C28.2753 13.4287 28.7694 13.6309 29.2829 14.0249C29.5429 14.2244 29.7756 14.4572 29.9751 14.7171C30.4019 15.2734 30.5828 15.922 30.6678 16.6762C30.75 17.4057 30.75 18.3204 30.75 19.4548V24.0549C30.75 25.4225 30.75 26.5248 30.6335 27.3918C30.5125 28.2919 30.2536 29.0497 29.6517 29.6517C29.0497 30.2536 28.2919 30.5125 27.3918 30.6335C26.5248 30.75 25.4225 30.75 24.0549 30.75L15.9451 30.75C14.5775 30.75 13.4752 30.75 12.6083 30.6335C11.7081 30.5125 10.9503 30.2536 10.3484 29.6517C9.74644 29.0497 9.48754 28.2919 9.36653 27.3918C9.24997 26.5248 9.24998 25.4225 9.25 24.0549V24.0549L9.25 19.4548V19.4548C9.24999 18.3204 9.24998 17.4057 9.33222 16.6762C9.41725 15.922 9.59808 15.2734 10.0249 14.7171C10.2244 14.4572 10.4572 14.2244 10.7171 14.0249C11.2306 13.6309 11.7247 13.4287 12.4079 13.3345C12.9363 13.2617 13.6014 13.2516 14.4859 13.2502C14.683 13.2499 14.7815 13.2498 14.8575 13.1982C14.9336 13.1466 14.9701 13.0554 15.0431 12.8728L15.1895 12.5069C15.2226 12.424 15.257 12.3355 15.2929 12.2432C15.445 11.8518 15.6245 11.39 15.8557 10.979C16.1539 10.4488 16.5787 9.92009 17.2506 9.58742C17.6278 9.40069 18.0188 9.32201 18.4515 9.28515C18.8644 9.24998 19.3645 9.24999 19.9686 9.25H19.9686ZM16 22C16 19.7909 17.7909 18 20 18C22.2091 18 24 19.7909 24 22C24 24.2091 22.2091 26 20 26C17.7909 26 16 24.2091 16 22ZM19.9955 13C19.4457 13 19 13.4477 19 14C19 14.5502 19.4567 15 20.0045 15C20.5543 15 21 14.5523 21 14C21 13.4498 20.5433 13 19.9955 13Z" fill="#8F9AA3"/>
</svg>

Before

Width:  |  Height:  |  Size: 910 B

After

Width:  |  Height:  |  Size: 2.0 KiB

@ -301,6 +301,12 @@
"medicinesSubtitle": "الوصفات",
"vitalSigns": "علامات حيوية",
"vitalSignsSubTitle": "التقارير",
"vitalSignNormal": "طبيعي",
"vitalSignLow": "منخفض",
"vitalSignHigh": "مرتفع",
"vitalSignObese": "بدين",
"vitalSignOverweight": "زيادة الوزن",
"vitalSignUnderweight": "نقص الوزن",
"myMedical": "نشط",
"myMedicalSubtitle": "الأدوية",
"myDoctor": "أطبائي",
@ -694,7 +700,7 @@
"retouch": "روتوش",
"bikini": "بيكيني",
"totalMinutes": "إجمالي الدقائق",
"feedback": "ملاحظات",
"feedback": "رأيك يهمنا",
"likeToHear": "نود سماع ملاحظاتك، ومخاوفك بشأن خدمات الرعاية الصحية وتجربة الخدمات الإلكترونية. يرجى استخدام النموذج أدناه",
"emptySubject": "يرجى إدخال الموضوع",
"emptyMessage": "يرجى إدخال الرسالة",
@ -702,10 +708,10 @@
"complainAppo": "شكوى بخصوص الموعد",
"complainAppoAlt": "شكوى بدون موعد",
"messageType": "نوع الرسالة",
"feedbackType": "نوع الملاحظات",
"feedbackType": "نوع الرسالة",
"compliment": "تقدير",
"suggestion": "اقتراح",
"yourFeedback": "تم إرسال ملاحظاتك",
"yourFeedback": "تم إرسال رأيك",
"selectPart": "يرجى اختيار الجزء الذي تشتكي منه",
"number": "رقم",
"notClassified": "غير مصنف",
@ -717,6 +723,11 @@
"bloodDonation": "التبرع بالدم",
"bloodDonationInfo": "من خلال هذه الخدمة، يمكنك تسجيل اسمك كمتبرع بالدم حيث سيتواصل معك بنك الدم في مجموعة الحبيب الطبية في حالة الحاجة إلى فصيلة الدم.",
"bloodInstruction": "أدخل المعلومات المطلوبة، من أجل التسجيل في خدمة التبرع بالدم",
"aboutBloodDonation": "عن التبرع بالدم",
"bloodDonationDescription": "التبرع بالدم هو عمل نبيل ينقذ الأرواح. كل تبرع واحد يمكن أن ينقذ ما يصل إلى ثلاثة أشخاص. من خلال التبرع بالدم، فإنك تساعد المرضى الذين يحتاجون إلى عمليات نقل دم، وحالات الطوارئ، والعلاجات الطبية.",
"bloodDonationBenefit1": "عملية آمنة وسريعة (10-15 دقيقة)",
"bloodDonationBenefit2": "يمكنك التبرع كل 8-12 أسبوع",
"bloodDonationBenefit3": "فحص صحي مجاني لدمك",
"viewTerms": "لعرض الشروط والأحكام",
"wantConnectHmgNetwork": "عزيزي العميل، لا يوجد وصول إلى الإنترنت، هل ترغب في الاتصال بشبكة مجموعة الحبيب الطبية لاستخدام تطبيقنا، تأكد من أنك في نطاق شبكة مجموعة الحبيب الطبية",
"failedToAccessHmgServices": "متصل بشبكة مجموعة الحبيب الطبية،\n\nلكن فشل في الوصول إلى خدمات مجموعة الحبيب الطبية",
@ -936,7 +947,7 @@
"hospitalInformation": "معلومات المستشفى",
"fetchingAppointmentShare": "جاري جلب تفاصيل الموعد...",
"bookingYourAppointment": "جاري حجز موعدك...",
"selectLiveCareClinic": "اختر عيادة لايف كير",
"selectLiveCareClinic": "عيادة لايف كير",
"checkingForExistingDentalPlan": "جاري التحقق من وجود خطة أسنان حالية، يرجى الانتظار...",
"dentalTreatmentPlan": "خطة علاج الأسنان",
"youHaveExistingTreatmentPlan": "لديك خطة علاج حالية: ",
@ -1249,7 +1260,7 @@
"quickLinks": "روابط سريعة",
"viewMedicalFileLandingPage": "عرض الملف الطبي",
"immediateLiveCareRequest": "طلب لايف كير فوري",
"yourTurnIsAfterPatients": "دورك بعد {count} مريض.",
"yourTurnIsAfterPatients": "دورك بعد {count} مريض. يرجى الانتظار حتى يحين دورك، وسيناديك الطبيب قريباً.",
"dontHaveHHCOrders": "ليس لديك أي أوامر رعاية صحية منزلية حتى الآن.",
"hhcOrders": "أوامر الرعاية الصحية المنزلية",
"requestedServices": "الخدمات المطلوبة",
@ -1578,7 +1589,7 @@
"noFreeSlot": "لا توجد مواعيد متاحة في التاريخ المحدد. يرجى اختيار تاريخ آخر أو إعادة المحاولة لاحقاً.",
"noThanksIKnowTheClinic": "لا، شكراً. أنا أعرف العيادة.",
"unableToSendOTP": "تعذر إرسال رمز التحقق لمرة واحدة",
"loadingAIAnalysis": "جارٍ تحليل نتائج المختبر، يرجى التحلي بالصبر والسماح للذكاء الاصطناعي بالقيام بالمهمة، قد يستغرق هذا بعض الوقت.",
"loadingAIAnalysis": "نقوم بتحليل نتائجك، يرجى التحلي بالصبر والسماح للذكاء الاصطناعي بالقيام بالمهمة، قد يستغرق هذا بعض الوقت.",
"symptoms": "أعراض",
"insuranceInActive": "التأمين غير نشط",
"insuranceInActiveContactSupport": "لديك تأمين ساري المفعول، ولكنه غير مُفعّل حاليًا في مجموعة حبيب الطبية. يُرجى التواصل مع خدمة عملاء مجموعة حبيب الطبية.",
@ -1653,5 +1664,61 @@
"oneWaySubtitle": "نقل من الموقع إلى المستشفى",
"twoWaySubtitle": "نقل من الموقع إلى المستشفى والعودة مرة أخرى",
"toHospitalSubtitle": "نقل من موقعك الحالي إلى المستشفى",
"fromHospitalSubtitle": "نقل من المستشفى إلى منزلك"
"fromHospitalSubtitle": "نقل من المستشفى إلى منزلك",
"clinicSchedule": "جدول العيادة",
"doctorAddedToFavourite": "تمت إضافة إلى قائمة المفضلة",
"doctorRemovedFromFavourite": "تمت إزالة من قائمة الأطباء المفضلين",
"addNewFavorite": "إضافة مفضلة جديدة",
"favourites": "المفضلة",
"pendingAncillaryOrders": "مدفوعات طلبات الاضافية",
"liveCarePermissions": "يتطلب لايف كير أذونات الكاميرا والميكروفون وإشعارات، يرجى السماح لها بالمتابعة.",
"containsImage": "يحتوي على صورة",
"servicesSelected": "الخدمة (الخدمات) المختارة",
"selectMedicalFileFromFamily": "اختر ملف طبي من عائلتك",
"anyFileFromHMG": "أي ملف طبي نشط من مجموعة حبيب الطبية",
"noDistance": "المسافة غير متوفرة",
"enterAmount": "يرجى إدخال المبلغ للمتابعة.",
"selectHospitalContinue": "يرجى اختيار المستشفى للمتابعة.",
"year": "سنة",
"notifyPrescription": "أبلغني قبل وقت الاستهلاك",
"doctorRatingAppointment": "طبيب\nتصنيف",
"ratingSubmitted": "تم إرسال التقييم",
"rateTheDoctor": "يرجى تقييم الطبيب",
"downloadInvoice": "تحميل الفاتورة",
"approvalDetails": "تفاصيل الموافقة",
"obese": "سمين",
"overWeight": "وزن زائد",
"underWeight": "وزن ناقص",
"nearestGate": "أقرب بوابة",
"RRTDDetails": "فريق الاستجابة السريعة يقوم بتقديم رعاية صحية متكاملة لجميع الحالات الطارئة و المستقرة، الخدمة تشمل جميع الأعمار بما في ذلك حالات الأطفال.",
"fromHospitalSubtitle": "نقل من المستشفى إلى منزلك",
"normalMapType": "افتراضي",
"satelliteMapType": "قمر صناعي",
"terrainMapType": "تضاريس",
"hybridMapType": "هجين",
"openCamera": "افتح الكاميرا",
"openGallery": "افتح المعرض",
"openFiles": "فتح الملفات",
"generateAiAnalysisRadResult": "بإنشاء تحليل الذكاء الاصطناعي لهذا التقرير",
"grantLocationPermission": "يرجى منح إذن الوصول إلى الموقع من إعدادات التطبيق للحصول على نتائج أفضل.",
"NoBookedAppointments": "لا توجد مواعيد محجوزة",
"aiDisclaimerRad": "سيتم مشاركة بيانات نتائج الأشعة الخاصة بك بشكل آمن مع محلل الذكاء الاصطناعي لدينا لتحليلها. يساعد هذا في توفير رؤى صحية مخصصة. هل ترغب في المتابعة؟",
"imageSizeTooLarge": "حجم الصورة كبير جداً. الحد الأقصى للحجم هو 1 ميجابايت",
"profileImageUpdatedSuccessfully": "تم تحديث صورة الملف الشخصي بنجاح",
"failedToProcessImage": "فشل في معالجة الصورة. يرجى المحاولة مرة أخرى",
"cameraGalleryPermissionMessage": "مطلوب إذن الكاميرا والمعرض لتحميل صورة الملف الشخصي. يرجى منح الأذونات من إعدادات التطبيق.",
"cameraGalleryPermissionRequired": "مطلوب أذونات الكاميرا والمعرض",
"cameraPermissionMessage": "مطلوب إذن الكاميرا لالتقاط الصور. يرجى منح الإذن من إعدادات التطبيق.",
"cameraPermissionRequired": "مطلوب إذن الكاميرا",
"galleryPermissionMessage": "مطلوب إذن المعرض لتحديد الصور. يرجى منح الإذن من إعدادات التطبيق.",
"galleryPermissionRequired": "مطلوب إذن المعرض",
"failedToCheckPermissions": "فشل في التحقق من الأذونات. يرجى المحاولة مرة أخرى",
"takePhotoWithCamera": "التقط صورة بالكاميرا",
"selectFromPhotoLibrary": "اختر من مكتبة الصور",
"selectDocumentFiles": "اختر ملفات المستندات",
"profilePicture": "صورة الملف الشخصي",
"aiDisclaimerRad": "سيتم مشاركة بيانات نتائج الأشعة الخاصة بك بشكل آمن مع محلل الذكاء الاصطناعي لدينا لتحليلها. يساعد هذا في توفير رؤى صحية مخصصة. هل ترغب في المتابعة؟",
"sendingAskDrRequest": "جارٍ إرسال الطلب...",
"askDrRequestSentSuccess": "تم إرسال الطلب بنجاح، وسيتواصل معك الطبيب قريباً.",
"onlineCheckIn24HoursOnly": "لا تتوفر خدمة تسجيل الوصول عبر الإنترنت إلا خلال 4 ساعات من موعد الحجز.لا تتوفر خدمة تسجيل الوصول عبر الإنترنت إلا خلال 4 ساعات من موعد الحجز."
}

@ -300,6 +300,12 @@
"medicinesSubtitle": "Prescriptions",
"vitalSigns": "Vital Signs",
"vitalSignsSubTitle": "Reports",
"vitalSignNormal": "Normal",
"vitalSignLow": "Low",
"vitalSignHigh": "High",
"vitalSignObese": "Obese",
"vitalSignOverweight": "Overweight",
"vitalSignUnderweight": "Underweight",
"myMedical": "Active",
"myMedicalSubtitle": "Medications",
"myDoctor": "My Doctors",
@ -710,6 +716,11 @@
"bloodDonation": "Blood Donation",
"bloodDonationInfo": "Through this service, you can register your name as a blood donor where the blood bank in the Habib Medical Group will communicate you in case of need for blood type.",
"bloodInstruction": "Enter the required information, in order to register for Blood Donation Service",
"aboutBloodDonation": "About Blood Donation",
"bloodDonationDescription": "Blood donation is a noble act that saves lives. Every single donation can save up to three people. By donating blood, you help patients in need of transfusions, emergencies, and medical treatments.",
"bloodDonationBenefit1": "Safe and quick process (10-15 minutes)",
"bloodDonationBenefit2": "You can donate every 8-12 weeks",
"bloodDonationBenefit3": "Free health check of your blood",
"viewTerms": "To view the terms and conditions",
"wantConnectHmgNetwork": "Dear customer there is no internet access, do you want to connect with HMG network to use our app, make sure you are in range of HMG network",
"failedToAccessHmgServices": "Connected with HMG Network,\n\nBut failed to access HMG services",
@ -930,7 +941,7 @@
"hospitalInformation": "Hospital Information",
"fetchingAppointmentShare": "Fetching Appointment Share...",
"bookingYourAppointment": "Booking your appointment...",
"selectLiveCareClinic": "Select LiveCare Clinic",
"selectLiveCareClinic": "LiveCare Clinic",
"checkingForExistingDentalPlan": "Checking for an existing dental plan, Please wait...",
"dentalTreatmentPlan": "Dental treatment plan",
"youHaveExistingTreatmentPlan": "You have an existing treatment plan: ",
@ -1241,7 +1252,7 @@
"quickLinks": "Quick Links",
"viewMedicalFileLandingPage": "View medical file",
"immediateLiveCareRequest": "Immediate LiveCare Request",
"yourTurnIsAfterPatients": "Your turn is after {count} patients.",
"yourTurnIsAfterPatients": "Your turn is after {count} patients. Please wait for your turn, The doctor will call you shortly.",
"dontHaveHHCOrders": "You don't have any Home Health Care orders yet.",
"hhcOrders": "HHC Orders",
"requestedServices": "Requested Services",
@ -1309,7 +1320,7 @@
"rejectedYourFamilyMemberRequest": "{status} your family member request",
"notAvailable": "N/A",
"selectAProfile": "Please select a profile",
"switchFamilyFile": "Switch from the below list of medical file",
"switchFamilyFile": "Switch from the below list of medical files",
"medicalFiles": "Medical Files",
"addANewFamilyMember": "Add a new family member",
"viewInvoiceDetails": "View invoice details",
@ -1572,7 +1583,7 @@
"contextDependent": "Context Dependent",
"calculatedValue": "Calculated Value",
"calculationFormula": "Calculation Formula",
"loadingAIAnalysis": "Analysing your lab results, Please be patient and let the AI do the magic, This might take some time.",
"loadingAIAnalysis": "Analysing your results, Please be patient and let the AI do the magic, This might take some time.",
"symptoms": "Symptoms",
"insuranceInActive": "Insurance Inactive",
"insuranceInActiveContactSupport": "You have a valid insurance but it is currently inactive in HMG. Please contact HMG customer support to activate it.",
@ -1645,5 +1656,61 @@
"oneWaySubtitle": "Pickup from location to hospital",
"twoWaySubtitle": "Round trip from location to hospital and back",
"toHospitalSubtitle": "Transfer from your current location to the hospital",
"fromHospitalSubtitle": "Transfer from the hospital back to your home"
"fromHospitalSubtitle": "Transfer from the hospital back to your home",
"clinicSchedule": "Clinic Schedule",
"doctorAddedToFavourite": "Added to favorites list",
"doctorRemovedFromFavourite": "Removed from favorites list",
"favourites": "Favourites",
"addNewFavorite": "Add new favourite",
"pendingAncillaryOrders": "Pending ancillary orders",
"liveCarePermissions": "LiveCare requires Camera, Microphone & Notifications permissions to enable virtual consultation between patient & doctor, Open app settings to allow them.",
"containsImage": "Contains Image",
"servicesSelected": "Service(s) Selected",
"selectMedicalFileFromFamily": "Select a medical file from your family",
"anyFileFromHMG": "Any active medical file from HMG",
"noDistance": "Distance not available",
"enterAmount": "Please enter amount to continue.",
"selectHospitalContinue": "Please select hospital to continue.",
"year": "Year",
"notifyPrescription": "Notify me before the consumption time",
"doctorRatingAppointment": "Doctor\nRating",
"ratingSubmitted": "Rating Submitted",
"rateTheDoctor": "Please rate the doctor",
"downloadInvoice": "Download Invoice",
"approvalDetails": "Approval Details",
"obese": "Obese",
"overWeight": "OverWeight",
"underWeight": "UnderWeight",
"nearestGate": "Nearest Gate",
"RRTDDetails": "The Rapid response team provides a comprehensive medical service for all sorts of urgent and stable cases of all ages, including adult and paediatric patients.",
"fromHospitalSubtitle": "Transfer from the hospital back to your home",
"normalMapType": "Default",
"satelliteMapType": "Satellite",
"terrainMapType": "Terrain",
"hybridMapType": "Hybrid",
"openCamera": "Open Camera",
"openGallery": "Open Gallery",
"openFiles": "Open Files",
"generateAiAnalysisRadResult": "Generate AI analysis for this result",
"grantLocationPermission": "Please grant location permission from app settings to see better results.",
"NoBookedAppointments": "No Booked Appointments",
"aiDisclaimerRad": "Your radiology result data will be securely shared with our AI Analyzer for analysis. This helps provide personalized health insights. Do you want to proceed?",
"imageSizeTooLarge": "Image size is too large. Maximum size is 1MB",
"profileImageUpdatedSuccessfully": "Profile image updated successfully",
"failedToProcessImage": "Failed to process image. Please try again",
"cameraGalleryPermissionMessage": "Camera and Gallery permissions are required to upload profile picture. Please grant permissions from app settings.",
"cameraGalleryPermissionRequired": "Camera and Gallery permissions are required",
"cameraPermissionMessage": "Camera permission is required to take photos. Please grant permission from app settings.",
"cameraPermissionRequired": "Camera permission is required",
"galleryPermissionMessage": "Gallery permission is required to select photos. Please grant permission from app settings.",
"galleryPermissionRequired": "Gallery permission is required",
"failedToCheckPermissions": "Failed to check permissions. Please try again",
"takePhotoWithCamera": "Take a photo with your camera",
"selectFromPhotoLibrary": "Select from your photo library",
"selectDocumentFiles": "Select document files",
"profilePicture": "Profile Picture",
"aiDisclaimerRad": "Your radiology result data will be securely shared with our AI Analyzer for analysis. This helps provide personalized health insights. Do you want to proceed?",
"sendingAskDrRequest": "Sending Request...",
"askDrRequestSentSuccess": "Request has been sent successfully, The doctor will get back to you soon.",
"onlineCheckIn24HoursOnly": "Online Check-In is only available within 4 hours of the appointment time."
}

@ -142,6 +142,11 @@ PODS:
- flutter_callkit_incoming (0.0.1):
- CryptoSwift
- Flutter
- flutter_image_compress_common (1.0.0):
- Flutter
- Mantle
- SDWebImage
- SDWebImageWebPCoder
- flutter_inappwebview_ios (0.0.1):
- Flutter
- flutter_inappwebview_ios/Core (= 0.0.1)
@ -242,6 +247,18 @@ PODS:
- just_audio (0.0.1):
- Flutter
- FlutterMacOS
- libwebp (1.5.0):
- libwebp/demux (= 1.5.0)
- libwebp/mux (= 1.5.0)
- libwebp/sharpyuv (= 1.5.0)
- libwebp/webp (= 1.5.0)
- libwebp/demux (1.5.0):
- libwebp/webp
- libwebp/mux (1.5.0):
- libwebp/demux
- libwebp/sharpyuv (1.5.0)
- libwebp/webp (1.5.0):
- libwebp/sharpyuv
- local_auth_darwin (0.0.1):
- Flutter
- FlutterMacOS
@ -249,6 +266,9 @@ PODS:
- Flutter
- manage_calendar_events (0.0.1):
- Flutter
- Mantle (2.2.0):
- Mantle/extobjc (= 2.2.0)
- Mantle/extobjc (2.2.0)
- map_launcher (0.0.1):
- Flutter
- MapboxCommon (23.11.0)
@ -296,9 +316,12 @@ PODS:
- PromisesObjC (2.4.0)
- PromisesSwift (2.4.0):
- PromisesObjC (= 2.4.0)
- SDWebImage (5.21.7):
- SDWebImage/Core (= 5.21.7)
- SDWebImage/Core (5.21.7)
- SDWebImage (5.21.5):
- SDWebImage/Core (= 5.21.5)
- SDWebImage/Core (5.21.5)
- SDWebImageWebPCoder (0.15.0):
- libwebp (~> 1.0)
- SDWebImage/Core (~> 5.17)
- share_plus (0.0.1):
- Flutter
- shared_preferences_foundation (0.0.1):
@ -342,6 +365,7 @@ DEPENDENCIES:
- FLAnimatedImage
- Flutter (from `Flutter`)
- flutter_callkit_incoming (from `.symlinks/plugins/flutter_callkit_incoming/ios`)
- flutter_image_compress_common (from `.symlinks/plugins/flutter_image_compress_common/ios`)
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
- flutter_ios_voip_kit_karmm (from `.symlinks/plugins/flutter_ios_voip_kit_karmm/ios`)
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
@ -395,6 +419,8 @@ SPEC REPOS:
- GoogleDataTransport
- GoogleMaps
- GoogleUtilities
- libwebp
- Mantle
- MapboxCommon
- MapboxCoreMaps
- MapboxCoreNavigation
@ -411,6 +437,7 @@ SPEC REPOS:
- PromisesObjC
- PromisesSwift
- SDWebImage
- SDWebImageWebPCoder
- Solar-dev
- SwiftProtobuf
- SwiftyGif
@ -446,6 +473,8 @@ EXTERNAL SOURCES:
:path: Flutter
flutter_callkit_incoming:
:path: ".symlinks/plugins/flutter_callkit_incoming/ios"
flutter_image_compress_common:
:path: ".symlinks/plugins/flutter_image_compress_common/ios"
flutter_inappwebview_ios:
:path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
flutter_ios_voip_kit_karmm:
@ -532,6 +561,7 @@ SPEC CHECKSUMS:
FLAnimatedImage: bbf914596368867157cc71b38a8ec834b3eeb32b
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
flutter_callkit_incoming: 417dd1b46541cdd5d855ad795ccbe97d1c18155e
flutter_image_compress_common: ec1d45c362c9d30a3f6a0426c297f47c52007e3e
flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4
flutter_ios_voip_kit_karmm: 7ea37381a8841c92d186edf1f4604df5cc437579
flutter_local_notifications: ff50f8405aaa0ccdc7dcfb9022ca192e8ad9688f
@ -550,9 +580,11 @@ SPEC CHECKSUMS:
image_picker_ios: 4f2f91b01abdb52842a8e277617df877e40f905b
in_app_review: 436034b18594851a7328d7f1c2ed5ec235b79cfc
just_audio: a42c63806f16995daf5b219ae1d679deb76e6a79
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
local_auth_darwin: 63c73d6d28cc3e239be2b6aa460ea6e317cd5100
location: d5cf8598915965547c3f36761ae9cc4f4e87d22e
manage_calendar_events: 9b2889799340398027b3e3f5c4891d41599ec257
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
map_launcher: 8a4e484aaddff55680a7da9bc9e25f512067fb87
MapboxCommon: 119f3759f7dc9457f0695848108ab323eb643cb4
MapboxCoreMaps: ca17f67baced23f8c952166ac6314c35bad3f66c
@ -569,17 +601,18 @@ SPEC CHECKSUMS:
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba
PayFortSDK: e12f79051ae15b2e29536771a45354900180bfcb
PayFortSDK: 233eabe9a45601fdbeac67fa6e5aae46ed8faf82
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
Polyline: 2a1f29f87f8d9b7de868940f4f76deb8c678a5b1
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf
SDWebImage: e9c98383c7572d713c1a0d7dd2783b10599b9838
SDWebImageWebPCoder: 0e06e365080397465cc73a7a9b472d8a3bd0f377
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6
Solar-dev: 4612dc9878b9fed2667d23b327f1d4e54e16e8d0
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
SwiftProtobuf: 9e106a71456f4d3f6a3b0c8fd87ef0be085efc38
SwiftProtobuf: e1b437c8e31a4c5577b643249a0bb62ed4f02153
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
Turf: aa2ede4298009639d10db36aba1a7ebaad072a5e
url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa

@ -89,6 +89,8 @@
<string>This app requires motion detection access to function properly.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app requires photo library access to select image as document &amp; upload it.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>This app requires photo library access to upload profile picture and documents.</string>
<key>NSRemindersUsageDescription</key>
<string>This app requires calendar access to set reminders for Virtual &amp; Normal Appointments.</string>
<key>NSSpeechRecognitionUsageDescription</key>

Binary file not shown.

@ -25,6 +25,7 @@ class ApiConsts {
static String GET_TAMARA_PAYMENT_STATUS = 'https://mdlaboratories.com/tamaralive/api/OnlineTamara/order_status?orderid=';
static String QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail";
// static String QLINE_URL = "https://qline.hmg.com/api/PatientCall/PatientInQueue_Detail";
static String CHAT_URL = "https://chat.hmg.com/Index.aspx?RequestedId=";
@ -44,7 +45,7 @@ class ApiConsts {
GET_TAMARA_INSTALLMENTS_URL = "https://mdlaboratories.com/tamaralive/Home/GetInstallments";
GET_TAMARA_PAYMENT_STATUS = 'https://mdlaboratories.com/tamaralive/api/OnlineTamara/order_status?orderid=';
rcBaseUrl = 'https://rc.hmg.com/';
QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail";
QLINE_URL = "https://qline.hmg.com/api/PatientCall/PatientInQueue_Detail";
CHAT_URL = "https://chat.hmg.com/Index.aspx?RequestedId=";
break;
case AppEnvironmentTypeEnum.dev:
@ -80,7 +81,7 @@ class ApiConsts {
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=';
rcBaseUrl = 'https://rc.hmg.com/';
QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail";
QLINE_URL = "https://qline.hmg.com/api/PatientCall/PatientInQueue_Detail";
CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId=";
break;
case AppEnvironmentTypeEnum.qa:
@ -230,8 +231,18 @@ class ApiConsts {
static String getPatientBloodGroup = "services/PatientVarification.svc/REST/BloodDonation_GetBloodGroupDetails";
static String getPatientBloodAgreement = "Services/PatientVarification.svc/REST/CheckUserAgreementForBloodDonation";
static String getPatientBloodTypeNew = "Services/Patients.svc/REST/HIS_GetPatientBloodType_New";
static String getAiOverViewLabOrders = "Services/Patients.svc/REST/HMGAI_Lab_Analyze_Orders_API";
static String getAiOverViewLabOrder = "Services/Patients.svc/REST/HMGAI_Lab_Analyzer_API";
// static String getAiOverViewLabOrders = "Services/Patients.svc/REST/HMGAI_Lab_Analyze_Orders_API";
// static String getAiOverViewLabOrder = "Services/Patients.svc/REST/HMGAI_Lab_Analyzer_API";
static String getAiAnalysisURL = "https://api.labpredict.com";
static String getAiRadiologyAnalysis = "$getAiAnalysisURL/radiology";
static String getAiOverViewLabOrdersNew = "$getAiAnalysisURL/analyze_lab_orders";
static String getAiOverViewLabOrderNew = "$getAiAnalysisURL/analyze";
static String googleCloudStorageARTranslationFileURL = "https://storage.googleapis.com/hmg-patientapp-translations/ar-SA.json";
static String googleCloudStorageENTranslationFileURL = "https://storage.googleapis.com/hmg-patientapp-translations/en-US.json";
static String googleCloudStorageENTranslationFileBaseURL = "https://storage.googleapis.com/hmg-patientapp-translations";
// ************ static values for Api ****************
static final double appVersionID = 20.9;
@ -450,6 +461,7 @@ var IS_LAST_APPOITMENT_RATED = 'Services/Doctors.svc/REST/IsLastAppoitmentRated_
var GET_APPOINTMENT_DETAILS_BY_NO = 'Services/MobileNotifications.svc/REST/GetAppointmentDetailsByApptNo';
var NEW_RATE_APPOINTMENT_URL = "Services/Doctors.svc/REST/AppointmentsRating_InsertAppointmentRate";
var NEW_RATE_DOCTOR_URL = "Services/Doctors.svc/REST/DoctorsRating_InsertDoctorRate";
var GET_DOCTORS_RATING_CHECK = "Services/Doctors.svc/REST/GetDoctorsRatingCheck";
//var GET_QR_PARKING = 'Services/SWP.svc/REST/GetQRParkingByID';
@ -464,7 +476,8 @@ var GET_ACTIVE_APPOINTMENTS_LIST_URL = "Services/Doctors.svc/Rest/Dr_GetAppointm
var GET_DOCTORS_LIST_URL = "Services/Doctors.svc/REST/SearchDoctorsByTime";
//URL to dental doctors list
var GET_DENTAL_DOCTORS_LIST_URL = "Services/Doctors.svc/REST/Dental_DoctorChiefComplaintMapping";
// var GET_DENTAL_DOCTORS_LIST_URL = "Services/Doctors.svc/REST/Dental_DoctorChiefComplaintMapping";
var GET_DENTAL_DOCTORS_LIST_URL = "Services/Doctors.svc/REST/Dental_DoctorChiefComplaintMapping_Nearest";
//URL to get doctor free slots
var GET_DOCTOR_FREE_SLOTS = "Services/Doctors.svc/REST/GetDoctorFreeSlots";
@ -815,8 +828,8 @@ var FILTERED_PRODUCTS = 'products?categoryids=';
var GET_DOCTOR_LIST_CALCULATION = "Services/Doctors.svc/REST/GetCallculationDoctors";
var GET_ALL_APPOINTMENTS_FOR_DENTAL_CLINIC = "Services/Patients.svc/REST/GetDentalAppointments";
// var GET_ALL_APPOINTMENTS_FOR_DENTAL_CLINIC = "Services/Patients.svc/REST/GetDentalAppointments";
var GET_ALL_APPOINTMENTS_FOR_DENTAL_CLINIC ='Services/Patients.svc/REST/GetAllInvoices';
var GET_DENTAL_APPOINTMENT_INVOICE = "Services/Patients.svc/REST/HIS_eInvoiceForDentalByAppointmentNo";
var SEND_DENTAL_APPOINTMENT_INVOICE_EMAIL = "Services/Notifications.svc/REST/SendInvoiceForDental";
@ -934,6 +947,12 @@ const PROFILE_SETTING = 'Services/Patients.svc/REST/GetPateintInfoForUpdate';
const SAVE_SETTING = 'Services/Patients.svc/REST/UpdatePateintInfo';
const GET_PROFILE_IMAGE = 'Services/Patients.svc/REST/Patient_GetProfileImageData';
const UPLOAD_PROFILE_IMAGE = 'Services/Patients.svc/REST/Patient_InsertProfileImageData';
const DOWNLOAD_INVOICE_PDF = 'Services/Notifications.svc/REST/DownloadInvoiceReport';
class ApiKeyConstants {
static final String googleMapsApiKey = 'AIzaSyB6TERnxIr0yJ3qG4ULBZbu0sAD4tGqtng';
}

@ -220,6 +220,7 @@ class AppAssets {
static const String ibw = '$svgBasePath/ibw.svg';
static const String general_health = '$svgBasePath/general_health.svg';
static const String women_health = '$svgBasePath/women_health.svg';
static const String add_new_family_icon = '$svgBasePath/add_new_family_icon.svg';
static const String height = '$svgBasePath/height.svg';
static const String weight = '$svgBasePath/weight.svg';
@ -236,6 +237,7 @@ class AppAssets {
static const String forward_top_nav_icon = '$svgBasePath/forward_top_nav_icon.svg';
static const String back_top_nav_icon = '$svgBasePath/back_top_nav_icon.svg';
static const String bluetooth = '$svgBasePath/bluetooth.svg';
static const String calendar_filled_icon = '$svgBasePath/calendar_filled_icon.svg';
//smartwatch
static const String watchActivity = '$svgBasePath/watch_activity.svg';
@ -354,6 +356,12 @@ class AppAssets {
static const String darkModeIcon = '$svgBasePath/dark_mode_icon.svg';
static const String biometricLockIcon = '$svgBasePath/biometric_lock_icon.svg';
static const String imageIcon = '$svgBasePath/image_icon.svg';
static const String appointmentRatingIcon = '$svgBasePath/appointment_rating_icon.svg';
static const String invoiceListIcon = '$svgBasePath/invoice_list_icon.svg';
static const String open_camera = '$svgBasePath/open_camera.svg';
static const String gallery = '$svgBasePath/gallery.svg';
static const String files = '$svgBasePath/files.svg';
static const String online_checkin_icon = '$svgBasePath/online_checkin_icon.svg';
// PNGS //
static const String hmgLogo = '$pngBasePath/hmg_logo.png';
@ -392,6 +400,7 @@ class AppAssets {
static const String fullBodyFrontFemale = '$pngBasePath/full_body_front_female.png';
static const String fullBodyBackFemale = '$pngBasePath/full_body_back_female.png';
static const String bmiFullBody = '$pngBasePath/bmi_image_1.png';
static const String defaultMEP = '$pngBasePath/default_MEP.png';
}
class AppAnimations {
@ -412,4 +421,6 @@ class AppAnimations {
static const String ambulance = '$lottieBasePath/ambulance.json';
static const String ambulanceAlert = '$lottieBasePath/ambulance_alert.json';
static const String rrtAmbulance = '$lottieBasePath/rrt_ambulance.json';
static const String hourGlass = '$lottieBasePath/HourGlass.json';
}

@ -10,14 +10,19 @@ import 'package:hmg_patient_app_new/features/authentication/models/resp_models/a
import 'package:hmg_patient_app_new/features/authentication/models/resp_models/check_user_staus_nhic_response_model.dart';
import 'package:hmg_patient_app_new/features/authentication/models/resp_models/select_device_by_imei.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart';
import '../features/authentication/models/request_models/registration_payload_model.dart';
class AppState {
NavigationService navigationService;
CacheService cacheService;
AppState({required this.navigationService}){
static const String _profileImageKey = 'profile_image_data';
AppState({required this.navigationService, required this.cacheService}){
checkIfGMSAvailableOrNot();
_loadProfileImageFromCache();
}
double userLat = 0.0;
@ -84,6 +89,31 @@ class AppState {
set setUserBloodGroup(String value) => _userBloodGroup = value;
String? _profileImageData;
String? get getProfileImageData => _profileImageData;
set setProfileImageData(String? value) {
_profileImageData = value;
// Persist to cache
if (value != null && value.isNotEmpty) {
cacheService.saveString(key: _profileImageKey, value: value);
} else {
cacheService.remove(key: _profileImageKey);
}
}
/// Load profile image from cache on app initialization
void _loadProfileImageFromCache() {
_profileImageData = cacheService.getString(key: _profileImageKey);
}
/// Clear profile image from cache (e.g., on logout)
void clearProfileImageCache() {
_profileImageData = null;
cacheService.remove(key: _profileImageKey);
}
SelectDeviceByImeiRespModelElement? _selectDeviceByImeiRespModelElement;
void setSelectDeviceByImeiRespModelElement(SelectDeviceByImeiRespModelElement? value) {
@ -175,4 +205,92 @@ class AppState {
setRatedVisible(bool value) {
isRatedVisible = value;
}
Map<String, dynamic> _userSwitchPayloads = {};
int _childSwitchCounter = 0;
Map<String, dynamic> get getUserSwitchPayloads => _userSwitchPayloads;
void addUserSwitchPayload(dynamic payload, {bool isSuperUser = false}) {
var patientID = payload is Map ? payload['patientid'] : null;
if (patientID == null) {
if (isSuperUser) {
_userSwitchPayloads['superUser'] = payload;
_childSwitchCounter = 0;
} else {
_childSwitchCounter++;
if (_childSwitchCounter == 1) {
_userSwitchPayloads['child'] = payload;
} else {
_userSwitchPayloads['child$_childSwitchCounter'] = payload;
}
}
return;
}
String? existingKey;
for (var entry in _userSwitchPayloads.entries) {
if (entry.value is Map && entry.value['PatientID'] == patientID) {
existingKey = entry.key;
break;
}
}
if (isSuperUser) {
if (existingKey != null && existingKey != 'superUser') {
_userSwitchPayloads.remove(existingKey);
}
_userSwitchPayloads['superUser'] = payload;
_childSwitchCounter = 0;
} else {
if (existingKey != null) {
_userSwitchPayloads[existingKey] = payload;
} else {
_childSwitchCounter++;
if (_childSwitchCounter == 1) {
_userSwitchPayloads['child'] = payload;
} else {
_userSwitchPayloads['child$_childSwitchCounter'] = payload;
}
}
}
}
dynamic getUserSwitchPayload({required String userType}) {
return _userSwitchPayloads[userType];
}
dynamic getUserSwitchPayloadByIndex({bool isSuperUser = false, int? childIndex}) {
if (isSuperUser) {
return _userSwitchPayloads['superUser'];
} else if (childIndex != null) {
if (childIndex == 1) {
return _userSwitchPayloads['child'];
} else {
return _userSwitchPayloads['child$childIndex'];
}
}
return null;
}
void clearUserSwitchPayloads() {
_userSwitchPayloads.clear();
_childSwitchCounter = 0;
}
void clearChildUsersKeepSuperUser() {
// Keep the super user payload
final superUserPayload = _userSwitchPayloads['superUser'];
// Clear all payloads
_userSwitchPayloads.clear();
_childSwitchCounter = 0;
// Restore super user if it existed
if (superUserPayload != null) {
_userSwitchPayloads['superUser'] = superUserPayload;
}
}
}

@ -113,7 +113,12 @@ class AppDependencies {
getIt.registerLazySingleton<LocalAuthentication>(() => LocalAuthentication());
getIt.registerLazySingleton<GAnalytics>(() => GAnalytics());
getIt.registerLazySingleton<AppState>(() => AppState(navigationService: getIt()));
// Register CacheService before AppState so it can be injected
final sharedPreferences = await SharedPreferences.getInstance();
getIt.registerLazySingleton<CacheService>(() => CacheServiceImp(sharedPreferences: sharedPreferences, loggerService: getIt()));
getIt.registerLazySingleton<AppState>(() => AppState(navigationService: getIt(), cacheService: getIt()));
getIt.registerLazySingleton<LocationUtils>(() => LocationUtils(
isShowConfirmDialog: false,
navigationService: getIt(),
@ -126,8 +131,6 @@ class AppDependencies {
navigationService: getIt(),
));
final sharedPreferences = await SharedPreferences.getInstance();
getIt.registerLazySingleton<CacheService>(() => CacheServiceImp(sharedPreferences: sharedPreferences, loggerService: getIt()));
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
getIt.registerLazySingleton<NotificationService>(() => NotificationServiceImp(

@ -99,7 +99,7 @@ class LocationUtils {
title: LocaleKeys.notice.tr(context: navigationService.navigatorKey.currentContext!),
navigationService.navigatorKey.currentContext!,
child: Utils.getWarningWidget(
loadingText: "Please grant location permission from app settings to see better results",
loadingText: LocaleKeys.grantLocationPermission.tr(),
isShowActionButtons: true,
onCancelTap: () {
navigationService.pop();
@ -124,7 +124,7 @@ class LocationUtils {
title: LocaleKeys.notice.tr(context: navigationService.navigatorKey.currentContext!),
navigationService.navigatorKey.currentContext!,
child: Utils.getWarningWidget(
loadingText: "Please grant location permission from app settings to see better results",
loadingText: LocaleKeys.grantLocationPermission.tr(),
isShowActionButtons: true,
onCancelTap: () {
navigationService.pop();
@ -285,7 +285,7 @@ class LocationUtils {
title: LocaleKeys.notice.tr(context: navigationService.navigatorKey.currentContext!),
navigationService.navigatorKey.currentContext!,
child: Utils.getWarningWidget(
loadingText: "Please grant location permission from app settings to see better results",
loadingText: LocaleKeys.grantLocationPermission.tr(),
isShowActionButtons: true,
onCancelTap: () {
navigationService.pop();

@ -0,0 +1,103 @@
import 'dart:io';
import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as path;
class ImageCompressionHelper {
/// Compress and resize image to max 200x200 and ensure file size < 1MB
/// Returns the compressed image file
static Future<File?> compressAndResizeImage(File imageFile) async {
try {
// Check if file exists
if (!await imageFile.exists()) {
print('Image file does not exist');
return null;
}
print('Original file size: ${await imageFile.length() / 1024} KB');
// Get temporary directory
final tempDir = await getTemporaryDirectory();
final targetPath = path.join(
tempDir.path,
'compressed_${DateTime.now().millisecondsSinceEpoch}.jpg',
);
// Start with quality 85
int quality = 85;
const int maxFileSizeBytes = 1048576; // 1MB
// Compress the image
var result = await FlutterImageCompress.compressWithFile(
imageFile.absolute.path,
quality: quality,
format: CompressFormat.jpeg,
);
if (result == null || result.isEmpty) {
print('Compression returned null or empty');
return null;
}
// Write to file
File resultFile = File(targetPath);
await resultFile.writeAsBytes(result);
int fileSize = await resultFile.length();
print('First compression: ${fileSize / 1024} KB, quality: $quality');
// If file size is still > 1MB, reduce quality progressively
int attempts = 0;
while (fileSize > maxFileSizeBytes && quality > 10 && attempts < 10) {
quality -= 10;
attempts++;
result = await FlutterImageCompress.compressWithFile(
imageFile.absolute.path,
quality: quality,
format: CompressFormat.jpeg,
);
if (result == null || result.isEmpty) {
print('Compression returned null on attempt $attempts');
break;
}
await resultFile.writeAsBytes(result);
fileSize = await resultFile.length();
print('Compression attempt $attempts: ${fileSize / 1024} KB, quality: $quality');
}
// Final validation
if (await resultFile.exists()) {
final finalSize = await resultFile.length();
print('Final compressed image size: ${finalSize / 1024} KB');
if (finalSize > maxFileSizeBytes) {
print('Warning: Final file size (${finalSize / 1024} KB) exceeds 1MB limit');
}
return resultFile;
}
return null;
} catch (e, stackTrace) {
print('Error compressing image: $e');
print('Stack trace: $stackTrace');
return null;
}
}
/// Get file size in KB
static Future<double> getFileSizeInKB(File file) async {
int bytes = await file.length();
return bytes / 1024;
}
/// Get file size in MB
static Future<double> getFileSizeInMB(File file) async {
int bytes = await file.length();
return bytes / (1024 * 1024);
}
}

@ -396,10 +396,10 @@ class PushNotificationHandler {
final deviceInfo = DeviceInfoPlugin();
final androidInfo = await deviceInfo.androidInfo;
int sdkInt = androidInfo.version.sdkInt ?? 0;
if (sdkInt >= 33) {
await FlutterCallkitIncoming.requestFullIntentPermission();
}
// int sdkInt = androidInfo.version.sdkInt ?? 0;
// if (sdkInt >= 33) {
// await FlutterCallkitIncoming.requestFullIntentPermission();
// }
}
}

@ -355,7 +355,7 @@ class Utils {
repeat: false, reverse: false, frameRate: FrameRate(60), width: width.w, height: height.h, fit: BoxFit.fill),
SizedBox(height: 16.h),
(noDataText ?? LocaleKeys.noDataAvailable.tr())
.toText14(weight: FontWeight.w500, color: AppColors.greyTextColor, isCenter: true)
.toText14(isBold: true, color: AppColors.greyTextColor, isCenter: true)
.paddingSymmetrical(64.w, 0.h),
SizedBox(height: 16.h),
callToActionButton
@ -371,7 +371,7 @@ class Utils {
Lottie.asset(AppAnimations.loadingAnimation,
repeat: true, reverse: false, frameRate: FrameRate(60), width: 80.h, height: 80.h, fit: BoxFit.fill),
SizedBox(height: 8.h),
(loadingText ?? LocaleKeys.loadingText.tr()).toText16(color: AppColors.blackColor, isCenter: true),
(loadingText ?? LocaleKeys.loadingText.tr()).toText16(color: AppColors.blackColor, isCenter: true, isBold: true),
SizedBox(height: 8.h),
],
).center;
@ -384,7 +384,7 @@ class Utils {
children: [
Lottie.asset(AppAnimations.checkmark, 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).paddingSymmetrical(24.h, 0.h),
(loadingText ?? LocaleKeys.loadingText.tr()).toText16(color: AppColors.blackColor, isBold: true).paddingSymmetrical(24.h, 0.h),
SizedBox(height: 8.h),
],
).center;
@ -404,6 +404,85 @@ class Utils {
).center;
}
static Widget getOnlineCheckInWidget({
String? loadingText,
bool isShowActionButtons = false,
bool showOkButton = false,
Widget? bodyWidget,
Function? onConfirmTap,
Function? onCancelTap,
String? cancelText,
String? confirmText,
}) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Utils.buildSvgWithAssets(icon: AppAssets.online_checkin_icon, width: 80.h, height: 80.h, applyThemeColor: false),
SizedBox(height: 16.h),
(loadingText ?? LocaleKeys.loadingText.tr()).toText16(color: AppColors.blackColor, letterSpacing: 0, weight: FontWeight.w600),
SizedBox(height: 16.h),
bodyWidget ?? SizedBox.shrink(),
SizedBox(height: 16.h),
isShowActionButtons
? Row(
children: [
Expanded(
child: CustomButton(
text: cancelText ?? LocaleKeys.cancel.tr(),
onPressed: () {
if (onCancelTap != null) {
onCancelTap();
}
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
// icon: AppAssets.cancel,
// iconColor: AppColors.whiteColor,
),
),
SizedBox(width: 8.h),
Expanded(
child: CustomButton(
text: confirmText ?? LocaleKeys.confirm.tr(),
onPressed: () async {
if (onConfirmTap != null) {
onConfirmTap();
}
},
backgroundColor: AppColors.bgGreenColor,
borderColor: AppColors.bgGreenColor,
textColor: Colors.white,
// icon: AppAssets.confirm,
),
),
],
)
: showOkButton
? Row(
children: [
Expanded(
child: CustomButton(
text: LocaleKeys.ok.tr(),
onPressed: () async {
if (onConfirmTap != null) {
onConfirmTap();
}
},
backgroundColor: AppColors.bgGreenColor,
borderColor: AppColors.bgGreenColor,
textColor: Colors.white,
// icon: AppAssets.confirm,
),
),
],
)
: SizedBox.shrink(),
],
).center;
}
static Widget getWarningWidget({
String? loadingText,
bool isShowActionButtons = false,
@ -844,7 +923,8 @@ class Utils {
),
TextSpan(
// text: NumberFormat.currency(locale: 'en_US', symbol: " ", decimalDigits: 0).format(habibWalletAmount),
text: " ${habibWalletAmount.toStringAsFixed(2)}",
// text: " ${(NumberFormat.decimalPattern().format(habibWalletAmount)).toStringAsFixed(2)}",
text: " ${(NumberFormat.decimalPattern().format(habibWalletAmount))}",
style: TextStyle(
color: resolvedTextColor,
fontSize: fontSize ?? 32.f,

@ -24,7 +24,7 @@ extension CapExtension on String {
extension EmailValidator on String {
Widget get toWidget => Text(this);
Widget toText8({Color? color, FontWeight? fontWeight, bool isBold = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow}) => Text(
Widget toText8({bool isEnglishOnly = false, Color? color, FontWeight? fontWeight, bool isBold = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow}) => Text(
this,
maxLines: maxlines,
overflow: textOverflow,
@ -34,6 +34,7 @@ extension EmailValidator on String {
fontWeight: fontWeight ?? (isBold ? FontWeight.bold : FontWeight.normal),
color: color ?? AppColors.blackColor,
letterSpacing: 0,
fontFamily: getIt.get<AppState>().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins',
),
);
@ -60,7 +61,7 @@ extension EmailValidator on String {
color: color ?? AppColors.blackColor,
letterSpacing: letterSpacing,
decoration: isUnderLine ? TextDecoration.underline : null,
fontFamily: isEnglishOnly ? "Poppins" : getIt.get<AppState>().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins',
fontFamily: getIt.get<AppState>().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins',
decorationColor: color ?? AppColors.blackColor),
);
@ -80,7 +81,7 @@ extension EmailValidator on String {
maxLines: maxlines,
overflow: textOverflow,
style: TextStyle(
fontFamily: isEnglishOnly ? "Poppins" : getIt.get<AppState>().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins',
fontFamily: getIt.get<AppState>().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins',
fontSize: 9.f,
fontStyle: fontStyle ?? FontStyle.normal,
fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal),
@ -101,7 +102,7 @@ extension EmailValidator on String {
color: color ?? AppColors.blackColor,
letterSpacing: letterSpacing,
decoration: isUnderLine ? TextDecoration.underline : null,
fontFamily: isEnglishOnly ? "Poppins" : getIt.get<AppState>().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins',
fontFamily: getIt.get<AppState>().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins',
),
);
@ -127,7 +128,7 @@ extension EmailValidator on String {
height: height,
decorationColor: isUnderLine ? AppColors.blackColor : null,
decoration: isUnderLine ? TextDecoration.underline : null,
fontFamily: isEnglishOnly ? "Poppins" : getIt.get<AppState>().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins',
fontFamily: getIt.get<AppState>().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins',
),
);
@ -210,7 +211,7 @@ extension EmailValidator on String {
height: height,
fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal),
decoration: isUnderLine ? TextDecoration.underline : null,
fontFamily: fontFamily ?? (isEnglishOnly ? "Poppins" : getIt.get<AppState>().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'),
fontFamily: fontFamily ?? (isEnglishOnly ? "Poppins" : getIt.get<AppState>().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins'),
decorationColor: color ?? AppColors.blackColor),
);
@ -251,14 +252,14 @@ extension EmailValidator on String {
overflow: textOverflow,
fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal),
decoration: isUnderLine ? TextDecoration.underline : null,
fontFamily: fontFamily ?? (isEnglishOnly ? "Poppins" : getIt.get<AppState>().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'),
fontFamily: fontFamily ?? (isEnglishOnly ? "Poppins" : getIt.get<AppState>().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins'),
decorationColor: decorationColor ?? AppColors.errorColor),
);
Widget toText17({bool isEnglishOnly = false, Color? color, bool isBold = false, bool isCenter = false}) => Text(
this,
textAlign: isCenter ? TextAlign.center : null,
style: TextStyle(color: color ?? AppColors.blackColor, fontSize: 17.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, fontFamily: isEnglishOnly ? "Poppins" : getIt.get<AppState>().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'),
style: TextStyle(color: color ?? AppColors.blackColor, fontSize: 17.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, fontFamily: getIt.get<AppState>().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins'),
);
Widget toText18({Color? color, FontWeight? weight, bool isBold = false, bool isCenter = false, int? maxlines, TextOverflow? textOverflow, bool isEnglishOnly = false,}) => Text(
@ -266,7 +267,7 @@ extension EmailValidator on String {
textAlign: isCenter ? TextAlign.center : null,
this,
overflow: textOverflow,
style: TextStyle(fontSize: 18.f, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), color: color ?? AppColors.blackColor, letterSpacing: -0.4, fontFamily: (isEnglishOnly ? "Poppins" : getIt.get<AppState>().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'),),
style: TextStyle(fontSize: 18.f, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), color: color ?? AppColors.blackColor, letterSpacing: -0.4, fontFamily: (isEnglishOnly ? "Poppins" : getIt.get<AppState>().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins'),),
);
Widget toText19({Color? color, bool isBold = false}) => Text(
@ -300,7 +301,7 @@ extension EmailValidator on String {
this,
textAlign: isCenter ? TextAlign.center : null,
style: TextStyle(
fontFamily: (isEnglishOnly ? "Poppins" : getIt.get<AppState>().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'),
fontFamily: (isEnglishOnly ? "Poppins" : getIt.get<AppState>().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins'),
height: 23 / 24, color: color ?? AppColors.blackColor, fontSize: 24.f, letterSpacing: letterSpacing ?? -1, fontWeight: isBold ? FontWeight.bold : fontWeight ?? FontWeight.normal),
);
@ -308,26 +309,26 @@ extension EmailValidator on String {
this,
textAlign: isCenter ? TextAlign.center : null,
style: TextStyle(
fontFamily: (isEnglishOnly ? "Poppins" : getIt.get<AppState>().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'),
fontFamily: (isEnglishOnly ? "Poppins" : getIt.get<AppState>().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins'),
height: height ?? 23 / 26, color: color ?? AppColors.blackColor, fontSize: 26.f, letterSpacing: letterSpacing ?? -1, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)),
);
Widget toText28({bool isEnglishOnly = false, Color? color, bool isBold = false, double? height, bool isCenter = false, double? letterSpacing}) => Text(
this,
textAlign: isCenter ? TextAlign.center : null,
style: TextStyle(height: height ?? 23 / 28, color: color ?? AppColors.blackColor, fontSize: 28.f, letterSpacing: letterSpacing ?? -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, fontFamily: isEnglishOnly ? "Poppins" : getIt.get<AppState>().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'),
style: TextStyle(height: height ?? 23 / 28, color: color ?? AppColors.blackColor, fontSize: 28.f, letterSpacing: letterSpacing ?? -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, fontFamily: getIt.get<AppState>().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins'),
);
Widget toText32({bool isEnglishOnly = false, FontWeight? weight, Color? color, bool isBold = false, bool isCenter = false}) => Text(
this,
textAlign: isCenter ? TextAlign.center : null,
style: TextStyle(
height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 32.f, letterSpacing: -1, fontFamily: isEnglishOnly ? "Poppins" : getIt.get<AppState>().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins', fontWeight: isBold ? FontWeight.bold : weight ?? FontWeight.normal),
height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 32.f, letterSpacing: -1, fontFamily: getIt.get<AppState>().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins', fontWeight: isBold ? FontWeight.bold : weight ?? FontWeight.normal),
);
Widget toText44({Color? color, bool isBold = false, bool isEnglishOnly = false,}) => Text(
this,
style: TextStyle(height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 44.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, fontFamily: (isEnglishOnly ? "Poppins" : getIt.get<AppState>().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'),),
style: TextStyle(height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 44.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, fontFamily: (isEnglishOnly ? "Poppins" : getIt.get<AppState>().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins'),),
);
Widget toSectionHeading({String upperHeading = "", String lowerHeading = ""}) {
@ -433,7 +434,7 @@ extension DynamicTextStyleExtension on BuildContext {
String? fontFamily,
bool isLanguageSwitcher = false}) {
AppState appState = getIt.get<AppState>();
final family = appState.getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins';
final family = appState.getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins';
return TextStyle(
fontFamily: fontFamily ?? family,
fontSize: fontSize,
@ -464,7 +465,7 @@ class FontUtils {
/// Get the appropriate font family for a specific language
static String getFontFamilyForLanguage(bool isArabic) {
return isArabic ? 'GESSTwo' : 'Poppins';
return isArabic ? 'CairoArabic' : 'Poppins';
}
}

@ -155,6 +155,7 @@ extension SmoothContainerExtension on ShapeBorder {
BorderRadius? customBorder,
bool hasShadow = false,
bool hasDenseShadow = false,
List<BoxShadow>? isCustomShadow,
}) {
final bgColor = backgroundColor ?? color;
return ShapeDecoration(
@ -174,7 +175,7 @@ extension SmoothContainerExtension on ShapeBorder {
offset: const Offset(1, 0),
)
]
: [],
: isCustomShadow ?? [],
);
}
}

@ -6,6 +6,7 @@ import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
import 'package:hmg_patient_app_new/features/authentication/models/resp_models/select_device_by_imei.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
@ -214,56 +215,6 @@ class AuthenticationRepoImp implements AuthenticationRepo {
int? patientID,
int? loginType}) async {
AppState appState = getIt.get<AppState>();
// if (isRegister) {
// newRequest["activationCode"] = activationCode ?? "0000";
// newRequest["isSilentLogin"] = activationCode != null ? false : true;
// } else {
// newRequest.activationCode = activationCode ?? "0000";
// newRequest.isSilentLogin = activationCode != null ? false : true;
// newRequest.projectOutSA = newRequest.zipCode == '966' ? false : true;
// newRequest.isDentalAllowedBackend = false;
// newRequest.forRegisteration = newRequest.isRegister ?? false;
// newRequest.isRegister = false;
// }
// Map<String, dynamic> familyRequest = {};
// if (isFormFamilyFile) {
// AppState appState = getIt.get<AppState>();
// familyRequest = {};
// familyRequest['PatientShareRequestID'] = patientShareRequestID;
// familyRequest['ResponseID'] = responseID;
// familyRequest['Status'] = 3;
// familyRequest["PatientID"] = appState.getAuthenticatedUser()!.patientId ?? 0;
// familyRequest["LogInTokenID"] = appState.getFamilyFileTokenID;
// familyRequest["activationCode"] = activationCode ?? "0000";
// familyRequest["PatientMobileNumber"] = newRequest.patientMobileNumber;
// familyRequest["PatientIdentificationID"] = newRequest.patientIdentificationID;
// }
// Map<String, dynamic> switchRequest = {};
// if (isSwitchUser) {
// switchRequest = newRequest.toJson();
//
// switchRequest['PatientID'] = responseID;
// switchRequest['IsSilentLogin'] = true;
// switchRequest['LogInTokenID'] = null;
// switchRequest['SearchType'] = 2;
// if (loginType != 0) {
// switchRequest['SuperUser'] = patientID;
// switchRequest['DeviceToken'] = null;
// } else {
// switchRequest["LoginType"] = 2;
// }
//
// if (appState.getSuperUserID == responseID) {
// // switchRequest['LoginType'] = 3;
// switchRequest['PatientIdentificationID'] = "";
// // switchRequest['ProjectOutSA'] = newRequest.zipCode == '966' ? false : true;
// switchRequest.remove('NationalID');
// switchRequest.remove('isDentalAllowedBackend');
// switchRequest.remove('ProjectOutSA');
// switchRequest.remove('ForRegisteration');
// appState.setSuperUserID = null;
// }
// }
if (isRegister) {
newRequest["activationCode"] = activationCode ?? "0000";
@ -321,7 +272,6 @@ class AuthenticationRepoImp implements AuthenticationRepo {
}
}
// Determine which API to use based on zipCode
final zipCode = _getZipCodeFromRequest(newRequest);
final isOthersCountry = zipCode == '0';
final endpoint = isFormFamilyFile
@ -336,15 +286,44 @@ class AuthenticationRepoImp implements AuthenticationRepo {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
// Determine the payload to send
var requestBody = isFormFamilyFile
? familyRequest
: isRegister
? newRequest
: isSwitchUser
? switchRequest
: newRequest.toJson();
if (isSwitchUser) {
Map<String, dynamic> allUsersPayload;
var switchingPatientID;
var superUserPatientID;
bool isSwitchingToSuperUser = false;
allUsersPayload = appState.getUserSwitchPayloads;
switchingPatientID = switchRequest["PatientID"];
superUserPatientID = allUsersPayload['superUser']['PatientID'];
isSwitchingToSuperUser = (appState.getSuperUserID == responseID) || (allUsersPayload.containsKey('superUser') && superUserPatientID == switchingPatientID);
if (isSwitchingToSuperUser) {
var superUserPayload = appState.getUserSwitchPayloadByIndex(isSuperUser: true);
if (superUserPayload != null) {
requestBody['DeviceToken'] = newRequest.deviceToken;
requestBody['ProjectOutSA'] = 0;
requestBody['PatientIdentificationID'] = "";
requestBody.removeWhere((key, value) => ['NationalID', 'isDentalAllowedBackend', 'ForRegisteration'].contains(key));
appState.setIsChildLoggedIn = false;
appState.clearChildUsersKeepSuperUser();
}
} else {
appState.setIsChildLoggedIn = true;
}
}
await apiClient.post(
endpoint,
body: isFormFamilyFile
? familyRequest
: isRegister
? newRequest
: isSwitchUser
? switchRequest
: newRequest.toJson(),
body: requestBody,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
@ -611,7 +590,7 @@ class AuthenticationRepoImp implements AuthenticationRepo {
}
@override
Future<Either<Failure, GenericApiModel<dynamic>>> getServicePrivilege() {
Future<Either<Failure, GenericApiModel>> getServicePrivilege() {
Map<String, dynamic> mapDevice = {};
try {
GenericApiModel<dynamic>? apiResponse;
@ -692,6 +671,7 @@ class AuthenticationRepoImp implements AuthenticationRepo {
@override
Future<Either<Failure, GenericApiModel<dynamic>>> getPatientBloodType() async {
// Map<String, dynamic> requestBody = {"PatientType": 1, "PatientTypeID": 1, "TokenID": "@dm!n", "PatientID": 5957754};
Map<String, dynamic> requestBody = {};
try {
GenericApiModel<dynamic>? apiResponse;

@ -47,6 +47,7 @@ import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:sms_otp_auto_verify/sms_otp_auto_verify.dart';
import '../insurance/insurance_view_model.dart';
import '../profile_settings/profile_settings_repo.dart';
import 'models/request_models/get_user_mobile_device_data.dart';
import 'models/request_models/insert_patient_mobile_deviceinfo.dart';
import 'models/request_models/patient_insert_device_imei_request.dart';
@ -101,7 +102,10 @@ class AuthenticationViewModel extends ChangeNotifier {
var healthId;
int getDeviceLastLogin = 1;
bool isInitialLoginNavigated = false;
Future<void> onLoginPressed() async {
setIsInitialLoginNavigated(false);
try {
LoaderBottomSheet.showLoader();
//TODO: We will remove this delay
@ -111,7 +115,7 @@ class AuthenticationViewModel extends ChangeNotifier {
if (data != null) {
await _handleExistingImeiData(data);
} else {
await _handleNewImeiRegistration();
await handleNewImeiRegistration();
}
} catch (e) {
log("Error in onLoginPressed: $e");
@ -199,6 +203,11 @@ class AuthenticationViewModel extends ChangeNotifier {
}
}
setIsInitialLoginNavigated(bool isInitialLoginNavigated) {
this.isInitialLoginNavigated = isInitialLoginNavigated;
notifyListeners();
}
void onTermAccepted() {
isTermsAccepted = !isTermsAccepted;
notifyListeners();
@ -216,7 +225,7 @@ class AuthenticationViewModel extends ChangeNotifier {
_appState.deviceToken = await Utils.getStringFromPrefs(CacheConst.pushToken);
}
Future<void> selectDeviceImei({required Function(dynamic data) onSuccess, Function(String)? onError}) async {
Future<void> selectDeviceImei({bool isNavigate = true, required Function(dynamic data) onSuccess, Function(String)? onError}) async {
// LoadingUtils.showFullScreenLoading();
// String firebaseToken = _appState.deviceToken;
String firebaseToken = await Utils.getStringFromPrefs(CacheConst.pushToken);
@ -231,7 +240,9 @@ class AuthenticationViewModel extends ChangeNotifier {
// LoadingUtils.hideFullScreenLoader();
// await _errorHandlerService.handleError(failure: failure);
LoaderBottomSheet.hideLoader();
_navigationService.pushPage(page: LoginScreen());
if (isNavigate) {
_navigationService.pushPage(page: LoginScreen());
}
},
(apiResponse) {
// LoadingUtils.hideFullScreenLoader();
@ -263,33 +274,42 @@ class AuthenticationViewModel extends ChangeNotifier {
}
}
Future<void> _handleNewImeiRegistration() async {
await selectDeviceImei(onSuccess: (dynamic respData) async {
try {
if (respData != null) {
dynamic data = await SelectDeviceByImeiRespModelElement.fromJson(respData.toJson());
_appState.setSelectDeviceByImeiRespModelElement(data);
LoaderBottomSheet.hideLoader();
Future<void> handleNewImeiRegistration({bool isNavigate = true}) async {
print("handleNewImeiRegistration called");
await selectDeviceImei(
isNavigate: isNavigate,
onSuccess: (dynamic respData) async {
try {
if (respData != null) {
dynamic data = await SelectDeviceByImeiRespModelElement.fromJson(respData.toJson());
_appState.setSelectDeviceByImeiRespModelElement(data);
LoaderBottomSheet.hideLoader();
// TODO: Navigate to SavedLogin when available
// SelectDeviceByImeiRespModelElement savedData =
// SelectDeviceByImeiRespModelElement.fromJson(respData);
_navigationService.pushPage(page: SavedLogin());
// _navigationService.pushPage(page: LoginScreen());
} else {
print("print login........");
// TODO: Navigate to SavedLogin when available
// SelectDeviceByImeiRespModelElement savedData =
// SelectDeviceByImeiRespModelElement.fromJson(respData);
if (!isInitialLoginNavigated) {
_navigationService.pushPage(page: SavedLogin());
}
// _navigationService.pushPage(page: LoginScreen());
} else {
if (isNavigate) {
LoaderBottomSheet.hideLoader();
_navigationService.pushPage(page: LoginScreen());
}
}
} catch (e) {
if (isNavigate) {
log("Error processing IMEI registration response: $e");
LoaderBottomSheet.hideLoader();
_navigationService.pushPage(page: LoginScreen());
}
}
},
onError: (String error) {
LoaderBottomSheet.hideLoader();
_navigationService.pushPage(page: LoginScreen());
}
} catch (e) {
log("Error processing IMEI registration response: $e");
LoaderBottomSheet.hideLoader();
_navigationService.pushPage(page: LoginScreen());
}
}, onError: (String error) {
LoaderBottomSheet.hideLoader();
_dialogService.showErrorBottomSheet(message: error, onOkPressed: () {});
});
_dialogService.showErrorBottomSheet(message: error, onOkPressed: () {});
});
}
Future<void> checkUserAuthentication({required OTPTypeEnum otpTypeEnum, Function(dynamic)? onSuccess, Function(String)? onError}) async {
@ -385,7 +405,7 @@ class AuthenticationViewModel extends ChangeNotifier {
mobileNumber: phoneNumber,
selectedLoginType: otpTypeEnum.toInt(),
zipCode: selectedCountrySignup.countryCode,
nationalId:nationalIdOrFileNumber,
nationalId: nationalIdOrFileNumber,
isFileNo: isForRegister ? isPatientHasFile(request: payload) : false,
patientId: isFormFamilyFile ? _appState.getAuthenticatedUser()!.patientId : 0,
isForRegister: isForRegister,
@ -611,17 +631,35 @@ class AuthenticationViewModel extends ChangeNotifier {
} else {
if (activation.list != null && activation.list!.isNotEmpty) {
if (isSwitchUser) {
// Clear profile image cache when switching users
_appState.clearProfileImageCache();
log("🔄 Switching user - cleared profile image cache");
if (_appState.getIsChildLoggedIn) {
_appState.setSuperUserID = null;
_appState.setIsChildLoggedIn = false;
activation.list!.first.isParentUser = true;
} else {
_appState.setSuperUserID = _appState.getAuthenticatedUser()?.patientId;
_appState.setIsChildLoggedIn = true;
// _appState.setIsChildLoggedIn = false;
activation.list!.first.isParentUser = false;
var allUsersPayload = _appState.getUserSwitchPayloads;
if ((allUsersPayload.containsKey('superUser') && allUsersPayload['superUser'] != null)) {
_appState.addUserSwitchPayload(activation.list!.first.toJson(), isSuperUser: false);
} else {
_appState.addUserSwitchPayload(activation.list!.first.toJson(), isSuperUser: true);
}
} else {
var allUsersPayload = _appState.getUserSwitchPayloads;
_appState.setSuperUserID = allUsersPayload['superUser']?['PatientID'];
activation.list!.first.isParentUser = true;
_appState.addUserSwitchPayload(activation.list!.first.toJson(), isSuperUser: true);
}
// Fetch profile image after switching user
await _fetchProfileImage(activation.list!.first.patientId);
} else {
activation.list!.first.isParentUser = true;
_appState.addUserSwitchPayload(activation.list!.first.toJson(), isSuperUser: true);
// Fetch profile image after login
await _fetchProfileImage(activation.list!.first.patientId);
}
activation.list!.first.bloodGroup = activation.patientBloodType;
activation.list!.first.zipCode = selectedCountrySignup == CountryEnum.others ? '0' : selectedCountrySignup.countryCode;
@ -629,8 +667,14 @@ class AuthenticationViewModel extends ChangeNotifier {
_appState.setPrivilegeModelList(activation.list!.first.listPrivilege!);
_appState.setUserBloodGroup = activation.patientBlodType ?? "N/A";
// Refresh privileges after user switching to ensure correct privileges are loaded
if (isSwitchUser) {
print("🔄 Switching user - refreshing privileges");
await getServicePrivilege();
}
// Fetch patient blood type from new API
await getPatientBloodTypeNew();
// await getPatientBloodTypeNew();
}
// _appState.setUserBloodGroup = (activation.patientBlodType ?? "");
_appState.setAppAuthToken = activation.authenticationTokenId;
@ -722,6 +766,8 @@ class AuthenticationViewModel extends ChangeNotifier {
}
Future<void> navigateToHomeScreen() async {
setIsInitialLoginNavigated(true);
getPatientBloodTypeNew();
Navigator.pushAndRemoveUntil(
_navigationService.navigatorKey.currentContext!,
CustomPageRoute(
@ -1018,7 +1064,9 @@ class AuthenticationViewModel extends ChangeNotifier {
nationalId: _appState.getAuthenticatedUser()!.patientIdentificationNo,
gender: _appState.getAuthenticatedUser()!.gender)
.toJson());
resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async {
resultEither.fold((failure) async {
// await _errorHandlerService.handleError(failure: failure);
}, (apiResponse) async {
if (apiResponse.messageStatus == 2) {}
if (apiResponse.messageStatus == 1) {
log("Insert Device Data Success");
@ -1141,6 +1189,7 @@ class AuthenticationViewModel extends ChangeNotifier {
_appState.setIsAuthenticated = false;
_appState.setAppAuthToken = "";
_appState.setUserBloodGroup = "";
_appState.clearProfileImageCache(); // Clear profile image cache
_appState.setSuperUserID = null;
_appState.setIsChildLoggedIn = false;
_appState.setFamilyFileTokenID = "";
@ -1152,6 +1201,7 @@ class AuthenticationViewModel extends ChangeNotifier {
_appState.setVidaPlusProjectList([]);
_appState.setHMCProjectList([]);
_appState.setProjectsDetailList([]);
_appState.clearUserSwitchPayloads();
await clearDefaultInputValues();
_navigationService.pushAndReplace(AppRoutes.landingScreen);
} catch (e) {
@ -1171,7 +1221,7 @@ class AuthenticationViewModel extends ChangeNotifier {
(apiResponse) {
if (apiResponse.messageStatus == 1 && apiResponse.data != null) {
// Extract blood type from response
String? bloodType = apiResponse.data['GetPatientBloodType'];
String? bloodType = apiResponse.data['GetPatientBloodType'][0]["BloodType"];
if (bloodType != null && bloodType.isNotEmpty) {
_appState.setUserBloodGroup = bloodType;
log("Blood type updated from new API: $bloodType");
@ -1183,4 +1233,42 @@ class AuthenticationViewModel extends ChangeNotifier {
log("Error calling getPatientBloodType: $e");
}
}
/// Fetch profile image after login and store in AppState
Future<void> _fetchProfileImage(int? patientID) async {
if (patientID == null) return;
try {
log("Fetching profile image for patient ID: $patientID");
final profileSettingsRepo = GetIt.instance<ProfileSettingsRepo>();
final result = await profileSettingsRepo.getProfileImage(patientID: patientID);
result.fold(
(failure) {
// Silently fail - profile image is optional
log("Failed to fetch profile image: ${failure.message}");
},
(apiResponse) {
if (apiResponse.data != null && apiResponse.data['Patient_GetProfileImageDataList'] != null) {
var imageList = apiResponse.data['Patient_GetProfileImageDataList'];
if (imageList is List && imageList.isNotEmpty) {
String? imageData = imageList[0]['ImageData'];
if (imageData != null && imageData.isNotEmpty) {
_appState.setProfileImageData = imageData;
log("✅ Profile image loaded and cached successfully");
} else {
log("⚠️ Profile image data is empty");
}
} else {
log("⚠️ Profile image list is empty");
}
} else {
log("⚠️ No profile image data in response");
}
},
);
} catch (e) {
log("❌ Error fetching profile image: $e");
}
}
}

@ -1,11 +1,14 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/doctor_response_mapper.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart';
import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_repo.dart';
import 'package:hmg_patient_app_new/features/blood_donation/models/blood_group_hospitals_model.dart';
@ -20,6 +23,7 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
class BloodDonationViewModel extends ChangeNotifier {
@ -263,7 +267,13 @@ class BloodDonationViewModel extends ChangeNotifier {
await bloodDonationRepo.updateBloodGroup(request: payload);
await addUserAgreementForBloodDonation();
LoaderBottomSheet.hideLoader();
dialogService.showSuccessBottomSheetWithoutH(message: "Blood Group Updated Successfully", onOkPressed: () {});
showCommonBottomSheetWithoutHeight(GetIt.instance<NavigationService>().navigatorKey.currentContext!,
child: Utils.getSuccessWidget(loadingText: "Blood Group Updated Successfully").paddingSymmetrical(0.h, 24.h),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: false,
isAutoDismiss: true);
// dialogService.showSuccessBottomSheetWithoutH(message: "Blood Group Updated Successfully", onOkPressed: () {});
}
Future<void> addUserAgreementForBloodDonation() async {

@ -26,7 +26,7 @@ class HospitalBottomSheetBodySelection extends StatelessWidget {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if(!isHideTitle) LocaleKeys.selectHospital.tr(context: context).toText16(weight: FontWeight.w500, color: AppColors.greyTextColor),
if(!isHideTitle) LocaleKeys.selectHospital.tr(context: context).toText16(isBold: true, color: AppColors.greyTextColor),
SizedBox(height: 16.h),
SizedBox(
height: MediaQuery.sizeOf(context).height * .4,
@ -75,7 +75,8 @@ class HospitalBottomSheetBodySelection extends StatelessWidget {
children: [
Utils.buildSvgWithAssets(icon: AppAssets.hmg).paddingOnly(right: 10),
Expanded(
child: Text(hospital.projectName ?? "", style: TextStyle(fontWeight: FontWeight.w600, fontSize: 16.f, color: AppColors.blackColor)),
child: (hospital.projectName ?? "").toString().toText16(isBold: true, color: AppColors.blackColor),
// Text(hospital.projectName ?? "", style: TextStyle(isBold: true, fontSize: 16.f, color: AppColors.blackColor)),
)
],
);

@ -86,7 +86,7 @@ abstract class BookAppointmentsRepo {
{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});
{bool isGetNearestAppointments, Function(dynamic)? onSuccess, Function(String)? onError});
Future<Either<Failure, GenericApiModel<List<LaserBodyPart>>>> getLaserClinics(int laserCategoryID, int projectID, int languageID, {Function(dynamic)? onSuccess, Function(String)? onError});
@ -877,12 +877,13 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo {
@override
Future<Either<Failure, GenericApiModel<List<DoctorsListResponseModel>>>> getDentalChiefComplaintDoctorsList(int projectID, int chiefComplaintID,
{Function(dynamic)? onSuccess, Function(String)? onError}) async {
{bool isGetNearestAppointments = false, Function(dynamic)? onSuccess, Function(String)? onError}) async {
Map<String, dynamic> mapDevice = {
"ProjectID": projectID,
"ChiefComplaintID": chiefComplaintID,
"isDentalAllowedBackend": true,
"IsPublicRequest": true,
"IsGetNearAppointment": isGetNearestAppointments
};
try {

@ -331,6 +331,42 @@ class BookAppointmentsViewModel extends ChangeNotifier {
notifyListeners();
}
void filterDoctors(String query) {
if (query.isEmpty) {
filteredDoctorList = List.from(doctorsList);
_groupDoctorsList();
setIsNearestAppointmentSelected(isNearestAppointmentSelected);
notifyListeners();
return;
}
filteredDoctorList = doctorsList.where((doctor) {
final name = (doctor.name ?? '').toLowerCase();
final clinicName = (doctor.clinicName ?? '').toLowerCase();
final projectName = (doctor.projectName ?? '').toLowerCase();
final q = query.toLowerCase();
return name.contains(q) || clinicName.contains(q) || projectName.contains(q);
}).toList();
// Re-group filtered list
final clinicMap = <String, List<DoctorsListResponseModel>>{};
final hospitalMap = <String, List<DoctorsListResponseModel>>{};
for (var doctor in filteredDoctorList) {
final clinicKey = (doctor.clinicName ?? 'Unknown').trim();
clinicMap.putIfAbsent(clinicKey, () => []).add(doctor);
final hospitalKey = (doctor.projectName ?? 'Unknown').trim();
hospitalMap.putIfAbsent(hospitalKey, () => []).add(doctor);
}
doctorsListByClinic = clinicMap.values.toList();
doctorsListByHospital = hospitalMap.values.toList();
doctorsListGrouped = isSortByClinic ? doctorsListByClinic : doctorsListByHospital;
notifyListeners();
}
void filterClinics(String? query) {
if (query!.isEmpty) {
_filteredClinicsList = List.from(clinicsList);
@ -364,9 +400,17 @@ class BookAppointmentsViewModel extends ChangeNotifier {
notifyListeners();
}
refreshDoctorsList() {
setIsDoctorsListLoading(true);
if(selectedClinic.clinicID == 17) {
getDentalChiefComplaintDoctorsList(projectID: currentlySelectedHospitalFromRegionFlow != null ? int.parse(currentlySelectedHospitalFromRegionFlow!) : 0);
} else {
getDoctorsList(isNearest: isNearestAppointmentSelected);
}
}
setIsNearestAppointmentSelected(bool isNearestAppointmentSelected) {
this.isNearestAppointmentSelected = isNearestAppointmentSelected;
if (isNearestAppointmentSelected) {
for (var group in doctorsListGrouped) {
group.sort((a, b) {
@ -610,7 +654,7 @@ 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 = true, int doctorId = 0, String doctorName = "", 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();
filteredDoctorList.clear();
doctorsListGrouped.clear();
@ -638,7 +682,7 @@ class BookAppointmentsViewModel extends ChangeNotifier {
clearSearchFilters();
getFiltersFromDoctorList();
_groupDoctorsList();
setIsNearestAppointmentSelected(true);
setIsNearestAppointmentSelected(isNearest);
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
@ -1004,7 +1048,8 @@ class BookAppointmentsViewModel extends ChangeNotifier {
);
LoaderBottomSheet.showLoader(
loadingText: LocaleKeys.reschedulingAppo.tr(context: navigationService.navigatorKey.currentContext!),
// loadingText: LocaleKeys.reschedulingAppo.tr(context: navigationService.navigatorKey.currentContext!),
loadingText: LocaleKeys.loadingText.tr(context: navigationService.navigatorKey.currentContext!),
);
await cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel).then((val) async {
LoaderBottomSheet.hideLoader();
@ -1427,8 +1472,10 @@ class BookAppointmentsViewModel extends ChangeNotifier {
Future<void> getDentalChiefComplaintDoctorsList({int projectID = 0, Function(dynamic)? onSuccess, Function(String)? onError}) async {
doctorsList.clear();
notifyListeners();
projectID = currentlySelectedHospitalFromRegionFlow != null ? int.parse(currentlySelectedHospitalFromRegionFlow!) : projectID;
final result = await bookAppointmentsRepo.getDentalChiefComplaintDoctorsList(projectID, selectedChiefComplaintID);
final result = await bookAppointmentsRepo.getDentalChiefComplaintDoctorsList(projectID, selectedChiefComplaintID, isGetNearestAppointments: isNearestAppointmentSelected);
result.fold(
(failure) async {
@ -1445,6 +1492,7 @@ class BookAppointmentsViewModel extends ChangeNotifier {
// clearSearchFilters();
// getFiltersFromDoctorList();
_groupDoctorsList();
setIsNearestAppointmentSelected(isNearestAppointmentSelected);
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);

@ -193,7 +193,7 @@ class DoctorsListResponseModel {
serviceID = json['ServiceID'];
setupID = json['SetupID'];
speciality = json['Speciality'] != null ? json['Speciality'].cast<String>() : [];
// specialityN = json['SpecialityN'].cast<String>();
specialityN = json['SpecialityN'] != null ? json['SpecialityN'].cast<String>() : [];
transactionType = json['TransactionType'];
virtualEmploymentType = json['VirtualEmploymentType'];
workingHours = json['WorkingHours'];

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

@ -713,7 +713,7 @@ class EmergencyServicesViewModel extends ChangeNotifier {
appointments = response.data;
if (appointments?.isEmpty == true) {
haveAnAppointment = false;
dialogService.showErrorBottomSheet(message: "Unable to Fetch Appointment");
dialogService.showErrorBottomSheet(message: LocaleKeys.NoBookedAppointments.tr());
}
notifyListeners();
});

@ -1,4 +1,5 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
@ -21,6 +22,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:sms_otp_auto_verify/sms_otp_auto_verify.dart';
import 'models/req_models/check_activation_e_referral_req_model.dart';
import 'models/resq_models/get_covid_payment_info_resp.dart';
@ -632,6 +634,9 @@ class HmgServicesViewModel extends ChangeNotifier {
Function(GenericApiModel)? onSuccess,
Function(String)? onError,
}) async {
// Get and set the SMS signature for auto-fill
requestModel.sMSSignature = await getSignature();
notifyListeners();
final result = await hmgServicesRepo.sendEReferralActivationCode(requestModel);
@ -660,6 +665,14 @@ class HmgServicesViewModel extends ChangeNotifier {
);
}
Future<String?> getSignature() async {
if (Platform.isAndroid) {
return await SmsVerification.getAppSignature();
} else {
return null;
}
}
Future<void> checkEReferralActivationCode({
required CheckActivationCodeForEReferralRequestModel requestModel,
Function(GenericApiModel)? onSuccess,

@ -10,6 +10,7 @@ class SendActivationCodeForEReferralRequestModel {
dynamic sessionID;
bool? isDentalAllowedBackend;
int? deviceTypeID;
String? sMSSignature;
SendActivationCodeForEReferralRequestModel(
{this.patientMobileNumber,
@ -22,7 +23,8 @@ class SendActivationCodeForEReferralRequestModel {
this.patientOutSA,
this.sessionID,
this.isDentalAllowedBackend,
this.deviceTypeID});
this.deviceTypeID,
this.sMSSignature});
SendActivationCodeForEReferralRequestModel.fromJson(Map<String, dynamic> json) {
patientMobileNumber = json['PatientMobileNumber'];
@ -36,6 +38,7 @@ class SendActivationCodeForEReferralRequestModel {
sessionID = json['SessionID'];
isDentalAllowedBackend = json['isDentalAllowedBackend'];
deviceTypeID = json['DeviceTypeID'];
sMSSignature = json['SMSSignature'];
}
Map<String, dynamic> toJson() {
@ -51,6 +54,7 @@ class SendActivationCodeForEReferralRequestModel {
data['SessionID'] = this.sessionID;
data['isDentalAllowedBackend'] = this.isDentalAllowedBackend;
data['DeviceTypeID'] = this.deviceTypeID;
data['SMSSignature'] = this.sMSSignature;
return data;
}
}

@ -1,6 +1,18 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
/// Enum for vital sign status to avoid string-based comparisons
enum VitalSignStatus {
normal,
high,
low,
obese,
overweight,
underweight,
}
/// UI-only helper model for Vital Sign cards.
///
/// Keeps presentation logic (chip colors, icon colors, simple status rules)
@ -20,13 +32,12 @@ class VitalSignUiModel {
/// Returns a color scheme for a card based on its [status] and [label].
///
/// Rules (mirrors existing behavior in Medical File page):
/// Rules:
/// - Height is always blue.
/// - High => red scheme.
/// - Low => yellow scheme.
/// - High, Obese, Overweight => red scheme.
/// - Low, Underweight => yellow scheme.
/// - Otherwise => green scheme (Normal).
static VitalSignUiModel scheme({required String? status, required String label}) {
final s = (status ?? '').toLowerCase();
static VitalSignUiModel scheme({required VitalSignStatus? status, required String label}) {
final l = label.toLowerCase();
// Height should always be blue.
@ -39,7 +50,10 @@ class VitalSignUiModel {
);
}
if (s.contains('high')) {
// High, Obese, Overweight => red scheme (health concerns)
if (status == VitalSignStatus.high ||
status == VitalSignStatus.obese ||
status == VitalSignStatus.overweight) {
return VitalSignUiModel(
iconBg: AppColors.chipSecondaryLightRedColor,
iconFg: AppColors.primaryRedColor,
@ -48,8 +62,8 @@ class VitalSignUiModel {
);
}
// Warning for both low and overweight/underweight BMI, since they can indicate potential health issues.
if (s.contains('low') || s.contains('underweight') || s.contains('overweight')) {
// Low, Underweight => yellow scheme (warning)
if (status == VitalSignStatus.low || status == VitalSignStatus.underweight) {
final Color yellowBg = AppColors.highAndLow.withValues(alpha: 0.12);
return VitalSignUiModel(
iconBg: yellowBg,
@ -69,19 +83,37 @@ class VitalSignUiModel {
);
}
/// Convert VitalSignStatus enum to localized string
static String statusToString(VitalSignStatus status) {
switch (status) {
case VitalSignStatus.normal:
return LocaleKeys.vitalSignNormal.tr();
case VitalSignStatus.high:
return LocaleKeys.vitalSignHigh.tr();
case VitalSignStatus.low:
return LocaleKeys.vitalSignLow.tr();
case VitalSignStatus.obese:
return LocaleKeys.vitalSignObese.tr();
case VitalSignStatus.overweight:
return LocaleKeys.vitalSignOverweight.tr();
case VitalSignStatus.underweight:
return LocaleKeys.vitalSignUnderweight.tr();
}
}
/// Simple, user-friendly classification:
/// - Low: systolic < 90 OR diastolic < 60
/// - High: systolic >= 140 OR diastolic >= 90
/// - Normal: otherwise
/// Returns null if values are missing/unparseable.
static String? bloodPressureStatus({dynamic systolic, dynamic diastolic}) {
static VitalSignStatus? bloodPressureStatus({dynamic systolic, dynamic diastolic}) {
final int? s = toIntOrNull(systolic);
final int? d = toIntOrNull(diastolic);
if (s == null || d == null) return null;
if (s < 90 || d < 60) return 'Low';
if (s >= 140 || d >= 90) return 'High';
return 'Normal';
if (s < 90 || d < 60) return VitalSignStatus.low;
if (s >= 140 || d >= 90) return VitalSignStatus.high;
return VitalSignStatus.normal;
}
static int? toIntOrNull(dynamic v) {
@ -91,28 +123,47 @@ class VitalSignUiModel {
return int.tryParse(v.toString());
}
static String bmiStatus(dynamic bmi) {
String bmiStatus = 'Normal';
static VitalSignStatus? bmiStatus(dynamic bmi) {
// Return null if BMI is not available or is 0
final double bmiResult = double.tryParse(bmi.toString()) ?? 0;
if (bmiResult >= 30) {
bmiStatus = "High";
} else if (bmiResult < 30 && bmiResult >= 25) {
bmiStatus = "Overweight";
} else if (bmiResult < 25 && bmiResult >= 18.5) {
bmiStatus = "Normal";
} else if (bmiResult < 18.5) {
bmiStatus = "Underweight";
if (bmiResult == 0 || bmi == null) {
return null;
}
// BMI >= 25 (Overweight or Obese) => High
if (bmiResult >= 25) {
return VitalSignStatus.high;
}
// BMI >= 18.5 and < 25 => Normal
else if (bmiResult >= 18.5) {
return VitalSignStatus.normal;
}
// BMI < 18.5 (Underweight) => Low
else {
return VitalSignStatus.low;
}
}
/// Weight status based on BMI with detailed classification
/// Returns: Obese, Overweight, Normal, Underweight
static VitalSignStatus? weightStatus(dynamic bmi) {
// Return null if BMI is not available or is 0
final double bmiResult = double.tryParse(bmi.toString()) ?? 0;
// if (bmi == null) return 'N/A';
// final double bmiValue = double.tryParse(bmi.toString()) ?? 0;
// if (bmiValue < 18.5) return 'Underweight';
// if (bmiValue < 25) return 'Normal';
// if (bmiValue < 30) return 'Overweight';
// return 'High';
if (bmiResult == 0 || bmi == null) {
return null;
}
return bmiStatus;
if (bmiResult >= 30) {
return VitalSignStatus.obese;
} else if (bmiResult >= 25) {
return VitalSignStatus.overweight;
} else if (bmiResult >= 18.5) {
return VitalSignStatus.normal;
} else {
return VitalSignStatus.underweight;
}
}
}

@ -5,6 +5,7 @@ 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_clinic_schedule_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/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';
@ -13,6 +14,9 @@ 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<List<LiveCareClinicScheduleResponse>>>> getLiveCareImmediateClinicSchedule(int age, int genderID, int serviceID,
{Function(dynamic)? onSuccess, Function(String)? onError});
Future<Either<Failure, GenericApiModel<LiveCareImmediateAppointmentFeesList>>> getLiveCareImmediateAppointmentFees(int age, int genderID, int serviceID,
{Function(dynamic)? onSuccess, Function(String)? onError});
@ -203,4 +207,47 @@ class ImmediateLiveCareRepoImp implements ImmediateLiveCareRepo {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<List<LiveCareClinicScheduleResponse>>>> getLiveCareImmediateClinicSchedule(int age, int genderID, int serviceID,
{Function(dynamic p1)? onSuccess, Function(String p1)? onError}) async {
Map<String, dynamic> mapDevice = {
"Age": age,
"Gender": genderID,
"ServiceID": serviceID,
};
try {
GenericApiModel<List<LiveCareClinicScheduleResponse>>? apiResponse;
Failure? failure;
await apiClient.post(
GET_LIVECARE_CLINIC_TIMING,
body: mapDevice,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
// onError!(error);
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final respObject = response['PatientER_GetClinicsServiceTimingsList'];
final liveCareScheduleObj = respObject.map((item) => LiveCareClinicScheduleResponse.fromJson(item as Map<String, dynamic>)).toList().cast<LiveCareClinicScheduleResponse>();
apiResponse = GenericApiModel<List<LiveCareClinicScheduleResponse>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: liveCareScheduleObj,
);
} 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()));
}
}
}

@ -11,6 +11,7 @@ import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_repo.dart';
import 'package:hmg_patient_app_new/features/immediate_livecare/models/resp_models/get_livecare_clinic_schedule_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/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';
@ -46,6 +47,8 @@ class ImmediateLiveCareViewModel extends ChangeNotifier {
List<PatientLiveCareHistory> patientLiveCareHistoryList = [];
bool patientHasPendingLiveCareRequest = false;
List<LiveCareClinicScheduleResponse> liveCareClinicScheduleList = [];
late AppState _appState;
Duration countdownDuration = Duration(minutes: 1, seconds: 0);
@ -265,6 +268,33 @@ class ImmediateLiveCareViewModel extends ChangeNotifier {
);
}
Future<void> getLiveCareImmediateClinicSchedule({int? serviceID, Function(dynamic)? onSuccess, Function(String)? onError}) async {
liveCareClinicScheduleList.clear();
notifyListeners();
final result =
await immediateLiveCareRepo.getLiveCareImmediateClinicSchedule(_appState.getAuthenticatedUser()!.age!, _appState.getAuthenticatedUser()!.gender!, serviceID!);
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) {
liveCareClinicScheduleList = apiResponse.data!;
// immediateLiveCareClinicsList.sort((a, b) => b.isOnline!.compareTo(a.isOnline!));
// isImmediateLiveCareClinicsLoading = false;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
void startTimer() {
timer = Timer.periodic(const Duration(seconds: 1), (_) => addTime());
notifyListeners();

@ -0,0 +1,71 @@
class LiveCareClinicScheduleResponse {
int? iD;
int? serviceID;
int? dayOfWeek;
String? dayOfWeekStr;
bool? isActive;
String? createdOn;
String? createdBy;
bool? projectOutSA;
String? dayOfWeekStrN;
List<ShiftTimings>? shiftTimings;
LiveCareClinicScheduleResponse({this.iD, this.serviceID, this.dayOfWeek, this.dayOfWeekStr, this.isActive, this.createdOn, this.createdBy, this.projectOutSA, this.dayOfWeekStrN, this.shiftTimings});
LiveCareClinicScheduleResponse.fromJson(Map<String, dynamic> json) {
iD = json['ID'];
serviceID = json['ServiceID'];
dayOfWeek = json['DayOfWeek'];
dayOfWeekStr = json['DayOfWeekStr'];
isActive = json['IsActive'];
createdOn = json['CreatedOn'];
createdBy = json['CreatedBy'];
projectOutSA = json['ProjectOutSA'];
dayOfWeekStrN = json['DayOfWeekStrN'];
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 = new Map<String, dynamic>();
data['ID'] = this.iD;
data['ServiceID'] = this.serviceID;
data['DayOfWeek'] = this.dayOfWeek;
data['DayOfWeekStr'] = this.dayOfWeekStr;
data['IsActive'] = this.isActive;
data['CreatedOn'] = this.createdOn;
data['CreatedBy'] = this.createdBy;
data['ProjectOutSA'] = this.projectOutSA;
data['DayOfWeekStrN'] = this.dayOfWeekStrN;
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 = new Map<String, dynamic>();
data['EndTime'] = this.endTime;
data['ShiftID'] = this.shiftID;
data['StartTime'] = this.startTime;
return data;
}
}

@ -26,9 +26,9 @@ abstract class LabRepo {
Future<Either<Failure, GenericApiModel<dynamic>>> getLabResultsByAppointmentNo({required num appointmentNo, required num projectID, required num clinicID});
Future<Either<Failure, GenericApiModel<LabOrdersResponseByAi>>> getPatientAiOverViewLabOrders(var payload);
Future<Either<Failure, GenericApiModel<LabOrdersResponseByAi>>> getPatientAiOverViewLabOrders({required int langId, required dynamic payload});
Future<Either<Failure, GenericApiModel<LabOrderResponseByAi>>> getPatientAiOverViewLabOrder(var payload);
Future<Either<Failure, GenericApiModel<LabOrderResponseByAi>>> getPatientAiOverViewLabOrder(var payloa);
}
class LabRepoImp implements LabRepo {
@ -319,16 +319,24 @@ class LabRepoImp implements LabRepo {
GenericApiModel<LabOrderResponseByAi>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.getAiOverViewLabOrder,
ApiConsts.getAiOverViewLabOrderNew,
body: labOrder,
isAllowAny: true,
isExternal: true,
apiHeaders: {
'Accept-Language': 'en-US,en;q=0.7',
'Content-Type': 'application/json',
'X-API-KEY': '9d9d2ac4c1fc302299dbb0fbb1b7fea1',
'accept': 'application/json',
},
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
var res;
if (response["analysis_result"] != null) {
res = response["analysis_result"];
if (response != null) {
res = response;
}
apiResponse = GenericApiModel<LabOrderResponseByAi>(
messageStatus: messageStatus,
@ -350,21 +358,29 @@ class LabRepoImp implements LabRepo {
}
@override
Future<Either<Failure, GenericApiModel<LabOrdersResponseByAi>>> getPatientAiOverViewLabOrders(var labOrder) async {
Future<Either<Failure, GenericApiModel<LabOrdersResponseByAi>>> getPatientAiOverViewLabOrders({required int langId, required dynamic payload}) async {
try {
GenericApiModel<LabOrdersResponseByAi>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.getAiOverViewLabOrders,
body: {"lab_results": labOrder},
ApiConsts.getAiOverViewLabOrdersNew,
isAllowAny: true,
isExternal: true,
apiHeaders: {
'Accept-Language': 'en-US,en;q=0.7',
'Content-Type': 'application/json',
'X-API-KEY': '9d9d2ac4c1fc302299dbb0fbb1b7fea1',
'accept': 'application/json',
},
body: {"lab_results": payload, "language_id": langId},
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
var res;
if (response["lab_analysis_result"] != null) {
res = response["lab_analysis_result"];
if (response != null) {
res = response;
}
apiResponse = GenericApiModel<LabOrdersResponseByAi>(
messageStatus: messageStatus,

@ -447,20 +447,19 @@ class LabViewModel extends ChangeNotifier {
minY = resultValue;
}
// if (highRefrenceValue < double.parse(element.referenceHigh ?? "0.0")) {
highRefrenceValue = double.parse(element.referenceHigh ?? "0.0");
flagForHighReferenceRange = element.calculatedResultFlag;
highRefrenceValue = double.parse(element.referenceHigh ?? "0.0");
flagForHighReferenceRange = element.calculatedResultFlag;
// }
// if (lowRefenceValue > double.parse(element.referenceLow ?? "0.0")) {
lowRefenceValue = double.parse(element.referenceLow ?? "0.0");
flagForLowReferenceRange = element.calculatedResultFlag;
lowRefenceValue = double.parse(element.referenceLow ?? "0.0");
flagForLowReferenceRange = element.calculatedResultFlag;
// }
if(double.parse(element.referenceHigh ?? "0.0") == 0 ){
if (double.parse(element.referenceHigh ?? "0.0") == 0) {
highRefrenceValue = double.negativeInfinity;
}
if(double.parse(element.referenceLow ?? "0.0") == 0 ){
if (double.parse(element.referenceLow ?? "0.0") == 0) {
lowRefenceValue = double.negativeInfinity;
}
@ -473,18 +472,16 @@ class LabViewModel extends ChangeNotifier {
time: DateUtil.convertStringToDate(element.verifiedOnDateTime),
referenceValue: element.calculatedResultFlag ?? "",
));
} catch (e) {
}
} catch (e) {}
});
if (flagForLowReferenceRange == null && flagForHighReferenceRange == null) {
highRefrenceValue = maxY;
lowRefenceValue = minY;
}
//
if (lowRefenceValue != double.negativeInfinity &&minY > lowRefenceValue) {
if (lowRefenceValue != double.negativeInfinity && minY > lowRefenceValue) {
minY = lowRefenceValue - getInterval();
}else if (highRefrenceValue != double.negativeInfinity && minY > highRefrenceValue) {
} else if (highRefrenceValue != double.negativeInfinity && minY > highRefrenceValue) {
minY = highRefrenceValue - getInterval();
}
@ -494,17 +491,16 @@ class LabViewModel extends ChangeNotifier {
lowTransformedReferenceValue = double.parse(transformValueInRange(lowRefenceValue, flagForLowReferenceRange ?? "").toStringAsFixed(1));
this.highRefrenceValue = double.parse(highRefrenceValue.toStringAsFixed(2));
this.lowRefenceValue = double.parse(lowRefenceValue.toStringAsFixed(2));
if(highRefrenceValue != double.negativeInfinity &&maxY<highRefrenceValue){
maxY = highRefrenceValue+ getInterval();
if (highRefrenceValue != double.negativeInfinity && maxY < highRefrenceValue) {
maxY = highRefrenceValue + getInterval();
maxX = maxY;
}else if(lowRefenceValue != double.negativeInfinity && maxY<lowRefenceValue){
maxY = lowRefenceValue+ getInterval()/2;
} else if (lowRefenceValue != double.negativeInfinity && maxY < lowRefenceValue) {
maxY = lowRefenceValue + getInterval() / 2;
maxX = maxY;
}
// if (maxY < highRefrenceValue) {
// minY = highRefrenceValue - getInterval();
// }
@ -530,15 +526,16 @@ class LabViewModel extends ChangeNotifier {
bool isNumeric(String s) {
return num.tryParse(s) != null;
}
num getInterval() {
// return .1;
var maxX = maxY;
if(maxX<1) return .2;
if(maxX >=1.0 && maxX < 5.0) return .3;
if(maxX >=5.0 && maxX < 10.0) return 1.5;
if(maxX >=10.0 && maxX < 50.0) return 2.5;
if(maxX >=50.0 && maxX < 100.0) return 5;
if(maxX >=100.0 && maxX < 200.0) return 10;
if (maxX < 1) return .2;
if (maxX >= 1.0 && maxX < 5.0) return .3;
if (maxX >= 5.0 && maxX < 10.0) return 1.5;
if (maxX >= 10.0 && maxX < 50.0) return 2.5;
if (maxX >= 50.0 && maxX < 100.0) return 5;
if (maxX >= 100.0 && maxX < 200.0) return 10;
return 15;
}
@ -617,7 +614,7 @@ class LabViewModel extends ChangeNotifier {
rangeStart = 40.0;
rangeEnd = 59.0;
break;
// throw ArgumentError('Invalid flag: $flag');
// throw ArgumentError('Invalid flag: $flag');
}
// Clamp input value to 0-100 and map it to the range bounds
@ -845,9 +842,10 @@ class LabViewModel extends ChangeNotifier {
notifyListeners();
}
Future<void> getAiOverviewLabOrders({required PatientLabOrdersResponseModel labOrder, required String loadingText}) async {
// LoadingUtils.showFullScreenLoader(loadingText: "Loading and analysing your data,\nPlease be patient and let the AI do the magic. \nPlease be patient, This might take some time.");
// LoadingUtils.showFullScreenLoader(loadingText: loadingText);
Future<void> getAiOverviewLabOrders({required int langId, required PatientLabOrdersResponseModel labOrder, required String loadingText}) async {
isLabAIAnalysisNeedsToBeShown = true;
notifyListeners();
LoaderBottomSheet.showLoader(
loadingText: loadingText,
showCloseButton: true,
@ -872,32 +870,36 @@ class LabViewModel extends ChangeNotifier {
}
orderData["results"] = testResults;
results.add(orderData);
final result = await labRepo.getPatientAiOverViewLabOrders(results);
final result = await labRepo.getPatientAiOverViewLabOrders(payload: results, langId: langId);
result.fold(
(failure) async {
LoadingUtils.hideFullScreenLoader();
LoaderBottomSheet.hideLoader();
if (isLabAIAnalysisNeedsToBeShown) {
await errorHandlerService.handleError(failure: failure);
}
},
(apiResponse) {
LoadingUtils.hideFullScreenLoader();
LoaderBottomSheet.hideLoader();
if (apiResponse.messageStatus == 2) {
} else if (apiResponse.messageStatus == 1) {
if (isLabAIAnalysisNeedsToBeShown) {
labOrdersResponseByAi = apiResponse.data;
navigationService.push(
MaterialPageRoute(builder: (_) => LabAiAnalysisDetailedPage()),
CustomPageRoute(
direction: AxisDirection.down,
page: LabAiAnalysisDetailedPage(),
),
);
}
}
},
);
LoadingUtils.hideFullScreenLoader();
// LoadingUtils.hideFullScreenLoader();
}
Future<void> getAiOverviewSingleLabResult({required String langId, required LabResult recentLabResult, required String loadingText}) async {
Future<void> getAiOverviewSingleLabResult({required int langId, required LabResult recentLabResult, required String loadingText}) async {
LoaderBottomSheet.showLoader(
loadingText: loadingText,
showCloseButton: true,

@ -215,27 +215,40 @@ class MedicalFileViewModel extends ChangeNotifier {
final result = await medicalFileRepo.getPatientVaccinesList();
result.fold(
// (failure) async => await errorHandlerService.handleError(
// failure: failure,
// onOkPressed: () {
// onError!(failure.message);
// },
// ),
(failure) async {
// onError!(failure.message);
print("❌ Vaccine API Failure: ${failure.message}");
isPatientVaccineListLoading = false;
notifyListeners();
if (onError != null) {
onError(failure.message);
}
},
(apiResponse) {
print("📦 Vaccine API Response - Status: ${apiResponse.messageStatus}, Data: ${apiResponse.data}");
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
print("⚠️ Vaccine API Error: ${apiResponse.errorMessage}");
isPatientVaccineListLoading = false;
notifyListeners();
if (onError != null) {
onError(apiResponse.errorMessage ?? "Unknown error");
}
} else if (apiResponse.messageStatus == 1) {
patientVaccineList = apiResponse.data!;
if (apiResponse.data != null) {
patientVaccineList = apiResponse.data!;
print("✅ Vaccine data loaded: ${patientVaccineList.length} items");
} else {
print("⚠️ Vaccine API returned success but data is null");
}
isPatientVaccineListLoading = false;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
} else {
print("⚠️ Unexpected messageStatus: ${apiResponse.messageStatus}");
isPatientVaccineListLoading = false;
notifyListeners();
}
},
);

@ -1,160 +1,169 @@
import 'dart:convert';
class PatientVaccineResponseModel {
String? setupID;
int? projectID;
int? patientID;
String? setupId;
int? projectId;
int? patientId;
int? invoiceNo;
String? procedureID;
String? procedureId;
String? vaccineName;
Null? vaccineNameN;
dynamic vaccineNameN;
String? invoiceDate;
int? doctorID;
int? clinicID;
int? doctorId;
int? clinicId;
String? firstName;
String? middleName;
String? lastName;
Null? firstNameN;
Null? middleNameN;
Null? lastNameN;
dynamic firstNameN;
dynamic middleNameN;
dynamic lastNameN;
String? dateofBirth;
int? actualDoctorRate;
String? age;
String? clinicName;
Null? decimalDoctorRate;
Null? doctorImageURL;
double? decimalDoctorRate;
dynamic doctorImageUrl;
String? doctorName;
int? doctorRate;
int? doctorStarsRate;
double? doctorStarsRate;
String? doctorTitle;
int? gender;
Null? genderDescription;
Null? invoiceNoVP;
dynamic genderDescription;
dynamic invoiceNoVp;
bool? isActiveDoctorProfile;
bool? isDoctorAllowVedioCall;
bool? isExecludeDoctor;
int? noOfPatientsRate;
String? patientName;
String? projectName;
String? qR;
String? qr;
List<dynamic>? speciality;
String? vaccinationDate;
PatientVaccineResponseModel(
{this.setupID,
this.projectID,
this.patientID,
this.invoiceNo,
this.procedureID,
this.vaccineName,
this.vaccineNameN,
this.invoiceDate,
this.doctorID,
this.clinicID,
this.firstName,
this.middleName,
this.lastName,
this.firstNameN,
this.middleNameN,
this.lastNameN,
this.dateofBirth,
this.actualDoctorRate,
this.age,
this.clinicName,
this.decimalDoctorRate,
this.doctorImageURL,
this.doctorName,
this.doctorRate,
this.doctorStarsRate,
this.doctorTitle,
this.gender,
this.genderDescription,
this.invoiceNoVP,
this.isActiveDoctorProfile,
this.isDoctorAllowVedioCall,
this.isExecludeDoctor,
this.noOfPatientsRate,
this.patientName,
this.projectName,
this.qR,
this.vaccinationDate});
PatientVaccineResponseModel({
this.setupId,
this.projectId,
this.patientId,
this.invoiceNo,
this.procedureId,
this.vaccineName,
this.vaccineNameN,
this.invoiceDate,
this.doctorId,
this.clinicId,
this.firstName,
this.middleName,
this.lastName,
this.firstNameN,
this.middleNameN,
this.lastNameN,
this.dateofBirth,
this.actualDoctorRate,
this.age,
this.clinicName,
this.decimalDoctorRate,
this.doctorImageUrl,
this.doctorName,
this.doctorRate,
this.doctorStarsRate,
this.doctorTitle,
this.gender,
this.genderDescription,
this.invoiceNoVp,
this.isActiveDoctorProfile,
this.isDoctorAllowVedioCall,
this.isExecludeDoctor,
this.noOfPatientsRate,
this.patientName,
this.projectName,
this.qr,
this.speciality,
this.vaccinationDate,
});
factory PatientVaccineResponseModel.fromRawJson(String str) => PatientVaccineResponseModel.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
PatientVaccineResponseModel.fromJson(Map<String, dynamic> json) {
setupID = json['SetupID'];
projectID = json['ProjectID'];
patientID = json['PatientID'];
invoiceNo = json['InvoiceNo'];
procedureID = json['ProcedureID'];
vaccineName = json['VaccineName'];
vaccineNameN = json['VaccineNameN'];
invoiceDate = json['InvoiceDate'];
doctorID = json['DoctorID'];
clinicID = json['ClinicID'];
firstName = json['FirstName'];
middleName = json['MiddleName'];
lastName = json['LastName'];
firstNameN = json['FirstNameN'];
middleNameN = json['MiddleNameN'];
lastNameN = json['LastNameN'];
dateofBirth = json['DateofBirth'];
actualDoctorRate = json['ActualDoctorRate'];
age = json['Age'];
clinicName = json['ClinicName'];
decimalDoctorRate = json['DecimalDoctorRate'];
doctorImageURL = json['DoctorImageURL'];
doctorName = json['DoctorName'];
doctorRate = json['DoctorRate'];
doctorStarsRate = json['DoctorStarsRate'];
doctorTitle = json['DoctorTitle'];
gender = json['Gender'];
genderDescription = json['GenderDescription'];
invoiceNoVP = json['InvoiceNo_VP'];
isActiveDoctorProfile = json['IsActiveDoctorProfile'];
isDoctorAllowVedioCall = json['IsDoctorAllowVedioCall'];
isExecludeDoctor = json['IsExecludeDoctor'];
noOfPatientsRate = json['NoOfPatientsRate'];
patientName = json['PatientName'];
projectName = json['ProjectName'];
qR = json['QR'];
vaccinationDate = json['VaccinationDate'];
}
factory PatientVaccineResponseModel.fromJson(Map<String, dynamic> json) => PatientVaccineResponseModel(
setupId: json["SetupID"],
projectId: json["ProjectID"],
patientId: json["PatientID"],
invoiceNo: json["InvoiceNo"],
procedureId: json["ProcedureID"],
vaccineName: json["VaccineName"],
vaccineNameN: json["VaccineNameN"],
invoiceDate: json["InvoiceDate"],
doctorId: json["DoctorID"],
clinicId: json["ClinicID"],
firstName: json["FirstName"],
middleName: json["MiddleName"],
lastName: json["LastName"],
firstNameN: json["FirstNameN"],
middleNameN: json["MiddleNameN"],
lastNameN: json["LastNameN"],
dateofBirth: json["DateofBirth"],
actualDoctorRate: json["ActualDoctorRate"],
age: json["Age"],
clinicName: json["ClinicName"],
decimalDoctorRate: json["DecimalDoctorRate"]?.toDouble(),
doctorImageUrl: json["DoctorImageURL"],
doctorName: json["DoctorName"],
doctorRate: json["DoctorRate"],
doctorStarsRate: json["DoctorStarsRate"]?.toDouble(),
doctorTitle: json["DoctorTitle"],
gender: json["Gender"],
genderDescription: json["GenderDescription"],
invoiceNoVp: json["InvoiceNo_VP"],
isActiveDoctorProfile: json["IsActiveDoctorProfile"],
isDoctorAllowVedioCall: json["IsDoctorAllowVedioCall"],
isExecludeDoctor: json["IsExecludeDoctor"],
noOfPatientsRate: json["NoOfPatientsRate"],
patientName: json["PatientName"],
projectName: json["ProjectName"],
qr: json["QR"],
speciality: json["Speciality"] == null ? [] : List<dynamic>.from(json["Speciality"]!.map((x) => x)),
vaccinationDate: json["VaccinationDate"],
);
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['SetupID'] = this.setupID;
data['ProjectID'] = this.projectID;
data['PatientID'] = this.patientID;
data['InvoiceNo'] = this.invoiceNo;
data['ProcedureID'] = this.procedureID;
data['VaccineName'] = this.vaccineName;
data['VaccineNameN'] = this.vaccineNameN;
data['InvoiceDate'] = this.invoiceDate;
data['DoctorID'] = this.doctorID;
data['ClinicID'] = this.clinicID;
data['FirstName'] = this.firstName;
data['MiddleName'] = this.middleName;
data['LastName'] = this.lastName;
data['FirstNameN'] = this.firstNameN;
data['MiddleNameN'] = this.middleNameN;
data['LastNameN'] = this.lastNameN;
data['DateofBirth'] = this.dateofBirth;
data['ActualDoctorRate'] = this.actualDoctorRate;
data['Age'] = this.age;
data['ClinicName'] = this.clinicName;
data['DecimalDoctorRate'] = this.decimalDoctorRate;
data['DoctorImageURL'] = this.doctorImageURL;
data['DoctorName'] = this.doctorName;
data['DoctorRate'] = this.doctorRate;
data['DoctorStarsRate'] = this.doctorStarsRate;
data['DoctorTitle'] = this.doctorTitle;
data['Gender'] = this.gender;
data['GenderDescription'] = this.genderDescription;
data['InvoiceNo_VP'] = this.invoiceNoVP;
data['IsActiveDoctorProfile'] = this.isActiveDoctorProfile;
data['IsDoctorAllowVedioCall'] = this.isDoctorAllowVedioCall;
data['IsExecludeDoctor'] = this.isExecludeDoctor;
data['NoOfPatientsRate'] = this.noOfPatientsRate;
data['PatientName'] = this.patientName;
data['ProjectName'] = this.projectName;
data['QR'] = this.qR;
data['VaccinationDate'] = this.vaccinationDate;
return data;
}
Map<String, dynamic> toJson() => {
"SetupID": setupId,
"ProjectID": projectId,
"PatientID": patientId,
"InvoiceNo": invoiceNo,
"ProcedureID": procedureId,
"VaccineName": vaccineName,
"VaccineNameN": vaccineNameN,
"InvoiceDate": invoiceDate,
"DoctorID": doctorId,
"ClinicID": clinicId,
"FirstName": firstName,
"MiddleName": middleName,
"LastName": lastName,
"FirstNameN": firstNameN,
"MiddleNameN": middleNameN,
"LastNameN": lastNameN,
"DateofBirth": dateofBirth,
"ActualDoctorRate": actualDoctorRate,
"Age": age,
"ClinicName": clinicName,
"DecimalDoctorRate": decimalDoctorRate,
"DoctorImageURL": doctorImageUrl,
"DoctorName": doctorName,
"DoctorRate": doctorRate,
"DoctorStarsRate": doctorStarsRate,
"DoctorTitle": doctorTitle,
"Gender": gender,
"GenderDescription": genderDescription,
"InvoiceNo_VP": invoiceNoVp,
"IsActiveDoctorProfile": isActiveDoctorProfile,
"IsDoctorAllowVedioCall": isDoctorAllowVedioCall,
"IsExecludeDoctor": isExecludeDoctor,
"NoOfPatientsRate": noOfPatientsRate,
"PatientName": patientName,
"ProjectName": projectName,
"QR": qr,
"Speciality": speciality == null ? [] : List<dynamic>.from(speciality!.map((x) => x)),
"VaccinationDate": vaccinationDate,
};
}

@ -0,0 +1,30 @@
class AppointmentRatedResponseModel {
int? appointmentNo;
int? clinicID;
int? doctorID;
bool? isAllowedToRate;
bool? isAppointmentRated;
int? projectID;
AppointmentRatedResponseModel({this.appointmentNo, this.clinicID, this.doctorID, this.isAllowedToRate, this.isAppointmentRated, this.projectID});
AppointmentRatedResponseModel.fromJson(Map<String, dynamic> json) {
appointmentNo = json['AppointmentNo'];
clinicID = json['ClinicID'];
doctorID = json['DoctorID'];
isAllowedToRate = json['IsAllowedToRate'];
isAppointmentRated = json['IsAppointmentRated'];
projectID = json['ProjectID'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['AppointmentNo'] = this.appointmentNo;
data['ClinicID'] = this.clinicID;
data['DoctorID'] = this.doctorID;
data['IsAllowedToRate'] = this.isAllowedToRate;
data['IsAppointmentRated'] = this.isAppointmentRated;
data['ProjectID'] = this.projectID;
return data;
}
}

@ -75,9 +75,7 @@ class HospitalsModel {
}
String? getName(bool isArabic) {
if (isArabic) {
return "$nameN";
}
return name;
}

@ -51,6 +51,7 @@ class PatientAppointmentHistoryResponseModel {
bool? isExecludeDoctor;
dynamic isFollowup;
bool? isLiveCareAppointment;
bool? isLiveCareClinic;
bool? isInOutPatient;
bool? isMedicalReportRequested;
bool? isOnlineCheckedIN;
@ -128,6 +129,7 @@ class PatientAppointmentHistoryResponseModel {
this.isExecludeDoctor,
this.isFollowup,
this.isLiveCareAppointment,
this.isLiveCareClinic,
this.isMedicalReportRequested,
this.isOnlineCheckedIN,
this.latitude,
@ -205,6 +207,7 @@ class PatientAppointmentHistoryResponseModel {
isExecludeDoctor = json['IsExecludeDoctor'];
isFollowup = json['IsFollowup'];
isLiveCareAppointment = json['IsLiveCareAppointment'];
isLiveCareClinic = json['IsLiveCareClinic'];
isInOutPatient = json['IsInOutPatient'];
isMedicalReportRequested = json['IsMedicalReportRequested'];
isOnlineCheckedIN = json['IsOnlineCheckedIN'];

@ -1,4 +1,48 @@
class PatientQueueDetails {
Data? patientQueueData;
PatientQueueDetails({this.patientQueueData});
PatientQueueDetails.fromJson(Map<String, dynamic> json) {
patientQueueData = json['data'] != null ? new Data.fromJson(json['data']) : null;
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
if (this.patientQueueData != null) {
data['data'] = this.patientQueueData!.toJson();
}
return data;
}
}
class Data {
List<PatientInQueueList>? patientInQueueList;
int? isClinicConfigured;
Data({this.patientInQueueList, this.isClinicConfigured});
Data.fromJson(Map<String, dynamic> json) {
if (json['patientInQueueList'] != null) {
patientInQueueList = <PatientInQueueList>[];
json['patientInQueueList'].forEach((v) {
patientInQueueList!.add(new PatientInQueueList.fromJson(v));
});
}
isClinicConfigured = json['isClinicConfigured'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
if (this.patientInQueueList != null) {
data['patientInQueueList'] = this.patientInQueueList!.map((v) => v.toJson()).toList();
}
data['isClinicConfigured'] = this.isClinicConfigured;
return data;
}
}
class PatientInQueueList {
int? patientID;
String? patientName;
String? queueNo;
@ -7,16 +51,14 @@ class PatientQueueDetails {
String? calledOn;
bool? servingNow;
PatientQueueDetails(
{this.patientID,
PatientInQueueList({this.patientID,
this.patientName,
this.queueNo,
this.callType,
this.roomNo,
this.calledOn,
this.servingNow});
this.calledOn, this.servingNow});
PatientQueueDetails.fromJson(Map<String, dynamic> json) {
PatientInQueueList.fromJson(Map<String, dynamic> json) {
patientID = json['patientID'];
patientName = json['patientName'];
queueNo = json['queueNo'];
@ -38,3 +80,47 @@ class PatientQueueDetails {
return data;
}
}
// class PatientQueueDetails {
// int? patientID;
// String? patientName;
// String? queueNo;
// int? callType;
// String? roomNo;
// String? calledOn;
// bool? servingNow;
// int? isClinicConfigured;
//
// PatientQueueDetails(
// {this.patientID,
// this.patientName,
// this.queueNo,
// this.callType,
// this.roomNo,
// this.calledOn,
// this.servingNow, this.isClinicConfigured});
//
// PatientQueueDetails.fromJson(Map<String, dynamic> json) {
// patientID = json['patientID'];
// patientName = json['patientName'];
// queueNo = json['queueNo'];
// callType = json['callType'];
// roomNo = json['roomNo'];
// calledOn = json['calledOn'];
// servingNow = json['servingNow'];
// isClinicConfigured = json['isClinicConfigured'];
// }
//
// Map<String, dynamic> toJson() {
// final Map<String, dynamic> data = Map<String, dynamic>();
// data['patientID'] = this.patientID;
// data['patientName'] = this.patientName;
// data['queueNo'] = this.queueNo;
// data['callType'] = this.callType;
// data['roomNo'] = this.roomNo;
// data['calledOn'] = this.calledOn;
// data['servingNow'] = this.servingNow;
// data['isClinicConfigured'] = this.isClinicConfigured;
// return data;
// }
// }

@ -9,6 +9,7 @@ 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/core/utils/utils.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_favorite_doctors_list.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/appointment_rated_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/rate_appointment_resp_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ask_doctor_request_type_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart';
@ -76,11 +77,18 @@ abstract class MyAppointmentsRepo {
Future<Either<Failure, GenericApiModel<void>>> sendDoctorRate(
int rate, int appointmentNo, int projectID, int doctorID, int clinicID, String note, String appoDate, String docName, String projectName, String clinicName);
Future<Either<Failure, GenericApiModel<List<PatientQueueDetails>>>> getPatientAppointmentQueueDetails({required int appointmentNo, required int patientID});
Future<Either<Failure, GenericApiModel<PatientQueueDetails>>> getPatientAppointmentQueueDetails({required int appointmentNo, required int patientID, required int clinicID});
Future<Either<Failure, GenericApiModel<dynamic>>> convertPatientToCash({required int projectID});
Future<Either<Failure, GenericApiModel<dynamic>>> sendPatientUpdateRequest();
Future<Either<Failure, GenericApiModel<dynamic>>> getDoctorsRatingCheck({
required int doctorID,
required int projectID,
required int clinicID,
required int appointmentNo,
});
}
class MyAppointmentsRepoImp implements MyAppointmentsRepo {
@ -825,19 +833,23 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo {
}
@override
Future<Either<Failure, GenericApiModel<List<PatientQueueDetails>>>> getPatientAppointmentQueueDetails({required int appointmentNo, required int patientID}) async {
Map<String, dynamic> mapDevice = {"appointmentNo": appointmentNo, "patientID": patientID, "apiKey": "EE17D21C7943485D9780223CCE55DCE5"};
Future<Either<Failure, GenericApiModel<PatientQueueDetails>>> getPatientAppointmentQueueDetails({required int appointmentNo, required int patientID, required int clinicID}) async {
Map<String, dynamic> mapDevice = {"appointmentNo": appointmentNo, "patientID": patientID, "clinicID": clinicID, "apiKey": "EE17D21C7943485D9780223CCE55DCE5"};
try {
GenericApiModel<List<PatientQueueDetails>>? apiResponse;
GenericApiModel<PatientQueueDetails>? apiResponse;
Failure? failure;
await apiClient.post(ApiConsts.QLINE_URL, onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
}, onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['data'];
final queueList = list.map((item) => PatientQueueDetails.fromJson(item as Map<String, dynamic>)).toList().cast<PatientQueueDetails>();
// final list = response['data'];
final queueData = response;
// final queueList = list.map((item) => PatientQueueDetails.fromJson(item as Map<String, dynamic>)).toList().cast<PatientQueueDetails>();
final queueList = PatientQueueDetails.fromJson(queueData);
apiResponse = GenericApiModel<List<PatientQueueDetails>>(
queueList.patientQueueData!.isClinicConfigured = 1;
apiResponse = GenericApiModel<PatientQueueDetails>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
@ -1077,4 +1089,50 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<AppointmentRatedResponseModel>>> getDoctorsRatingCheck({
required int doctorID,
required int projectID,
required int clinicID,
required int appointmentNo,
}) async {
Map<String, dynamic> mapDevice = {
"DoctorID": doctorID,
"ProjectID": projectID,
"ClinicID": clinicID,
"AppointmentNo": appointmentNo,
};
try {
GenericApiModel<AppointmentRatedResponseModel>? apiResponse;
Failure? failure;
await apiClient.post(
GET_DOCTORS_RATING_CHECK,
body: mapDevice,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
AppointmentRatedResponseModel appointmentRatedResponseModel = AppointmentRatedResponseModel.fromJson(response['DoctorsRatingResponseModel'][0]);
apiResponse = GenericApiModel<AppointmentRatedResponseModel>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: appointmentRatedResponseModel,
);
} 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()));
}
}
}

@ -7,6 +7,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/features/book_appointments/models/resp_models/get_favorite_doctors_list.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/appointemnet_filters.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/appointment_rated_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ask_doctor_request_type_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
@ -54,8 +55,8 @@ class MyAppointmentsViewModel extends ChangeNotifier {
bool isAppointmentQueueDetailsLoading = false;
bool isPatientHasQueueAppointment = false;
int currentQueueStatus = 0;
List<PatientQueueDetails> patientQueueDetailsList = [];
late PatientQueueDetails currentPatientQueueDetails;
List<PatientInQueueList> patientQueueDetailsList = [];
late PatientInQueueList currentPatientQueueDetails;
List<PatientAppointmentHistoryResponseModel> patientAppointmentsHistoryList = [];
List<PatientAppointmentHistoryResponseModel> filteredAppointmentList = [];
@ -87,6 +88,11 @@ class MyAppointmentsViewModel extends ChangeNotifier {
bool isShowNotEligibleDialog = false;
String notEligibleErrorMsg = "";
bool isAppointmentAlreadyRated = false;
AppointmentRatedResponseModel? appointmentRatedResponseModel;
bool isAppointmentRatedResponseLoading = false;
MyAppointmentsViewModel({required this.myAppointmentsRepo, required this.errorHandlerService, required this.appState});
void onTabChange(int index) {
@ -308,14 +314,14 @@ class MyAppointmentsViewModel extends ChangeNotifier {
// Build grouped list immediately so the UI has data for the default (By Clinic) view.
_groupAppointmentsByClinicAndHospital();
if (patientArrivedAppointmentsHistoryList.isNotEmpty) {
isPatientHasQueueAppointment = false;
notifyListeners();
if (Utils.isDateToday(DateUtil.convertStringToDate(patientArrivedAppointmentsHistoryList.first.appointmentDate))) {
// getPatientAppointmentQueueDetails(appointmentNo: patientArrivedAppointmentsHistoryList.first.appointmentNo, patientID: patientArrivedAppointmentsHistoryList.first.patientID);
getPatientAppointmentQueueDetails();
}
// if (patientArrivedAppointmentsHistoryList.isNotEmpty) {
isPatientHasQueueAppointment = false;
notifyListeners();
if (Utils.isDateToday(DateUtil.convertStringToDate(patientArrivedAppointmentsHistoryList.first.appointmentDate))) {
// getPatientAppointmentQueueDetails(appointmentNo: patientArrivedAppointmentsHistoryList.first.appointmentNo, patientID: patientArrivedAppointmentsHistoryList.first.patientID);
getPatientAppointmentQueueDetails();
}
// }
debugPrint('Upcoming Appointments: ${patientUpcomingAppointmentsHistoryList.length}');
debugPrint('Arrived Appointments: ${patientArrivedAppointmentsHistoryList.length}');
@ -670,9 +676,9 @@ class MyAppointmentsViewModel extends ChangeNotifier {
Future<void> getPatientFavouriteDoctors({bool forceRefresh = false, Function(dynamic)? onSuccess, Function(String)? onError}) async {
// If data is already fetched and not forcing refresh, skip API call
if (isFavouriteDoctorsDataFetched && !forceRefresh) {
return;
}
// if (isFavouriteDoctorsDataFetched && !forceRefresh) {
// return;
// }
isPatientFavouriteDoctorsLoading = true;
patientFavouriteDoctorsList.clear();
@ -923,8 +929,11 @@ class MyAppointmentsViewModel extends ChangeNotifier {
//TODO: Discuss With Haroon, Is the User Has no data it return No Element Bad State;
isPatientHasQueueAppointment = false;
isAppointmentQueueDetailsLoading = true;
patientQueueDetailsList.clear();
notifyListeners();
final result = await myAppointmentsRepo.getPatientAppointmentQueueDetails(
clinicID: patientArrivedAppointmentsHistoryList.first.clinicID,
appointmentNo: patientArrivedAppointmentsHistoryList.first.appointmentNo, patientID: patientArrivedAppointmentsHistoryList.first.patientID);
isAppointmentQueueDetailsLoading = false;
@ -940,23 +949,53 @@ class MyAppointmentsViewModel extends ChangeNotifier {
if (apiResponse.messageStatus == 2) {
onError!(apiResponse.errorMessage!);
} else if (apiResponse.messageStatus == 1) {
if (apiResponse.data != null && apiResponse.data!.isNotEmpty) {
isPatientHasQueueAppointment = true;
patientQueueDetailsList = apiResponse.data!;
for (var element in patientQueueDetailsList) {
if (element.patientID == patientArrivedAppointmentsHistoryList.first.patientID) {
currentPatientQueueDetails = element;
currentQueueStatus = element.callType!;
// currentQueueStatus = 2;
break;
if (apiResponse.data != null) {
patientQueueDetailsList.clear();
patientQueueDetailsList = apiResponse.data!.patientQueueData!.patientInQueueList!;
if (apiResponse.data!.patientQueueData!.isClinicConfigured == 1) {
currentPatientQueueDetails = PatientInQueueList();
isPatientHasQueueAppointment = true;
for (var element in patientQueueDetailsList) {
if (element.patientID == patientArrivedAppointmentsHistoryList.first.patientID) {
currentPatientQueueDetails = element;
currentQueueStatus = element.callType!;
// currentQueueStatus = 2;
break;
}
}
// patientQueueDetailsList.first.callType = 1;
patientQueueDetailsList.removeWhere((element) => element.patientID == patientArrivedAppointmentsHistoryList.first.patientID);
} else {
patientQueueDetailsList.clear();
isPatientHasQueueAppointment = false;
}
// patientQueueDetailsList.first.callType = 1;
patientQueueDetailsList.removeWhere((element) => element.patientID == patientArrivedAppointmentsHistoryList.first.patientID);
} else {
patientQueueDetailsList.clear();
isPatientHasQueueAppointment = false;
}
// if (apiResponse.data != null && apiResponse.data!.isNotEmpty) {
// patientQueueDetailsList = apiResponse.data!;
//
// // if (patientQueueDetailsList.first.isClinicConfigured == 1) {
// isPatientHasQueueAppointment = true;
// for (var element in patientQueueDetailsList) {
// if (element.patientID == patientArrivedAppointmentsHistoryList.first.patientID) {
// currentPatientQueueDetails = element;
// currentQueueStatus = element.callType!;
// // currentQueueStatus = 2;
// break;
// }
// }
// // patientQueueDetailsList.first.callType = 1;
// patientQueueDetailsList.removeWhere((element) => element.patientID == patientArrivedAppointmentsHistoryList.first.patientID);
// // } else {
// // isPatientHasQueueAppointment = false;
// // }
// } else {
// isPatientHasQueueAppointment = false;
// }
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse.data);
@ -995,4 +1034,79 @@ class MyAppointmentsViewModel extends ChangeNotifier {
},
);
}
Future<void> getDoctorsRatingCheck({
required int doctorID,
required int projectID,
required int clinicID,
required int appointmentNo,
Function(dynamic)? onSuccess,
Function(String)? onError,
}) async {
isAppointmentRatedResponseLoading = true;
notifyListeners();
final result = await myAppointmentsRepo.getDoctorsRatingCheck(
doctorID: doctorID,
projectID: projectID,
clinicID: clinicID,
appointmentNo: appointmentNo,
);
result.fold(
(failure) async {
// await errorHandlerService.handleError(failure: failure);
if (onError != null) onError(failure.message);
isAppointmentRatedResponseLoading = false;
notifyListeners();
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
if (onError != null) onError(apiResponse.errorMessage ?? "Unknown error");
isAppointmentRatedResponseLoading = false;
notifyListeners();
} else if (apiResponse.messageStatus == 1) {
appointmentRatedResponseModel = apiResponse.data!;
isAppointmentRatedResponseLoading = false;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse.data);
}
}
},
);
}
Future<void> submitDoctorRating(
{required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel,
required int docRate,
required String docNote,
Function(dynamic)? onSuccess,
Function(String)? onError}) async {
final result = await myAppointmentsRepo.sendDoctorRate(
docRate,
patientAppointmentHistoryResponseModel.appointmentNo!,
patientAppointmentHistoryResponseModel.projectID!,
patientAppointmentHistoryResponseModel.doctorID!,
patientAppointmentHistoryResponseModel.clinicID!,
docNote,
patientAppointmentHistoryResponseModel.appointmentDate!,
patientAppointmentHistoryResponseModel.doctorNameObj!,
patientAppointmentHistoryResponseModel.projectName!,
patientAppointmentHistoryResponseModel.clinicName!);
result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),
(apiResponse) {
if (apiResponse.messageStatus == 2) {
onError?.call(apiResponse.errorMessage ?? 'Unknown error');
} else if (apiResponse.messageStatus == 1) {
notifyListeners();
if (onSuccess != null) {
// onSuccess(apiResponse.data);
}
}
},
);
}
}

@ -1,88 +1,93 @@
import 'dart:convert';
class GetInvoicesListResponseModel {
String? setupId;
int? projectID;
int? patientID;
int? projectId;
int? patientId;
int? appointmentNo;
String? appointmentDate;
String? appointmentDateN;
int? clinicID;
int? doctorID;
dynamic appointmentDateN;
int? clinicId;
int? doctorId;
int? invoiceNo;
int? status;
String? arrivedOn;
String? doctorName;
String? doctorNameN;
String? clinicName;
num? decimalDoctorRate;
String? doctorImageURL;
num? doctorRate;
num? patientNumber;
double? decimalDoctorRate;
String? doctorImageUrl;
int? doctorRate;
int? patientNumber;
String? projectName;
GetInvoicesListResponseModel(
{this.setupId,
this.projectID,
this.patientID,
this.appointmentNo,
this.appointmentDate,
this.appointmentDateN,
this.clinicID,
this.doctorID,
this.invoiceNo,
this.status,
this.arrivedOn,
this.doctorName,
this.doctorNameN,
this.clinicName,
this.decimalDoctorRate,
this.doctorImageURL,
this.doctorRate,
this.patientNumber,
this.projectName});
GetInvoicesListResponseModel({
this.setupId,
this.projectId,
this.patientId,
this.appointmentNo,
this.appointmentDate,
this.appointmentDateN,
this.clinicId,
this.doctorId,
this.invoiceNo,
this.status,
this.arrivedOn,
this.doctorName,
this.doctorNameN,
this.clinicName,
this.decimalDoctorRate,
this.doctorImageUrl,
this.doctorRate,
this.patientNumber,
this.projectName,
});
factory GetInvoicesListResponseModel.fromRawJson(String str) => GetInvoicesListResponseModel.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
GetInvoicesListResponseModel.fromJson(Map<String, dynamic> json) {
setupId = json['SetupId'];
projectID = json['ProjectID'];
patientID = json['PatientID'];
appointmentNo = json['AppointmentNo'];
appointmentDate = json['AppointmentDate'];
appointmentDateN = json['AppointmentDateN'];
clinicID = json['ClinicID'];
doctorID = json['DoctorID'];
invoiceNo = json['InvoiceNo'];
status = json['Status'];
arrivedOn = json['ArrivedOn'];
doctorName = json['DoctorName'];
doctorNameN = json['DoctorNameN'];
clinicName = json['ClinicName'];
decimalDoctorRate = json['DecimalDoctorRate'];
doctorImageURL = json['DoctorImageURL'];
doctorRate = json['DoctorRate'];
patientNumber = json['PatientNumber'];
projectName = json['ProjectName'];
}
factory GetInvoicesListResponseModel.fromJson(Map<String, dynamic> json) => GetInvoicesListResponseModel(
setupId: json["SetupId"],
projectId: json["ProjectID"],
patientId: json["PatientID"],
appointmentNo: json["AppointmentNo"],
appointmentDate: json["AppointmentDate"],
appointmentDateN: json["AppointmentDateN"],
clinicId: json["ClinicID"],
doctorId: json["DoctorID"],
invoiceNo: json["InvoiceNo"],
status: json["Status"],
arrivedOn: json["ArrivedOn"],
doctorName: json["DoctorName"],
doctorNameN: json["DoctorNameN"],
clinicName: json["ClinicName"],
decimalDoctorRate: json["DecimalDoctorRate"]?.toDouble(),
doctorImageUrl: json["DoctorImageURL"],
doctorRate: json["DoctorRate"],
patientNumber: json["PatientNumber"],
projectName: json["ProjectName"],
);
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['SetupId'] = this.setupId;
data['ProjectID'] = this.projectID;
data['PatientID'] = this.patientID;
data['AppointmentNo'] = this.appointmentNo;
data['AppointmentDate'] = this.appointmentDate;
data['AppointmentDateN'] = this.appointmentDateN;
data['ClinicID'] = this.clinicID;
data['DoctorID'] = this.doctorID;
data['InvoiceNo'] = this.invoiceNo;
data['Status'] = this.status;
data['ArrivedOn'] = this.arrivedOn;
data['DoctorName'] = this.doctorName;
data['DoctorNameN'] = this.doctorNameN;
data['ClinicName'] = this.clinicName;
data['DecimalDoctorRate'] = this.decimalDoctorRate;
data['DoctorImageURL'] = this.doctorImageURL;
data['DoctorRate'] = this.doctorRate;
data['PatientNumber'] = this.patientNumber;
data['ProjectName'] = this.projectName;
return data;
}
Map<String, dynamic> toJson() => {
"SetupId": setupId,
"ProjectID": projectId,
"PatientID": patientId,
"AppointmentNo": appointmentNo,
"AppointmentDate": appointmentDate,
"AppointmentDateN": appointmentDateN,
"ClinicID": clinicId,
"DoctorID": doctorId,
"InvoiceNo": invoiceNo,
"Status": status,
"ArrivedOn": arrivedOn,
"DoctorName": doctorName,
"DoctorNameN": doctorNameN,
"ClinicName": clinicName,
"DecimalDoctorRate": decimalDoctorRate,
"DoctorImageURL": doctorImageUrl,
"DoctorRate": doctorRate,
"PatientNumber": patientNumber,
"ProjectName": projectName,
};
}

@ -13,6 +13,8 @@ abstract class MyInvoicesRepo {
Future<Either<Failure, GenericApiModel<GetInvoiceDetailsResponseModel>>> getInvoiceDetails({required num appointmentNo, required num invoiceNo, required int projectID});
Future<Either<Failure, GenericApiModel<dynamic>>> sendInvoiceEmail({required num appointmentNo, required int projectID});
Future<Either<Failure, GenericApiModel<dynamic>>> downloadInvoice({required String setupId, required int invoiceNo, required int projectID});
}
class MyInvoicesRepoImp implements MyInvoicesRepo {
@ -36,7 +38,7 @@ class MyInvoicesRepoImp implements MyInvoicesRepo {
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['List_DentalAppointments'];
final list = response['List_AllInvoices'];
final invoicesList = list.map((item) => GetInvoicesListResponseModel.fromJson(item as Map<String, dynamic>)).toList().cast<GetInvoicesListResponseModel>();
@ -138,4 +140,42 @@ class MyInvoicesRepoImp implements MyInvoicesRepo {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel>> downloadInvoice({required String setupId, required int invoiceNo, required int projectID}) async {
Map<String, dynamic> mapDevice = {
"SetupID": setupId,
"ProjectID": projectID,
"InvoiceNo": invoiceNo,
};
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
DOWNLOAD_INVOICE_PDF,
body: mapDevice,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
apiResponse = GenericApiModel(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: response["InvoiceReportPDFContent"],
);
} 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()));
}
}
}

@ -26,6 +26,8 @@ class MyInvoicesViewModel extends ChangeNotifier {
TextEditingController filterSearchController = TextEditingController();
String? selectedFilterItem;
String? downloadInvoicePDFBase64 = "";
MyInvoicesViewModel({required this.myInvoicesRepo, required this.errorHandlerService, required this.navServices});
setInvoicesListLoading() {
@ -68,7 +70,6 @@ class MyInvoicesViewModel extends ChangeNotifier {
Future<void> getInvoiceDetails({required num appointmentNo, required num invoiceNo, required int projectID, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await myInvoicesRepo.getInvoiceDetails(appointmentNo: appointmentNo, invoiceNo: invoiceNo, projectID: projectID);
result.fold(
(failure) async {
isInvoiceDetailsLoading = false;
@ -93,6 +94,32 @@ class MyInvoicesViewModel extends ChangeNotifier {
);
}
Future<void> downloadInvoicePDF({required String setupId, required int invoiceNo, required int projectID, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await myInvoicesRepo.downloadInvoice(setupId: setupId, invoiceNo: invoiceNo, projectID: projectID);
result.fold(
(failure) async {
if (onError != null) {
onError(failure.message);
}
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
if (apiResponse.data != null && apiResponse.data!.isNotEmpty) {
downloadInvoicePDFBase64 = apiResponse.data!;
} else {
downloadInvoicePDFBase64 = "";
}
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
Future<void> sendInvoiceEmail({required num appointmentNo, required int projectID, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await myInvoicesRepo.sendInvoiceEmail(appointmentNo: appointmentNo, projectID: projectID);

@ -33,6 +33,7 @@ class PrescriptionDetailResponseModel {
num? scaleOffset;
String? startDate;
bool? hasReminder;
bool? isHomeMedicineDeliverySupported;
PrescriptionDetailResponseModel(
{this.address,
@ -68,7 +69,7 @@ class PrescriptionDetailResponseModel {
this.sKU,
this.scaleOffset,
this.startDate,
this.hasReminder = false});
this.hasReminder = false, this.isHomeMedicineDeliverySupported});
PrescriptionDetailResponseModel.fromJson(Map<String, dynamic> json) {
address = json['Address'];

@ -99,7 +99,11 @@ class PrescriptionsRepoImp implements PrescriptionsRepo {
// throw Exception("prescription list is empty");
}
final prescriptionOrders = list.map((item) => PrescriptionDetailResponseModel.fromJson(item as Map<String, dynamic>)).toList().cast<PrescriptionDetailResponseModel>();
final List<PrescriptionDetailResponseModel> prescriptionOrders =
list.map((item) => PrescriptionDetailResponseModel.fromJson(item as Map<String, dynamic>)).toList().cast<PrescriptionDetailResponseModel>();
if (prescriptionOrders.isNotEmpty) {
prescriptionOrders.first.isHomeMedicineDeliverySupported = response['IsHomeMedicineDeliverySupported'] ?? false;
}
apiResponse = GenericApiModel<List<PrescriptionDetailResponseModel>>(
messageStatus: messageStatus,

@ -16,6 +16,18 @@ abstract class ProfileSettingsRepo {
Future<Either<Failure, GenericApiModel<dynamic>>> deactivateAccount();
Future<Either<Failure, GenericApiModel<dynamic>>> getProfileSettings();
/// Get patient profile image
Future<Either<Failure, GenericApiModel<dynamic>>> getProfileImage({
required int patientID,
});
/// Upload patient profile image
Future<Either<Failure, GenericApiModel<dynamic>>> uploadProfileImage({
required int patientID,
required String imageData,
required bool isActive,
});
}
class ProfileSettingsRepoImp implements ProfileSettingsRepo {
@ -137,5 +149,89 @@ class ProfileSettingsRepoImp implements ProfileSettingsRepo {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<dynamic>>> getProfileImage({
required int patientID,
}) async {
final Map<String, dynamic> body = {
'PatientID': patientID,
};
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
GET_PROFILE_IMAGE,
body: body,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
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<dynamic>>> uploadProfileImage({
required int patientID,
required String imageData,
required bool isActive,
}) async {
final Map<String, dynamic> body = {
'PatientID': patientID,
'PatientsProfileImageData': imageData,
'IsActive': isActive,
};
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
UPLOAD_PROFILE_IMAGE,
body: body,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
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()));
}
}
}

@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:get_it/get_it.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/features/profile_settings/models/get_patient_info_response_model.dart';
import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_repo.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
@ -41,6 +42,12 @@ class ProfileSettingsViewModel extends ChangeNotifier {
late GetPatientInfoForUpdate getPatientInfoForUpdate;
bool isPatientProfileLoading = false;
// Profile image state
bool isProfileImageLoading = false;
bool isUploadingProfileImage = false;
String? profileImageData;
String? profileImageError;
ProfileSettingsViewModel({
required CacheService cacheService,
required this.profileSettingsRepo,
@ -172,4 +179,116 @@ class ProfileSettingsViewModel extends ChangeNotifier {
isCloseButtonVisible: true,
);
}
// Profile image
Future<void> getProfileImage({
required int patientID,
Function(dynamic)? onSuccess,
Function(String)? onError,
bool forceRefresh = false, // Add flag to force refresh
}) async {
// Skip API call if image is already loaded and not forcing refresh
if (!forceRefresh && GetIt.instance<AppState>().getProfileImageData != null) {
// Use cached data
profileImageData = GetIt.instance<AppState>().getProfileImageData;
if (onSuccess != null) {
onSuccess(profileImageData);
}
return;
}
isProfileImageLoading = true;
profileImageError = null;
notifyListeners();
final result = await profileSettingsRepo.getProfileImage(patientID: patientID);
result.fold(
(failure) {
isProfileImageLoading = false;
profileImageError = failure.message;
notifyListeners();
if (onError != null) {
onError(failure.message);
}
},
(response) {
isProfileImageLoading = false;
// Extract image data from response
if (response.data != null && response.data['Patient_GetProfileImageDataList'] != null) {
var imageList = response.data['Patient_GetProfileImageDataList'];
if (imageList is List && imageList.isNotEmpty) {
profileImageData = imageList[0]['PatientsProfileImageData'];
// Store in AppState for global access
GetIt.instance<AppState>().setProfileImageData = profileImageData;
} else {
profileImageData = null;
GetIt.instance<AppState>().setProfileImageData = null;
}
} else {
profileImageData = null;
GetIt.instance<AppState>().setProfileImageData = null;
}
notifyListeners();
onSuccess?.call(response.data);
},
);
}
/// Clear cached profile image data
void clearProfileImageCache() {
profileImageData = null;
GetIt.instance<AppState>().setProfileImageData = null;
notifyListeners();
}
/// Sync profile image data from AppState
void syncProfileImageFromAppState() {
final appStateImageData = GetIt.instance<AppState>().getProfileImageData;
if (appStateImageData != null && appStateImageData.isNotEmpty) {
if (profileImageData != appStateImageData) {
profileImageData = appStateImageData;
notifyListeners();
}
}
}
Future<void> uploadProfileImage({
required int patientID,
required String imageData,
Function(dynamic)? onSuccess,
Function(String)? onError,
}) async {
isUploadingProfileImage = true;
profileImageError = null;
notifyListeners();
final result = await profileSettingsRepo.uploadProfileImage(
patientID: patientID,
imageData: imageData,
isActive: true,
);
result.fold(
(failure) {
isUploadingProfileImage = false;
profileImageError = failure.message;
notifyListeners();
if (onError != null) {
onError(failure.message);
} else {
errorHandlerService.handleError(failure: failure);
}
},
(response) {
isUploadingProfileImage = false;
profileImageData = imageData;
// Store in AppState for global access
GetIt.instance<AppState>().setProfileImageData = imageData;
notifyListeners();
onSuccess?.call(response.data);
},
);
}
}

@ -0,0 +1,22 @@
import 'dart:convert';
class RadiologyAiAnalysisResponse {
String? summary;
RadiologyAiAnalysisResponse({
this.summary,
});
factory RadiologyAiAnalysisResponse.fromRawJson(String str) => RadiologyAiAnalysisResponse.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory RadiologyAiAnalysisResponse.fromJson(Map<String, dynamic> json) => RadiologyAiAnalysisResponse(
summary: json["summary"],
);
Map<String, dynamic> toJson() => {
"summary": summary,
};
}

@ -6,6 +6,7 @@ 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/authentication/models/resp_models/authenticated_user_resp_model.dart';
import 'package:hmg_patient_app_new/features/radiology/models/resp_models/patient_radiology_response_model.dart';
import 'package:hmg_patient_app_new/features/radiology/models/resp_models/radiology_ai_analysis_response_model.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
abstract class RadiologyRepo {
@ -17,6 +18,8 @@ abstract class RadiologyRepo {
{required PatientRadiologyResponseModel patientRadiologyResponseModel, required AuthenticatedUser authenticatedUser});
Future<Either<Failure, GenericApiModel<List<PatientRadiologyResponseModel>>>> getPatientRadiologyOrderByAppointment({required num appointmentNo, required num projectID});
Future<Either<Failure, GenericApiModel<RadiologyAiAnalysisResponse>>> getRadiologyAiAnalysis({required String reportData, required int langId,});
}
class RadiologyRepoImp implements RadiologyRepo {
@ -218,4 +221,50 @@ class RadiologyRepoImp implements RadiologyRepo {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<RadiologyAiAnalysisResponse>>> getRadiologyAiAnalysis({required String reportData, required int langId}) async {
Map<String, dynamic> payload = {
"text": reportData,
"language_id": langId
};
try {
GenericApiModel<RadiologyAiAnalysisResponse>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.getAiRadiologyAnalysis,
body: payload,
isExternal: true,
isAllowAny: true,
apiHeaders: {
'Accept-Language': 'en-US,en;q=0.7',
'Content-Type': 'application/json',
'X-API-KEY': '9d9d2ac4c1fc302299dbb0fbb1b7fea1',
'accept': 'application/json',
},
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
apiResponse = GenericApiModel<RadiologyAiAnalysisResponse>(
messageStatus: 1,
statusCode: statusCode,
errorMessage: null,
data: RadiologyAiAnalysisResponse.fromJson(response),
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
}

@ -1,12 +1,16 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/features/authentication/models/resp_models/authenticated_user_resp_model.dart';
import 'package:hmg_patient_app_new/features/radiology/models/resp_models/radiology_ai_analysis_response_model.dart';
import 'package:hmg_patient_app_new/features/radiology/radiology_repo.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/radiology/radiology_result_page.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'models/resp_models/patient_radiology_response_model.dart';
@ -14,6 +18,7 @@ import 'models/resp_models/patient_radiology_response_model.dart';
class RadiologyViewModel extends ChangeNotifier {
bool isRadiologyOrdersLoading = false;
bool isRadiologyPDFReportLoading = false;
bool isRadiologyAIAnalysisNeedsToBeShown = true;
RadiologyRepo radiologyRepo;
ErrorHandlerService errorHandlerService;
@ -25,6 +30,8 @@ class RadiologyViewModel extends ChangeNotifier {
String radiologyImageURL = "";
String patientRadiologyReportPDFBase64 = "";
RadiologyAiAnalysisResponse? radiologyAiAnalysisResponse;
late List<String> _radiologySuggestionsList = [];
List<String> get radiologySuggestions => _radiologySuggestionsList;
@ -120,8 +127,7 @@ class RadiologyViewModel extends ChangeNotifier {
);
}
Future<void> getRadiologyImage(
{required PatientRadiologyResponseModel patientRadiologyResponseModel, Function(dynamic)? onSuccess, Function(String)? onError}) async {
Future<void> getRadiologyImage({required PatientRadiologyResponseModel patientRadiologyResponseModel, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await radiologyRepo.getRadiologyImage(patientRadiologyResponseModel: patientRadiologyResponseModel);
result.fold(
@ -142,12 +148,8 @@ class RadiologyViewModel extends ChangeNotifier {
}
Future<void> getRadiologyPDF(
{required PatientRadiologyResponseModel patientRadiologyResponseModel,
required AuthenticatedUser authenticatedUser,
Function(dynamic)? onSuccess,
Function(String)? onError}) async {
final result =
await radiologyRepo.getRadiologyReportPDF(patientRadiologyResponseModel: patientRadiologyResponseModel, authenticatedUser: authenticatedUser);
{required PatientRadiologyResponseModel patientRadiologyResponseModel, required AuthenticatedUser authenticatedUser, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await radiologyRepo.getRadiologyReportPDF(patientRadiologyResponseModel: patientRadiologyResponseModel, authenticatedUser: authenticatedUser);
result.fold(
(failure) async => await errorHandlerService.handleError(
@ -211,4 +213,46 @@ class RadiologyViewModel extends ChangeNotifier {
}
notifyListeners();
}
void setIsRadiologyAIAnalysisNeedsToBeShown(bool value) {
isRadiologyAIAnalysisNeedsToBeShown = value;
notifyListeners();
}
void closeRadiologyAIAnalysis() {
radiologyAiAnalysisResponse = null;
notifyListeners();
}
Future<void> getRadiologyAiAnalysis({required String reportData, required String loadingText, required int langId}) async {
LoaderBottomSheet.showLoader(
loadingText: loadingText,
showCloseButton: true,
onCloseTap: () {
setIsRadiologyAIAnalysisNeedsToBeShown(false);
},
);
final result = await radiologyRepo.getRadiologyAiAnalysis(reportData: reportData, langId: langId);
result.fold(
(failure) async {
LoaderBottomSheet.hideLoader();
if (isRadiologyAIAnalysisNeedsToBeShown) {
await errorHandlerService.handleError(failure: failure);
}
},
(apiResponse) {
LoaderBottomSheet.hideLoader();
if (apiResponse.messageStatus == 1) {
if (isRadiologyAIAnalysisNeedsToBeShown) {
radiologyAiAnalysisResponse = apiResponse.data;
notifyListeners();
}
}
},
);
LoaderBottomSheet.hideLoader();
}
}

@ -13,13 +13,13 @@ class TodoSectionViewModel extends ChangeNotifier {
String? notificationsCount = "0";
initializeTodoSectionViewModel() async {
if (isAncillaryOrdersNeedReloading) {
patientAncillaryOrdersList.clear();
isAncillaryOrdersLoading = true;
// if (isAncillaryOrdersNeedReloading) {
patientAncillaryOrdersList.clear();
isAncillaryOrdersLoading = true;
isAncillaryDetailsProceduresLoading = true;
notificationsCount = "0";
getPatientOnlineAncillaryOrderList();
}
// }
getPatientDashboard();
}
@ -63,13 +63,14 @@ class TodoSectionViewModel extends ChangeNotifier {
}
Future<void> getPatientOnlineAncillaryOrderList({Function(dynamic)? onSuccess, Function(String)? onError}) async {
if (!isAncillaryOrdersNeedReloading) {
return;
}
// if (!isAncillaryOrdersNeedReloading) {
// return;
// }
patientAncillaryOrdersList.clear();
isAncillaryOrdersLoading = true;
notifyListeners();
final result = await todoSectionRepo.getOnlineAncillaryOrderList();
result.fold(

@ -0,0 +1,27 @@
import 'dart:convert';
import 'dart:io';
import 'dart:ui';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
class RemoteFileLoader extends AssetLoader {
@override
Future<Map<String, dynamic>?> load(String path, Locale locale) async {
final directory = await getApplicationDocumentsDirectory();
// easy_localization uses toLanguageTag() which gives 'en-US' or 'ar-SA'
final fileName = "${locale.toLanguageTag()}.json";
final file = File("${directory.path}/$fileName");
if (await file.exists()) {
final String jsonString = await file.readAsString();
return jsonDecode(jsonString);
} else {
// If the download failed or hasn't happened yet, load from local assets
// path is the 'path' property from the EasyLocalization widget
final localData = await rootBundle.loadString('$path/$fileName');
return jsonDecode(localData);
}
}
}

@ -0,0 +1,36 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
class TranslationService {
// Replace with your actual hosting URL
static final String baseUrl = ApiConsts.googleCloudStorageENTranslationFileBaseURL;
static Future<void> downloadTranslations() async {
final directory = await getApplicationDocumentsDirectory();
// Ensure these match your locale codes exactly (en-US, ar-SA)
List<String> files = ["en-US.json", "ar-SA.json"];
for (String fileName in files) {
try {
final String timestamp = DateTime.now().millisecondsSinceEpoch.toString();
String url = "$baseUrl/$fileName?v=$timestamp";
debugPrint(url);
// final response = await http.get(Uri.parse("$baseUrl/$fileName"));
final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
final file = File("${directory.path}/$fileName");
await file.writeAsBytes(response.bodyBytes);
} else {
print("Failed to download $fileName: Status ${response.statusCode}");
}
} catch (e) {
print("Network error while downloading $fileName: $e");
}
}
}
}

File diff suppressed because it is too large Load Diff

@ -302,6 +302,12 @@ abstract class LocaleKeys {
static const medicinesSubtitle = 'medicinesSubtitle';
static const vitalSigns = 'vitalSigns';
static const vitalSignsSubTitle = 'vitalSignsSubTitle';
static const vitalSignNormal = 'vitalSignNormal';
static const vitalSignLow = 'vitalSignLow';
static const vitalSignHigh = 'vitalSignHigh';
static const vitalSignObese = 'vitalSignObese';
static const vitalSignOverweight = 'vitalSignOverweight';
static const vitalSignUnderweight = 'vitalSignUnderweight';
static const myMedical = 'myMedical';
static const myMedicalSubtitle = 'myMedicalSubtitle';
static const myDoctor = 'myDoctor';
@ -716,6 +722,11 @@ abstract class LocaleKeys {
static const bloodDonation = 'bloodDonation';
static const bloodDonationInfo = 'bloodDonationInfo';
static const bloodInstruction = 'bloodInstruction';
static const aboutBloodDonation = 'aboutBloodDonation';
static const bloodDonationDescription = 'bloodDonationDescription';
static const bloodDonationBenefit1 = 'bloodDonationBenefit1';
static const bloodDonationBenefit2 = 'bloodDonationBenefit2';
static const bloodDonationBenefit3 = 'bloodDonationBenefit3';
static const viewTerms = 'viewTerms';
static const wantConnectHmgNetwork = 'wantConnectHmgNetwork';
static const failedToAccessHmgServices = 'failedToAccessHmgServices';
@ -1646,5 +1657,59 @@ abstract class LocaleKeys {
static const twoWaySubtitle = 'twoWaySubtitle';
static const toHospitalSubtitle = 'toHospitalSubtitle';
static const fromHospitalSubtitle = 'fromHospitalSubtitle';
static const clinicSchedule = 'clinicSchedule';
static const doctorAddedToFavourite = 'doctorAddedToFavourite';
static const doctorRemovedFromFavourite = 'doctorRemovedFromFavourite';
static const addNewFavorite = 'addNewFavorite';
static const favourites = 'favourites';
static const pendingAncillaryOrders = 'pendingAncillaryOrders';
static const liveCarePermissions = 'liveCarePermissions';
static const containsImage = 'containsImage';
static const servicesSelected = 'servicesSelected';
static const selectMedicalFileFromFamily = 'selectMedicalFileFromFamily';
static const anyFileFromHMG = 'anyFileFromHMG';
static const noDistance = 'noDistance';
static const enterAmount = 'enterAmount';
static const selectHospitalContinue = 'selectHospitalContinue';
static const year = 'year';
static const notifyPrescription = 'notifyPrescription';
static const doctorRatingAppointment = 'doctorRatingAppointment';
static const ratingSubmitted = 'ratingSubmitted';
static const rateTheDoctor = 'rateTheDoctor';
static const downloadInvoice = 'downloadInvoice';
static const approvalDetails = 'approvalDetails';
static const obese = 'obese';
static const overWeight = 'overWeight';
static const underWeight = 'underWeight';
static const nearestGate = 'nearestGate';
static const RRTDDetails = 'RRTDDetails';
static const normalMapType = 'normalMapType';
static const satelliteMapType = 'satelliteMapType';
static const terrainMapType = 'terrainMapType';
static const hybridMapType = 'hybridMapType';
static const openCamera = 'openCamera';
static const openGallery = 'openGallery';
static const openFiles = 'openFiles';
static const generateAiAnalysisRadResult = 'generateAiAnalysisRadResult';
static const grantLocationPermission = 'grantLocationPermission';
static const NoBookedAppointments = 'NoBookedAppointments';
static const aiDisclaimerRad = 'aiDisclaimerRad';
static const imageSizeTooLarge = 'imageSizeTooLarge';
static const profileImageUpdatedSuccessfully = 'profileImageUpdatedSuccessfully';
static const failedToProcessImage = 'failedToProcessImage';
static const cameraGalleryPermissionMessage = 'cameraGalleryPermissionMessage';
static const cameraGalleryPermissionRequired = 'cameraGalleryPermissionRequired';
static const cameraPermissionMessage = 'cameraPermissionMessage';
static const cameraPermissionRequired = 'cameraPermissionRequired';
static const galleryPermissionMessage = 'galleryPermissionMessage';
static const galleryPermissionRequired = 'galleryPermissionRequired';
static const failedToCheckPermissions = 'failedToCheckPermissions';
static const takePhotoWithCamera = 'takePhotoWithCamera';
static const selectFromPhotoLibrary = 'selectFromPhotoLibrary';
static const selectDocumentFiles = 'selectDocumentFiles';
static const profilePicture = 'profilePicture';
static const sendingAskDrRequest = 'sendingAskDrRequest';
static const askDrRequestSentSuccess = 'askDrRequestSentSuccess';
static const onlineCheckIn24HoursOnly = 'onlineCheckIn24HoursOnly';
}

@ -10,6 +10,7 @@ import 'package:flutter/services.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/features/ask_doctor/ask_doctor_view_model.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart';
import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_view_model.dart';
@ -41,6 +42,8 @@ import 'package:hmg_patient_app_new/features/services_price_list/services_price_
import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_provider.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart';
import 'package:hmg_patient_app_new/features/translations/custom_asset_loader.dart';
import 'package:hmg_patient_app_new/features/translations/translation_service.dart';
import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart';
import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart';
import 'package:hmg_patient_app_new/features/health_trackers/health_trackers_view_model.dart';
@ -48,6 +51,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/theme/colors.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';
@ -95,6 +99,9 @@ Future<void> callInitializations() async {
WidgetsFlutterBinding.ensureInitialized();
await EasyLocalization.ensureInitialized();
// Download the latest JSONs from your server
// await TranslationService.downloadTranslations();
// await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
try {
@ -125,6 +132,7 @@ void main() async {
Locale('ar', 'SA'),
],
path: 'assets/langs',
// assetLoader: RemoteFileLoader(),
fallbackLocale: Locale('en', 'US'),
child: MultiProvider(providers: <SingleChildWidget>[
ChangeNotifierProvider<LabViewModel>(

@ -137,7 +137,7 @@ class _ActiveMedicationPageState extends State<ActiveMedicationPage> {
style: TextStyle(
color: AppColors.textColor,
fontSize: 27.f,
fontWeight: FontWeight.w600)),
fontWeight: FontWeight.w600,)),
SizedBox(height: 16.h),
SizedBox(
height: 65.h,
@ -163,7 +163,7 @@ class _ActiveMedicationPageState extends State<ActiveMedicationPage> {
style: TextStyle(
color: AppColors.textColor,
fontSize: 16,
fontWeight: FontWeight.w500,
fontWeight: FontWeight.w600,
),
),
WidgetSpan(
@ -174,7 +174,7 @@ class _ActiveMedicationPageState extends State<ActiveMedicationPage> {
style: TextStyle(
fontSize: 12,
color: AppColors.textColor,
fontWeight: FontWeight.w500,
fontWeight: FontWeight.w600,
),
),
),
@ -184,7 +184,7 @@ class _ActiveMedicationPageState extends State<ActiveMedicationPage> {
style: TextStyle(
color: AppColors.textColor,
fontSize: 16,
fontWeight: FontWeight.w500,
fontWeight: FontWeight.w600,
),
),
],
@ -194,7 +194,7 @@ class _ActiveMedicationPageState extends State<ActiveMedicationPage> {
style: TextStyle(
color: AppColors.primaryRedBorderColor,
fontSize: 12.f,
fontWeight: FontWeight.w500)),
fontWeight: FontWeight.w600,)),
SizedBox(height: 16.h),
Expanded(
child: SingleChildScrollView(
@ -630,7 +630,7 @@ class _ActiveMedicationPageState extends State<ActiveMedicationPage> {
style: TextStyle(
fontSize: 12.f,
color: AppColors.greyTextColor,
fontWeight: FontWeight.w500,
fontWeight: FontWeight.w600,
),
),
],
@ -722,7 +722,7 @@ class _ActiveMedicationPageState extends State<ActiveMedicationPage> {
? AppColors.primaryRedBorderColor
: AppColors.greyTextColor,
fontSize: 11.f,
fontWeight: FontWeight.w500),
fontWeight: FontWeight.w600,),
),
SizedBox(height: 5.h),
Text("${date.day}",

@ -31,6 +31,7 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/appointments/appointment_payment_page.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_doctor_card.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_rating_widget.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/ask_doctor_request_type_select.dart';
import 'package:hmg_patient_app_new/presentation/ask_doctor/ask_doctor_page.dart';
import 'package:hmg_patient_app_new/presentation/ask_doctor/doctor_response_page.dart';
@ -71,16 +72,25 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
String mapURL = "";
bool isAppointmentWithin4Hours = false;
@override
void initState() {
scheduleMicrotask(() async {
if (!AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) {
// CalenderUtilsNew calendarUtils = await CalenderUtilsNew.instance;
// var doesExist = await calendarUtils.checkIfEventExist("${widget.patientAppointmentHistoryResponseModel.appointmentNo}");
// myAppointmentsViewModel.setAppointmentReminder(doesExist, widget.patientAppointmentHistoryResponseModel);
// setState(() {});
if (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) {
myAppointmentsViewModel.getDoctorsRatingCheck(
doctorID: widget.patientAppointmentHistoryResponseModel.doctorID,
clinicID: widget.patientAppointmentHistoryResponseModel.clinicID,
projectID: widget.patientAppointmentHistoryResponseModel.projectID,
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo);
} else {
Duration difference = DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate).difference(DateTime.now());
if (difference.inMinutes > 240) {
isAppointmentWithin4Hours = false;
} else {
isAppointmentWithin4Hours = true;
}
}
});
super.initState();
}
@ -231,53 +241,53 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
LocaleKeys.appointmentStatus.tr(context: context).toText16(isBold: true),
SizedBox(height: 4.h),
(!AppointmentType.isConfirmed(widget.patientAppointmentHistoryResponseModel)
? LocaleKeys.notConfirmed.tr(context: context).toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500)
: LocaleKeys.confirmed.tr(context: context).toText12(color: AppColors.successColor, fontWeight: FontWeight.w500)),
? LocaleKeys.notConfirmed.tr(context: context).toText12(color: AppColors.primaryRedColor, isBold: true)
: LocaleKeys.confirmed.tr(context: context).toText12(color: AppColors.successColor, isBold: true)),
SizedBox(height: 16.h),
],
),
((!AppointmentType.isConfirmed(widget.patientAppointmentHistoryResponseModel) && widget.patientAppointmentHistoryResponseModel.nextAction != 10)
? CustomButton(
text: LocaleKeys.confirm.tr(),
onPressed: () async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.confirmingAppointmentPleaseWait.tr(context: context));
await myAppointmentsViewModel.confirmAppointment(
patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel,
onSuccess: (apiResponse) {
LoaderBottomSheet.hideLoader();
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
myAppointmentsViewModel.initAppointmentsViewModel();
// myAppointmentsViewModel.getPatientAppointments(true, false);
showCommonBottomSheetWithoutHeight(
title: "",
context,
child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentConfirmedSuccessfully.tr(context: context)),
callBackFunc: () {
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
},
isFullScreen: false,
isCloseButtonVisible: false,
isAutoDismiss: true
);
});
},
backgroundColor: AppColors.successColor,
borderColor: AppColors.successColor,
textColor: Colors.white,
fontSize: 14.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
height: 40.h,
icon: AppAssets.confirm_appointment_icon,
iconColor: Colors.white,
iconSize: 16.h,
)
: SizedBox.shrink())
// ((!AppointmentType.isConfirmed(widget.patientAppointmentHistoryResponseModel) && widget.patientAppointmentHistoryResponseModel.nextAction != 10)
// ? CustomButton(
// text: LocaleKeys.confirm.tr(),
// onPressed: () async {
// LoaderBottomSheet.showLoader(loadingText: LocaleKeys.confirmingAppointmentPleaseWait.tr(context: context));
// await myAppointmentsViewModel.confirmAppointment(
// patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel,
// onSuccess: (apiResponse) {
// LoaderBottomSheet.hideLoader();
// myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
// myAppointmentsViewModel.initAppointmentsViewModel();
// // myAppointmentsViewModel.getPatientAppointments(true, false);
// showCommonBottomSheetWithoutHeight(
// title: "",
// context,
// child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentConfirmedSuccessfully.tr(context: context)),
// callBackFunc: () {
// Navigator.pushAndRemoveUntil(
// context,
// CustomPageRoute(
// page: LandingNavigation(),
// ),
// (r) => false);
// },
// isFullScreen: false,
// isCloseButtonVisible: false,
// isAutoDismiss: true
// );
// });
// },
// backgroundColor: AppColors.successColor,
// borderColor: AppColors.successColor,
// textColor: Colors.white,
// fontSize: 14.f,
// isBold: true,
// borderRadius: 12.r,
// height: 40.h,
// icon: AppAssets.confirm_appointment_icon,
// iconColor: Colors.white,
// iconSize: 16.h,
// )
// : SizedBox.shrink())
],
),
//TODO Add countdown timer in case of LiveCare Appointment
@ -290,7 +300,7 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.doctorWillCallYou.tr(context: context).toText14(color: AppColors.greyTextColor, weight: FontWeight.w500),
LocaleKeys.doctorWillCallYou.tr(context: context).toText14(color: AppColors.greyTextColor, isBold: true),
],
),
),
@ -336,7 +346,7 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.01),
textColor: Colors.white,
fontSize: 14.f,
fontWeight: FontWeight.w500,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
height: 40.h,
@ -357,12 +367,12 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
AppCustomChipWidget(
labelText: bookAppointmentsVM.isAppointmentNearestGateLoading
? "Floor: Ground Floor"
: "Floor: ${getIt.get<AppState>().isArabic() ? bookAppointmentsViewModel.appointmentNearestGateResponseModel!.clinicLocationN : bookAppointmentsViewModel.appointmentNearestGateResponseModel!.clinicLocation}",
).toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading),
: "${LocaleKeys.floor.tr(context: context)}: ${getIt.get<AppState>().isArabic() ? bookAppointmentsViewModel.appointmentNearestGateResponseModel!.clinicLocationN : bookAppointmentsViewModel.appointmentNearestGateResponseModel!.clinicLocation}",
).toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading),
AppCustomChipWidget(
labelText:
"Nearest Gate: ${getIt.get<AppState>().isArabic() ? bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumberN : bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumber}")
.toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading),
"${LocaleKeys.nearestGate.tr(context: context)}: ${getIt.get<AppState>().isArabic() ? bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumberN : bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumber}")
.toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading),
],
) : SizedBox.shrink(),
],
@ -386,13 +396,13 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.setReminder.tr(context: context).toText13(isBold: true),
LocaleKeys.notifyMeBeforeAppointment.tr(context: context).toText11(color: AppColors.textColorLight, weight: FontWeight.w500),
LocaleKeys.notifyMeBeforeAppointment.tr(context: context).toText11(color: AppColors.textColorLight, isBold: true),
],
),
const Spacer(),
Switch(
// activeThumbColor: AppColors.successColor,
activeTrackColor: AppColors.successColor.withValues(alpha: .15),
activeThumbColor: AppColors.successColor,
// activeTrackColor: AppColors.successColor.withValues(alpha: .15),
value: widget.patientAppointmentHistoryResponseModel.hasReminder!,
onChanged: (newValue) async {
CalenderUtilsNew calender = CalenderUtilsNew.instance;
@ -451,15 +461,16 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
],
)
// : SizedBox.shrink()
: GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 16.h,
mainAxisSpacing: 16.w,
mainAxisExtent: 115.h,
),
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
: Consumer<MyAppointmentsViewModel>(builder: (context, myAppointmentsVM, child) {
return GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 16.h,
mainAxisSpacing: 16.w,
mainAxisExtent: 115.h,
),
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
shrinkWrap: true,
children: [
MedicalFileCard(
@ -543,11 +554,15 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
clinicDescription: widget.patientAppointmentHistoryResponseModel.clinicName,
decimalDoctorRate: widget.patientAppointmentHistoryResponseModel.decimalDoctorRate,
name: widget.patientAppointmentHistoryResponseModel.projectName,
isHomeMedicineDeliverySupported: false,
isHomeMedicineDeliverySupported: val.data.first.isHomeMedicineDeliverySupported ?? false,
projectID: widget.patientAppointmentHistoryResponseModel.projectID,
clinicID: widget.patientAppointmentHistoryResponseModel.clinicID,
doctorID: widget.patientAppointmentHistoryResponseModel.doctorID,
setupID: widget.patientAppointmentHistoryResponseModel.setupID,
isInOutPatient: widget.patientAppointmentHistoryResponseModel.isInOutPatient,
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo,
episodeID: widget.patientAppointmentHistoryResponseModel.episodeID,
dischargeNo: 0,
);
Navigator.of(context).push(
CustomPageRoute(
@ -597,9 +612,55 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
page: DoctorResponsePage(),
),
);
}),
],
),
}),
myAppointmentsVM.isAppointmentRatedResponseLoading
? MedicalFileCard(
label: "Medical File",
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.appointmentRatingIcon,
isLargeText: true,
iconSize: 36.w,
isLocked: false,
).toShimmer2()
: MedicalFileCard(
label: !((myAppointmentsVM.appointmentRatedResponseModel!).isAllowedToRate ?? false)
? LocaleKeys.ratingSubmitted.tr(context: context)
: LocaleKeys.doctorRatingAppointment.tr(context: context),
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.appointmentRatingIcon,
isLargeText: true,
iconSize: 36.w,
isLocked: !(myAppointmentsVM.appointmentRatedResponseModel!.isAllowedToRate ?? false),
).onPress(() {
if ((myAppointmentsVM.appointmentRatedResponseModel!.isAllowedToRate ?? true)) {
showCommonBottomSheetWithoutHeight(
context,
title: LocaleKeys.doctorRating.tr(context: context),
isCloseButtonVisible: true,
child: StatefulBuilder(
builder: (context, setState) {
return AppointmentRatingWidget(
patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel,
);
},
),
callBackFunc: () {
myAppointmentsViewModel.getDoctorsRatingCheck(
doctorID: widget.patientAppointmentHistoryResponseModel.doctorID,
clinicID: widget.patientAppointmentHistoryResponseModel.clinicID,
projectID: widget.patientAppointmentHistoryResponseModel.projectID,
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo);
},
isFullScreen: false,
);
}
}),
],
);
}),
// Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
@ -686,7 +747,7 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
// // "Prescribed By: ${widget.patientAppointmentHistoryResponseModel.doctorTitle} ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}"
// // .needTranslation
// // .toText10(
// // weight: FontWeight.w500,
// // weight: FontWeight.w600,
// // color: AppColors.greyTextColor,
// // letterSpacing: -0.4),
// // ],
@ -733,7 +794,7 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
// // // borderColor: AppColors.secondaryLightRedColor,
// // // textColor: AppColors.primaryRedColor,
// // // fontSize: 14,
// // // fontWeight: FontWeight.w500,
// // // isBold: true,
// // // borderRadius: 12.h,
// // // height: 40.h,
// // // icon: AppAssets.appointment_calendar_icon,
@ -761,7 +822,7 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
// // borderColor: AppColors.secondaryLightRedColor,
// // textColor: AppColors.primaryRedColor,
// // fontSize: 14.f,
// // fontWeight: FontWeight.w500,
// // isBold: true,
// // borderRadius: 12.r,
// // height: 40.h,
// // icon: AppAssets.requests,
@ -790,7 +851,7 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
// // borderColor: AppColors.secondaryLightRedColor,
// // textColor: AppColors.primaryRedColor,
// // fontSize: 14.f,
// // fontWeight: FontWeight.w500,
// // isBold: true,
// // borderRadius: 12.r,
// // height: 40.h,
// // icon: AppAssets.requests,
@ -843,8 +904,7 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
children: [
Expanded(
child: LocaleKeys.upcomingPaymentNow
.tr(context: context)
.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor)),
.tr(context: context).toText12(isBold: true, color: AppColors.greyTextColor)),
"VAT 15%(${widget.patientAppointmentHistoryResponseModel.patientTaxAmount})".toText14(isBold: true, color: AppColors.greyTextColor, letterSpacing: -0.64),
],
),
@ -871,11 +931,12 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
],
).paddingOnly(left: 16.h, top: 24.h, right: 16.h, bottom: 0.h),
AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)
?
(widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! ||
widget.patientAppointmentHistoryResponseModel.isExecludeDoctor! ||
Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID))
? CustomButton(
? (!(widget.patientAppointmentHistoryResponseModel.isActiveDoctor ?? true) ||
widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! ||
widget.patientAppointmentHistoryResponseModel.isExecludeDoctor! ||
!Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID))
? SizedBox.shrink()
: CustomButton(
text: LocaleKeys.rebookAppointment.tr(context: context),
onPressed: () {
openDoctorScheduleCalendar();
@ -884,15 +945,14 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
borderColor: AppColors.primaryRedColor,
textColor: Colors.white,
fontSize: 16.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
height: 50.h,
icon: AppAssets.add_icon,
iconColor: Colors.white,
iconSize: 18.h,
).paddingSymmetrical(16.h, 24.h)
: SizedBox.shrink()
: CustomButton(
text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction),
onPressed: () {
@ -903,7 +963,7 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01),
textColor: widget.patientAppointmentHistoryResponseModel.nextAction == 15 ? AppColors.textColor : Colors.white,
fontSize: 16.f,
fontWeight: FontWeight.w500,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
height: 56.h,
@ -933,7 +993,7 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
SizedBox(height: 4.h),
LocaleKeys.confirmYourLocationDesc.tr(context: context).toText12(
color: AppColors.greyTextColor,
fontWeight: FontWeight.w500,
isBold: true,
),
SizedBox(height: 24.h),
CustomButton(
@ -947,7 +1007,7 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
borderColor: AppColors.primaryRedColor,
textColor: Colors.white,
fontSize: 16.f,
fontWeight: FontWeight.w500,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
// padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 56.h,
@ -973,7 +1033,7 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
borderColor: AppColors.chipSecondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 16.f,
fontWeight: FontWeight.w500,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
// padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 56.h,
@ -1086,10 +1146,14 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
case 90:
showCommonBottomSheetWithoutHeight(context,
title: LocaleKeys.onlineCheckIn.tr(context: context),
child: AppointmentCheckinBottomSheet(
patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel,
myAppointmentsViewModel: myAppointmentsViewModel,
),
child: isAppointmentWithin4Hours
? AppointmentCheckinBottomSheet(
patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel,
myAppointmentsViewModel: myAppointmentsViewModel,
)
: Utils.getOnlineCheckInWidget(
loadingText: LocaleKeys.onlineCheckIn24HoursOnly.tr(context: context),
),
callBackFunc: () {},
isFullScreen: false);
default:

@ -111,7 +111,7 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
Image.asset(AppAssets.mada, width: 72.h, height: 25.h)
.toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading),
SizedBox(height: 16.h),
"Mada".toText16(isBold: true).toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading),
LocaleKeys.mada.tr(context: context).toText16(isBold: true).toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading),
],
),
SizedBox(width: 8.h),
@ -153,7 +153,7 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
],
).toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading),
SizedBox(height: 16.h),
"Visa or Mastercard"
LocaleKeys.visaOrMastercard.tr(context: context)
.toText16(isBold: true)
.toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading),
],
@ -193,7 +193,7 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
Image.asset(AppAssets.tamaraEng, width: 72.h, height: 25.h)
.toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading),
SizedBox(height: 16.h),
"Tamara"
LocaleKeys.tamara.tr(context: context)
.toText16(isBold: true)
.toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading),
],
@ -249,7 +249,7 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
LocaleKeys.insuranceExpiredOrInactive.tr(context: context)
.toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500)
.toText14(color: AppColors.primaryRedColor, isBold: true)
.paddingSymmetrical(24.h, 0.h),
CustomButton(
text: LocaleKeys.updateInsurance.tr(context: context),
@ -265,7 +265,7 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
borderColor: AppColors.secondaryLightRedBorderColor,
textColor: AppColors.whiteColor,
fontSize: 10.f,
fontWeight: FontWeight.w500,
fontWeight: FontWeight.w600,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(15, 0, 15, 0),
height: 30.h,

@ -15,6 +15,7 @@ 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/routes/custom_page_route.dart';
import 'package:lottie/lottie.dart';
import 'package:provider/provider.dart';
class AppointmentQueuePage extends StatelessWidget {
@ -32,182 +33,177 @@ class AppointmentQueuePage extends StatelessWidget {
children: [
Expanded(
child: CollapsingListView(
title: LocaleKeys.queueing.tr(context: context),
child: SingleChildScrollView(
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: myAppointmentsVM.isAppointmentQueueDetailsLoading
? AppColors.whiteColor
: Utils.getCardBorderColor(myAppointmentsVM.currentQueueStatus),
width: 2.w),
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
title: LocaleKeys.queueing.tr(context: context),
child: RefreshIndicator(
color: AppColors.primaryRedColor,
onRefresh: () async {
await myAppointmentsVM.getPatientAppointmentQueueDetails();
},
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
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: myAppointmentsVM.isAppointmentQueueDetailsLoading ? AppColors.whiteColor : Utils.getCardBorderColor(myAppointmentsVM.currentQueueStatus), width: 2.w),
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AppCustomChipWidget(
labelText: myAppointmentsVM.currentQueueStatus == 0 ? LocaleKeys.inQueue.tr(context: context) : LocaleKeys.yourTurn.tr(context: context),
backgroundColor: Utils.getCardBorderColor(myAppointmentsVM.currentQueueStatus).withValues(alpha: 0.20),
textColor: Utils.getCardBorderColor(myAppointmentsVM.currentQueueStatus),
),
Utils.buildSvgWithAssets(icon: AppAssets.waiting_icon, width: 24.h, height: 24.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AppCustomChipWidget(
labelText: myAppointmentsVM.currentQueueStatus == 0 ? LocaleKeys.inQueue.tr(context: context) : LocaleKeys.yourTurn.tr(context: context),
backgroundColor: Utils.getCardBorderColor(myAppointmentsVM.currentQueueStatus).withValues(alpha: 0.20),
textColor: Utils.getCardBorderColor(myAppointmentsVM.currentQueueStatus),
).toShimmer2(isShow: myAppointmentsVM.patientQueueDetailsList.isEmpty),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.greyColor,
borderRadius: 10.h,
hasShadow: false,
),
padding: EdgeInsets.all(6.h),
child: Lottie.asset(AppAnimations.hourGlass, repeat: true, reverse: false, frameRate: FrameRate(60), width: 40.h, height: 40.h, fit: BoxFit.fill)),
],
).toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading),
SizedBox(height: 10.h),
"Hala ${appState!.getAuthenticatedUser()!.firstName}!!!".toText16(isBold: true).toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading),
SizedBox(height: 8.h),
LocaleKeys.thankYouForPatience
.tr(context: context)
.toText12(isBold: true, color: AppColors.textColorLight)
.toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading),
SizedBox(height: 8.h),
(myAppointmentsVM.currentPatientQueueDetails.queueNo ?? "IM-123")
.toText32(isBold: true)
.toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading || myAppointmentsVM.patientQueueDetailsList.isEmpty),
SizedBox(height: 8.h),
CustomButton(
text: Utils.getCardButtonText(myAppointmentsVM.currentQueueStatus, myAppointmentsVM.currentPatientQueueDetails.roomNo ?? ""),
onPressed: () {},
backgroundColor: Utils.getCardButtonColor(myAppointmentsVM.currentQueueStatus),
borderColor: Utils.getCardButtonColor(myAppointmentsVM.currentQueueStatus).withValues(alpha: 0.01),
textColor: Utils.getCardButtonTextColor(myAppointmentsVM.currentQueueStatus),
fontSize: 12.f,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
height: 40.h,
iconColor: AppColors.whiteColor,
iconSize: 18.h,
).toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading || myAppointmentsVM.patientQueueDetailsList.isEmpty),
],
).toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading),
SizedBox(height: 10.h),
"Hala ${appState!.getAuthenticatedUser()!.firstName}!!!"
.toText16(isBold: true)
.toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading),
SizedBox(height: 8.h),
LocaleKeys.thankYouForPatience.tr(context: context)
.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight)
.toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading),
SizedBox(height: 8.h),
myAppointmentsVM.currentPatientQueueDetails.queueNo!
.toText32(isBold: true)
.toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading),
SizedBox(height: 8.h),
CustomButton(
text: Utils.getCardButtonText(
myAppointmentsVM.currentQueueStatus, myAppointmentsVM.currentPatientQueueDetails.roomNo ?? ""),
onPressed: () {},
backgroundColor: Utils.getCardButtonColor(myAppointmentsVM.currentQueueStatus),
borderColor: Utils.getCardButtonColor(myAppointmentsVM.currentQueueStatus).withValues(alpha: 0.01),
textColor: Utils.getCardButtonTextColor(myAppointmentsVM.currentQueueStatus),
fontSize: 12.f,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
height: 40.h,
iconColor: AppColors.whiteColor,
iconSize: 18.h,
).toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading),
],
),
),
),
SizedBox(height: 16.h),
myAppointmentsVM.patientQueueDetailsList.isNotEmpty
? Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: true,
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.servingNow.tr(context: context)
.toText16(isBold: true)
.toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading),
SizedBox(height: 18.h),
ListView.separated(
padding: EdgeInsets.zero,
shrinkWrap: true,
itemCount: myAppointmentsVM.patientQueueDetailsList.length,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
myAppointmentsVM.patientQueueDetailsList[index].queueNo!.toText17(isBold: true),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
),
),
SizedBox(height: 16.h),
myAppointmentsVM.patientQueueDetailsList.isNotEmpty
? Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: true,
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.servingNow.tr(context: context).toText16(isBold: true).toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading),
SizedBox(height: 18.h),
ListView.separated(
padding: EdgeInsets.zero,
shrinkWrap: true,
itemCount: myAppointmentsVM.patientQueueDetailsList.length,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Room: ${myAppointmentsVM.patientQueueDetailsList[index].roomNo}"
.toText12(fontWeight: FontWeight.w500),
SizedBox(width: 8.w),
AppCustomChipWidget(
deleteIcon: myAppointmentsVM.patientQueueDetailsList[index].callType == 1
? AppAssets.call_for_vitals
: AppAssets.call_for_doctor,
labelText: myAppointmentsVM.patientQueueDetailsList[index].callType == 1
? LocaleKeys.callForVitalSigns.tr(context: context)
: LocaleKeys.callForDoctor.tr(context: context),
iconColor: myAppointmentsVM.patientQueueDetailsList[index].callType == 1
? AppColors.primaryRedColor
: AppColors.successColor,
textColor: myAppointmentsVM.patientQueueDetailsList[index].callType == 1
? AppColors.primaryRedColor
: AppColors.successColor,
iconSize: 14.w,
backgroundColor: myAppointmentsVM.patientQueueDetailsList[index].callType == 1
? AppColors.primaryRedColor.withValues(alpha: 0.1)
: AppColors.successColor.withValues(alpha: 0.1),
labelPadding: EdgeInsetsDirectional.only(start: 8.h, end: -2.h),
myAppointmentsVM.patientQueueDetailsList[index].queueNo!.toText17(isBold: true),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
"Room: ${myAppointmentsVM.patientQueueDetailsList[index].roomNo}".toText12(isBold: true),
SizedBox(width: 8.w),
AppCustomChipWidget(
deleteIcon: myAppointmentsVM.patientQueueDetailsList[index].callType == 1 ? AppAssets.call_for_vitals : AppAssets.call_for_doctor,
labelText: myAppointmentsVM.patientQueueDetailsList[index].callType == 1
? LocaleKeys.callForVitalSigns.tr(context: context)
: LocaleKeys.callForDoctor.tr(context: context),
iconColor: myAppointmentsVM.patientQueueDetailsList[index].callType == 1 ? AppColors.primaryRedColor : AppColors.successColor,
textColor: myAppointmentsVM.patientQueueDetailsList[index].callType == 1 ? AppColors.primaryRedColor : AppColors.successColor,
iconSize: 14.w,
backgroundColor: myAppointmentsVM.patientQueueDetailsList[index].callType == 1
? AppColors.primaryRedColor.withValues(alpha: 0.1)
: AppColors.successColor.withValues(alpha: 0.1),
labelPadding: EdgeInsetsDirectional.only(start: 8.h, end: -2.h),
),
],
),
],
),
],
);
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 8.h),
).toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading),
],
),
),
)
: SizedBox.shrink(),
SizedBox(height: 16.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.4,
hasShadow: true,
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
);
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 8.h),
).toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading),
],
),
),
)
: SizedBox.shrink(),
SizedBox(height: 16.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.4,
hasShadow: true,
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Utils.buildSvgWithAssets(icon: AppAssets.bulb_icon, width: 24.w, height: 24.h),
SizedBox(width: 8.w),
LocaleKeys.thingsToAskDoctor.tr(context: context).toText16(isBold: true),
Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.bulb_icon, width: 24.w, height: 24.h),
SizedBox(width: 8.w),
LocaleKeys.thingsToAskDoctor.tr(context: context).toText16(isBold: true),
],
),
SizedBox(height: 8.h),
"${LocaleKeys.improveOverallHealth.tr(context: context)}".toText12(isBold: true, color: AppColors.textColorLight),
SizedBox(height: 4.h),
"${LocaleKeys.routineScreenings.tr(context: context)}".toText12(isBold: true, color: AppColors.textColorLight),
SizedBox(height: 4.h),
"${LocaleKeys.whatIsThisMedicationFor.tr(context: context)}".toText12(isBold: true, color: AppColors.textColorLight),
SizedBox(height: 4.h),
"${LocaleKeys.sideEffectsToKnow.tr(context: context)}".toText12(isBold: true, color: AppColors.textColorLight),
SizedBox(height: 4.h),
"${LocaleKeys.whenFollowUp.tr(context: context)}".toText12(isBold: true, color: AppColors.textColorLight),
SizedBox(height: 16.h),
],
),
SizedBox(height: 8.h),
"${LocaleKeys.improveOverallHealth.tr(context: context)}"
.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight),
SizedBox(height: 4.h),
"${LocaleKeys.routineScreenings.tr(context: context)}"
.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight),
SizedBox(height: 4.h),
"${LocaleKeys.whatIsThisMedicationFor.tr(context: context)}"
.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight),
SizedBox(height: 4.h),
"${LocaleKeys.sideEffectsToKnow.tr(context: context)}"
.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight),
SizedBox(height: 4.h),
"${LocaleKeys.whenFollowUp.tr(context: context)}"
.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight),
SizedBox(height: 16.h),
],
).toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading),
),
).toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading),
),
),
],
),
],
),
),
),
),
),
),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
@ -229,7 +225,7 @@ class AppointmentQueuePage extends StatelessWidget {
borderColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
fontSize: 16.f,
fontWeight: FontWeight.w500,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
height: 50.h,

@ -113,7 +113,7 @@ class _MyAppointmentsPageState extends State<MyAppointmentsPage> {
borderColor: myAppointmentsVM.isAppointmentsSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2),
textColor: myAppointmentsVM.isAppointmentsSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor,
fontSize: 12,
fontWeight: FontWeight.w500,
isBold: true,
borderRadius: 10,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
@ -128,7 +128,7 @@ class _MyAppointmentsPageState extends State<MyAppointmentsPage> {
borderColor: myAppointmentsVM.isAppointmentsSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor,
textColor: myAppointmentsVM.isAppointmentsSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor,
fontSize: 12,
fontWeight: FontWeight.w500,
isBold: true,
borderRadius: 10,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
@ -191,7 +191,7 @@ class _MyAppointmentsPageState extends State<MyAppointmentsPage> {
children: [
AppCustomChipWidget(
labelText:
"${myAppointmentsVM.patientAppointmentsViewList[index].patientDoctorAppointmentList!.length} Appointments"),
"${myAppointmentsVM.patientAppointmentsViewList[index].patientDoctorAppointmentList!.length} ${LocaleKeys.appointments.tr(context: context)}", isEnglishOnly: true,),
Icon(isExpanded ? Icons.expand_less : Icons.expand_more),
],
),
@ -266,7 +266,7 @@ class _MyAppointmentsPageState extends State<MyAppointmentsPage> {
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
fontSize: 14.f,
fontWeight: FontWeight.w500,
isBold: true,
borderRadius: 12.r,
padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0),
height: 40.h,

@ -89,7 +89,7 @@ class _MyDoctorsPageState extends State<MyDoctorsPage> {
borderColor: isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2),
textColor: isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor,
fontSize: 12,
fontWeight: FontWeight.w500,
isBold: true,
borderRadius: 10,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
@ -107,7 +107,7 @@ class _MyDoctorsPageState extends State<MyDoctorsPage> {
borderColor: isSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor,
textColor: isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor,
fontSize: 12,
fontWeight: FontWeight.w500,
isBold: true,
borderRadius: 10,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
@ -234,7 +234,7 @@ class _MyDoctorsPageState extends State<MyDoctorsPage> {
SizedBox(height: 8.h),
Text(
displayName,
style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600),
style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600,),
overflow: TextOverflow.ellipsis,
),
],
@ -276,7 +276,7 @@ class _MyDoctorsPageState extends State<MyDoctorsPage> {
).circle(100),
SizedBox(width: 8.h),
Expanded(
child: (doctor?.doctorName ?? "").toString().toText14(weight: FontWeight.w500),
child: (doctor?.doctorName ?? "").toString().toText14(isBold: true),
),
],
),
@ -318,7 +318,7 @@ class _MyDoctorsPageState extends State<MyDoctorsPage> {
LoaderBottomSheet.hideLoader();
Navigator.of(context).push(
CustomPageRoute(
page: DoctorProfilePage(),
page: DoctorProfilePage(isDoctorAllowedToBook: !doctor?.isLiveCareClinic),
),
);
}, onError: (err) {
@ -336,7 +336,7 @@ class _MyDoctorsPageState extends State<MyDoctorsPage> {
borderColor: doctor?.isActiveDoctorProfile == false ? AppColors.inputLabelTextColor.withValues(alpha: 0.01) : AppColors.secondaryLightRedColor,
textColor: doctor?.isActiveDoctorProfile == false ? AppColors.inputLabelTextColor.withValues(alpha: 1.0) : AppColors.primaryRedColor,
fontSize: 14,
fontWeight: FontWeight.w500,
isBold: true,
borderRadius: 12,
padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,

@ -40,6 +40,7 @@ class AppointmentCard extends StatelessWidget {
final ContactUsViewModel? contactUsViewModel;
final BookAppointmentsViewModel bookAppointmentsViewModel;
final bool isForRate;
// bool isAppointmentWithin4Hours = false;
const AppointmentCard(
{super.key,
@ -205,7 +206,7 @@ class AppointmentCard extends StatelessWidget {
richText: isLoading
? 'Cardiology'.toText10().toShimmer2(isShow: isLoading)
: "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)}"
.toText10(isEnglishOnly: true),
.toText10(isEnglishOnly: true, isBold: true),
).toShimmer2(isShow: isLoading),
),
@ -243,42 +244,123 @@ class AppointmentCard extends StatelessWidget {
(patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) ||
!Utils.isClinicAllowedForRebook(patientAppointmentHistoryResponseModel.clinicID ?? 0))) &&
AppointmentType.isArrived(patientAppointmentHistoryResponseModel)) {
// if (((patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) &&
// DateTime.now().difference(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15)) {
// return Row(
// children: [
// Expanded(
// flex: 6,
// child: CustomButton(
// text: LocaleKeys.askDoctor.tr(context: context),
// onPressed: () async {
// LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingDoctorAvailability.tr(context: context));
// await myAppointmentsViewModel.isDoctorAvailable(
// projectID: patientAppointmentHistoryResponseModel.projectID,
// doctorId: patientAppointmentHistoryResponseModel.doctorID,
// clinicId: patientAppointmentHistoryResponseModel.clinicID,
// onSuccess: (value) async {
// if (value) {
// await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) {
// LoaderBottomSheet.hideLoader();
// showCommonBottomSheetWithoutHeight(
// context,
// title: LocaleKeys.askDoctor.tr(context: context),
// child: AskDoctorRequestTypeSelect(
// askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList,
// myAppointmentsViewModel: myAppointmentsViewModel,
// patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel,
// ),
// callBackFunc: () {},
// isFullScreen: false,
// isCloseButtonVisible: true,
// );
// });
// } else {
// print("Doctor is not available");
// }
// });
// },
// backgroundColor: AppColors.secondaryLightRedColor,
// borderColor: AppColors.secondaryLightRedColor,
// textColor: AppColors.primaryRedColor,
// fontSize: (isFoldable || isTablet) ? 12.f : 14.f,
// fontWeight: FontWeight.w600,
// borderRadius: 12.r,
// padding: EdgeInsets.symmetric(horizontal: 10.w),
// // height: isTablet || isFoldable ? 46.h : 40.h,
// height: 40.h,
// icon: AppAssets.ask_doctor_icon,
// iconColor: AppColors.primaryRedColor,
// iconSize: 16.h,
// ),
// ),
// SizedBox(width: 8.h),
// Expanded(
// flex: 1,
// child: Container(
// height: (isFoldable || isTablet) ? 50.h : 40.h,
// decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
// color: AppColors.textColor,
// borderRadius: 10.h,
// side: BorderSide(
// color: AppColors.textColor,
// width: 1.2,
// ),
// ),
// child: Transform.flip(
// flipX: appState.isArabic(),
// child: Utils.buildSvgWithAssets(
// icon: AppAssets.forward_arrow_icon,
// iconColor: AppColors.whiteColor,
// width: 24.w,
// height: 24.h,
// fit: BoxFit.contain,
// ),
// ),
// ).toShimmer2(isShow: isLoading).onPress(() {
// _goToDetails(context);
// }),
// ),
// ],
// );
// } else {
return CustomButton(
text: isFromMedicalReport ? LocaleKeys.selectAppointment.tr(context: context) : LocaleKeys.viewDetails.tr(context: context),
onPressed: () {
if (isFromMedicalReport) {
if (isForFeedback) {
contactUsViewModel!.setPatientFeedbackSelectedAppointment(patientAppointmentHistoryResponseModel);
onPressed: () {
if (isFromMedicalReport) {
if (isForFeedback) {
contactUsViewModel!.setPatientFeedbackSelectedAppointment(patientAppointmentHistoryResponseModel);
} else {
medicalFileViewModel!.setSelectedMedicalReportAppointment(patientAppointmentHistoryResponseModel);
}
Navigator.pop(context, false);
} else {
medicalFileViewModel!.setSelectedMedicalReportAppointment(patientAppointmentHistoryResponseModel);
Navigator.of(context)
.push(
CustomPageRoute(
page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel),
),
)
.then((_) {
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
});
}
Navigator.pop(context, false);
} else {
Navigator.of(context)
.push(
CustomPageRoute(
page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel),
),
)
.then((_) {
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
});
}
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: (isFoldable || isTablet) ? 12.f : 14.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
// height: isTablet || isFoldable ? 46.h : 40.h,
height: 40.h,
icon: isFromMedicalReport ? AppAssets.checkmark_icon : null,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
);
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: (isFoldable || isTablet) ? 12.f : 14.f,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
// height: isTablet || isFoldable ? 46.h : 40.h,
height: 40.h,
icon: isFromMedicalReport ? AppAssets.checkmark_icon : null,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
);
// }
} else {
if (isFromMedicalReport) {
if (isForEyeMeasurements) {
@ -298,7 +380,7 @@ class AppointmentCard extends StatelessWidget {
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: (isFoldable || isTablet) ? 12.f : 14.f,
fontWeight: FontWeight.w500,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
// height: isTablet || isFoldable ? 46.h : 40.h,
@ -309,9 +391,9 @@ class AppointmentCard extends StatelessWidget {
);
}
}
return Row(
children: [
return (patientAppointmentHistoryResponseModel.isActiveDoctor ?? true)
? Row(
children: [
Expanded(
flex: 6,
child: (AppointmentType.isArrived(patientAppointmentHistoryResponseModel)
@ -323,7 +405,7 @@ class AppointmentCard extends StatelessWidget {
borderColor: AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.01),
textColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction),
fontSize: (isFoldable || isTablet) ? 12.f : 14.f,
fontWeight: FontWeight.w500,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
height: 40.h,
@ -361,60 +443,91 @@ class AppointmentCard extends StatelessWidget {
}),
),
],
);
)
: CustomButton(
text: LocaleKeys.viewDetails.tr(context: context),
onPressed: () {
Navigator.of(context)
.push(
CustomPageRoute(
page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel),
),
)
.then((_) {
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
});
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: (isFoldable || isTablet) ? 12.f : 14.f,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
// height: isTablet || isFoldable ? 46.h : 40.h,
height: 40.h,
icon: isFromMedicalReport ? AppAssets.checkmark_icon : null,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
);
}
}
Widget _getArrivedButton(BuildContext context) {
final isRecent = DateTime.now().difference(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15;
if (isRecent) {
return CustomButton(
text: LocaleKeys.askDoctor.tr(context: context),
onPressed: () async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingDoctorAvailability.tr(context: context));
await myAppointmentsViewModel.isDoctorAvailable(
projectID: patientAppointmentHistoryResponseModel.projectID,
doctorId: patientAppointmentHistoryResponseModel.doctorID,
clinicId: patientAppointmentHistoryResponseModel.clinicID,
onSuccess: (value) async {
if (value) {
await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
title: LocaleKeys.askDoctor.tr(context: context),
child: AskDoctorRequestTypeSelect(
askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList,
myAppointmentsViewModel: myAppointmentsViewModel,
patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel,
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else {
print("Doctor is not available");
}
});
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: (isFoldable || isTablet) ? 12.f : 14.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
// height: isTablet || isFoldable ? 46.h : 40.h,
height: 40.h,
icon: AppAssets.ask_doctor_icon,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
);
}
// final isRecent = DateTime.now().difference(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15;
//
// if (isRecent) {
// return CustomButton(
// text: LocaleKeys.askDoctor.tr(context: context),
// onPressed: () async {
// LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingDoctorAvailability.tr(context: context));
// await myAppointmentsViewModel.isDoctorAvailable(
// projectID: patientAppointmentHistoryResponseModel.projectID,
// doctorId: patientAppointmentHistoryResponseModel.doctorID,
// clinicId: patientAppointmentHistoryResponseModel.clinicID,
// onSuccess: (value) async {
// if (value) {
// await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) {
// LoaderBottomSheet.hideLoader();
// showCommonBottomSheetWithoutHeight(
// context,
// title: LocaleKeys.askDoctor.tr(context: context),
// child: AskDoctorRequestTypeSelect(
// askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList,
// myAppointmentsViewModel: myAppointmentsViewModel,
// patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel,
// ),
// callBackFunc: () {},
// isFullScreen: false,
// isCloseButtonVisible: true,
// );
// });
// } else {
// print("Doctor is not available");
// }
// });
// },
// backgroundColor: AppColors.secondaryLightRedColor,
// borderColor: AppColors.secondaryLightRedColor,
// textColor: AppColors.primaryRedColor,
// fontSize: (isFoldable || isTablet) ? 12.f : 14.f,
// fontWeight: FontWeight.w600,
// borderRadius: 12.r,
// padding: EdgeInsets.symmetric(horizontal: 10.w),
// // height: isTablet || isFoldable ? 46.h : 40.h,
// height: 40.h,
// icon: AppAssets.ask_doctor_icon,
// iconColor: AppColors.primaryRedColor,
// iconSize: 16.h,
// );
// }
return CustomButton(
borderSide: BorderSide(
color: AppColors.textColor,
width: 1.2,
),
text: LocaleKeys.rebookSameDoctor.tr(context: context),
onPressed: () => openDoctorScheduleCalendar(context),
backgroundColor: AppColors.transparent,
@ -422,7 +535,7 @@ class AppointmentCard extends StatelessWidget {
textColor: AppColors.blackColor,
borderWidth: 1.h,
fontSize: (isFoldable || isTablet) ? 12.f : 14.f,
fontWeight: FontWeight.w500,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
height: 40.h,

@ -120,7 +120,7 @@ class AppointmentCheckinBottomSheet extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
title.toText16(isBold: true, color: AppColors.textColor),
subTitle.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor),
subTitle.toText12(isBold: true, color: AppColors.greyTextColor),
],
),
),
@ -168,7 +168,7 @@ class AppointmentCheckinBottomSheet extends StatelessWidget {
// page: AppointmentQueuePage(),
// ),
// );
}, isFullScreen: false);
}, isFullScreen: false, isAutoDismiss: true, isCloseButtonVisible: false);
},
onError: (error) {
LoaderBottomSheet.hideLoader();

@ -126,7 +126,7 @@ class AppointmentDoctorCard extends StatelessWidget {
DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate),
false,
)}"
.toText10(isEnglishOnly: true),
.toText10(isBold: true),
),
),
AppCustomChipWidget(
@ -142,8 +142,10 @@ class AppointmentDoctorCard extends StatelessWidget {
],
),
),
Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown).onPress(() async {
DoctorsListResponseModel selectedDoctor = DoctorsListResponseModel();
patientAppointmentHistoryResponseModel.isLiveCareAppointment!
? SizedBox.shrink()
: Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown).onPress(() async {
DoctorsListResponseModel selectedDoctor = DoctorsListResponseModel();
selectedDoctor.doctorID = patientAppointmentHistoryResponseModel.doctorID;
selectedDoctor.doctorImageURL = patientAppointmentHistoryResponseModel.doctorImageURL;
selectedDoctor.name = patientAppointmentHistoryResponseModel.doctorNameObj;
@ -161,7 +163,9 @@ class AppointmentDoctorCard extends StatelessWidget {
LoaderBottomSheet.hideLoader();
Navigator.of(context).push(
CustomPageRoute(
page: DoctorProfilePage(),
page: DoctorProfilePage(
isDoctorAllowedToBook: !(patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false),
),
),
);
}, onError: (err) {
@ -202,7 +206,7 @@ class AppointmentDoctorCard extends StatelessWidget {
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 14.f,
fontWeight: FontWeight.w500,
isBold: true,
borderRadius: 12.r,
padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0),
height: 40.h,
@ -221,7 +225,7 @@ class AppointmentDoctorCard extends StatelessWidget {
// borderColor: AppColors.greyColor,
// textColor: AppColors.blackColor,
// fontSize: 12.f,
// fontWeight: FontWeight.w500,
// isBold: true,
// borderRadius: 12.r,
// padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0),
// height: 40.h,
@ -242,7 +246,7 @@ class AppointmentDoctorCard extends StatelessWidget {
borderColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
fontSize: 14.f,
fontWeight: FontWeight.w500,
isBold: true,
borderRadius: 12.r,
height: 40.h,
icon: AppAssets.cancel,
@ -261,9 +265,9 @@ class AppointmentDoctorCard extends StatelessWidget {
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 14.f,
fontWeight: FontWeight.w500,
isBold: true,
borderRadius: 12.r,
height: 40.h,
height: 46.h,
icon: AppAssets.appointment_calendar_icon,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
@ -280,9 +284,9 @@ class AppointmentDoctorCard extends StatelessWidget {
borderColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
fontSize: 14.f,
fontWeight: FontWeight.w500,
isBold: true,
borderRadius: 12.r,
height: 40.h,
height: 46.h,
icon: AppAssets.cancel,
iconColor: AppColors.whiteColor,
iconSize: 16.h,

@ -0,0 +1,181 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/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/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/doctor_row_appointment_rating.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/loader/bottomsheet_loader.dart';
import 'package:provider/provider.dart';
class AppointmentRatingWidget extends StatefulWidget {
const AppointmentRatingWidget({super.key, required this.patientAppointmentHistoryResponseModel});
final PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel;
@override
State<AppointmentRatingWidget> createState() => _AppointmentRatingWidgetState();
}
class _AppointmentRatingWidgetState extends State<AppointmentRatingWidget> {
late MyAppointmentsViewModel myAppointmentsViewModel;
late BookAppointmentsViewModel bookAppointmentsViewModel;
int rating = 0;
String notes = "";
@override
Widget build(BuildContext context) {
myAppointmentsViewModel = Provider.of<MyAppointmentsViewModel>(context, listen: false);
bookAppointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context, listen: false);
return SizedBox(
height: MediaQuery.of(context).size.height * 0.6,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Scrollable main content
Expanded(
child: Padding(
padding: const EdgeInsets.only(top: 0.0, left: 0, right: 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// Doctor row
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: false,
),
child: BuildDoctorRowAppointmentRating(
patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel,
)),
SizedBox(height: 16),
// Rating box
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: false,
),
width: double.infinity,
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.rateTheDoctor.tr(context: context).toText16(isBold: true),
SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
...List.generate(
5,
(index) => AnimatedSwitcher(
duration: Duration(milliseconds: 1000),
switchInCurve: Curves.elasticOut,
switchOutCurve: Curves.elasticIn,
transitionBuilder: (Widget child, Animation<double> animation) {
return ScaleTransition(child: child, scale: animation);
},
child: Container(
key: ValueKey<int>(rating),
child: IconButton(
iconSize: 45.0,
onPressed: () {
setState(() {
rating = index + 1;
});
},
color: rating >= (index + 1) ? Color.fromRGBO(255, 186, 0, 1.0) : Colors.grey[400],
icon: Icon(rating >= (index + 1) ? Icons.star : Icons.star)),
),
),
)
],
),
],
),
),
),
SizedBox(height: 12),
// Note text field
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: false,
),
child: Padding(
padding: EdgeInsets.all(16.0),
child: TextField(
maxLines: 4,
decoration: InputDecoration.collapsed(
hintText: LocaleKeys.notes.tr(context: context),
hintStyle: TextStyle(fontSize: 16.f, fontWeight: FontWeight.w600, color: Color(0xff2B353E), letterSpacing: -0.64, height: 23 / 16)),
onChanged: (value) {
setState(() {
notes = value;
});
},
))),
],
),
),
),
// Bottom action buttons pinned to bottom
SafeArea(
top: false,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 12.0),
child: Row(
children: [
Expanded(
child: CustomButton(
text: LocaleKeys.later.tr(context: context),
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
onPressed: () {
Navigator.pop(context);
},
),
),
SizedBox(width: 10),
Expanded(
child: CustomButton(
text: LocaleKeys.submit.tr(context: context),
isDisabled: rating == 0,
onPressed: rating == 0 ? null : () async {
// Set up clinic rating and show clinic rating view
// appointmentRatingViewModel!.setTitle(LocaleKeys.rateDoctor.tr(context: context),);
// appointmentRatingViewModel!.setSubTitle(LocaleKeys.howWasYourLastVisitWithDoctor.tr(context: context),);
// appointmentRatingViewModel!.setClinicOrDoctor(true);
LoaderBottomSheet.showLoader();
await myAppointmentsViewModel.submitDoctorRating(docRate: rating, docNote: notes, patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel);
// await appointmentRatingViewModel!.submitClinicRating(clinicRate: rating, clinicNote: note);
LoaderBottomSheet.hideLoader();
Navigator.pop(context);
setState(() {});
},
),
),
],
),
),
),
],
),
);
}
}

@ -1,5 +1,6 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
@ -10,6 +11,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/
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/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/common_bottom_sheet.dart';
@ -47,7 +49,7 @@ class AskDoctorRequestTypeSelect extends StatelessWidget {
listTileTheme: ListTileThemeData(horizontalTitleGap: 4),
),
child: RadioListTile<int>(
title: (askDoctorRequestTypeList[index].description ?? '').toText14(weight: FontWeight.w500),
title: (askDoctorRequestTypeList[index].description ?? '').toText14(isBold: true),
value: index,
fillColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
@ -95,8 +97,8 @@ class AskDoctorRequestTypeSelect extends StatelessWidget {
child: CustomButton(
text: LocaleKeys.confirm.tr(),
onPressed: () async {
Navigator.pop(context);
LoaderBottomSheet.showLoader(loadingText: "Sending Request...");
Navigator.of(context).pop();
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.sendingAskDrRequest.tr(context: context));
await myAppointmentsViewModel.sendAskDocCallRequest(
patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel,
requestType: selectedParameterCodeValue.toString(),
@ -104,17 +106,10 @@ class AskDoctorRequestTypeSelect extends StatelessWidget {
userMobileNumber: myAppointmentsViewModel.appState.getAuthenticatedUser()!.mobileNumber!,
onSuccess: (val) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getSuccessWidget(loadingText: "Request has been sent successfully, you will be contacted soon."),
callBackFunc: () {
Navigator.of(context).pop();
},
title: "",
isCloseButtonVisible: true,
isDismissible: false,
isFullScreen: false,
);
showCommonBottomSheetWithoutHeight(GetIt.instance<NavigationService>().navigatorKey.currentContext!,
child: Padding(padding: EdgeInsets.symmetric(horizontal: 24.h), child: Utils.getSuccessWidget(loadingText: LocaleKeys.askDrRequestSentSuccess.tr())), callBackFunc: () {
Navigator.of(context).pop();
}, title: "", isCloseButtonVisible: false, isDismissible: false, isFullScreen: false, isAutoDismiss: true);
},
onError: (errMessage) {
LoaderBottomSheet.hideLoader();

@ -0,0 +1,67 @@
import 'package:flutter/material.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/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/appointment_details_resp_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/widgets/chip/app_custom_chip_widget.dart';
import 'dart:ui' as ui;
class BuildDoctorRowAppointmentRating extends StatelessWidget {
final PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel;
BuildDoctorRowAppointmentRating({super.key, required this.patientAppointmentHistoryResponseModel});
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(16),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.network(
patientAppointmentHistoryResponseModel.doctorImageURL!,
width: 63.h,
height: 63.h,
fit: BoxFit.cover,
).circle(100),
SizedBox(width: 16.h),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
patientAppointmentHistoryResponseModel.doctorNameObj!.toString().toText16(isBold: true, maxlines: 1),
SizedBox(height: 8.h),
Wrap(
direction: Axis.horizontal,
spacing: 3.h,
runSpacing: 4.h,
children: [
AppCustomChipWidget(
labelText: patientAppointmentHistoryResponseModel.projectName.toString(),
),
AppCustomChipWidget(
labelText: patientAppointmentHistoryResponseModel.clinicName.toString(),
),
Directionality(
textDirection: ui.TextDirection.ltr,
child: AppCustomChipWidget(
labelPadding: EdgeInsetsDirectional.only(start: -8.w, end: 6.w),
icon: AppAssets.ic_date_filter,
labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false),
isEnglishOnly: true,
),
),
],
),
],
),
),
],
),
);
}
}

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

Loading…
Cancel
Save