diff --git a/AuthKey_JG87SJ23A4.p8 b/AuthKey_JG87SJ23A4.p8 new file mode 100644 index 00000000..6a3c2a53 --- /dev/null +++ b/AuthKey_JG87SJ23A4.p8 @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgaPetsOq3tqUlswBN +BFUWIIdEg4dcTCGRUKbAEx8ViFygCgYIKoZIzj0DAQehRANCAAS6IKkZ4tCeBlN6 +DtXiBekCiIbgHxWk8AWppP0H3mJ+I2s8cIEaquL4/yeLDcHzAHf8tHcYhF/TvggT +nc30yDLv +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/README.md b/README.md index 1a459e37..3fea310f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,11 @@ +# Translation prompt & Command + +Read the context for both files & generate translations strings in the en-US.json & ar-SA.json files for the strings containing "needTranslation". Also replace the strings in the dart with the generated Strings. Also do not remove existing properties & do no generate duplicate strings in the json files + +flutter3_32 pub run easy_localization:generate -O ./lib/generated -f keys -o locale_keys.g.dart --source-dir ./assets/langs + + + # HMG Patient App A comprehensive Flutter-based mobile application for HMG (Habib Medical Group) patients, diff --git a/android/app/libs/PenNavUI.aar b/android/app/libs/PenNavUI.aar index 7832df8c..a5658605 100644 Binary files a/android/app/libs/PenNavUI.aar and b/android/app/libs/PenNavUI.aar differ diff --git a/android/app/libs/PenguinRenderer.aar b/android/app/libs/PenguinRenderer.aar index 2926e9ad..b498726f 100644 Binary files a/android/app/libs/PenguinRenderer.aar and b/android/app/libs/PenguinRenderer.aar differ diff --git a/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinView.kt b/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinView.kt index 6c7306d8..3925ca56 100644 --- a/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinView.kt +++ b/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinView.kt @@ -320,6 +320,7 @@ internal class PenguinView( if(clinicId.isEmpty()) return navigateTo(clinicId) +// navigateTo("3-1") } /** diff --git a/assets/images/clinicIcons/1.svg b/assets/images/clinicIcons/1.svg new file mode 100644 index 00000000..70f4171f --- /dev/null +++ b/assets/images/clinicIcons/1.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/104.svg b/assets/images/clinicIcons/104.svg new file mode 100644 index 00000000..3fd875ff --- /dev/null +++ b/assets/images/clinicIcons/104.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/images/clinicIcons/105.svg b/assets/images/clinicIcons/105.svg new file mode 100644 index 00000000..a9f4bd1c --- /dev/null +++ b/assets/images/clinicIcons/105.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/clinicIcons/106.svg b/assets/images/clinicIcons/106.svg new file mode 100644 index 00000000..bd103ad1 --- /dev/null +++ b/assets/images/clinicIcons/106.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/107.svg b/assets/images/clinicIcons/107.svg new file mode 100644 index 00000000..b1946db9 --- /dev/null +++ b/assets/images/clinicIcons/107.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/images/clinicIcons/108.svg b/assets/images/clinicIcons/108.svg new file mode 100644 index 00000000..a29fe001 --- /dev/null +++ b/assets/images/clinicIcons/108.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/clinicIcons/11.svg b/assets/images/clinicIcons/11.svg new file mode 100644 index 00000000..1c276aad --- /dev/null +++ b/assets/images/clinicIcons/11.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/110.svg b/assets/images/clinicIcons/110.svg new file mode 100644 index 00000000..6d533e2d --- /dev/null +++ b/assets/images/clinicIcons/110.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/111.svg b/assets/images/clinicIcons/111.svg new file mode 100644 index 00000000..46ba92f4 --- /dev/null +++ b/assets/images/clinicIcons/111.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/118.svg b/assets/images/clinicIcons/118.svg new file mode 100644 index 00000000..238cd5ac --- /dev/null +++ b/assets/images/clinicIcons/118.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/12.svg b/assets/images/clinicIcons/12.svg new file mode 100644 index 00000000..cb6a23f7 --- /dev/null +++ b/assets/images/clinicIcons/12.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/images/clinicIcons/126.svg b/assets/images/clinicIcons/126.svg new file mode 100644 index 00000000..9d26d975 --- /dev/null +++ b/assets/images/clinicIcons/126.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/128.svg b/assets/images/clinicIcons/128.svg new file mode 100644 index 00000000..0888a5f3 --- /dev/null +++ b/assets/images/clinicIcons/128.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/clinicIcons/130.svg b/assets/images/clinicIcons/130.svg new file mode 100644 index 00000000..5598ebb3 --- /dev/null +++ b/assets/images/clinicIcons/130.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/131.svg b/assets/images/clinicIcons/131.svg new file mode 100644 index 00000000..dbf94b5c --- /dev/null +++ b/assets/images/clinicIcons/131.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/clinicIcons/134.svg b/assets/images/clinicIcons/134.svg new file mode 100644 index 00000000..42f84173 --- /dev/null +++ b/assets/images/clinicIcons/134.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/14.svg b/assets/images/clinicIcons/14.svg new file mode 100644 index 00000000..46ba92f4 --- /dev/null +++ b/assets/images/clinicIcons/14.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/140.svg b/assets/images/clinicIcons/140.svg new file mode 100644 index 00000000..56c7a538 --- /dev/null +++ b/assets/images/clinicIcons/140.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/clinicIcons/148.svg b/assets/images/clinicIcons/148.svg new file mode 100644 index 00000000..bd103ad1 --- /dev/null +++ b/assets/images/clinicIcons/148.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/149.svg b/assets/images/clinicIcons/149.svg new file mode 100644 index 00000000..0888a5f3 --- /dev/null +++ b/assets/images/clinicIcons/149.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/clinicIcons/16.svg b/assets/images/clinicIcons/16.svg new file mode 100644 index 00000000..e1ed3160 --- /dev/null +++ b/assets/images/clinicIcons/16.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/17.svg b/assets/images/clinicIcons/17.svg new file mode 100644 index 00000000..44bad059 --- /dev/null +++ b/assets/images/clinicIcons/17.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/clinicIcons/18.svg b/assets/images/clinicIcons/18.svg new file mode 100644 index 00000000..1148ad6b --- /dev/null +++ b/assets/images/clinicIcons/18.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/19.svg b/assets/images/clinicIcons/19.svg new file mode 100644 index 00000000..5b7d43bd --- /dev/null +++ b/assets/images/clinicIcons/19.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/2.svg b/assets/images/clinicIcons/2.svg new file mode 100644 index 00000000..b4b15214 --- /dev/null +++ b/assets/images/clinicIcons/2.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/clinicIcons/20.svg b/assets/images/clinicIcons/20.svg new file mode 100644 index 00000000..9e6db603 --- /dev/null +++ b/assets/images/clinicIcons/20.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/clinicIcons/21.svg b/assets/images/clinicIcons/21.svg new file mode 100644 index 00000000..9b9ee825 --- /dev/null +++ b/assets/images/clinicIcons/21.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/clinicIcons/23.svg b/assets/images/clinicIcons/23.svg new file mode 100644 index 00000000..4f8b915d --- /dev/null +++ b/assets/images/clinicIcons/23.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/clinicIcons/24.svg b/assets/images/clinicIcons/24.svg new file mode 100644 index 00000000..70f4171f --- /dev/null +++ b/assets/images/clinicIcons/24.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/25.svg b/assets/images/clinicIcons/25.svg new file mode 100644 index 00000000..70f4171f --- /dev/null +++ b/assets/images/clinicIcons/25.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/250.svg b/assets/images/clinicIcons/250.svg new file mode 100644 index 00000000..b4b15214 --- /dev/null +++ b/assets/images/clinicIcons/250.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/clinicIcons/252.svg b/assets/images/clinicIcons/252.svg new file mode 100644 index 00000000..9545d249 --- /dev/null +++ b/assets/images/clinicIcons/252.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/clinicIcons/253.svg b/assets/images/clinicIcons/253.svg new file mode 100644 index 00000000..71182316 --- /dev/null +++ b/assets/images/clinicIcons/253.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/254.svg b/assets/images/clinicIcons/254.svg new file mode 100644 index 00000000..5e24ad49 --- /dev/null +++ b/assets/images/clinicIcons/254.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/clinicIcons/255.svg b/assets/images/clinicIcons/255.svg new file mode 100644 index 00000000..e9ccd2bb --- /dev/null +++ b/assets/images/clinicIcons/255.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/clinicIcons/257.svg b/assets/images/clinicIcons/257.svg new file mode 100644 index 00000000..b4b15214 --- /dev/null +++ b/assets/images/clinicIcons/257.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/clinicIcons/26.svg b/assets/images/clinicIcons/26.svg new file mode 100644 index 00000000..b82e118d --- /dev/null +++ b/assets/images/clinicIcons/26.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/clinicIcons/260.svg b/assets/images/clinicIcons/260.svg new file mode 100644 index 00000000..60ed9c2b --- /dev/null +++ b/assets/images/clinicIcons/260.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/clinicIcons/262.svg b/assets/images/clinicIcons/262.svg new file mode 100644 index 00000000..371c0ba5 --- /dev/null +++ b/assets/images/clinicIcons/262.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/assets/images/clinicIcons/264.svg b/assets/images/clinicIcons/264.svg new file mode 100644 index 00000000..b1f231e1 --- /dev/null +++ b/assets/images/clinicIcons/264.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/27.svg b/assets/images/clinicIcons/27.svg new file mode 100644 index 00000000..6da1ed72 --- /dev/null +++ b/assets/images/clinicIcons/27.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/28.svg b/assets/images/clinicIcons/28.svg new file mode 100644 index 00000000..483893ce --- /dev/null +++ b/assets/images/clinicIcons/28.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/clinicIcons/29.svg b/assets/images/clinicIcons/29.svg new file mode 100644 index 00000000..009a406f --- /dev/null +++ b/assets/images/clinicIcons/29.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/clinicIcons/3.svg b/assets/images/clinicIcons/3.svg new file mode 100644 index 00000000..7a313ee7 --- /dev/null +++ b/assets/images/clinicIcons/3.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/30.svg b/assets/images/clinicIcons/30.svg new file mode 100644 index 00000000..d16a33a4 --- /dev/null +++ b/assets/images/clinicIcons/30.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/clinicIcons/31.svg b/assets/images/clinicIcons/31.svg new file mode 100644 index 00000000..93becf0b --- /dev/null +++ b/assets/images/clinicIcons/31.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/32.svg b/assets/images/clinicIcons/32.svg new file mode 100644 index 00000000..9256566c --- /dev/null +++ b/assets/images/clinicIcons/32.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/clinicIcons/34.svg b/assets/images/clinicIcons/34.svg new file mode 100644 index 00000000..b592ad80 --- /dev/null +++ b/assets/images/clinicIcons/34.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/35.svg b/assets/images/clinicIcons/35.svg new file mode 100644 index 00000000..c5ac8796 --- /dev/null +++ b/assets/images/clinicIcons/35.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/36.svg b/assets/images/clinicIcons/36.svg new file mode 100644 index 00000000..1d171d41 --- /dev/null +++ b/assets/images/clinicIcons/36.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/images/clinicIcons/38.svg b/assets/images/clinicIcons/38.svg new file mode 100644 index 00000000..9448feb8 --- /dev/null +++ b/assets/images/clinicIcons/38.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/images/clinicIcons/4.svg b/assets/images/clinicIcons/4.svg new file mode 100644 index 00000000..beb7239c --- /dev/null +++ b/assets/images/clinicIcons/4.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/40.svg b/assets/images/clinicIcons/40.svg new file mode 100644 index 00000000..168ece67 --- /dev/null +++ b/assets/images/clinicIcons/40.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/clinicIcons/41.svg b/assets/images/clinicIcons/41.svg new file mode 100644 index 00000000..93becf0b --- /dev/null +++ b/assets/images/clinicIcons/41.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/42.svg b/assets/images/clinicIcons/42.svg new file mode 100644 index 00000000..5b7d43bd --- /dev/null +++ b/assets/images/clinicIcons/42.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/44.svg b/assets/images/clinicIcons/44.svg new file mode 100644 index 00000000..e9edf9aa --- /dev/null +++ b/assets/images/clinicIcons/44.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/46.svg b/assets/images/clinicIcons/46.svg new file mode 100644 index 00000000..926ad199 --- /dev/null +++ b/assets/images/clinicIcons/46.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/clinicIcons/5.svg b/assets/images/clinicIcons/5.svg new file mode 100644 index 00000000..d25576e0 --- /dev/null +++ b/assets/images/clinicIcons/5.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/50.svg b/assets/images/clinicIcons/50.svg new file mode 100644 index 00000000..662a64ea --- /dev/null +++ b/assets/images/clinicIcons/50.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/clinicIcons/51.svg b/assets/images/clinicIcons/51.svg new file mode 100644 index 00000000..8ef6791f --- /dev/null +++ b/assets/images/clinicIcons/51.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/clinicIcons/53.svg b/assets/images/clinicIcons/53.svg new file mode 100644 index 00000000..56c7a538 --- /dev/null +++ b/assets/images/clinicIcons/53.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/clinicIcons/55.svg b/assets/images/clinicIcons/55.svg new file mode 100644 index 00000000..a94748cf --- /dev/null +++ b/assets/images/clinicIcons/55.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/images/clinicIcons/56.svg b/assets/images/clinicIcons/56.svg new file mode 100644 index 00000000..be814451 --- /dev/null +++ b/assets/images/clinicIcons/56.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/58.svg b/assets/images/clinicIcons/58.svg new file mode 100644 index 00000000..49871663 --- /dev/null +++ b/assets/images/clinicIcons/58.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/59.svg b/assets/images/clinicIcons/59.svg new file mode 100644 index 00000000..87e22694 --- /dev/null +++ b/assets/images/clinicIcons/59.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/6.svg b/assets/images/clinicIcons/6.svg new file mode 100644 index 00000000..50e0c7b9 --- /dev/null +++ b/assets/images/clinicIcons/6.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/clinicIcons/67.svg b/assets/images/clinicIcons/67.svg new file mode 100644 index 00000000..4f8b915d --- /dev/null +++ b/assets/images/clinicIcons/67.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/clinicIcons/68.svg b/assets/images/clinicIcons/68.svg new file mode 100644 index 00000000..d16a33a4 --- /dev/null +++ b/assets/images/clinicIcons/68.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/clinicIcons/69.svg b/assets/images/clinicIcons/69.svg new file mode 100644 index 00000000..70f4171f --- /dev/null +++ b/assets/images/clinicIcons/69.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/7.svg b/assets/images/clinicIcons/7.svg new file mode 100644 index 00000000..b1ec62e7 --- /dev/null +++ b/assets/images/clinicIcons/7.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/74.svg b/assets/images/clinicIcons/74.svg new file mode 100644 index 00000000..62371818 --- /dev/null +++ b/assets/images/clinicIcons/74.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/78.svg b/assets/images/clinicIcons/78.svg new file mode 100644 index 00000000..60ed9c2b --- /dev/null +++ b/assets/images/clinicIcons/78.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/clinicIcons/79.svg b/assets/images/clinicIcons/79.svg new file mode 100644 index 00000000..1decce94 --- /dev/null +++ b/assets/images/clinicIcons/79.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/clinicIcons/8.svg b/assets/images/clinicIcons/8.svg new file mode 100644 index 00000000..d16a33a4 --- /dev/null +++ b/assets/images/clinicIcons/8.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/clinicIcons/83.svg b/assets/images/clinicIcons/83.svg new file mode 100644 index 00000000..87e22694 --- /dev/null +++ b/assets/images/clinicIcons/83.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/84.svg b/assets/images/clinicIcons/84.svg new file mode 100644 index 00000000..ed242ec2 --- /dev/null +++ b/assets/images/clinicIcons/84.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/clinicIcons/85.svg b/assets/images/clinicIcons/85.svg new file mode 100644 index 00000000..076d2018 --- /dev/null +++ b/assets/images/clinicIcons/85.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/clinicIcons/86.svg b/assets/images/clinicIcons/86.svg new file mode 100644 index 00000000..4615e671 --- /dev/null +++ b/assets/images/clinicIcons/86.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/clinicIcons/87.svg b/assets/images/clinicIcons/87.svg new file mode 100644 index 00000000..d0b95ae1 --- /dev/null +++ b/assets/images/clinicIcons/87.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/clinicIcons/88.svg b/assets/images/clinicIcons/88.svg new file mode 100644 index 00000000..93f30422 --- /dev/null +++ b/assets/images/clinicIcons/88.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/89.svg b/assets/images/clinicIcons/89.svg new file mode 100644 index 00000000..768ef4a2 --- /dev/null +++ b/assets/images/clinicIcons/89.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/images/clinicIcons/9.svg b/assets/images/clinicIcons/9.svg new file mode 100644 index 00000000..aa0acfba --- /dev/null +++ b/assets/images/clinicIcons/9.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/90.svg b/assets/images/clinicIcons/90.svg new file mode 100644 index 00000000..6f8a44b0 --- /dev/null +++ b/assets/images/clinicIcons/90.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/91.svg b/assets/images/clinicIcons/91.svg new file mode 100644 index 00000000..42344286 --- /dev/null +++ b/assets/images/clinicIcons/91.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/93.svg b/assets/images/clinicIcons/93.svg new file mode 100644 index 00000000..be1b4a69 --- /dev/null +++ b/assets/images/clinicIcons/93.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/98.svg b/assets/images/clinicIcons/98.svg new file mode 100644 index 00000000..93f30422 --- /dev/null +++ b/assets/images/clinicIcons/98.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/Cardiac Surgery.svg b/assets/images/clinicIcons/Cardiac Surgery.svg new file mode 100644 index 00000000..20758717 --- /dev/null +++ b/assets/images/clinicIcons/Cardiac Surgery.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/clinicIcons/Colorectal Oncology Clinic 2.svg b/assets/images/clinicIcons/Colorectal Oncology Clinic 2.svg new file mode 100644 index 00000000..b3898e57 --- /dev/null +++ b/assets/images/clinicIcons/Colorectal Oncology Clinic 2.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/Dental Employee Clinic.svg b/assets/images/clinicIcons/Dental Employee Clinic.svg new file mode 100644 index 00000000..44bad059 --- /dev/null +++ b/assets/images/clinicIcons/Dental Employee Clinic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/clinicIcons/ENT Oncology Clinic 2.svg b/assets/images/clinicIcons/ENT Oncology Clinic 2.svg new file mode 100644 index 00000000..7defb9c0 --- /dev/null +++ b/assets/images/clinicIcons/ENT Oncology Clinic 2.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/clinicIcons/ER CLINIC.svg b/assets/images/clinicIcons/ER CLINIC.svg new file mode 100644 index 00000000..cb239be1 --- /dev/null +++ b/assets/images/clinicIcons/ER CLINIC.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/clinicIcons/EXCELLENCE CLINIC.svg b/assets/images/clinicIcons/EXCELLENCE CLINIC.svg new file mode 100644 index 00000000..b6cc121a --- /dev/null +++ b/assets/images/clinicIcons/EXCELLENCE CLINIC.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/clinicIcons/Frame.svg b/assets/images/clinicIcons/Frame.svg new file mode 100644 index 00000000..c573a489 --- /dev/null +++ b/assets/images/clinicIcons/Frame.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/clinicIcons/Gynecology oncology Clinic.svg b/assets/images/clinicIcons/Gynecology oncology Clinic.svg new file mode 100644 index 00000000..9bef9cbe --- /dev/null +++ b/assets/images/clinicIcons/Gynecology oncology Clinic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/clinicIcons/Interventional Radiology Clinic.svg b/assets/images/clinicIcons/Interventional Radiology Clinic.svg new file mode 100644 index 00000000..5b7d43bd --- /dev/null +++ b/assets/images/clinicIcons/Interventional Radiology Clinic.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/LIVER , GALLBLADDER, PANCREATIC CLINIC.svg b/assets/images/clinicIcons/LIVER , GALLBLADDER, PANCREATIC CLINIC.svg new file mode 100644 index 00000000..117b29b9 --- /dev/null +++ b/assets/images/clinicIcons/LIVER , GALLBLADDER, PANCREATIC CLINIC.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/clinicIcons/Maternal-Fetal Medicine Clinic.svg b/assets/images/clinicIcons/Maternal-Fetal Medicine Clinic.svg new file mode 100644 index 00000000..708d85fb --- /dev/null +++ b/assets/images/clinicIcons/Maternal-Fetal Medicine Clinic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/clinicIcons/Maxilliofacial Surgery.svg b/assets/images/clinicIcons/Maxilliofacial Surgery.svg new file mode 100644 index 00000000..66a0fbd1 --- /dev/null +++ b/assets/images/clinicIcons/Maxilliofacial Surgery.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/OB-GYNE physiotherapy clinic-1.svg b/assets/images/clinicIcons/OB-GYNE physiotherapy clinic-1.svg new file mode 100644 index 00000000..5e24ad49 --- /dev/null +++ b/assets/images/clinicIcons/OB-GYNE physiotherapy clinic-1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/clinicIcons/Occupational Therapy Clinic.svg b/assets/images/clinicIcons/Occupational Therapy Clinic.svg new file mode 100644 index 00000000..31815199 --- /dev/null +++ b/assets/images/clinicIcons/Occupational Therapy Clinic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/clinicIcons/Orthopedic Oncology Surgery Clinic.svg b/assets/images/clinicIcons/Orthopedic Oncology Surgery Clinic.svg new file mode 100644 index 00000000..226d56c7 --- /dev/null +++ b/assets/images/clinicIcons/Orthopedic Oncology Surgery Clinic.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/images/clinicIcons/RRT Clinic 2.svg b/assets/images/clinicIcons/RRT Clinic 2.svg new file mode 100644 index 00000000..f4e451a4 --- /dev/null +++ b/assets/images/clinicIcons/RRT Clinic 2.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/Radiation Oncology Clinic 2.svg b/assets/images/clinicIcons/Radiation Oncology Clinic 2.svg new file mode 100644 index 00000000..ac8e5e15 --- /dev/null +++ b/assets/images/clinicIcons/Radiation Oncology Clinic 2.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/clinicIcons/Retinopathy of prematurity(ROP).svg b/assets/images/clinicIcons/Retinopathy of prematurity(ROP).svg new file mode 100644 index 00000000..814b1a73 --- /dev/null +++ b/assets/images/clinicIcons/Retinopathy of prematurity(ROP).svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/SKIN AND HAIR CARE.svg b/assets/images/clinicIcons/SKIN AND HAIR CARE.svg new file mode 100644 index 00000000..dbe39722 --- /dev/null +++ b/assets/images/clinicIcons/SKIN AND HAIR CARE.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/clinicIcons/SPA SKIN CARE.svg b/assets/images/clinicIcons/SPA SKIN CARE.svg new file mode 100644 index 00000000..55ce4736 --- /dev/null +++ b/assets/images/clinicIcons/SPA SKIN CARE.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/clinicIcons/Smell Clinic 2.svg b/assets/images/clinicIcons/Smell Clinic 2.svg new file mode 100644 index 00000000..53417591 --- /dev/null +++ b/assets/images/clinicIcons/Smell Clinic 2.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/images/clinicIcons/Surgical Oncology Clinic.svg b/assets/images/clinicIcons/Surgical Oncology Clinic.svg new file mode 100644 index 00000000..7e7f1db7 --- /dev/null +++ b/assets/images/clinicIcons/Surgical Oncology Clinic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/clinicIcons/Thoracic Clinic.svg b/assets/images/clinicIcons/Thoracic Clinic.svg new file mode 100644 index 00000000..9d26d975 --- /dev/null +++ b/assets/images/clinicIcons/Thoracic Clinic.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/clinicIcons/Urology Oncology Clinic.svg b/assets/images/clinicIcons/Urology Oncology Clinic.svg new file mode 100644 index 00000000..935af2ee --- /dev/null +++ b/assets/images/clinicIcons/Urology Oncology Clinic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/png/full_body_back_female.png b/assets/images/png/full_body_back_female.png new file mode 100644 index 00000000..9b3aa74d Binary files /dev/null and b/assets/images/png/full_body_back_female.png differ diff --git a/assets/images/png/full_body_back.png b/assets/images/png/full_body_back_male.png similarity index 100% rename from assets/images/png/full_body_back.png rename to assets/images/png/full_body_back_male.png diff --git a/assets/images/png/full_body_front_female.png b/assets/images/png/full_body_front_female.png new file mode 100644 index 00000000..c89608b0 Binary files /dev/null and b/assets/images/png/full_body_front_female.png differ diff --git a/assets/images/png/full_body_front.png b/assets/images/png/full_body_front_male.png similarity index 100% rename from assets/images/png/full_body_front.png rename to assets/images/png/full_body_front_male.png diff --git a/assets/images/png/smartwatches/Huawei_Watch.png b/assets/images/png/smartwatches/Huawei_Watch.png new file mode 100644 index 00000000..97c416e4 Binary files /dev/null and b/assets/images/png/smartwatches/Huawei_Watch.png differ diff --git a/assets/images/png/smartwatches/Whoop_Watch.png b/assets/images/png/smartwatches/Whoop_Watch.png new file mode 100644 index 00000000..b3ac20cd Binary files /dev/null and b/assets/images/png/smartwatches/Whoop_Watch.png differ diff --git a/assets/images/svg/ai_overview.svg b/assets/images/svg/ai_overview.svg new file mode 100644 index 00000000..95c29aac --- /dev/null +++ b/assets/images/svg/ai_overview.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/svg/ar_lang_icon.svg b/assets/images/svg/ar_lang_icon.svg new file mode 100644 index 00000000..d1c2be0c --- /dev/null +++ b/assets/images/svg/ar_lang_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/arrow-back_new.svg b/assets/images/svg/arrow-back_new.svg new file mode 100644 index 00000000..cae96941 --- /dev/null +++ b/assets/images/svg/arrow-back_new.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/book_appo_bottom.svg b/assets/images/svg/book_appo_bottom.svg index d09107ec..18b65b8f 100644 --- a/assets/images/svg/book_appo_bottom.svg +++ b/assets/images/svg/book_appo_bottom.svg @@ -1,3 +1,3 @@ - - + + diff --git a/assets/images/svg/calculator_icon.svg b/assets/images/svg/calculator_icon.svg new file mode 100644 index 00000000..989a4db8 --- /dev/null +++ b/assets/images/svg/calculator_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/change_language_home_page.svg b/assets/images/svg/change_language_home_page.svg new file mode 100644 index 00000000..716a3b02 --- /dev/null +++ b/assets/images/svg/change_language_home_page.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/images/svg/close_bottom_nav_new.svg b/assets/images/svg/close_bottom_nav_new.svg new file mode 100644 index 00000000..1b7b9378 --- /dev/null +++ b/assets/images/svg/close_bottom_nav_new.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/close_bottom_nav_trans.svg b/assets/images/svg/close_bottom_nav_trans.svg new file mode 100644 index 00000000..e00aaf96 --- /dev/null +++ b/assets/images/svg/close_bottom_nav_trans.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/close_bottom_sheet_icon.svg b/assets/images/svg/close_bottom_sheet_icon.svg index 599ce85f..e00aaf96 100644 --- a/assets/images/svg/close_bottom_sheet_icon.svg +++ b/assets/images/svg/close_bottom_sheet_icon.svg @@ -1,4 +1,3 @@ - - - + + diff --git a/assets/images/svg/colored_dots_icon.svg b/assets/images/svg/colored_dots_icon.svg new file mode 100644 index 00000000..7f05dc57 --- /dev/null +++ b/assets/images/svg/colored_dots_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/doctor_response_icon.svg b/assets/images/svg/doctor_response_icon.svg new file mode 100644 index 00000000..1c4b156a --- /dev/null +++ b/assets/images/svg/doctor_response_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/en_lang_icon.svg b/assets/images/svg/en_lang_icon.svg new file mode 100644 index 00000000..fa1c1435 --- /dev/null +++ b/assets/images/svg/en_lang_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/feedback_fill.svg b/assets/images/svg/feedback_fill.svg new file mode 100644 index 00000000..bb7c6a23 --- /dev/null +++ b/assets/images/svg/feedback_fill.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/guard_icon.svg b/assets/images/svg/guard_icon.svg new file mode 100644 index 00000000..e3a68d9d --- /dev/null +++ b/assets/images/svg/guard_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/high_caution.svg b/assets/images/svg/high_caution.svg new file mode 100644 index 00000000..d268468f --- /dev/null +++ b/assets/images/svg/high_caution.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/home_bottom.svg b/assets/images/svg/home_bottom.svg index 1bf82f98..70c3f1f2 100644 --- a/assets/images/svg/home_bottom.svg +++ b/assets/images/svg/home_bottom.svg @@ -1,3 +1,4 @@ - - + + + diff --git a/assets/images/svg/home_bottom_fill.svg b/assets/images/svg/home_bottom_fill.svg new file mode 100644 index 00000000..30ed655f --- /dev/null +++ b/assets/images/svg/home_bottom_fill.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/information-circle.svg b/assets/images/svg/information-circle.svg new file mode 100644 index 00000000..f1a850e4 --- /dev/null +++ b/assets/images/svg/information-circle.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/insurance_history_icon.svg b/assets/images/svg/insurance_history_icon.svg index 4ae2a1c5..e81bd873 100644 --- a/assets/images/svg/insurance_history_icon.svg +++ b/assets/images/svg/insurance_history_icon.svg @@ -1,4 +1,4 @@ - - - + + + diff --git a/assets/images/svg/location_fill.svg b/assets/images/svg/location_fill.svg new file mode 100644 index 00000000..a5afffb7 --- /dev/null +++ b/assets/images/svg/location_fill.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/my_child_vaccine.svg b/assets/images/svg/my_child_vaccine.svg new file mode 100644 index 00000000..f671baf8 --- /dev/null +++ b/assets/images/svg/my_child_vaccine.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/my_files_bottom.svg b/assets/images/svg/my_files_bottom.svg index 813805e0..a21fb4b3 100644 --- a/assets/images/svg/my_files_bottom.svg +++ b/assets/images/svg/my_files_bottom.svg @@ -1,4 +1,4 @@ - - + + diff --git a/assets/images/svg/my_files_bottom_fill.svg b/assets/images/svg/my_files_bottom_fill.svg new file mode 100644 index 00000000..e4916e00 --- /dev/null +++ b/assets/images/svg/my_files_bottom_fill.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/news.svg b/assets/images/svg/news.svg index 82f9dcfc..6ed40d3a 100644 --- a/assets/images/svg/news.svg +++ b/assets/images/svg/news.svg @@ -1,6 +1,6 @@ - - - - - + + + + + diff --git a/assets/images/svg/news_fill.svg b/assets/images/svg/news_fill.svg new file mode 100644 index 00000000..ed9697cc --- /dev/null +++ b/assets/images/svg/news_fill.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/services_bottom.svg b/assets/images/svg/services_bottom.svg index baa3d6b8..2b0733f2 100644 --- a/assets/images/svg/services_bottom.svg +++ b/assets/images/svg/services_bottom.svg @@ -1,6 +1,6 @@ - - - - + + + + diff --git a/assets/images/svg/services_bottom_fill.svg b/assets/images/svg/services_bottom_fill.svg new file mode 100644 index 00000000..41f4f5b2 --- /dev/null +++ b/assets/images/svg/services_bottom_fill.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/svg/symptom_bottom_icon.svg b/assets/images/svg/symptom_bottom_icon.svg index bc729711..e9b3bd03 100644 --- a/assets/images/svg/symptom_bottom_icon.svg +++ b/assets/images/svg/symptom_bottom_icon.svg @@ -1,3 +1,3 @@ - + diff --git a/assets/images/svg/symptom_bottom_icon_fill.svg b/assets/images/svg/symptom_bottom_icon_fill.svg new file mode 100644 index 00000000..7192ae8a --- /dev/null +++ b/assets/images/svg/symptom_bottom_icon_fill.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 19db9820..11ab9414 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -408,7 +408,7 @@ "brand": "العلامة التجارية", "power": "القوة", "diameter": "القطر", - "remarks": "ملاحظات", + "remarks": "ملاحظات: ", "activeMedications": "الأدوية النشطة", "expDate": "تاريخ انتهاء الصلاحية النشط:", "route": "الطريق", @@ -1213,5 +1213,363 @@ "virtualTour": "جولة افتراضية", "carParking": "موقف السيارات", "latestNews": "آخر الأخبار", - "hmgContact": "اتصل بمجموعة الحبيب الطبية" -} \ No newline at end of file + "hmgContact": "اتصل بمجموعة الحبيب الطبية", + "durationCannotExceed90": "لا يجوز أن تتجاوز المدة 90 دقيقة", + "unexpectedError": "حدث خطأ غير متوقع", + "gettingAmbulanceTransportOption": "جاري الحصول على خيارات نقل الإسعاف", + "fetchingAppointment": "جاري جلب الموعد", + "doYouWantToCancelTheRequest": "هل تريد إلغاء الطلب", + "cancellingRequest": "جاري إلغاء الطلب", + "fetchingTermsAndConditions": "جاري جلب الشروط والأحكام", + "selectLocationPrescriptionDelivery": "يرجى تحديد موقع توصيل الوصفة الطبية", + "noRadiologyOrders": "لم يتم العثور على أي طلبات تصوير شعاعي", + "ageIsRequired": "العمر مطلوب", + "invalidAge": "العمر غير صالح", + "ageMustBeBetween11And120": "يجب أن يكون العمر بين 11 و 120", + "heightIsRequired": "الطول مطلوب", + "invalidHeight": "الطول غير صالح", + "weightIsRequired": "الوزن مطلوب", + "invalidWeight": "الوزن غير صالح", + "timeToDrinkWater": "حان وقت شرب الماء! 💧", + "stayHydratedDrinkWater": "ابق رطبًا! اشرب {amount} مل من الماء.", + "visitPharmacyOnline": "زيارة الصيدلة على الانترنت", + "howAreYouFeelingToday": "كيف حالك اليوم؟", + "checkYourSymptomsWithScale": "تحقق من أعراضك باستخدام ذا المقياس", + "checkYourSymptoms": "تحقق من أعراضك", + "noUpcomingAppointmentPleaseBook": "ليس لديك أي مواعيد قادمة. يرجى حجز موعد", + "youHaveEROnlineCheckInRequest": "لديك طلب تسجيل وصول عبر الإنترنت للطوارئ", + "quickLinks": "روابط سريعة", + "viewMedicalFileLandingPage": "عرض الملف الطبي", + "immediateLiveCareRequest": "طلب LiveCare الفوري", + "yourTurnIsAfterPatients": "دورك بعد {count} مريض.", + "dontHaveHHCOrders": "ليس لديك أي أوامر رعاية صحية منزلية حتى الآن.", + "hhcOrders": "أوامر الرعاية الصحية المنزلية", + "requestedServices": "الخدمات المطلوبة", + "selectServices": "اختر الخدمات", + "selectedServices": "الخدمات المختارة", + "createNewRequest": "إنشاء طلب جديد", + "youHaveNoPendingRequests": "ليس لديك أي طلبات معلقة.", + "noInsuranceDataFound": "لم يتم العثور على بيانات التأمين...", + "noInsuranceUpdateRequest": "لم يتم العثور على أي طلبات لتحديث بيانات التأمين.", + "policyNumberInsurancePage": "الوثيقة: {number}", + "insuranceExpired": "التأمين منتهي الصلاحية", + "insuranceActive": "التأمين نشط", + "patientCardID": "رقم بطاقة المريض: {id}", + "noInsuranceApprovals": "لم تحصل على أي موافقات تأمينية حتى الآن.", + "noInsuranceWithHMG": "ليس لديك تأمين مسجل لدى مجموعة حبيب الطبية.", + "referenceRange": "النطاق المرجعي", + "downloadReport": "تنزيل التقرير", + "generatingReport": "جارٍ إنشاء التقرير، يرجى الانتظار...", + "noLabResults": "ليس لديك أي نتائج مختبرية حتى الآن.", + "labResultDetails": "تفاصيل نتائج المختبر", + "resultOf": "نتيجة", + "whatIsThisResult": "ما هي هذه النتيجة؟", + "lastTested": "آخر اختبار", + "byVisit": "حسب الزيارة", + "byTest": "حسب التحليل", + "results": "نتائج", + "viewResults": "عرض النتائج", + "rebook": "إعادة الحجز", + "noOphthalmologyAppointments": "لم يتم العثور على أي مواعيد في قسم طب العيون...", + "noVitalSignsRecordedYet": "لا توجد علامات حيوية مسجلة بعد", + "appointmentsAndVisits": "المواعيد والزيارات", + "labAndRadiology": "المختبر والأشعة", + "activeMedicationsAndPrescriptions": "الأدوية النشطة والوصفات الطبية", + "allPrescriptions": "جميع الوصفات", + "allMedications": "جميع الأدوية", + "youDontHaveAnyPrescriptionsYet": "ليس لديك أي وصفات طبية بعد.", + "youDontHaveAnyCompletedVisitsYet": "ليس لديك أي زيارات مكتملة بعد", + "others": "أخرى", + "allergyInfo": "معلومات الحساسية", + "vaccineInfo": "معلومات اللقاحات", + "updateInsuranceInfo": "تحديث التأمين", + "myInvoicesList": "قائمة فواتيري", + "ancillaryOrdersList": "قائمة الطلبات المساعدة", + "youDontHaveAnySickLeavesYet": "ليس لديك أي إجازات مرضية بعد.", + "medicalReports": "التقارير الطبية", + "sickLeaveReport": "تقرير الإجازة المرضية", + "weightTracker": "متتبع الوزن", + "askYourDoctor": "اسأل طبيبك", + "internetPairing": "الاقتران بالإنترنت", + "requested": "مطلوب", + "youDontHaveAnyMedicalReportsYet": "ليس لديك أي تقارير طبية بعد.", + "requestMedicalReport": "طلب تقرير طبي", + "youDoNotHaveAnyAppointmentsToRequestMedicalReport": "ليس لديك أي مواعيد لطلب تقرير طبي.", + "areYouSureYouWantToRequestMedicalReport": "هل أنت متأكد أنك تريد طلب تقرير طبي لهذا الموعد؟", + "yourMedicalReportRequestSubmittedSuccessfully": "تم إرسال طلب التقرير الطبي بنجاح.", + "monthlyHealthSummaryReportDisclaimer": "يعكس تقرير الملخص الصحي الشهري هذا المؤشرات الصحية ونتائج التحليل لأحدث الزيارات. يرجى ملاحظة أن هذا سيتم إرساله تلقائيًا من النظام ولا يعتبر تقريرًا رسميًا لذا لا ينبغي اتخاذ أي قرار طبي بناءً عليه", + "updatingMonthlyReportStatus": "جاري تحديث حالة التقرير الشهري...", + "monthlyReportStatusUpdatedSuccessfully": "تم تحديث حالة التقرير الشهري بنجاح", + "whoCanViewMyMedicalFile": "من يمكنه عرض ملفي الطبي؟", + "acceptedYourRequestToBeYourFamilyMember": "{status} طلبك لتكون فردًا من عائلتك", + "canViewYourFile": "يمكنه عرض ملفك", + "hasARequestPendingToBeYourFamilyMember": "لديه طلب {status} ليكون فردًا من عائلتك", + "wantsToAddYouAsTheirFamilyMember": "يريد إضافتك كفرد من عائلته", + "rejectedYourRequestToBeYourFamilyMember": "{status} طلبك لتكون فردًا من عائلتك", + "rejectedYourFamilyMemberRequest": "{status} طلب فرد عائلتك", + "notAvailable": "غير متاح", + "selectAProfile": "الرجاء تحديد ملف تعريف", + "switchFamilyFile": "قم بالتبديل من قائمة الملفات الطبية أدناه", + "medicalFiles": "الملفات الطبية", + "addANewFamilyMember": "إضافة فرد جديد من العائلة", + "viewInvoiceDetails": "عرض تفاصيل الفاتورة", + "outPatient": "مريض خارجي", + "invoiceDetails": "تفاصيل الفاتورة", + "sendingEmailPleaseWait": "جاري إرسال ال��ريد الإلكتروني، يرجى الانتظار...", + "emailSentSuccessfullyMessage": "تم إرسال البريد الإلكتروني بنجاح.", + "discount": "خصم", + "paid": "مدفوع", + "fetchingInvoiceDetails": "جارٍ جلب تفاصيل الفاتورة، يرجى الانتظار...", + "scanQRCode": "مسح رمز الاستجابة السريعة", + "parkingSlotDetails": "تفاصيل موقف السيارة", + "slotNumber": "رقم الموقف: {code}", + "basement": "الطابق: {description}", + "parkingDate": "التاريخ: {date}", + "parkedSince": "متوقف منذ: {time}", + "resetDirection": "إعادة تعيين الاتجاه", + "noPrescriptionOrdersYet": "ليس لديك أي طلبات وصفات طبية حتى الآن.", + "fetchingPrescriptionPDFPleaseWait": "جاري جلب ملف الوصفة الطبية، يرجى الانتظار...", + "ratingValue": "التقييم: {rating}", + "downloadPrescription": "تحميل الوصفة الطبية", + "fetchingPrescriptionDetails": "جاري جلب تفاصيل الوصفة الطبية...", + "switchBackFamilyFile": "العودة إلى ملف العائلة", + "profileAndSettings": "الملف الشخصي والإعدادات", + "quickActions": "إجراءات سريعة", + "notificationsSettings": "إعدادات الإشعارات", + "touchIDFaceIDServices": "خدمات Touch ID / Face ID", + "personalInformation": "المعلومات الشخصية", + "updateEmailAddress": "تحديث عنوان البريد الإلكتروني", + "helpAndSupport": "المساعدة والدعم", + "permissionsProfile": "الأذونات", + "privacyPolicy": "سياسة الخصوصية", + "deactivateAccount": "إلغاء تنشيط الحساب", + "ageYearsOld": "{age} {yearsOld}", + "youDontHaveRadiologyOrders": "ليس لديك أي نتائج للأشعة حتى الآن.", + "radiologyResult": "نتيجة الأشعة", + "viewRadiologyImage": "عرض صورة الأشعة", + "rateClinic": "تقييم العيادة", + "back": "رجوع", + "rateDoctor": "تقييم الطبيب", + "howWasYourLastVisitWithDoctor": "كيف كانت زيارتك الأخيرة مع الطبيب؟", + "dateOfBirthSymptoms": "ما هو تاريخ ميلادك؟", + "genderSymptoms": "ما هو جنسك؟", + "heightSymptoms": "كم طولك؟", + "weightSymptoms": "ما هو وزنك؟", + "femaleGender": "أنثى", + "previous": "سابق", + "selectedOrgans": "الهيئات المختارة", + "noOrgansSelected": "لم يتم تحديد أي أعضاء بعد", + "organSelector": "محدد الأعضاء", + "noPredictionsAvailable": "لا توجد تنبؤات متاحة", + "areYouSureYouWantToRestartOrganSelection": "هل أنت متأكد أنك تريد إعادة تشغيل اختيار الأعضاء؟", + "possibleConditions": "الحالات المحتملة", + "pleaseSelectAtLeastOneRiskBeforeProceeding": "يرجى اختيار عامل خطر واحد على الأقل قبل المتابعة", + "aboveYouSeeCommonRiskFactors": "أعلاه ترى عوامل الخطر الأكثر شيوعًا. على الرغم من أن /diagnosis قد تعيد أسئلة حول عوامل الخطر، ", + "readMore": "اقرأ المزيد", + "riskFactors": "عوامل الخطر", + "noRiskFactorsFound": "لم يتم العثور على عوامل خطر", + "basedOnYourSelectedSymptomsNoRiskFactors": "بناءً على الأعراض المحددة، لم يتم تحديد عوامل خطر إضافية.", + "messageNotification": "الرسالة", + "attachedImage": "الصورة المرفقة", + "failedToLoadImage": "فشل تحميل الصورة", + "typeNotification": "النوع", + "pleaseSelectAtLeastOneOptionBeforeProceeding": "يرجى اختيار خيار واحد على الأقل قبل المتابعة", + "suggestions": "الاقتراحات", + "pleaseGoBackAndSelectOrgansFirst": "يرجى العودة واختيار الأعضاء أولاً", + "symptomsSelector": "محدد الأعراض", + "possibleSymptomsRelatedTo": "الأعراض المحتملة المتعلقة بـ", + "suggestionAIListDescription": "هذه قائمة بالأعراض المقترحة من قبل الذكاء الاصطناعي، بناءً على المعلومات التي تم جمعها حتى الآن خلال المقابلة", + "analyzing": "جاري التحليل...", + "seekMedicalAdvice": "اطلب المشورة الطبية", + "monitorDescription": "لا حاجة لطلب المشورة الطبية. فقط حافظ على روتين صحي.", + "seekMedicalAdviceDescription": "ليست حالة طوارئ ولكن من الأفضل مراقبة الأعراض.", + "emergencyDescription": "تحتاج إلى استشارة الطبيب في أقرب وقت ممكن قبل فوات الأوان.", + "colorScience": "دلالات الألوان", + "noNeedToVisitDoctor": "لا حاجة لزيرة الطبيب", + "commonSymptom": "الأعراض الشائعة", + "bookAppointment": "احجز موعد", + "notApplicable": "لا ينطبق", + "emergencyTriage": "طوارئ", + "emergencyEvidenceDetected": "تم اكتشاف دليل طوارئ. يرجى طلب العناية الطبية.", + "noQuestionItemsAvailable": "لا توجد عناصر أسئلة متاحة", + "pleaseAnswerAllQuestionsBeforeProceeding": "يرجى الإجابة على جميع الأسئلة قبل المتابعة", + "triage": "الفرز", + "areYouSureYouWantToExitProgress": "هل أنت متأكد أنك تريد الخروج؟ سيتم فقدان تقدمك.", + "noQuestionAvailable": "لا يوجد سؤال متاح", + "possibleSymptom": "عرض محتمل: ", + "symptomsCheckerFindingScore": "- درجة نتائج فاحص الأعراض", + "notSet": "غير محدد", + "years": "سنوات", + "symptomsChecker": "فاحص الأعراض", + "helloIsYourInformationUpToDate": "مرحباً {name}، هل معلوماتك محدثة؟", + "noEditAll": "لا، تعديل الكل", + "yesItIs": "نعم، إنها كذلك", + "age": "العمر", + "youDontHaveAnyAncillaryOrdersYet": "ليس لديك أي طلبات مساعدة بعد.", + "invoiceWithNumber": "الفاتورة: {invoiceNo}", + "queued": "في قائمة الانتظار", + "checkInReady": "جاهز للتسجيل", + "checkIn": "تسجيل الوصول", + "viewDetails": "عرض التفاصيل", + "selectPaymentMethod": "اختر طريقة الدفع", + "processingPaymentPleaseWait": "جاري معالجة الدفع، يرجى الانتظار...", + "finalizingPaymentPleaseWait": "جاري إتمام الدفع، يرجى الانتظار...", + "generatingInvoicePleaseWait": "جاري إنشاء الفاتورة، يرجى الانتظار...", + "hereIsYourInvoiceNumber": "هذا هو رقم فاتورتك #: ", + "paymentCompletedSuccessfully": "تم الدفع بنجاح", + "failedToInitializeApplePay": "فشل في تهيئة Apple Pay. يرجى المحاولة مرة أخرى.", + "cash": "نقدي", + "approved": "موافق عليه", + "approvalRejectedPleaseVisitReceptionist": "تم رفض الموافقة - يرجى زيارة موظف الاستقبال", + "sentForApproval": "تم إرساله للموافقة", + "ancillaryOrderDetails": "تفاصيل الطلب المساعد", + "noProceduresAvailableForSelectedOrder": "لا توجد إجراءات متاحة للطلب المحدد.", + "procedures": "الإجراءات", + "totalAmount": "المبلغ الإجمالي", + "covered": "مغطى", + "vatPercent": "ضريبة القيمة المضافة (15%)", + "proceedToPayment": "المتابعة للدفع", + "supportedSmartWatches": "الساعات الذكية المدعومة", + "pleaseMakeSureSamsungWatchConnected": "يرجى التأكد من أن ساعة Samsung الخاصة بك متصلة بهاتفك، ومتزامنة ومحدثة بشكل نشط.", + "beforeSyncingDataFollowInstructions": "قبل مزامنة البيانات، يرجى التأكد من اتباع التعليمات بشكل صحيح.", + "viewWatchInstructions": "عرض تعليمات الساعة", + "healthConnectAppNotInstalled": "يبدو أنه ليس لديك تطبيق Health Connect مثبتًا. يرجى تثبيته من متجر Play لمزامنة بيانات صحتك.", + "setTimerOfReminder": "ضبط مؤقت التذكير", + "youHaveAppointmentWithDr": "لديك موعد مع د. ", + "hours": "ساعات", + "secs": "ثواني", + "noAllergiesDataFound": "لم يتم العثور على بيانات الحساسية...", + "heartRateDescription": "معدل ضربات قلبك يشير إلى عدد المرات التي ينبض فيها قلبك في الدقيقة", + "bloodOxygenDescription": "مستوى الأكسجين في الدم يشير إلى كمية الأكسجين التي تحملها خلايا الدم الحمراء", + "stepsDescription": "عدد الخطوات المتخذة على مدار اليوم", + "caloriesDescription": "السعرات الحرارية المحروقة أثناء النشاط البدني", + "distanceDescription": "المسافة المقطوعة على مدار اليوم", + "overview": "نظرة عامة", + "details": "التفاصيل", + "healthy": "صحي", + "warning": "تحذير", + "vitalSignDetails": "تفاصيل العلامات الحيوية", + "resultOf": "نتيجة {date}", + "resultOfNoDate": "نتيجة --", + "referenceRangeBetween": "النطاق المرجعي: {low} – {high} {unit}", + "referenceRangeMin": "النطاق المرجعي: ≥ {low} {unit}", + "referenceRangeMax": "النطاق المرجعي: ≤ {high} {unit}", + "noHistoryAvailable": "لا يوجد تاريخ متاح", + "bmiDescription": "مؤشر كتلة الجسم هو قياس يعتمد على الطول والوزن لتقدير دهون الجسم.", + "heightDescription": "يقاس الطول بالسنتيمتر ويستخدم لحساب مؤشر كتلة الجسم وتوصيات الجرعات.", + "weightDescription": "الوزن يساعد في تتبع الصحة العامة والتغذية والتغيرات مع مرور الوقت.", + "bloodPressureDescription": "ضغط الدم يعكس قوة الدم على جدران الشرايين. يظهر كانقباضي/انبساطي.", + "temperatureDescription": "درجة حرارة الجسم تعكس مدى سخونة جسمك وقد تتغير مع العدوى أو الالتهاب.", + "heartRateDescriptionVital": "معدل ضربات القلب يشير إلى عدد نبضات القلب في الدقيقة.", + "respiratoryRateDescription": "معدل التنفس هو عدد الأنفاس المأخوذة في الدقيقة.", + "bmiAdvice": "حافظ على نظام غذائي متوازن ونشاط منتظم. إذا كان مؤشر كتلة جسمك مرتفعًا أو منخفضًا، فكر في استشارة طبيبك.", + "heightAdvice": "لا حاجة لاتخاذ أي إجراء إلا إذا بدا قياسك غير صحيح. قم بتحديثه في زيارتك القادمة.", + "weightAdvice": "راقب تغيرات الوزن. الزيادة أو الخسارة المفاجئة قد تتطلب استشارة طبية.", + "bloodPressureAdvice": "استمر في تتبع ضغط دمك. يجب مناقشة القراءات المرتفعة أو المنخفضة مع طبيبك.", + "temperatureAdvice": "إذا كان لديك حمى مستمرة أو أعراض، اتصل بمقدم الرعاية الصحية.", + "heartRateAdvice": "تتبع اتجاهات معدل ضربات قلبك. إذا شعرت بدوار أو ألم في الصدر، اطلب الرعاية الطبية.", + "respiratoryRateAdvice": "إذا لاحظت ضيقًا في التنفس أو تنفسًا غير طبيعي، اطلب المشورة الطبية.", + "whatShouldIDoNext": "ماذا يجب أن أفعل بعد ذلك؟", + "customizeDrinkCup": "قم بتخصيص كوب مشروبك", + "tipsToStayHydrated": "نصائح للبقاء رطبًا", + "drinkBeforeYouFeelThirsty": "اشرب قبل أن تشعر بالعطش", + "keepRefillableBottleNextToYou": "احتفظ بزجاجة قابلة لإعادة التعبئة بجانبك", + "trackYourDailyIntakeToStayMotivated": "تتبع كمية الماء اليومية للحفاظ على الحافز", + "chooseSparklingWaterInsteadOfSoda": "اختر الماء الفوار بدلاً من الصودا", + "switchCup": "تبديل الكوب", + "plainWater": "ماء عادي", + "yourGoal": "هدفك", + "remaining": "المتبقي", + "hydrationStatus": "حالة الترطيب", + "areYouSureYouWantToCancelAllWaterReminders": "هل أنت متأكد أنك تريد إلغاء جميع تذكيرات الماء؟", + "remindersSet": "تم ضبط التذكيرات!", + "dailyWaterRemindersScheduledAt": "تم جدولة تذكيرات الماء اليومية في:", + "waterConsumption": "استهلاك المياه", + "selectNumberOfReminders": "حدد عدد التذكيرات", + "h2oSettings": "إعدادات H20", + "settingsSavedSuccessfully": "تم حفظ الإعدادات بنجاح", + "yourName": "اسمك", + "ageYears": "العمر (11-120) سنة", + "numberOfRemindersInADay": "عدد التذكيرات في اليوم", + + "medications": "الأدوية", + "someRemarksAboutPrescription": "ستجدون هنا بعض الملاحظات حول الوصفة الطبية", + "notifyMeBeforeConsumptionTime": "أبلغني قبل وقت الاستهلاك", + "noMedicationsToday": "لا أدوية اليوم", + "reminders": "تذكيرات", + "reminderAddedToCalendar": "تمت إضافة تذكير إلى التقويم ✅", + "errorWhileSettingCalendar": "حدث خطأ أثناء ضبط التقويم:{error}", + "instructions": "التعليمات", + "requests": "الطلبات", + "thisWeek": "هذا الأسبوع", + "lastMonth": "الشهر الماضي", + "lastSixMonths": "آخر 6 أشهر", + "selectTime": "حدد الوقت", + "pleaseWaitYouWillBeCalledForVitalSigns": "يرجى الانتظار! سيتم استدعاؤك لقياس العلامات الحيوية", + "pleaseVisitRoomForVitalSigns": "يرجى زيارة الغرفة {roomNumber} لقياس العلامات الحيوية", + "pleaseVisitRoomToTheDoctor": "يرجى زيارة الغرفة {roomNumber} لمقابلة الطبيب", + "erOnlineCheckInRequest": "طلب تسجيل الوصول عبر الإنترنت لقسم الطوارئ", + "indoor": "داخلي", + "navigation": "الملاحة", + "health": "الصحة", + "calculators": "الحاسبات", + "converters": "المحولات", + "guide": "الدليل", + "liveCareServiceDesc": "احصل على استشارة طبية من طبيب متخصص عبر مكالمة فيديو", + "bloodGroup": "فصيلة الدم: {bloodType}", + "medicalServices": "الخدمات الطبية", + "insuranceAndPayments": "التأمين والمدفوعات", + "trackerAndOthers": "المتتبع وأخرى", + "bmi": "مؤشر كتلة الجسم", + "yesPleaseINeedHelp": "نعم، من فضلك أريد المساعدة", + "aiOverView": "AI ملخص", + "generateAiAnalysis": "قم بإجراء تحليل لهذا الطلب AI", + "aiAnalysis": "AI تحليل", + "prediction": "تنبؤ", + "prevention": "وقاية", + "disclaimer": "تنصل", + "thisAboveInfo": "المعلومات المذكورة أعلاه مُولّدة بواسطة AI بناءً على نتائج تحاليلك المخبرية، وهي لأغراض إعلامية فقط. لا ينبغي اعتبارها تشخيصًا طبيًا أو خطة علاجية. استشر طبيبك دائمًا للحصول على نصيحة طبية مُخصصة قبل اتخاذ أي قرارات تتعلق بصحتك.", + "aiDisclaimer": "سيتم مشاركة بيانات نتائج تحاليلك المخبرية بشكل آمن مع خدمة الذكاء الاصطناعي لدينا لتحليلها. يساعد هذا في تقديم رؤى صحية مُخصصة. هل ترغب في المتابعة؟", + "consent": "موافقة", + "bmiNormalDescription": "مؤشر كتلة جسمك ضمن المعدل الصحي. استمر في اتباع نظام غذائي متوازن، وممارسة النشاط البدني بانتظام، وإجراء الفحوصات الدورية للحفاظ على صحة جيدة. راقب أي تغييرات ملحوظة مع مرور الوقت.", + "bmiUnderweightDescription": "يشير مؤشر كتلة الجسم إلى أنك تعاني من نقص الوزن. لذا، يُنصح بزيادة السعرات الحرارية المتناولة من خلال تناول الأطعمة الغنية بالعناصر الغذائية، وممارسة تمارين المقاومة لبناء العضلات، واستشارة أخصائي رعاية صحية للتحقق من وجود أسباب كامنة.", + "bmiOverweightDescription": "يشير مؤشر كتلة الجسم إلى زيادة الوزن. لذا، يُنصح بالسعي إلى إنقاص الوزن تدريجياً وبشكل مستدام من خلال تقليل السعرات الحرارية بشكل معتدل، وممارسة النشاط البدني بانتظام، واختيار الأطعمة الصحية. استشر طبيبك قبل إجراء أي تغييرات جذرية في نمط حياتك.", + "caloriesMaintenanceDescription": "السعرات الحرارية اليومية المُقدَّرة للحفاظ على وزنك الحالي بناءً على مستوى نشاطك ومعدل الأيض لديك. استخدم هذه الكمية كخط أساس: زدها قليلاً لزيادة الكتلة العضلية، أو قللها قليلاً لإنقاص الوزن تدريجياً. تابع تقدمك وعدّل الخطة كل أسبوعين إلى أربعة أسابيع.", + "caloriesModerateDeficitDescription": "نظام غذائي معتدل لتقليل السعرات الحرارية مصمم لإنقاص الوزن بشكل ثابت ومستدام (عادةً من 0.25 إلى 0.5 كيلوغرام أسبوعيًا). يُنصح بدمجه مع تمارين المقاومة وتناول كمية كافية من البروتين للحفاظ على الكتلة العضلية. تجنب التخفيضات الحادة في السعرات الحرارية واستشر طبيبًا مختصًا إذا كنت تعاني من أي حالة صحية.", + "caloriesAggressiveDeficitDescription": "يؤدي خفض السعرات الحرارية بشكل كبير إلى فقدان الوزن بشكل أسرع، ولكنه قد يصعب الاستمرار عليه ويزيد من خطر نقص العناصر الغذائية والإرهاق وفقدان الكتلة العضلية. لا يُنصح باتباعه على المدى الطويل؛ استشر طبيبًا أو أخصائي تغذية قبل الاستمرار.", + "bodyFatEssentialDescription": "نسبة الدهون في جسمك ضمن النطاق الأساسي/الرياضي. هذا المستوى شائع لدى الرياضيين ويشير إلى انخفاض نسبة الدهون في الجسم. مع أن هذا قد يكون مثاليًا للأداء، تأكد من الحفاظ على تغذية سليمة ووظائف هرمونية متوازنة. استشر طبيبًا إذا شعرت بالتعب، أو واجهت مشاكل هرمونية، أو أي مخاوف صحية أخرى.", + "bodyFatFitnessDescription": "نسبة الدهون في جسمك ضمن النطاق الصحي. وهذا يدل على مستوى صحي من الدهون في الجسم يرتبط بلياقة بدنية جيدة. حافظ على نمط حياتك الحالي، بما في ذلك ممارسة الرياضة بانتظام واتباع نظام غذائي متوازن، للحفاظ على هذا المستوى الصحي.", + "bodyFatAverageDescription": "نسبة الدهون في جسمك ضمن المعدل الطبيعي/المقبول، وهي نسبة تُعتبر صحية لعموم الناس. لتحسين تكوين جسمك، يُنصح بممارسة تمارين القوة، وتمارين القلب والأوعية الدموية، واتباع نظام غذائي متوازن. ركّز على التغييرات التدريجية والمستدامة للحفاظ على صحتك على المدى الطويل.", + "bodyFatObeseDescription": "نسبة الدهون في جسمك أعلى من المعدل الصحي الموصى به. قد يزيد هذا من المخاطر الصحية، بما في ذلك أمراض القلب والأوعية الدموية، والسكري، ومشاكل المفاصل. يُنصح باستشارة طبيب أو أخصائي تغذية معتمد لوضع خطة علاجية شخصية تشمل التغذية المتوازنة، والنشاط البدني المنتظم، وتعديلات نمط الحياة لتحقيق إدارة مستدامة للوزن.", + "bmrNormalDescription": "معدل الأيض الأساسي لديك ضمن النطاق المتوقع لحالتك. معدل الأيض الأساسي هو الطاقة التي يحتاجها جسمك في حالة الراحة للحفاظ على وظائفه الحيوية (التنفس، الدورة الدموية، درجة الحرارة). استخدم هذه القيمة كأساس لتقدير إجمالي احتياجاتك اليومية من الطاقة من خلال تطبيق مُعامل النشاط. حافظ على نظام غذائي متوازن وممارسة النشاط البدني بانتظام لدعم صحة التمثيل الغذائي.", + "bmrLowDescription": "معدل الأيض الأساسي لديك أقل من المعدل الطبيعي لحالتك. تشمل الأسباب المحتملة انخفاض كتلة العضلات، أو تراجع التمثيل الغذائي المرتبط بالتقدم في السن، أو التكيف الأيضي الناتج عن تقييد السعرات الحرارية لفترة طويلة. يُنصح بالتركيز على تمارين المقاومة لبناء العضلات والحفاظ عليها، والتأكد من تناول كميات كافية من البروتين والمغذيات الدقيقة، واستشارة أخصائي رعاية صحية إذا كان هذا الأمر غير متوقع.", + "bmrHighDescription": "معدل الأيض الأساسي لديك أعلى من المتوسط \u200B\u200Bبالنسبة لحالتك. قد يعكس هذا زيادة في كتلة العضلات، أو صغر السن، أو ارتفاع معدل الأيض لديك بشكل طبيعي، مما يعني أنك تحرق طاقة أكبر أثناء الراحة. استخدم هذه المعلومات لتحديد احتياجاتك من السعرات الحرارية: احتياجات أعلى للحفاظ على الوزن أو بناء العضلات، ونقص معتدل في السعرات الحرارية لإنقاص الوزن. استشر طبيبك إذا لاحظت أي تغيرات سريعة غير مبررة.", + "ibwAtTargetDescription": "Your weight is at the ideal target. Maintain your current habits — balanced diet and regular activity — to keep this.", + "ibwBelowTargetDescription": "You are below the ideal weight by {kg} kg. Aim to gradually increase weight with a modest calorie surplus, nutrient-dense foods, and resistance training.", + "ibwAboveTargetDescription": "You are above the ideal weight by {kg} kg. Aim for a gradual, sustainable weight reduction through a moderate calorie deficit, increased activity, and balanced nutrition.", + "ibwCloseToRangeDescription": "Your weight is close to the ideal range. Maintain a balanced diet and regular activity; follow personalized advice from a healthcare professional if needed.", + "ibwUnderweightDescription": "You are below the ideal weight by {kg} kg. Focus on increasing calorie intake with nutrient-dense foods, prioritize protein and resistance training to build healthy mass, and consider consulting a healthcare professional.", + "ibwUnderweightGenericDescription": "You appear underweight compared to the ideal. Consider increasing calorie intake with nutrient-dense foods and resistance training; seek professional guidance if needed.", + "ibwOverweightDescription": "You are above the ideal weight by {kg} kg. Aim for a gradual, sustainable weight loss strategy — moderate calorie deficit, regular physical activity, and balanced nutrition. Consult a professional before major changes.", + "ibwOverweightGenericDescription": "You appear above the ideal weight. Consider a gradual, sustainable calorie deficit combined with activity and balanced nutrition; consult a professional if needed.", + "dietVeryLowCarbsDescription": "Very low‑carb (ketogenic) approach: minimal carbohydrates, higher fat and moderate protein. May support rapid weight loss and improved blood sugar control for some, but can be hard to sustain and may cause nutrient gaps. Monitor hydration/electrolytes and consult a professional for long‑term use.", + "dietLowCarbsDescription": "Low‑carb, higher‑protein plan: reduced carbohydrates with increased protein to support satiety and muscle maintenance. Helpful for weight management and glycemic control when balanced with vegetables and healthy fats. Ensure adequate fiber and micronutrients.", + "dietModerateCarbsDescription": "Moderate carbohydrate intake with balanced protein and fats: provides steady energy for daily activity and exercise while allowing flexibility. Suitable for many people aiming for sustainable weight management and performance.", + "dietUSDAGuidelinesDescription": "USDA‑based balanced pattern: emphasizes fruits, vegetables, whole grains, lean proteins, and limited added sugars and saturated fats. Evidence‑based framework for general health, nutrient adequacy, and chronic disease prevention.", + "dietZoneDietDescription": "Zone Diet (~40% carbs / 30% protein / 30% fat): emphasizes hormonal balance and portion control. May improve body composition and energy for some, but requires planning; personalize with a nutrition professional.", + "contextDependent": "يعتمد السياق", + "calculatedValue": "القيمة المحسوبة", + "calculationFormula": "صيغة الحساب", + "confirmYourLocation": "قم بتأكيد موقعك", + "confirmYourLocationDesc": "يرجى تأكيد موقعك الحالي لعرض الاتجاهات المناسبة.", + "insideHospital": "أنا داخل المستشفى", + "outsideHospital": "أنا خارج المستشفى", + "servicePriceList": "قائمة أسعار الخدمات", + "servicePriceListDesc": "توضح قائمة أسعار الخدمات أدناه رسوم الخدمات الصحية المقدمة للمرضى بنظام الدفع النقدي. أما فيما يتعلق بالخدمات المشمولة بالتأمين، فسيتم تطبيق التغطية التأمينية والتحقق من الأهلية واحتساب نسب التحمل وفقًا لشروط وثيقة التأمين وجدول المنافع المعتمد لكل شركة تأمين.", + "servicePriceListRights": "يحق للمريض الحصول على متابعة مجانية في غضون 14 يومًا من الزيارة الأولى", + "serviceName": "اسم الخدمة", + "noFreeSlot": "لا توجد مواعيد متاحة في التاريخ المحدد. يرجى اختيار تاريخ آخر أو إعادة المحاولة لاحقاً.", + "noThanksIKnowTheClinic": "لا، شكراً. أنا أعرف العيادة.", + "unableToSendOTP": "تعذر إرسال رمز التحقق لمرة واحدة", + "loadingAIAnalysis": "جارٍ تحميل بياناتك وتحليلها، يُرجى التحلي بالصبر ودع الذكاء الاصطناعي يقوم بالمهمة. يُرجى التحلي بالصبر، فقد يستغرق هذا بعض الوقت." +} diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index ee489f5f..3f1bc78b 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -406,11 +406,8 @@ "brand": "Brand", "power": "Power", "diameter": "Diameter", - "remarks": "Remarks", "activeMedications": "Active Medications", "expDate": "Active Exp Date :", - "route": "Route", - "frequency": "Frequency", "dailyQuantity": "Daily Quantity :", "addReminder": "Add Reminder", "cancelReminder": "Cancel Reminder", @@ -621,7 +618,7 @@ "general": "General", "profile": "ملفي", "notifications": "إشعارات", - "notificationDetails": "تفاصيل الاشعار", + "notificationDetails": "Notification Details", "notificationDetailsa": "تفاصيل الاشعار", "infoMyDoctorPoints": "", "infoMyDoctor": "This service allows you to see all the doctors you have visited in Al Habib Medical Group, and through this service:", @@ -889,7 +886,6 @@ "pickADate": "Pick a Date", "confirmingAppointmentPleaseWait": "Confirming Appointment, Please Wait...", "appointmentConfirmedSuccessfully": "Appointment Confirmed Successfully", - "appointmentPayment": "Appointment Payment", "checkingPaymentStatusPleaseWait": "Checking payment status, Please wait...", "paymentFailedPleaseTryAgain": "Payment Failed! Please try again.", @@ -1207,5 +1203,364 @@ "virtualTour": "Virtual Tour", "carParking": "Car Parking", "latestNews": "Latest News", - "hmgContact": "HMG Contact" -} \ No newline at end of file + "hmgContact": "HMG Contact", + "durationCannotExceed90": "Duration can not exceed 90 mins", + "unexpectedError": "Unexpected Error Occurred", + "gettingAmbulanceTransportOption": "Getting Ambulance Transport Option", + "fetchingAppointment": "Fetching Appointment", + "doYouWantToCancelTheRequest": "Do you want to cancel the request", + "cancellingRequest": "Cancelling request", + "fetchingTermsAndConditions": "Fetching Terms And Conditions", + "selectLocationPrescriptionDelivery": "Please select the location for prescription delivery", + "noRadiologyOrders": "No Radiology Orders Found", + "ageIsRequired": "Age is required", + "invalidAge": "Invalid age", + "ageMustBeBetween11And120": "Age must be between 11 and 120", + "heightIsRequired": "Height is required", + "invalidHeight": "Invalid height", + "weightIsRequired": "Weight is required", + "invalidWeight": "Invalid weight", + "timeToDrinkWater": "Time to Drink Water! 💧", + "stayHydratedDrinkWater": "Stay hydrated! Drink {amount}ml of water.", + "visitPharmacyOnline": "Visit Pharmacy Online", + "howAreYouFeelingToday": "How are you feeling today?", + "checkYourSymptomsWithScale": "Check your symptoms with this scale", + "checkYourSymptoms": "Check your symptoms", + "noUpcomingAppointmentPleaseBook": "You do not have any upcoming appointment. Please book an appointment", + "youHaveEROnlineCheckInRequest": "You have ER Online Check-In Request", + "quickLinks": "Quick Links", + "viewMedicalFileLandingPage": "View medical file", + "immediateLiveCareRequest": "Immediate LiveCare Request", + "yourTurnIsAfterPatients": "Your turn is after {count} patients.", + "dontHaveHHCOrders": "You don't have any Home Health Care orders yet.", + "hhcOrders": "HHC Orders", + "requestedServices": "Requested Services", + "selectServices": "Select Services", + "selectedServices": "Selected Services", + "createNewRequest": "Create new request", + "youHaveNoPendingRequests": "You have no pending requests.", + "noInsuranceDataFound": "No insurance data found...", + "noInsuranceUpdateRequest": "No insurance update requests found.", + "policyNumberInsurancePage": "Policy: {number}", + "insuranceExpired": "Insurance Expired", + "insuranceActive": "Insurance Active", + "patientCardID": "Patient Card ID: {id}", + "noInsuranceApprovals": "You don't have any insurance approvals yet.", + "noInsuranceWithHMG": "You don't have insurance registered with HMG.", + "referenceRange": "Reference Range", + "downloadReport": "Download report", + "generatingReport": "Generating report, Please wait...", + "noLabResults": "You don't have any lab results yet.", + "labResultDetails": "Lab Result Details", + "resultOf": "Result of", + "whatIsThisResult": "What is this result?", + "lastTested": "Last Tested", + "byVisit": "By Visit", + "byTest": "By Test", + "results": "results", + "viewResults": "View Results", + "rebook": "Rebook", + "noOphthalmologyAppointments": "No Ophthalmology appointments found...", + "noVitalSignsRecordedYet": "No vital signs recorded yet", + "appointmentsAndVisits": "Appointments & visits", + "labAndRadiology": "Lab & Radiology", + "activeMedicationsAndPrescriptions": "Active Medications & Prescriptions", + "allPrescriptions": "All Prescriptions", + "allMedications": "All Medications", + "youDontHaveAnyPrescriptionsYet": "You don't have any prescriptions yet.", + "youDontHaveAnyCompletedVisitsYet": "You don't have any completed visits yet", + "others": "Others", + "allergyInfo": "Allergy Info", + "vaccineInfo": "Vaccine Info", + "updateInsuranceInfo": "Update Insurance", + "myInvoicesList": "My Invoices List", + "ancillaryOrdersList": "Ancillary Orders List", + "youDontHaveAnySickLeavesYet": "You don't have any sick leaves yet.", + "medicalReports": "Medical Reports", + "sickLeaveReport": "Sick Leave Report", + "weightTracker": "Weight Tracker", + "askYourDoctor": "Ask Your Doctor", + "internetPairing": "Internet Pairing", + "requested": "Requested", + "youDontHaveAnyMedicalReportsYet": "You don't have any medical reports yet.", + "requestMedicalReport": "Request medical report", + "youDoNotHaveAnyAppointmentsToRequestMedicalReport": "You do not have any appointments to request a medical report.", + "areYouSureYouWantToRequestMedicalReport": "Are you sure you want to request a medical report for this appointment?", + "yourMedicalReportRequestSubmittedSuccessfully": "Your medical report request has been successfully submitted.", + "monthlyHealthSummaryReportDisclaimer": "This monthly health summary report reflects the health indicators and analysis results of the latest visits. Please note that this will be sent automatically from the system and it's not considered as a official report so no medical decision should be taken based on it", + "updatingMonthlyReportStatus": "Updating Monthly Report Status...", + "monthlyReportStatusUpdatedSuccessfully": "Monthly Report Status Updated Successfully", + "whoCanViewMyMedicalFile": "Who can view my medical file?", + "acceptedYourRequestToBeYourFamilyMember": "{status} your request to be your family member", + "canViewYourFile": "can view your file", + "hasARequestPendingToBeYourFamilyMember": "has a request {status} to be your family member", + "wantsToAddYouAsTheirFamilyMember": "wants to add you as their family member", + "rejectedYourRequestToBeYourFamilyMember": "{status} your request to be your family member", + "rejectedYourFamilyMemberRequest": "{status} your family member request", + "notAvailable": "N/A", + "selectAProfile": "Please select a profile", + "switchFamilyFile": "Switch from the below list of medical file", + "medicalFiles": "Medical Files", + "addANewFamilyMember": "Add a new family member", + "viewInvoiceDetails": "View invoice details", + "outPatient": "OutPatient", + "invoiceDetails": "Invoice Details", + "sendingEmailPleaseWait": "Sending email, Please wait...", + "emailSentSuccessfullyMessage": "Email sent successfully.", + "discount": "Discount", + "paid": "Paid", + "fetchingInvoiceDetails": "Fetching invoice details, Please wait...", + "scanQRCode": "Scan QR code", + "parkingSlotDetails": "Parking Slot Details", + "slotNumber": "Slot: {code}", + "basement": "Basement: {description}", + "parkingDate": "Date: {date}", + "parkedSince": "Parked Since: {time}", + "resetDirection": "Reset Direction", + "noPrescriptionOrdersYet": "You don't have any prescription orders yet.", + "fetchingPrescriptionPDFPleaseWait": "Fetching prescription PDF, Please wait...", + "ratingValue": "Rating: {rating}", + "downloadPrescription": "Download Prescription", + "fetchingPrescriptionDetails": "Fetching prescription details...", + "switchBackFamilyFile": "Switch Back To Family File", + "profileAndSettings": "Profile & Settings", + "quickActions": "Quick Actions", + "notificationsSettings": "Notifications Settings", + "touchIDFaceIDServices": "Touch ID / Face ID Services", + "personalInformation": "Personal Information", + "updateEmailAddress": "Update Email Address", + "helpAndSupport": "Help & Support", + "permissionsProfile": "Permissions", + "privacyPolicy": "Privacy Policy", + "deactivateAccount": "Deactivate account", + "ageYearsOld": "{age} {yearsOld}", + "youDontHaveRadiologyOrders": "You don't have any radiology results yet.", + "radiologyResult": "Radiology Result", + "viewRadiologyImage": "View Radiology Image", + "rateClinic": "Rate Clinic", + "back": "Back", + "rateDoctor": "Rate Doctor", + "howWasYourLastVisitWithDoctor": "How was your last visit with doctor?", + "dateOfBirthSymptoms": "What is your Date of Birth?", + "genderSymptoms": "What is your gender?", + "heightSymptoms": "How tall are you?", + "weightSymptoms": "What is your weight?", + "femaleGender": "Female", + "previous": "Previous", + "selectedOrgans": "Selected Organs", + "noOrgansSelected": "No organs selected yet", + "organSelector": "Organ Selector", + "noPredictionsAvailable": "No Predictions available", + "areYouSureYouWantToRestartOrganSelection": "Are you sure you want to restart the organ selection?", + "possibleConditions": "Possible Conditions", + "pleaseSelectAtLeastOneRiskBeforeProceeding": "Please select at least one risk before proceeding", + "aboveYouSeeCommonRiskFactors": "Above you see the most common risk factors. Although /diagnosis may return questions about risk factors, ", + "readMore": "Read more", + "riskFactors": "Risk Factors", + "noRiskFactorsFound": "No risk factors found", + "basedOnYourSelectedSymptomsNoRiskFactors": "Based on your selected symptoms, no additional risk factors were identified.", + "messageNotification": "Message", + "attachedImage": "Attached Image", + "failedToLoadImage": "Failed to load image", + "typeNotification": "Type", + "pleaseSelectAtLeastOneOptionBeforeProceeding": "Please select at least one option before proceeding", + "suggestions": "Suggestions", + "pleaseGoBackAndSelectOrgansFirst": "Please go back and select organs first", + "symptomsSelector": "Symptoms Selector", + "possibleSymptomsRelatedTo": "Possible symptoms related to", + "suggestionAIListDescription": "This is a list of symptoms suggested by our AI, based on the information gathered so far during the interview", + "analyzing": "Analyzing...", + "seekMedicalAdvice": "Seek Medical Advice", + "monitorDescription": "No need to seek medical advice. Just keep healthy routine.", + "seekMedicalAdviceDescription": "Not emergency but better to monitor the symptoms.", + "emergencyDescription": "Need to consult doctor as soon as possible before getting too late.", + "colorScience": "Color Science", + "noNeedToVisitDoctor": "No need to visit doctor", + "commonSymptom": "Common Symptom", + "bookAppointment": "Book Appointment", + "notApplicable": "Not Applicable", + "emergencyTriage": "Emergency", + "emergencyEvidenceDetected": "Emergency evidence detected. Please seek medical attention.", + "noQuestionItemsAvailable": "No question items available", + "pleaseAnswerAllQuestionsBeforeProceeding": "Please answer all questions before proceeding", + "triage": "Triage", + "areYouSureYouWantToExitProgress": "Are you sure you want to exit? Your progress will be lost.", + "noQuestionAvailable": "No question available", + "possibleSymptom": "Possible symptom: ", + "symptomsCheckerFindingScore": "- Symptoms checker finding score", + "notSet": "Not set", + "years": "Years", + "symptomsChecker": "Symptoms Checker", + "helloIsYourInformationUpToDate": "Hello {name}, Is your information up to date?", + "noEditAll": "No, Edit all", + "yesItIs": "Yes, It is", + "age": "Age", + "youDontHaveAnyAncillaryOrdersYet": "You don't have any ancillary orders yet.", + "invoiceWithNumber": "Invoice: {invoiceNo}", + "queued": "Queued", + "checkInReady": "Check-in Ready", + "checkIn": "Check In", + "viewDetails": "View Details", + "selectPaymentMethod": "Select Payment Method", + "processingPaymentPleaseWait": "Processing payment, Please wait...", + "finalizingPaymentPleaseWait": "Finalizing payment, Please wait...", + "generatingInvoicePleaseWait": "Generating invoice, Please wait...", + "hereIsYourInvoiceNumber": "Here is your invoice #: ", + "paymentCompletedSuccessfully": "Payment Completed Successfully", + "failedToInitializeApplePay": "Failed to initialize Apple Pay. Please try again.", + "cash": "Cash", + "approved": "Approved", + "approvalRejectedPleaseVisitReceptionist": "Approval Rejected - Please visit receptionist", + "sentForApproval": "Sent For Approval", + "ancillaryOrderDetails": "Ancillary Order Details", + "noProceduresAvailableForSelectedOrder": "No Procedures available for the selected order.", + "procedures": "Procedures", + "totalAmount": "Total Amount", + "covered": "Covered", + "vatPercent": "VAT (15%)", + "proceedToPayment": "Proceed to Payment", + "supportedSmartWatches": "Supported Smart Watches", + "pleaseMakeSureSamsungWatchConnected": "Please make sure that your Samsung Watch is connected to your Phone, is actively synced & updated.", + "beforeSyncingDataFollowInstructions": "Before syncing data, please make sure that you have followed the instructions properly.", + "viewWatchInstructions": "View watch instructions", + "healthConnectAppNotInstalled": "Seems like you do not have Health Connect App installed. Please install it from the Play Store to sync your health data.", + "setTimerOfReminder": "Set the timer of reminder", + "youHaveAppointmentWithDr": "You have appointment with Dr. ", + "hours": "Hours", + "secs": "Secs", + "noAllergiesDataFound": "No allergies data found...", + "heartRateDescription": "Your heart rate indicates how many times your heart beats per minute", + "bloodOxygenDescription": "Blood oxygen level indicates how much oxygen your red blood cells are carrying", + "stepsDescription": "Number of steps taken throughout the day", + "caloriesDescription": "Calories burned during physical activity", + "distanceDescription": "Distance covered throughout the day", + "overview": "Overview", + "details": "Details", + "healthy": "Healthy", + "warning": "Warning", + "vitalSignDetails": "Vital Sign Details", + "resultOfNoDate": "Result of --", + "referenceRangeBetween": "Reference range: {low} – {high} {unit}", + "referenceRangeMin": "Reference range: ≥ {low} {unit}", + "referenceRangeMax": "Reference range: ≤ {high} {unit}", + "noHistoryAvailable": "No history available", + "bmiDescription": "BMI is a measurement based on height and weight that estimates body fat.", + "heightDescription": "Height is measured in centimeters and is used to calculate BMI and dosage recommendations.", + "weightDescription": "Weight helps track overall health, nutrition, and changes over time.", + "bloodPressureDescription": "Blood pressure reflects the force of blood against artery walls. It is shown as systolic/diastolic.", + "temperatureDescription": "Body temperature reflects how hot your body is and may change with infection or inflammation.", + "heartRateDescriptionVital": "Heart rate refers to the number of heart beats per minute.", + "respiratoryRateDescription": "Respiratory rate is the number of breaths taken per minute.", + "bmiAdvice": "Maintain a balanced diet and regular activity. If your BMI is high or low, consider consulting your doctor.", + "heightAdvice": "No action is needed unless your measurement looks incorrect. Update it during your next visit.", + "weightAdvice": "Monitor weight changes. Sudden gain or loss may require medical advice.", + "bloodPressureAdvice": "Keep tracking your blood pressure. High or low readings should be discussed with your doctor.", + "temperatureAdvice": "If you have a persistent fever or symptoms, contact your healthcare provider.", + "heartRateAdvice": "Track your heart rate trends. If you feel dizziness or chest pain, seek medical care.", + "respiratoryRateAdvice": "If you notice shortness of breath or abnormal breathing, seek medical advice.", + "whatShouldIDoNext": "What should I do next?", + "customizeDrinkCup": "Customize your drink cup", + "tipsToStayHydrated": "Tips to stay hydrated", + "drinkBeforeYouFeelThirsty": "Drink before you feel thirsty", + "keepRefillableBottleNextToYou": "Keep a refillable bottle next to you", + "trackYourDailyIntakeToStayMotivated": "Track your daily intake to stay motivated", + "chooseSparklingWaterInsteadOfSoda": "Choose sparkling water instead of soda", + "switchCup": "Switch Cup", + "plainWater": "Plain Water", + "yourGoal": "Your Goal", + "remaining": "Remaining", + "hydrationStatus": "Hydration Status", + "areYouSureYouWantToCancelAllWaterReminders": "Are you sure you want to cancel all water reminders?", + "remindersSet": "Reminders Set!", + "dailyWaterRemindersScheduledAt": "Daily water reminders scheduled at:", + "waterConsumption": "Water Consumption", + "selectNumberOfReminders": "Select Number of Reminders", + "h2oSettings": "H20 Settings", + "settingsSavedSuccessfully": "Settings saved successfully", + "yourName": "Your Name", + "ageYears": "Age (11-120) yrs", + "numberOfRemindersInADay": "Number of reminders in a day", + "medications": "Medications", + "remarks": "Remarks: ", + "someRemarksAboutPrescription": "some remarks about the prescription will be here", + "notifyMeBeforeConsumptionTime": "Notify me before the consumption time", + "noMedicationsToday": "No medications today", + "route": "Route", + "frequency": "Frequency", + "reminders": "Reminders", + "reminderAddedToCalendar": "Reminder added to calendar ✅", + "errorWhileSettingCalendar": "Error while setting calendar: {error}", + "instructions": "Instructions", + "requests": "Requests", + "thisWeek": "This Week", + "lastMonth": "Last Month", + "lastSixMonths": "Last 6 Months", + "selectTime": "Select Time", + "pleaseWaitYouWillBeCalledForVitalSigns": "Please wait! you will be called for vital signs", + "pleaseVisitRoomForVitalSigns": "Please visit Room {roomNumber} for vital signs", + "pleaseVisitRoomToTheDoctor": "Please visit Room {roomNumber} to the Doctor", + "erOnlineCheckInRequest": "ER Online Check-In Request", + "indoor": "Indoor", + "navigation": "Navigation", + "health": "Health", + "calculators": "Calculators", + "converters": "Converters", + "guide": "Guide", + "liveCareServiceDesc": "Get medical advice with a specialist doctor Via a video call", + "bloodGroup": "Blood: {bloodType}", + "medicalServices": "Medical Services", + "insuranceAndPayments": "Insurance & Payments", + "trackerAndOthers": "Tracker & Others", + "bmi": "BMI", + "yesPleaseINeedHelp": "Yes, Please I need help", + "confirmYourLocation": "Confirm Your Location", + "confirmYourLocationDesc": "Please confirm your present location to view appropriate directions.", + "insideHospital": "I am inside the hospital", + "outsideHospital": "I am outside the hospital", + "servicePriceList": "Services Price List", + "servicePriceListDesc": "Below is the services price list outline the healthcare services fees for cash payments, where the insurance coverage, eligibility, and co-payment deductions will be processed in accordance with the insurance policy terms and the table of benefits of each insurance providers:", + "servicePriceListRights": "The patient has the right to a free follow-up within 14 days of initial visit", + "serviceName": "Service Name", + "noFreeSlot": "No free slot available for the selected date. Please select another date or check back later.", + "noThanksIKnowTheClinic": "No, Thanks. I Know the clinic", + "unableToSendOTP": "Unable to send Verification OTP", + "aiOverView": "AI Overview", + "generateAiAnalysis": "Generate AI analysis for this order", + "aiAnalysis": "AI Analysis", + "prediction": "Prediction", + "prevention": "Prevention", + "disclaimer": "Disclaimer", + "thisAboveInfo": "The above information is generated by AI based on your lab results and is intended for informational purposes only. It should not be considered a medical diagnosis or treatment plan. Always consult with your healthcare provider for personalized medical advice and before making any health-related decisions.", + "aiDisclaimer": "Your lab result data will be securely shared with our AI Analyzer for analysis. This helps provide personalized health insights. Do you want to proceed?", + "consent": "Consent", + "bmiNormalDescription": "Your BMI is within the healthy range. Continue a balanced diet, regular physical activity, and routine checkups to maintain good health. Monitor any significant changes over time.", + "bmiUnderweightDescription": "BMI suggests you are underweight. Consider increasing calorie intake with nutrient-dense foods, include resistance training to build muscle, and consult a healthcare professional to check for underlying causes.", + "bmiOverweightDescription": "BMI indicates overweight. Aim for gradual, sustainable weight loss through a modest calorie deficit, regular physical activity, and healthier food choices. Seek medical advice before major lifestyle changes.", + "caloriesMaintenanceDescription": "Estimated daily calories to maintain your current weight given your activity level and metabolism. Use this as a baseline: increase slightly for muscle gain, or create a small deficit for gradual weight loss. Track progress and adjust every 2–4 weeks.", + "caloriesModerateDeficitDescription": "A moderate calorie reduction designed for steady, sustainable weight loss (typically 0.25–0.5 kg per week). Combine with resistance training and adequate protein to preserve muscle. Avoid extreme cuts and consult a professional if you have medical conditions.", + "caloriesAggressiveDeficitDescription": "A large calorie reduction that may produce faster weight loss but can be hard to sustain and increase risk of nutrient deficiencies, fatigue, or muscle loss. Not recommended long-term; seek guidance from a healthcare or nutrition professional before continuing.", + "bodyFatEssentialDescription": "Your body fat percentage is in the essential/athletic range. This level is typical for athletes and indicates very low body fat. While this may be optimal for performance, ensure you maintain adequate nutrition and hormone function. Consult a healthcare professional if you experience fatigue, hormonal issues, or other health concerns.", + "bodyFatFitnessDescription": "Your body fat percentage is in the fitness range. This indicates a healthy level of body fat associated with good physical conditioning. Maintain your current lifestyle habits including regular exercise and balanced nutrition to sustain this healthy range.", + "bodyFatAverageDescription": "Your body fat percentage is in the average/acceptable range. This is considered a healthy range for general population. To improve body composition, consider incorporating strength training, cardiovascular exercise, and a balanced diet. Focus on gradual, sustainable changes for long-term health.", + "bodyFatObeseDescription": "Your body fat percentage is above the recommended healthy range. This may increase health risks including cardiovascular disease, diabetes, and joint problems. Consider consulting a healthcare provider or registered dietitian to develop a personalized plan that includes balanced nutrition, regular physical activity, and lifestyle modifications for sustainable weight management.", + "bmrNormalDescription": "Your Basal Metabolic Rate (BMR) is within the expected range for your profile. BMR is the energy your body needs at rest to maintain vital functions (breathing, circulation, temperature). Use this value as the baseline for estimating total daily energy needs by applying an activity multiplier. Maintain a balanced diet and regular physical activity to support metabolic health.", + "bmrLowDescription": "Your BMR is lower than typical for your profile. Possible causes include lower lean muscle mass, age-related metabolic decline, or metabolic adaptation from prolonged calorie restriction. Consider focusing on resistance training to build/maintain muscle, ensure adequate protein and micronutrient intake, and consult a healthcare professional if this is unexpected.", + "bmrHighDescription": "Your BMR is higher than average for your profile. This can reflect greater lean mass, younger age, or a naturally higher metabolic rate, meaning you burn more energy at rest. Use this information to tailor calorie targets: higher needs for maintenance or muscle gain, moderate deficit for weight loss. Seek medical advice if you notice rapid unexplained changes.", + "ibwAtTargetDescription": "Your weight is at the ideal target. Maintain your current habits — balanced diet and regular activity — to keep this.", + "ibwBelowTargetDescription": "You are below the ideal weight by {kg} kg. Aim to gradually increase weight with a modest calorie surplus, nutrient-dense foods, and resistance training.", + "ibwAboveTargetDescription": "You are above the ideal weight by {kg} kg. Aim for a gradual, sustainable weight reduction through a moderate calorie deficit, increased activity, and balanced nutrition.", + "ibwCloseToRangeDescription": "Your weight is close to the ideal range. Maintain a balanced diet and regular activity; follow personalized advice from a healthcare professional if needed.", + "ibwUnderweightDescription": "You are below the ideal weight by {kg} kg. Focus on increasing calorie intake with nutrient-dense foods, prioritize protein and resistance training to build healthy mass, and consider consulting a healthcare professional.", + "ibwUnderweightGenericDescription": "You appear underweight compared to the ideal. Consider increasing calorie intake with nutrient-dense foods and resistance training; seek professional guidance if needed.", + "ibwOverweightDescription": "You are above the ideal weight by {kg} kg. Aim for a gradual, sustainable weight loss strategy — moderate calorie deficit, regular physical activity, and balanced nutrition. Consult a professional before major changes.", + "ibwOverweightGenericDescription": "You appear above the ideal weight. Consider a gradual, sustainable calorie deficit combined with activity and balanced nutrition; consult a professional if needed.", + "dietVeryLowCarbsDescription": "Very low‑carb (ketogenic) approach: minimal carbohydrates, higher fat and moderate protein. May support rapid weight loss and improved blood sugar control for some, but can be hard to sustain and may cause nutrient gaps. Monitor hydration/electrolytes and consult a professional for long‑term use.", + "dietLowCarbsDescription": "Low‑carb, higher‑protein plan: reduced carbohydrates with increased protein to support satiety and muscle maintenance. Helpful for weight management and glycemic control when balanced with vegetables and healthy fats. Ensure adequate fiber and micronutrients.", + "dietModerateCarbsDescription": "Moderate carbohydrate intake with balanced protein and fats: provides steady energy for daily activity and exercise while allowing flexibility. Suitable for many people aiming for sustainable weight management and performance.", + "dietUSDAGuidelinesDescription": "USDA‑based balanced pattern: emphasizes fruits, vegetables, whole grains, lean proteins, and limited added sugars and saturated fats. Evidence‑based framework for general health, nutrient adequacy, and chronic disease prevention.", + "dietZoneDietDescription": "Zone Diet (~40% carbs / 30% protein / 30% fat): emphasizes hormonal balance and portion control. May improve body composition and energy for some, but requires planning; personalize with a nutrition professional.", + "contextDependent": "Context Dependent", + "calculatedValue": "Calculated Value", + "calculationFormula": "Calculation Formula", + "loadingAIAnalysis": "Loading and analysing your data, Please be patient and let the AI do the magic. Please be patient, This might take some time." +} diff --git a/convert_json.dart b/convert_json.dart deleted file mode 100644 index 08f344d4..00000000 --- a/convert_json.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -void main() async { - final file = File('assets/json/body_symptoms_data.json'); - final content = await file.readAsString(); - - print('File size: ${content.length} characters'); - - // Split into two parts - final parts = content.split('export const SymptomsData = ['); - if (parts.length != 2) { - print('ERROR: Could not split file properly'); - exit(1); - } - - print('Split into ${parts.length} parts'); - - // Process BodySymptomsData - var bodyPart = parts[0] - .replaceAll('export const BodySymptomsData = ', '') - .trim() - .replaceAll(RegExp(r';$'), ''); - - print('Parsing body symptoms...'); - Map bodySymptomsData; - try { - bodySymptomsData = json.decode(bodyPart); - print('✅ Body symptoms parsed: ${bodySymptomsData.length} body parts'); - } catch (e) { - print('❌ Error parsing body symptoms: $e'); - exit(1); - } - - // Process SymptomsData - print('Processing symptoms data...'); - var symptomsPart = parts[1] - .trim() - .replaceAll(RegExp(r'\];$'), '') - .replaceAllMapped(RegExp(r'\n(\s*)id:'), (m) => '\n${m.group(1)}"id":') - .replaceAllMapped(RegExp(r'\n(\s*)type:'), (m) => '\n${m.group(1)}"type":') - .replaceAllMapped(RegExp(r'\n(\s*)name:'), (m) => '\n${m.group(1)}"name":') - .replaceAllMapped(RegExp(r'\n(\s*)common_name:'), (m) => '\n${m.group(1)}"common_name":'); - - print('Parsing symptoms...'); - List symptomsData; - try { - symptomsData = json.decode('[$symptomsPart]'); - print('✅ Symptoms parsed: ${symptomsData.length} symptoms'); - } catch (e) { - print('❌ Error parsing symptoms: $e'); - print('First 1000 chars:'); - print('[$symptomsPart]'.substring(0, 1000)); - exit(1); - } - - // Create final JSON - print('Creating final JSON structure...'); - final jsonStructure = { - 'bodySymptoms': bodySymptomsData, - 'symptoms': symptomsData, - }; - - // Write to file - print('Writing to file...'); - final encoder = JsonEncoder.withIndent(' '); - await file.writeAsString(encoder.convert(jsonStructure)); - - print('\n✅ SUCCESS! File converted to proper JSON'); - print('✅ Body parts: ${bodySymptomsData.length}'); - print('✅ Symptoms: ${symptomsData.length}'); -} - diff --git a/icon.jpg b/icon.jpg new file mode 100644 index 00000000..65ca5c05 Binary files /dev/null and b/icon.jpg differ diff --git a/ios/GoogleService-Info.plist b/ios/GoogleService-Info.plist index 633037cb..a9b1f53a 100644 --- a/ios/GoogleService-Info.plist +++ b/ios/GoogleService-Info.plist @@ -2,10 +2,6 @@ - CLIENT_ID - 815750722565-da8p56le8bd6apsbm9eft0jjl1rtpgkt.apps.googleusercontent.com - REVERSED_CLIENT_ID - com.googleusercontent.apps.815750722565-da8p56le8bd6apsbm9eft0jjl1rtpgkt ANDROID_CLIENT_ID 815750722565-m14h8mkosm7cnq6uh6rhqr54dn02d705.apps.googleusercontent.com API_KEY @@ -15,11 +11,11 @@ PLIST_VERSION 1 BUNDLE_ID - com.HMG.HMG-Smartphone + com.cloudsolutions.HMGPatientApp PROJECT_ID api-project-815750722565 STORAGE_BUCKET - api-project-815750722565.appspot.com + api-project-815750722565.firebasestorage.app IS_ADS_ENABLED IS_ANALYTICS_ENABLED @@ -31,7 +27,7 @@ IS_SIGNIN_ENABLED GOOGLE_APP_ID - 1:815750722565:ios:328ec247a81a2ca23c186c + 1:815750722565:ios:34ec03c09e02e21f3c186c DATABASE_URL https://api-project-815750722565.firebaseio.com diff --git a/ios/Podfile b/ios/Podfile index 9249e54d..4c8bed13 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -54,6 +54,8 @@ post_install do |installer| 'PERMISSION_EVENTS_FULL_ACCESS=1', ## dart: PermissionGroup.reminders 'PERMISSION_REMINDERS=1', + ## dart: PermissionGroup.notification + 'PERMISSION_NOTIFICATIONS=1', ] build_configuration.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64 i386' build_configuration.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0' diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 7a41ae2c..dd2d96ef 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -18,12 +18,12 @@ 61243B562EC5FA3700D46FA0 /* PenguinNavigator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61243B432EC5FA3700D46FA0 /* PenguinNavigator.swift */; }; 61243B572EC5FA3700D46FA0 /* PenguinViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61243B462EC5FA3700D46FA0 /* PenguinViewFactory.swift */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 766D8CB62EC60BE600D05E07 /* Penguin.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB32EC60BE600D05E07 /* Penguin.xcframework */; }; - 766D8CB72EC60BE600D05E07 /* Penguin.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB32EC60BE600D05E07 /* Penguin.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 766D8CB82EC60BE600D05E07 /* PenNavUI.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB42EC60BE600D05E07 /* PenNavUI.xcframework */; }; - 766D8CB92EC60BE600D05E07 /* PenNavUI.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB42EC60BE600D05E07 /* PenNavUI.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 766D8CBA2EC60BE600D05E07 /* PenguinINRenderer.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB52EC60BE600D05E07 /* PenguinINRenderer.xcframework */; }; - 766D8CBB2EC60BE600D05E07 /* PenguinINRenderer.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB52EC60BE600D05E07 /* PenguinINRenderer.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 765A5A8C2F35CD8B0003FF7D /* Penguin.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 765A5A802F35CD730003FF7D /* Penguin.xcframework */; }; + 765A5A8D2F35CD8B0003FF7D /* Penguin.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 765A5A802F35CD730003FF7D /* Penguin.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 765A5A8E2F35CD8B0003FF7D /* PenguinINRenderer.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 765A5A812F35CD730003FF7D /* PenguinINRenderer.xcframework */; }; + 765A5A8F2F35CD8B0003FF7D /* PenguinINRenderer.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 765A5A812F35CD730003FF7D /* PenguinINRenderer.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 765A5A902F35CD8B0003FF7D /* PenNavUI.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 765A5A822F35CD730003FF7D /* PenNavUI.xcframework */; }; + 765A5A912F35CD8B0003FF7D /* PenNavUI.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 765A5A822F35CD730003FF7D /* PenNavUI.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; @@ -41,15 +41,15 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ - 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + 765A5A922F35CD8B0003FF7D /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( - 766D8CB72EC60BE600D05E07 /* Penguin.xcframework in Embed Frameworks */, - 766D8CBB2EC60BE600D05E07 /* PenguinINRenderer.xcframework in Embed Frameworks */, - 766D8CB92EC60BE600D05E07 /* PenNavUI.xcframework in Embed Frameworks */, + 765A5A8F2F35CD8B0003FF7D /* PenguinINRenderer.xcframework in Embed Frameworks */, + 765A5A8D2F35CD8B0003FF7D /* Penguin.xcframework in Embed Frameworks */, + 765A5A912F35CD8B0003FF7D /* PenNavUI.xcframework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -73,9 +73,9 @@ 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7595037DD52211B91157B0F3 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - 766D8CB32EC60BE600D05E07 /* Penguin.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Penguin.xcframework; path = Frameworks/Penguin.xcframework; sourceTree = ""; }; - 766D8CB42EC60BE600D05E07 /* PenNavUI.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = PenNavUI.xcframework; path = Frameworks/PenNavUI.xcframework; sourceTree = ""; }; - 766D8CB52EC60BE600D05E07 /* PenguinINRenderer.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = PenguinINRenderer.xcframework; path = Frameworks/PenguinINRenderer.xcframework; sourceTree = ""; }; + 765A5A802F35CD730003FF7D /* Penguin.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Penguin.xcframework; sourceTree = ""; }; + 765A5A812F35CD730003FF7D /* PenguinINRenderer.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = PenguinINRenderer.xcframework; sourceTree = ""; }; + 765A5A822F35CD730003FF7D /* PenNavUI.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = PenNavUI.xcframework; sourceTree = ""; }; 769C9BF82E6F106D009F68A9 /* RunnerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerDebug.entitlements; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 8E12CEEB8E334EE22D5259D7 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; @@ -95,9 +95,9 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 766D8CB62EC60BE600D05E07 /* Penguin.xcframework in Frameworks */, - 766D8CBA2EC60BE600D05E07 /* PenguinINRenderer.xcframework in Frameworks */, - 766D8CB82EC60BE600D05E07 /* PenNavUI.xcframework in Frameworks */, + 765A5A8C2F35CD8B0003FF7D /* Penguin.xcframework in Frameworks */, + 765A5A902F35CD8B0003FF7D /* PenNavUI.xcframework in Frameworks */, + 765A5A8E2F35CD8B0003FF7D /* PenguinINRenderer.xcframework in Frameworks */, DE1EF78253E64BE86845D2CC /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -136,9 +136,9 @@ 766D8CB22EC60BE600D05E07 /* Frameworks */ = { isa = PBXGroup; children = ( - 766D8CB32EC60BE600D05E07 /* Penguin.xcframework */, - 766D8CB52EC60BE600D05E07 /* PenguinINRenderer.xcframework */, - 766D8CB42EC60BE600D05E07 /* PenNavUI.xcframework */, + 765A5A802F35CD730003FF7D /* Penguin.xcframework */, + 765A5A812F35CD730003FF7D /* PenguinINRenderer.xcframework */, + 765A5A822F35CD730003FF7D /* PenNavUI.xcframework */, D562310E31D1DDEFA02A6C12 /* Pods_Runner.framework */, ); name = Frameworks; @@ -235,10 +235,10 @@ 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, - 9705A1C41CF9048500538489 /* Embed Frameworks */, - 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 8372B02399CDF54531650AD4 /* [CP] Embed Pods Frameworks */, 81DE7C26F41956799E954FCE /* [CP] Copy Pods Resources */, + 765A5A922F35CD8B0003FF7D /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, ); buildRules = ( ); @@ -524,8 +524,8 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.HMG.HMG-Smartphone"; + MARKETING_VERSION = 0.0.1; + PRODUCT_BUNDLE_IDENTIFIER = com.cloudsolutions.HMGPatientApp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -710,8 +710,8 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.HMG.HMG-Smartphone"; + MARKETING_VERSION = 0.0.1; + PRODUCT_BUNDLE_IDENTIFIER = com.cloudsolutions.HMGPatientApp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -739,8 +739,8 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.HMG.HMG-Smartphone"; + MARKETING_VERSION = 0.0.1; + PRODUCT_BUNDLE_IDENTIFIER = com.cloudsolutions.HMGPatientApp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index 08f3422c..1eb27a20 100644 --- a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -109,7 +109,7 @@ "size" : "83.5x83.5" }, { - "filename" : "icon 1.jpg", + "filename" : "icon.jpg", "idiom" : "ios-marketing", "scale" : "1x", "size" : "1024x1024" diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index ab9828e6..9c805ac5 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -13,7 +13,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - Dr. Alhabib + Dr. Alhabib Beta CFBundlePackageType APPL CFBundleShortVersionString diff --git a/ios/Runner/Penguin/PenguinView.swift b/ios/Runner/Penguin/PenguinView.swift index 508fb74a..e683a35a 100644 --- a/ios/Runner/Penguin/PenguinView.swift +++ b/ios/Runner/Penguin/PenguinView.swift @@ -309,22 +309,13 @@ class PenguinView: NSObject, FlutterPlatformView, PIEventsDelegate, PenNavInitia PenNavUIManager.shared.eventsDelegate = self - - - print("====== after eventsDelegate onPenNavSuccess =========") - - PenNavUIManager.shared.navigate(to: "108") - - +// print("====== after onPenNavSuccess navigate =========") +// PenNavUIManager.shared.navigate(to: "3-1") // Present the Penguin UI on top of the Flutter view controller - + print("====== after onPenNavSuccess present =========") PenNavUIManager.shared.present(root: controller, view: _view) - - - - print("====== after present onPenNavSuccess =========") print(model?.clinicID) @@ -347,11 +338,21 @@ class PenguinView: NSObject, FlutterPlatformView, PIEventsDelegate, PenNavInitia let clientID = self.model?.clientID, !clientID.isEmpty else { - print("Error: Config Client ID is nil or empty") + print("Error: Config Clinic ID is nil or empty") return } + + print("====== after onPenNavSuccess navigate =========") +// PenNavUIManager.shared.navigate(to: "3-1") { success, error in + PenNavUIManager.shared.navigate(to: clinicID) { success, error in + if success { + print("Nav Success") + } else { + print("Nav Error") + } + } @@ -373,20 +374,20 @@ class PenguinView: NSObject, FlutterPlatformView, PIEventsDelegate, PenNavInitia - guard let token = token else { - - print("Token is nil") - - return - - } - - print("Token Generated") - - print(token); +// guard let token = token else { +// +// print("Token is nil") +// +// return +// +// } +// +// print("Token Generated") +// +// print(token); - +// // self?.handleNavigation(clinicID: clinicID, token: token) { success, errorMessage in // // if success { @@ -425,7 +426,8 @@ class PenguinView: NSObject, FlutterPlatformView, PIEventsDelegate, PenNavInitia PenNavUIManager.shared.setToken(token: token) - PenNavUIManager.shared.navigate(to: clinicID) +// PenNavUIManager.shared.navigate(to: clinicID) + PenNavUIManager.shared.navigate(to: "3-1") completion(true,nil) diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 722a45e8..231b037b 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -181,14 +181,27 @@ class ApiClientImp implements ApiClient { body[_appState.isAuthenticated ? 'TokenID' : 'LogInTokenID'] = _appState.appAuthToken; } + if (url.contains("HMGAI_Lab_Analyze_Orders_API")) { + url = "https://uat.hmgwebservices.com/Services/Patients.svc/REST/HMGAI_Lab_Analyze_Orders_API"; + body['TokenID'] = "@dm!n"; + } + + if (url.contains("HMGAI_Lab_Analyzer_API")) { + url = "https://uat.hmgwebservices.com/Services/Patients.svc/REST/HMGAI_Lab_Analyzer_API"; + body['TokenID'] = "@dm!n"; + } + // body['TokenID'] = "@dm!n"; - // body['PatientID'] = 1018977; + // body['PatientID'] = 4768663; // body['PatientTypeID'] = 1; - // // body['PatientOutSA'] = 0; // body['SessionID'] = "45786230487560q"; } + if(!url.contains("/paymentApi")) { + body['IsNewFlutterApp'] = true; + } + body.removeWhere((key, value) => value == null); } @@ -206,11 +219,11 @@ class ApiClientImp implements ApiClient { // Handle body encoding based on isBodyPlainText flag final dynamic requestBody = isBodyPlainText ? body : json.encode(body); + debugPrint("uri: ${Uri.parse(url.trim())}"); + debugPrint("body: ${json.encode(body)}", wrapWidth: 2048); final response = await http.post(Uri.parse(url.trim()), body: requestBody, headers: headers); final int statusCode = response.statusCode; - log("uri: ${Uri.parse(url.trim())}"); - log("body: ${json.encode(body)}"); - log("response.body: ${response.body}"); + // log("response.body: ${response.body}"); if (statusCode < 200 || statusCode >= 400) { onFailure('Error While Fetching data', statusCode, failureType: StatusCodeFailure("Error While Fetching data")); logApiEndpointError(endPoint, 'Error While Fetching data', statusCode); diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 720fda7a..f78c0201 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -83,6 +83,7 @@ var PRESCRIPTIONS = 'Services/Patients.svc/REST/GetPrescriptionApptList_Async'; var GET_PRESCRIPTIONS_ALL_ORDERS = 'Services/Patients.svc/REST/PatientER_GetPatientAllPresOrders'; var GET_PRESCRIPTION_REPORT = 'Services/Patients.svc/REST/INP_GetPrescriptionReport'; var SEND_PRESCRIPTION_EMAIL = 'Services/Notifications.svc/REST/SendPrescriptionEmail'; +var SEND_PRESCRIPTION_EMAIL_NEW = 'Services/Notifications.svc/REST/SendPrescriptionEmail_New'; var GET_PRESCRIPTION_REPORT_ENH = 'Services/Patients.svc/REST/GetPrescriptionReport_enh'; ///Lab Order @@ -379,6 +380,8 @@ var GET_PATIENT_SICK_LEAVE = 'Services/Patients.svc/REST/GetPatientSickLeave'; var GET_PATIENT_SICK_LEAVE_STATUS = 'Services/Patients.svc/REST/GetPatientSickLeave_Status'; +var GET_SERVICES_PRICE_LIST = 'Services/OUTPs.svc/REST/GetServicesPriceList'; + var SendSickLeaveEmail = 'Services/Notifications.svc/REST/SendSickLeaveEmail'; var GET_PATIENT_AdVANCE_BALANCE_AMOUNT = 'Services/Patients.svc/REST/GetPatientAdvanceBalanceAmount'; @@ -680,7 +683,7 @@ const DASHBOARD = 'Services/Patients.svc/REST/PatientDashboard'; class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT @@ -702,6 +705,8 @@ class ApiConsts { static String QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; + static String CHAT_URL = "https://chat.hmg.com/Index.aspx?RequestedId="; + // static String GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; // var payFortEnvironment = FortEnvironment.test; @@ -719,6 +724,7 @@ class ApiConsts { 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"; + CHAT_URL = "https://chat.hmg.com/Index.aspx?RequestedId="; break; case AppEnvironmentTypeEnum.dev: baseUrl = "https://uat.hmgwebservices.com/"; @@ -730,6 +736,7 @@ class ApiConsts { GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; rcBaseUrl = 'https://rc.hmg.com/uat/'; QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; + CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId"; break; case AppEnvironmentTypeEnum.uat: baseUrl = "https://uat.hmgwebservices.com/"; @@ -741,6 +748,7 @@ class ApiConsts { GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; rcBaseUrl = 'https://rc.hmg.com/uat/'; QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; + CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId"; break; case AppEnvironmentTypeEnum.preProd: baseUrl = "https://webservices.hmg.com/"; @@ -752,6 +760,7 @@ class ApiConsts { 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"; + CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId"; break; case AppEnvironmentTypeEnum.qa: baseUrl = "https://uat.hmgwebservices.com/"; @@ -763,6 +772,7 @@ class ApiConsts { GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; rcBaseUrl = 'https://rc.hmg.com/uat/'; QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; + CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId"; break; case AppEnvironmentTypeEnum.staging: baseUrl = "https://uat.hmgwebservices.com/"; @@ -774,6 +784,7 @@ class ApiConsts { GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; rcBaseUrl = 'https://rc.hmg.com/uat/'; QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; + CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId"; break; } } @@ -841,7 +852,7 @@ class ApiConsts { static final String getRiskFactors = '$symptomsCheckerApi/GetRiskFactors'; static final String getSuggestions = '$symptomsCheckerApi/GetSuggestion'; static final String diagnosis = '$symptomsCheckerApi/GetDiagnosis'; - static final String explain = '$symptomsCheckerApi/explain'; + static final String explain = '$symptomsCheckerApi/ExplainDiagnosisResult'; //E-REFERRAL SERVICES static final getAllRelationshipTypes = "Services/Patients.svc/REST/GetAllRelationshipTypes"; @@ -890,8 +901,15 @@ class ApiConsts { static String getProjectsHaveBDClinics = "Services/OUTPs.svc/REST/BD_getProjectsHaveBDClinics"; static String getClinicsBDFreeSlots = "Services/OUTPs.svc/REST/BD_GetFreeSlots"; + + static String getPatientBloodGroup = "services/PatientVarification.svc/REST/BloodDonation_GetBloodGroupDetails"; + static String getPatientBloodAgreement = "Services/PatientVarification.svc/REST/CheckUserAgreementForBloodDonation"; + static String getAiOverViewLabOrders = "Services/Patients.svc/REST/HMGAI_Lab_Analyze_Orders_API"; + static String getAiOverViewLabOrder = "Services/Patients.svc/REST/HMGAI_Lab_Analyzer_API"; + // ************ static values for Api **************** - static final double appVersionID = 50.3; + // static final double appVersionID = 19.7; + static final double appVersionID = 50.7; static final int appChannelId = 3; static final String appIpAddress = "10.20.10.20"; static final String appGeneralId = "Cs2020@2016\$2958"; diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 26da9fae..96c7cc09 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -4,6 +4,7 @@ class AppAssets { static const String hmg = '$svgBasePath/hmg.svg'; static const String arrow_back = '$svgBasePath/arrow-back.svg'; + static const String arrow_back_new = '$svgBasePath/arrow-back_new.svg'; static const String arrow_forward = '$svgBasePath/arrow_forward.svg'; static const String externalLink = '$svgBasePath/external_link.svg'; static const String calendar = '$svgBasePath/calendar.svg'; @@ -25,6 +26,7 @@ class AppAssets { static const String download = '$svgBasePath/download.svg'; static const String language = '$svgBasePath/language.svg'; static const String location = '$svgBasePath/location.svg'; + static const String locationFill = '$svgBasePath/location_fill.svg'; static const String pin_location = '$svgBasePath/pin_location.svg'; static const String whatsapp = '$svgBasePath/whatsapp.svg'; static const String card_user = '$svgBasePath/card_user.svg'; @@ -183,6 +185,7 @@ class AppAssets { static const String bulb_icon = '$svgBasePath/bulb_icon.svg'; static const String select_city_icon = '$svgBasePath/select_city_icon.svg'; static const String blood_donation_icon = '$svgBasePath/blood_donation_icon.svg'; + static const String my_child_vaccine_icon = '$svgBasePath/my_child_vaccine.svg'; static const String virtual_tour_icon = '$svgBasePath/virtual_tour_icon.svg'; static const String car_parking_icon = '$svgBasePath/car_parking_icon.svg'; static const String latest_news_icon = '$svgBasePath/latest_news_icon.svg'; @@ -230,17 +233,28 @@ class AppAssets { //bottom navigation// static const String homeBottom = '$svgBasePath/home_bottom.svg'; + static const String homeBottomFill = '$svgBasePath/home_bottom_fill.svg'; static const String bookAppoBottom = '$svgBasePath/book_appo_bottom.svg'; static const String myFilesBottom = '$svgBasePath/my_files_bottom.svg'; + static const String myFilesBottomFill = '$svgBasePath/my_files_bottom_fill.svg'; static const String toDoBottom = '$svgBasePath/todo_bottom.svg'; static const String servicesBottom = '$svgBasePath/services_bottom.svg'; + static const String servicesBottomFill = '$svgBasePath/services_bottom_fill.svg'; static const String closeBottomNav = '$svgBasePath/close_bottom_nav.svg'; + static const String close_bottom_nav_new = '$svgBasePath/close_bottom_nav_new.svg'; + static const String close_bottom_nav_trans = '$svgBasePath/close_bottom_nav_trans.svg'; static const String feedback = '$svgBasePath/feedback.svg'; + static const String feedbackFill = '$svgBasePath/feedback_fill.svg'; static const String news = '$svgBasePath/news.svg'; + static const String newsFill = '$svgBasePath/news_fill.svg'; static const String heart = '$svgBasePath/heart.svg'; static const String alertSquare = '$svgBasePath/alert-square.svg'; static const String arrowRight = '$svgBasePath/arrow-right.svg'; static const String tickIcon = '$svgBasePath/tick.svg'; + static const String guradIcon = '$svgBasePath/guard_icon.svg'; + static const String calculatorIcon = '$svgBasePath/calculator_icon.svg'; + static const String informationIcon = '$svgBasePath/information-circle.svg'; + static const String highCautionIcon = '$svgBasePath/high_caution.svg'; // Symptoms Checker static const String calendarGrey = '$svgBasePath/calendar-grey.svg'; @@ -253,6 +267,8 @@ class AppAssets { static const String homeBorderedIcon = '$svgBasePath/home_bordered.svg'; static const String symptomCheckerIcon = '$svgBasePath/symptom_checker_icon.svg'; static const String symptomCheckerBottomIcon = '$svgBasePath/symptom_bottom_icon.svg'; + static const String symptomCheckerBottomFillIcon = '$svgBasePath/symptom_bottom_icon_fill.svg'; + static const String coloredDotsIcon = '$svgBasePath/colored_dots_icon.svg'; // Water Monitor static const String waterBottle = '$svgBasePath/water_bottle.svg'; @@ -305,6 +321,13 @@ class AppAssets { static const String bloodPressure = '$svgBasePath/blood_pressure.svg'; static const String temperature = '$svgBasePath/temperature.svg'; + static const String arLangIcon = '$svgBasePath/ar_lang_icon.svg'; + static const String enLangIcon = '$svgBasePath/en_lang_icon.svg'; + + static const String doctorResponseIcon = '$svgBasePath/doctor_response_icon.svg'; + static const String changeLanguageHomePageIcon = '$svgBasePath/change_language_home_page.svg'; + static const String aiOverView = '$svgBasePath/ai_overview.svg'; + // PNGS // static const String hmgLogo = '$pngBasePath/hmg_logo.png'; static const String liveCareService = '$pngBasePath/livecare_service.png'; @@ -328,8 +351,10 @@ class AppAssets { static const String maleIcon = '$pngBasePath/male_icon.png'; static const String femaleIcon = '$pngBasePath/female_icon.png'; - static const String fullBodyFront = '$pngBasePath/full_body_front.png'; - static const String fullBodyBack = '$pngBasePath/full_body_back.png'; + static const String fullBodyFrontMale = '$pngBasePath/full_body_front_male.png'; + static const String fullBodyBackMale = '$pngBasePath/full_body_back_male.png'; + 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'; } @@ -352,4 +377,3 @@ class AppAnimations { static const String ambulanceAlert = '$lottieBasePath/ambulance_alert.json'; static const String rrtAmbulance = '$lottieBasePath/rrt_ambulance.json'; } - diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 45cd2a42..e7ac0515 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -4,6 +4,8 @@ import 'package:get_it/get_it.dart'; import 'package:hmg_patient_app_new/core/api/api_client.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/location_util.dart'; +import 'package:hmg_patient_app_new/features/ask_doctor/ask_doctor_repo.dart'; +import 'package:hmg_patient_app_new/features/ask_doctor/ask_doctor_view_model.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_repo.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'; @@ -52,6 +54,8 @@ import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_v import 'package:hmg_patient_app_new/features/qr_parking/qr_parking_repo.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_repo.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; +import 'package:hmg_patient_app_new/features/services_price_list/services_price_list_repo.dart'; +import 'package:hmg_patient_app_new/features/services_price_list/services_price_list_view_model.dart'; import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_provider.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_repo.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; @@ -59,9 +63,8 @@ import 'package:hmg_patient_app_new/features/todo_section/todo_section_repo.dart import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_repo.dart'; import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart'; -import 'package:hmg_patient_app_new/presentation/health_trackers/health_trackers_view_model.dart'; +import 'package:hmg_patient_app_new/features/health_trackers/health_trackers_view_model.dart'; import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; -import 'package:hmg_patient_app_new/presentation/monthly_reports/monthly_reports_page.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; @@ -167,6 +170,8 @@ class AppDependencies { getIt.registerLazySingleton(() => MonthlyReportsRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => QrParkingRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => NotificationsRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => AskDoctorRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => ServicesPriceListRepoImp(loggerService: getIt(), apiClient: getIt())); // ViewModels // Global/shared VMs → LazySingleton @@ -299,13 +304,16 @@ class AppDependencies { activePrescriptionsRepo: getIt() ), ); - // getIt.registerFactory( - // () => QrParkingViewModel( - // qrParkingRepo: getIt(), - // errorHandlerService: getIt(), - // cacheService: getIt(), - // ), - // ); + getIt.registerFactory( + () => QrParkingViewModel( + qrParkingRepo: getIt(), + errorHandlerService: getIt(), + cacheService: getIt(), + ), + ); + + getIt.registerLazySingleton(() => AskDoctorViewModel(askDoctorRepo: getIt(), errorHandlerService: getIt())); + getIt.registerLazySingleton(() => ServicesPriceListViewModel(servicesPriceListRepo: getIt(), errorHandlerService: getIt())); } } diff --git a/lib/core/enums.dart b/lib/core/enums.dart index 6dc3bf68..66137021 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -311,3 +311,150 @@ extension ServiceTypeEnumExt on ServiceTypeEnum { enum PossibleConditionsSeverityEnum { seekMedicalAdvice, monitorOnly, emergency } enum HealthTrackerTypeEnum { bloodSugar, bloodPressure, weightTracker } + +// Severity Enum +enum SeverityEnum { mild, moderate, severe } + +extension SeverityEnumExtension on SeverityEnum { + int get toInt { + switch (this) { + case SeverityEnum.mild: + return 0; + case SeverityEnum.moderate: + return 1; + case SeverityEnum.severe: + return 2; + } + } + + String get displayName { + AppState appState = getIt.get(); + bool isArabic = appState.getLanguageID() == 1 ? true : false; + switch (this) { + case SeverityEnum.mild: + return isArabic ? 'خفيف' : 'Mild'; + case SeverityEnum.moderate: + return isArabic ? 'متوسط' : 'Moderate'; + case SeverityEnum.severe: + return isArabic ? 'شديد' : 'Severe'; + } + } + + static SeverityEnum? fromInt(int value) { + switch (value) { + case 0: + return SeverityEnum.mild; + case 1: + return SeverityEnum.moderate; + case 2: + return SeverityEnum.severe; + default: + return null; + } + } +} + +// Triage Level Enum +enum TriageLevelEnum { emergencyAmbulance, emergency, consultation24, consultation, selfCare } + +extension TriageLevelEnumExtension on TriageLevelEnum { + int get toInt { + switch (this) { + case TriageLevelEnum.emergencyAmbulance: + return 0; + case TriageLevelEnum.emergency: + return 1; + case TriageLevelEnum.consultation24: + return 2; + case TriageLevelEnum.consultation: + return 3; + case TriageLevelEnum.selfCare: + return 4; + } + } + + String get displayName { + AppState appState = getIt.get(); + bool isArabic = appState.getLanguageID() == 1 ? true : false; + switch (this) { + case TriageLevelEnum.emergencyAmbulance: + return isArabic ? 'طوارئ - إسعاف' : 'Emergency - Ambulance'; + case TriageLevelEnum.emergency: + return isArabic ? 'طوارئ' : 'Emergency'; + case TriageLevelEnum.consultation24: + return isArabic ? 'استشارة خلال 24 ساعة' : 'Consultation within 24 hours'; + case TriageLevelEnum.consultation: + return isArabic ? 'استشارة' : 'Consultation'; + case TriageLevelEnum.selfCare: + return isArabic ? 'رعاية ذاتية' : 'Self Care'; + } + } + + static TriageLevelEnum? fromInt(int value) { + switch (value) { + case 0: + return TriageLevelEnum.emergencyAmbulance; + case 1: + return TriageLevelEnum.emergency; + case 2: + return TriageLevelEnum.consultation24; + case 3: + return TriageLevelEnum.consultation; + case 4: + return TriageLevelEnum.selfCare; + default: + return null; + } + } +} + +// Question Type Enum +enum QuestionTypeEnum { single, groupSingle, groupMultiple, duration } + +extension QuestionTypeEnumExtension on QuestionTypeEnum { + int get toInt { + switch (this) { + case QuestionTypeEnum.single: + return 0; + case QuestionTypeEnum.groupSingle: + return 1; + case QuestionTypeEnum.groupMultiple: + return 2; + case QuestionTypeEnum.duration: + return 3; + } + } + + String get displayName { + AppState appState = getIt.get(); + bool isArabic = appState.getLanguageID() == 1 ? true : false; + switch (this) { + case QuestionTypeEnum.single: + return isArabic ? 'سؤال واحد' : 'Single'; + case QuestionTypeEnum.groupSingle: + return isArabic ? 'مجموعة - اختيار واحد' : 'Group Single'; + case QuestionTypeEnum.groupMultiple: + return isArabic ? 'مجموعة - اختيار متعدد' : 'Group Multiple'; + case QuestionTypeEnum.duration: + return isArabic ? 'المدة' : 'Duration'; + } + } + + static QuestionTypeEnum? fromInt(int value) { + switch (value) { + case 0: + return QuestionTypeEnum.single; + case 1: + return QuestionTypeEnum.groupSingle; + case 2: + return QuestionTypeEnum.groupMultiple; + case 3: + return QuestionTypeEnum.duration; + default: + return null; + } + } +} + + + diff --git a/lib/core/location_util.dart b/lib/core/location_util.dart index 9dcdbb5d..cf26d24e 100644 --- a/lib/core/location_util.dart +++ b/lib/core/location_util.dart @@ -104,7 +104,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".needTranslation, + loadingText: "Please grant location permission from app settings to see better results", isShowActionButtons: true, onCancelTap: () { navigationService.pop(); @@ -265,7 +265,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".needTranslation, + loadingText: "Please grant location permission from app settings to see better results", isShowActionButtons: true, onCancelTap: () { navigationService.pop(); diff --git a/lib/core/utils/calendar_utils.dart b/lib/core/utils/calendar_utils.dart index 8c0db187..8b21111f 100644 --- a/lib/core/utils/calendar_utils.dart +++ b/lib/core/utils/calendar_utils.dart @@ -215,14 +215,14 @@ showReminderBottomSheet(BuildContext context, DateTime dateTime, String doctorNa Future _showReminderBottomSheet(BuildContext providedContext, DateTime dateTime, String doctorName, String eventId, String appoDateFormatted, String appoTimeFormatted, {required Function onSuccess, String? title, String? description, Function(int)? onMultiDateSuccess, bool? isMultiAllowed}) async { - showCommonBottomSheetWithoutHeight(providedContext, title: "Set the timer of reminder".needTranslation, child: PrescriptionReminderView( + showCommonBottomSheetWithoutHeight(providedContext, title: "Set the timer of reminder", child: PrescriptionReminderView( setReminder: (int value) async { if (!isMultiAllowed!) { if (onMultiDateSuccess == null) { CalendarUtils calendarUtils = await CalendarUtils.getInstance(); await calendarUtils.createOrUpdateEvent( - title: title ?? "You have appointment with Dr. ".needTranslation + doctorName, - description: description ?? "At " + appoDateFormatted + " " + appoTimeFormatted, + title: title ?? "You have appointment with Dr. $doctorName", + description: description ?? "At $appoDateFormatted $appoTimeFormatted", scheduleDateTime: dateTime, eventId: eventId, location: ''); diff --git a/lib/core/utils/date_util.dart b/lib/core/utils/date_util.dart index 746d2a7a..2b134735 100644 --- a/lib/core/utils/date_util.dart +++ b/lib/core/utils/date_util.dart @@ -110,7 +110,7 @@ class DateUtil { } static String formatDateToTime(DateTime date) { - return DateFormat('hh:mm a').format(date); + return DateFormat('hh:mm a', "en-US").format(date); } static String yearMonthDay(DateTime dateTime) { @@ -487,7 +487,9 @@ class DateUtil { } static String getFormattedDate(DateTime dateTime, String formattedString) { - return DateFormat(formattedString).format(dateTime); + String formattedDate = DateFormat(formattedString, "en-US").format(dateTime); + print(formattedDate); + return formattedDate; } static convertISODateToJsonDate(String isoDate) { diff --git a/lib/core/utils/loading_utils.dart b/lib/core/utils/loading_utils.dart index 1a99278d..bd24d30c 100644 --- a/lib/core/utils/loading_utils.dart +++ b/lib/core/utils/loading_utils.dart @@ -38,7 +38,7 @@ class LoadingUtils { child: Material( child: Center( child: isSuccessDialog ? Utils.getSuccessWidget(loadingText: loadingText) : Utils.getLoadingWidget(loadingText: loadingText), - ), + ).paddingSymmetrical(24.w, 0), ), ); }).then((value) { diff --git a/lib/core/utils/penguin_method_channel.dart b/lib/core/utils/penguin_method_channel.dart index 1f190376..69ef4be7 100644 --- a/lib/core/utils/penguin_method_channel.dart +++ b/lib/core/utils/penguin_method_channel.dart @@ -7,7 +7,7 @@ class PenguinMethodChannel { return await rootBundle.load("assets/images/progress-loading-red-crop-1.gif").then((data) => data.buffer.asUint8List()); } - Future launch(String storyboardName, String languageCode, String username, {NavigationClinicDetails? details}) async { + Future launch(String storyboardName, String languageCode, String username, bool isClinicNavigation, {NavigationClinicDetails? details}) async { // Uint8List image = await loadGif(); try { await _channel.invokeMethod('launchPenguin', { @@ -31,7 +31,7 @@ class PenguinMethodChannel { "isEnableReportIssue": true, "languageCode": languageCode, "mapBoxKey": "pk.eyJ1IjoicndhaWQiLCJhIjoiY2x5cGo4aHNjMGNsbTJyc2djempobGQxaSJ9.RCaC6WrUt4A4YnZNfxnONQ", - "clinicID": details?.clinicId ?? "", + "clinicID": isClinicNavigation ? "${details?.projectId}-${details?.clinicId}" : details?.clinicId ?? "", // "clinicID": "108", // 46 ,49, 133 "patientID": details?.patientId ?? "", "projectID": int.parse(details?.projectId ?? "-1"), diff --git a/lib/core/utils/push_notification_handler.dart b/lib/core/utils/push_notification_handler.dart index 88e8cc84..aef18f73 100644 --- a/lib/core/utils/push_notification_handler.dart +++ b/lib/core/utils/push_notification_handler.dart @@ -372,6 +372,14 @@ class PushNotificationHandler { } }); + if (Platform.isIOS) { + FirebaseMessaging.instance.getAPNSToken().then((String? token) { + print("Push Notification getAPNSToken: ${token!}"); + }).catchError((err) { + print("Push Notification getAPNSToken ERR: ${err.toString()}"); + }); + } + FirebaseMessaging.instance.getToken().then((String? token) { print("Push Notification getToken: ${token!}"); onToken(token!); diff --git a/lib/core/utils/request_utils.dart b/lib/core/utils/request_utils.dart index e57039cb..dd81a13c 100644 --- a/lib/core/utils/request_utils.dart +++ b/lib/core/utils/request_utils.dart @@ -210,8 +210,8 @@ class RequestUtils { List names = fullName != null ? fullName.split(" ") : []; var dob = appState.getUserRegistrationPayload.dob; - final DateFormat dateFormat1 = DateFormat('MM/dd/yyyy'); - final DateFormat dateFormat2 = DateFormat('dd/MM/yyyy'); + final DateFormat dateFormat1 = DateFormat('MM/dd/yyyy', "en-US"); + final DateFormat dateFormat2 = DateFormat('dd/MM/yyyy', "en-US"); DateTime gregorianDate = dateFormat2.parse(dob!); HijriGregDate hijriDate = HijriGregConverter.gregorianToHijri(gregorianDate); String? date = "${hijriDate.day}/${hijriDate.month}/${hijriDate.year}"; diff --git a/lib/core/utils/size_utils.dart b/lib/core/utils/size_utils.dart index 02b81957..d04ad70d 100644 --- a/lib/core/utils/size_utils.dart +++ b/lib/core/utils/size_utils.dart @@ -4,8 +4,11 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; // These are the Viewport values of your Figma Design. // These are used in the code as a reference to create your UI Responsively. -const num figmaDesignWidth = 375; // iPhone X / 12 base width -const num figmaDesignHeight = 812; // iPhone X / 12 base height +const num figmaDesignWidth = 430; // iPhone X / 12 base width, 375 +const num figmaDesignHeight = 927; // iPhone X / 12 base height 812 + +const num figmaDesignWidthTF = 375; +const num figmaDesignHeightTF = 812; extension ConstrainedResponsive on num { /// Width with max cap for tablets @@ -50,9 +53,9 @@ extension ResponsiveExtension on num { double get w { double baseScale = (this * _screenWidth) / figmaDesignWidth; - if (_isFoldable) { + if (_isFoldable|| isTablet ) { // For foldables, use more conservative width scaling - double scale = _screenWidth / figmaDesignWidth; + double scale = _screenWidth / figmaDesignWidthTF; scale = scale.clamp(0.8, 1.4); return this * scale; } @@ -64,9 +67,9 @@ extension ResponsiveExtension on num { double get h { double baseScale = (this * _screenHeight) / figmaDesignHeight; - if (_isFoldable) { + if (_isFoldable || isTablet ) { // For foldables, use height-based scaling but with constraints - double scale = (_screenHeight / figmaDesignHeight).clamp(0.8, 1.4); + double scale = (_screenHeight / figmaDesignHeightTF).clamp(0.8, 1.4); return this * scale; } @@ -79,7 +82,7 @@ extension ResponsiveExtension on num { if (_isFoldable || isTablet) { // Use the same logic as enhanced width for foldables - double scale = _screenWidth / figmaDesignWidth; + double scale = _screenWidth / figmaDesignWidthTF; scale = scale.clamp(0.8, 1.4); return this * scale; } diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 8978fcd6..60925bca 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -128,7 +128,7 @@ class Utils { static String convertStringToDateTime(String dateTimeString) { String timeString = dateTimeString; // Parse the time string using DateFormat - DateFormat format = DateFormat.Hms(); // 'Hms' = 'HH:mm:ss' + DateFormat format = DateFormat.Hms(["en-US"]); // 'Hms' = 'HH:mm:ss' DateTime time = format.parse(timeString); DateTime now = DateTime.now(); @@ -367,7 +367,7 @@ class Utils { crossAxisAlignment: CrossAxisAlignment.center, children: [ Lottie.asset(AppAnimations.loadingAnimation, - repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill), + 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), SizedBox(height: 8.h), @@ -814,11 +814,13 @@ class Utils { child: Utils.buildSvgWithAssets(icon: AppAssets.saudi_riyal_icon, width: iconSize.h, height: iconSize.h, iconColor: iconColor), ), TextSpan( - text: NumberFormat.currency(locale: 'en_US', symbol: " ", decimalDigits: 0).format(habibWalletAmount), + // text: NumberFormat.currency(locale: 'en_US', symbol: " ", decimalDigits: 0).format(habibWalletAmount), + text: " ${habibWalletAmount.toStringAsFixed(2)}", style: TextStyle( color: textColor, fontSize: fontSize ?? 32.f, letterSpacing: letterSpacing ?? -4, + fontFamily: "Poppins", fontWeight: fontWeight ?? FontWeight.w600, height: 1), ), @@ -959,11 +961,11 @@ class Utils { static String getCardButtonText(int currentQueueStatus, String roomNumber) { switch (currentQueueStatus) { case 0: - return "Please wait! you will be called for vital signs".needTranslation; + return LocaleKeys.pleaseWaitYouWillBeCalledForVitalSigns.tr(); case 1: - return "Please visit Room $roomNumber for vital signs".needTranslation; + return LocaleKeys.pleaseVisitRoomForVitalSigns.tr(namedArgs: {'roomNumber': roomNumber.toString()}); case 2: - return "Please visit Room $roomNumber to the Doctor".needTranslation; + return LocaleKeys.pleaseVisitRoomToTheDoctor.tr(namedArgs: {'roomNumber': roomNumber.toString()}); } return ""; } @@ -975,4 +977,27 @@ class Utils { return checkDate == today; } + + static bool isClinicAllowedForRebook(int clinicID) { + bool isAllowed = true; + + if (clinicID == 23 || clinicID == 47 || clinicID == 265 || clinicID == 253 || clinicID == 134) { + isAllowed = false; + } + + return isAllowed; + } + + static String toEnglishNumbers(String input) { + const english = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; + const arabic = ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩']; + const persian = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']; + + String result = input; + for (int i = 0; i < 10; i++) { + result = result.replaceAll(arabic[i], english[i]); + result = result.replaceAll(persian[i], english[i]); + } + return result; + } } diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 309dde19..f087f748 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -23,8 +23,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({Color? color, FontWeight? fontWeight, bool isBold = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow}) => Text( this, maxLines: maxlines, overflow: textOverflow, @@ -38,7 +37,8 @@ extension EmailValidator on String { ); Widget toText10( - {Color? color, + {bool isEnglishOnly = false, + Color? color, FontWeight? weight, bool isBold = false, bool isUnderLine = false, @@ -59,18 +59,36 @@ extension EmailValidator on String { color: color ?? AppColors.blackColor, letterSpacing: letterSpacing, decoration: isUnderLine ? TextDecoration.underline : null, + fontFamily: isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins', decorationColor: color ?? AppColors.blackColor), ); - Widget toText11( + Widget toText9( {Color? color, FontWeight? weight, + bool isBold = false, bool isUnderLine = false, bool isCenter = false, - bool isBold = false, - int maxLine = 0, + int? maxlines, + FontStyle? fontStyle, + TextOverflow? textOverflow, double letterSpacing = 0}) => Text( + this, + textAlign: isCenter ? TextAlign.center : null, + maxLines: maxlines, + overflow: textOverflow, + style: TextStyle( + fontSize: 9.f, + fontStyle: fontStyle ?? FontStyle.normal, + fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), + color: color ?? AppColors.blackColor, + letterSpacing: letterSpacing, + decoration: isUnderLine ? TextDecoration.underline : null, + decorationColor: color ?? AppColors.blackColor), + ); + + Widget toText11({Color? color, FontWeight? weight, bool isUnderLine = false, bool isCenter = false, bool isBold = false, int maxLine = 0, double letterSpacing = 0}) => Text( this, textAlign: isCenter ? TextAlign.center : null, maxLines: (maxLine > 0) ? maxLine : null, @@ -85,13 +103,14 @@ extension EmailValidator on String { ); Widget toText12( - {Color? color, + {bool isEnglishOnly = false, Color? color, bool isUnderLine = false, TextAlign textAlignment = TextAlign.start, bool isBold = false, FontWeight? fontWeight, bool isCenter = false, double? height, + double? letterSpacing, int maxLine = 0}) => Text( this, @@ -101,10 +120,11 @@ extension EmailValidator on String { fontSize: 12.f, fontWeight: fontWeight ?? (isBold ? FontWeight.bold : FontWeight.normal), color: color ?? AppColors.blackColor, - letterSpacing: 0, + letterSpacing: letterSpacing ?? 0, height: height, decorationColor: isUnderLine ? AppColors.blackColor : null, decoration: isUnderLine ? TextDecoration.underline : null, + fontFamily: isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins', ), ); @@ -150,15 +170,7 @@ extension EmailValidator on String { ), ); - Widget toText13( - {Color? color, - bool isUnderLine = false, - bool isBold = false, - bool isCenter = false, - int maxLine = 0, - FontWeight? weight, - double? letterSpacing = 0}) => - Text( + Widget toText13({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int maxLine = 0, FontWeight? weight, double? letterSpacing = 0}) => Text( this, textAlign: isCenter ? TextAlign.center : null, maxLines: (maxLine > 0) ? maxLine : null, @@ -171,6 +183,7 @@ extension EmailValidator on String { ); Widget toText14({ + bool isEnglishOnly = false, Color? color, bool isUnderLine = false, bool isBold = false, @@ -193,18 +206,11 @@ extension EmailValidator on String { height: height, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), decoration: isUnderLine ? TextDecoration.underline : null, + fontFamily: isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins', decorationColor: color ?? AppColors.blackColor), ); - Widget toText15( - {Color? color, - bool isUnderLine = false, - bool isBold = false, - bool isCenter = false, - FontWeight? weight, - int? maxlines, - double? letterSpacing = -1}) => - Text( + Widget toText15({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, FontWeight? weight, int? maxlines, double? letterSpacing = -1}) => Text( this, textAlign: isCenter ? TextAlign.center : null, maxLines: maxlines, @@ -216,8 +222,7 @@ extension EmailValidator on String { decoration: isUnderLine ? TextDecoration.underline : null), ); - Widget toText16( - {Color? color, + Widget toText16({bool isEnglishOnly = false, Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, @@ -232,6 +237,7 @@ extension EmailValidator on String { this, maxLines: maxlines, textAlign: isCenter ? TextAlign.center : null, + // locale: Locale('en', 'US'), style: TextStyle( color: color ?? AppColors.blackColor, fontSize: 16.f, @@ -240,14 +246,14 @@ extension EmailValidator on String { overflow: textOverflow, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), decoration: isUnderLine ? TextDecoration.underline : null, + fontFamily: isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins', decorationColor: decorationColor), ); - Widget toText17({Color? color, bool isBold = false, bool isCenter = false}) => Text( + 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), + style: TextStyle(color: color ?? AppColors.blackColor, fontSize: 17.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, fontFamily: isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'), ); Widget toText18({Color? color, FontWeight? weight, bool isBold = false, bool isCenter = false, int? maxlines, TextOverflow? textOverflow}) => Text( @@ -255,17 +261,12 @@ 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), + style: TextStyle(fontSize: 18.f, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), color: color ?? AppColors.blackColor, letterSpacing: -0.4), ); Widget toText19({Color? color, bool isBold = false}) => Text( this, - style: TextStyle( - fontSize: 19.f, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: color ?? AppColors.blackColor, letterSpacing: -0.4), + style: TextStyle(fontSize: 19.f, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: color ?? AppColors.blackColor, letterSpacing: -0.4), ); Widget toText20({ @@ -275,86 +276,51 @@ extension EmailValidator on String { }) => Text( this, - style: TextStyle( - fontSize: 20.f, - fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), - color: color ?? AppColors.blackColor, - letterSpacing: -0.4), + style: TextStyle(fontSize: 20.f, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), color: color ?? AppColors.blackColor, letterSpacing: -0.4), ); Widget toText21({Color? color, bool isBold = false, FontWeight? weight, int? maxlines}) => Text( this, maxLines: maxlines, - style: TextStyle( - color: color ?? AppColors.blackColor, - fontSize: 21.f, - letterSpacing: -1, - fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)), + style: TextStyle(color: color ?? AppColors.blackColor, fontSize: 21.f, letterSpacing: -1, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)), ); Widget toText22({Color? color, bool isBold = false, bool isCenter = false}) => Text( this, textAlign: isCenter ? TextAlign.center : null, - style: TextStyle( - height: 1, - color: color ?? AppColors.blackColor, - fontSize: 22.f, - letterSpacing: -1, - fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + style: TextStyle(height: 1, color: color ?? AppColors.blackColor, fontSize: 22.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); Widget toText24({Color? color, bool isBold = false, bool isCenter = false, FontWeight? fontWeight, double? letterSpacing}) => Text( this, textAlign: isCenter ? TextAlign.center : null, style: TextStyle( - height: 23 / 24, - color: color ?? AppColors.blackColor, - fontSize: 24.f, - letterSpacing: letterSpacing ?? -1, - fontWeight: isBold ? FontWeight.bold : fontWeight ?? FontWeight.normal), + height: 23 / 24, color: color ?? AppColors.blackColor, fontSize: 24.f, letterSpacing: letterSpacing ?? -1, fontWeight: isBold ? FontWeight.bold : fontWeight ?? FontWeight.normal), ); Widget toText26({Color? color, bool isBold = false, double? height, bool isCenter = false, FontWeight? weight, double? letterSpacing}) => Text( this, textAlign: isCenter ? TextAlign.center : null, style: TextStyle( - height: height ?? 23 / 26, - color: color ?? AppColors.blackColor, - fontSize: 26.f, - letterSpacing: letterSpacing ?? -1, - fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)), + height: height ?? 23 / 26, color: color ?? AppColors.blackColor, fontSize: 26.f, letterSpacing: letterSpacing ?? -1, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)), ); - Widget toText28({Color? color, bool isBold = false, double? height, bool isCenter = false, double? letterSpacing}) => Text( + 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), + 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().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'), ); - Widget toText32({FontWeight? weight, Color? color, bool isBold = false, bool isCenter = false}) => Text( + 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, - fontWeight: isBold ? FontWeight.bold : weight ?? FontWeight.normal), + height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 32.f, letterSpacing: -1, fontFamily: isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins', fontWeight: isBold ? FontWeight.bold : weight ?? FontWeight.normal), ); Widget toText44({Color? color, bool isBold = false}) => Text( this, - style: TextStyle( - height: 32 / 32, - color: color ?? AppColors.blackColor, - fontSize: 44.f, - letterSpacing: -1, - fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + style: TextStyle(height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 44.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); Widget toSectionHeading({String upperHeading = "", String lowerHeading = ""}) { @@ -390,9 +356,7 @@ extension EmailValidator on String { } bool isValidEmail() { - return RegExp( - r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$') - .hasMatch(this); + return RegExp(r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$').hasMatch(this); } String toFormattedDate() { @@ -459,11 +423,12 @@ extension DynamicTextStyleExtension on BuildContext { Locale? locale, TextBaseline? textBaseline, FontStyle? fontStyle, + String? fontFamily, bool isLanguageSwitcher = false}) { AppState appState = getIt.get(); final family = appState.getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'; return TextStyle( - fontFamily: family, + fontFamily: fontFamily ?? family, fontSize: fontSize, fontWeight: fontWeight, color: color, diff --git a/lib/features/ask_doctor/ask_doctor_repo.dart b/lib/features/ask_doctor/ask_doctor_repo.dart new file mode 100644 index 00000000..4e4a4006 --- /dev/null +++ b/lib/features/ask_doctor/ask_doctor_repo.dart @@ -0,0 +1,128 @@ +import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/core/api/api_client.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; +import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; +import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/features/ask_doctor/models/ask_doctor_appointments_list.dart'; +import 'package:hmg_patient_app_new/features/ask_doctor/models/doctor_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/services/logger_service.dart'; + +abstract class AskDoctorRepo { + Future>>> getAskDoctorAppointmentsList(); + + Future>>> getDoctorResponse({ + required String fromDate, + }); +} + +class AskDoctorRepoImp implements AskDoctorRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + AskDoctorRepoImp({ + required this.loggerService, + required this.apiClient, + }); + + @override + Future>>> getAskDoctorAppointmentsList() async { + Map mapDevice = { + "Top": 25, + "beforeDays": 15, + "exludType": 4, + }; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + IS_ALLOW_ASK_DOCTOR, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['PatientDoctorAppointmentResultList']; + + final clinicsList = list.map((item) => AskDoctorAppointmentHistoryList.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: clinicsList, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>>> getDoctorResponse({ + required String fromDate, + }) async { + Map mapDevice = { + "isDentalAllowedBackend": false, + "from": fromDate, + }; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_DOCTOR_RESPONSE, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['List_DoctorResponse']; + + if (list == null || list.isEmpty) { + // Return empty list if no responses + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: [], + ); + return; + } + + final doctorResponsesList = list + .map((item) => DoctorResponseModel.fromJson(item as Map)) + .toList() + .cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: doctorResponsesList, + ); + } 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) { + loggerService.logError("AskDoctorRepo.getDoctorResponse error: $e"); + return Left(UnknownFailure(e.toString())); + } + } +} diff --git a/lib/features/ask_doctor/ask_doctor_view_model.dart b/lib/features/ask_doctor/ask_doctor_view_model.dart new file mode 100644 index 00000000..2b9600e1 --- /dev/null +++ b/lib/features/ask_doctor/ask_doctor_view_model.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/features/ask_doctor/ask_doctor_repo.dart'; +import 'package:hmg_patient_app_new/features/ask_doctor/models/ask_doctor_appointments_list.dart'; +import 'package:hmg_patient_app_new/features/ask_doctor/models/doctor_response_model.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; + +class AskDoctorViewModel extends ChangeNotifier { + final AskDoctorRepo askDoctorRepo; + final ErrorHandlerService errorHandlerService; + + AskDoctorViewModel({ + required this.askDoctorRepo, + required this.errorHandlerService, + }); + + bool isAskDoctorAppointmentsLoading = false; + bool isDoctorResponsesLoading = false; + + List askDoctorAppointmentsList = []; + List doctorResponsesList = []; + + /// Initialize the view model + void initAskDoctorViewModel() { + askDoctorAppointmentsList.clear(); + doctorResponsesList.clear(); + isAskDoctorAppointmentsLoading = true; + getAskDoctorAppointmentsList(); + notifyListeners(); + } + + Future getAskDoctorAppointmentsList({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await askDoctorRepo.getAskDoctorAppointmentsList(); + + 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) { + askDoctorAppointmentsList = apiResponse.data!; + isAskDoctorAppointmentsLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + + /// Get doctor responses from a specific date + Future getDoctorResponses({ + String? fromDate, + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + isDoctorResponsesLoading = true; + notifyListeners(); + + // If no date provided, use current date minus 30 days + final dateToUse = "${DateTime.now().year}-${DateTime.now().month}-${DateTime.now().day} ${DateTime.now().hour}:${DateTime.now().minute}:00"; + + final result = await askDoctorRepo.getDoctorResponse(fromDate: dateToUse); + + result.fold( + (failure) async { + isDoctorResponsesLoading = false; + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + isDoctorResponsesLoading = false; + + if (apiResponse.messageStatus == 2) { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? "Unknown error"); + } + } else if (apiResponse.messageStatus == 1) { + doctorResponsesList = apiResponse.data ?? []; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } +} diff --git a/lib/features/ask_doctor/models/ask_doctor_appointments_list.dart b/lib/features/ask_doctor/models/ask_doctor_appointments_list.dart new file mode 100644 index 00000000..7f40d71c --- /dev/null +++ b/lib/features/ask_doctor/models/ask_doctor_appointments_list.dart @@ -0,0 +1,200 @@ +class AskDoctorAppointmentHistoryList { + String? setupID; + int? projectID; + int? appointmentNo; + String? appointmentDate; + bool? appointmentType; + String? bookDate; + bool? patientType; + int? patientID; + int? clinicID; + int? doctorID; + String? endDate; + String? startTime; + String? endTime; + bool? status; + bool? visitType; + bool? isReminderActivated; + int? companyID; + String? bookedOn; + String? confirmedOn; + String? arrivedOn; + String? editedOn; + int? appointmentDays; + int? isActive; + int? actualDoctorRate; + String? clinicName; + String? date; + num? decimalDoctorRate; + String? doctorCases; + String? doctorImageURL; + String? doctorInfo; + String? doctorName; + num? doctorRate; + num? doctorStarsRate; + String? doctorTitle; + int? gender; + String? genderDescription; + bool? isActiveDoctorProfile; + bool? isAppointmentAllowed; + bool? isDoctorAllowVedioCall; + bool? isLiveCareClinic; + String? nationalityFlagURL; + String? nationalityID; + String? nationalityName; + int? noOfPatientsRate; + String? projectName; + String? qR; + List? speciality; + + AskDoctorAppointmentHistoryList( + {this.setupID, + this.projectID, + this.appointmentNo, + this.appointmentDate, + this.appointmentType, + this.bookDate, + this.patientType, + this.patientID, + this.clinicID, + this.doctorID, + this.endDate, + this.startTime, + this.endTime, + this.status, + this.visitType, + this.isReminderActivated, + this.companyID, + this.bookedOn, + this.confirmedOn, + this.arrivedOn, + this.editedOn, + this.appointmentDays, + this.isActive, + this.actualDoctorRate, + this.clinicName, + this.date, + this.decimalDoctorRate, + this.doctorCases, + this.doctorImageURL, + this.doctorInfo, + this.doctorName, + this.doctorRate, + this.doctorStarsRate, + this.doctorTitle, + this.gender, + this.genderDescription, + this.isActiveDoctorProfile, + this.isAppointmentAllowed, + this.isDoctorAllowVedioCall, + this.isLiveCareClinic, + this.nationalityFlagURL, + this.nationalityID, + this.nationalityName, + this.noOfPatientsRate, + this.projectName, + this.qR, + this.speciality}); + + AskDoctorAppointmentHistoryList.fromJson(Map json) { + setupID = json['SetupID']; + projectID = json['ProjectID']; + appointmentNo = json['AppointmentNo']; + appointmentDate = json['AppointmentDate']; + appointmentType = json['AppointmentType']; + bookDate = json['BookDate']; + patientType = json['PatientType']; + patientID = json['PatientID']; + clinicID = json['ClinicID']; + doctorID = json['DoctorID']; + endDate = json['EndDate']; + startTime = json['StartTime']; + endTime = json['EndTime']; + status = json['Status']; + visitType = json['VisitType']; + isReminderActivated = json['IsReminderActivated']; + companyID = json['CompanyID']; + bookedOn = json['BookedOn']; + confirmedOn = json['ConfirmedOn']; + arrivedOn = json['ArrivedOn']; + editedOn = json['EditedOn']; + appointmentDays = json['AppointmentDays']; + isActive = json['IsActive']; + actualDoctorRate = json['ActualDoctorRate']; + clinicName = json['ClinicName']; + date = json['Date']; + decimalDoctorRate = json['DecimalDoctorRate']; + doctorCases = json['DoctorCases']; + doctorImageURL = json['DoctorImageURL']; + doctorInfo = json['DoctorInfo']; + doctorName = json['DoctorName']; + doctorRate = json['DoctorRate']; + doctorStarsRate = json['DoctorStarsRate']; + doctorTitle = json['DoctorTitle']; + gender = json['Gender']; + genderDescription = json['GenderDescription']; + isActiveDoctorProfile = json['IsActiveDoctorProfile']; + isAppointmentAllowed = json['IsAppointmentAllowed']; + isDoctorAllowVedioCall = json['IsDoctorAllowVedioCall']; + isLiveCareClinic = json['IsLiveCareClinic']; + nationalityFlagURL = json['NationalityFlagURL']; + nationalityID = json['NationalityID']; + nationalityName = json['NationalityName']; + noOfPatientsRate = json['NoOfPatientsRate']; + projectName = json['ProjectName']; + qR = json['QR']; + speciality = json['Speciality'].cast(); + } + + Map toJson() { + final Map data = new Map(); + data['SetupID'] = this.setupID; + data['ProjectID'] = this.projectID; + data['AppointmentNo'] = this.appointmentNo; + data['AppointmentDate'] = this.appointmentDate; + data['AppointmentType'] = this.appointmentType; + data['BookDate'] = this.bookDate; + data['PatientType'] = this.patientType; + data['PatientID'] = this.patientID; + data['ClinicID'] = this.clinicID; + data['DoctorID'] = this.doctorID; + data['EndDate'] = this.endDate; + data['StartTime'] = this.startTime; + data['EndTime'] = this.endTime; + data['Status'] = this.status; + data['VisitType'] = this.visitType; + data['IsReminderActivated'] = this.isReminderActivated; + data['CompanyID'] = this.companyID; + data['BookedOn'] = this.bookedOn; + data['ConfirmedOn'] = this.confirmedOn; + data['ArrivedOn'] = this.arrivedOn; + data['EditedOn'] = this.editedOn; + data['AppointmentDays'] = this.appointmentDays; + data['IsActive'] = this.isActive; + data['ActualDoctorRate'] = this.actualDoctorRate; + data['ClinicName'] = this.clinicName; + data['Date'] = this.date; + data['DecimalDoctorRate'] = this.decimalDoctorRate; + data['DoctorCases'] = this.doctorCases; + data['DoctorImageURL'] = this.doctorImageURL; + data['DoctorInfo'] = this.doctorInfo; + 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['IsActiveDoctorProfile'] = this.isActiveDoctorProfile; + data['IsAppointmentAllowed'] = this.isAppointmentAllowed; + data['IsDoctorAllowVedioCall'] = this.isDoctorAllowVedioCall; + data['IsLiveCareClinic'] = this.isLiveCareClinic; + data['NationalityFlagURL'] = this.nationalityFlagURL; + data['NationalityID'] = this.nationalityID; + data['NationalityName'] = this.nationalityName; + data['NoOfPatientsRate'] = this.noOfPatientsRate; + data['ProjectName'] = this.projectName; + data['QR'] = this.qR; + data['Speciality'] = this.speciality; + return data; + } +} diff --git a/lib/features/ask_doctor/models/doctor_response_model.dart b/lib/features/ask_doctor/models/doctor_response_model.dart new file mode 100644 index 00000000..b44ee0ac --- /dev/null +++ b/lib/features/ask_doctor/models/doctor_response_model.dart @@ -0,0 +1,180 @@ +class DoctorResponseModel { + int? projectID; + int? transactionNo; + int? patientID; + int? doctorID; + int? requestType; + String? requestTypeDescription; + dynamic requestTypeDescriptionN; + int? status; + String? remarks; + String? createdOn; + int? readStatus; + String? doctorResponse; + dynamic clinicID; + dynamic clinicName; + int? createdBy; + String? doctorName; + dynamic infoStatusDescription; + bool? isDoctorRespond; + bool? isPatientRead; + dynamic memberName; + dynamic memberNameN; + dynamic patientDateofBirth; + dynamic patientGender; + dynamic patientMobileNumber; + dynamic patientName; + dynamic patientNameN; + dynamic projectName; + dynamic statusDescription; + List? transactions; + + DoctorResponseModel( + {this.projectID, + this.transactionNo, + this.patientID, + this.doctorID, + this.requestType, + this.requestTypeDescription, + this.requestTypeDescriptionN, + this.status, + this.remarks, + this.createdOn, + this.readStatus, + this.doctorResponse, + this.clinicID, + this.clinicName, + this.createdBy, + this.doctorName, + this.infoStatusDescription, + this.isDoctorRespond, + this.isPatientRead, + this.memberName, + this.memberNameN, + this.patientDateofBirth, + this.patientGender, + this.patientMobileNumber, + this.patientName, + this.patientNameN, + this.projectName, + this.statusDescription, + this.transactions}); + + DoctorResponseModel.fromJson(Map json) { + projectID = json['ProjectID']; + transactionNo = json['TransactionNo']; + patientID = json['PatientID']; + doctorID = json['DoctorID']; + requestType = json['RequestType']; + requestTypeDescription = json['RequestTypeDescription']; + requestTypeDescriptionN = json['RequestTypeDescriptionN']; + status = json['Status']; + remarks = json['Remarks']; + createdOn = json['CreatedOn']; + readStatus = json['ReadStatus']; + doctorResponse = json['DoctorResponse']; + clinicID = json['ClinicID']; + clinicName = json['ClinicName']; + createdBy = json['CreatedBy']; + doctorName = json['DoctorName']; + infoStatusDescription = json['InfoStatusDescription']; + isDoctorRespond = json['IsDoctorRespond']; + isPatientRead = json['IsPatientRead']; + memberName = json['MemberName']; + memberNameN = json['MemberNameN']; + patientDateofBirth = json['PatientDateofBirth']; + patientGender = json['PatientGender']; + patientMobileNumber = json['PatientMobileNumber']; + patientName = json['PatientName']; + patientNameN = json['PatientNameN']; + projectName = json['ProjectName']; + statusDescription = json['StatusDescription']; + if (json['Transactions'] != null) { + transactions = []; + json['Transactions'].forEach((v) { + transactions!.add(new Transactions.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = new Map(); + data['ProjectID'] = this.projectID; + data['TransactionNo'] = this.transactionNo; + data['PatientID'] = this.patientID; + data['DoctorID'] = this.doctorID; + data['RequestType'] = this.requestType; + data['RequestTypeDescription'] = this.requestTypeDescription; + data['RequestTypeDescriptionN'] = this.requestTypeDescriptionN; + data['Status'] = this.status; + data['Remarks'] = this.remarks; + data['CreatedOn'] = this.createdOn; + data['ReadStatus'] = this.readStatus; + data['DoctorResponse'] = this.doctorResponse; + data['ClinicID'] = this.clinicID; + data['ClinicName'] = this.clinicName; + data['CreatedBy'] = this.createdBy; + data['DoctorName'] = this.doctorName; + data['InfoStatusDescription'] = this.infoStatusDescription; + data['IsDoctorRespond'] = this.isDoctorRespond; + data['IsPatientRead'] = this.isPatientRead; + data['MemberName'] = this.memberName; + data['MemberNameN'] = this.memberNameN; + data['PatientDateofBirth'] = this.patientDateofBirth; + data['PatientGender'] = this.patientGender; + data['PatientMobileNumber'] = this.patientMobileNumber; + data['PatientName'] = this.patientName; + data['PatientNameN'] = this.patientNameN; + data['ProjectName'] = this.projectName; + data['StatusDescription'] = this.statusDescription; + if (this.transactions != null) { + data['Transactions'] = this.transactions!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class Transactions { + int? lineItemNo; + String? doctorResponse; + int? infoEnteredBy; + String? infoEnteredOn; + String? createdOn; + int? infoStatus; + String? infoStatusDescription; + dynamic infoStatusDescriptionN; + + Transactions( + {this.lineItemNo, + this.doctorResponse, + this.infoEnteredBy, + this.infoEnteredOn, + this.createdOn, + this.infoStatus, + this.infoStatusDescription, + this.infoStatusDescriptionN}); + + Transactions.fromJson(Map json) { + lineItemNo = json['LineItemNo']; + doctorResponse = json['DoctorResponse']; + infoEnteredBy = json['InfoEnteredBy']; + infoEnteredOn = json['InfoEnteredOn']; + createdOn = json['CreatedOn']; + infoStatus = json['InfoStatus']; + infoStatusDescription = json['InfoStatusDescription']; + infoStatusDescriptionN = json['InfoStatusDescriptionN']; + } + + Map toJson() { + final Map data = new Map(); + data['LineItemNo'] = this.lineItemNo; + data['DoctorResponse'] = this.doctorResponse; + data['InfoEnteredBy'] = this.infoEnteredBy; + data['InfoEnteredOn'] = this.infoEnteredOn; + data['CreatedOn'] = this.createdOn; + data['InfoStatus'] = this.infoStatus; + data['InfoStatusDescription'] = this.infoStatusDescription; + data['InfoStatusDescriptionN'] = this.infoStatusDescriptionN; + return data; + } +} diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 6379456e..509b7cc4 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -7,6 +7,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show rootBundle; import 'package:get_it/get_it.dart'; import 'package:hijri_gregorian_calendar/hijri_gregorian_calendar.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/cache_consts.dart'; import 'package:hmg_patient_app_new/core/common_models/nationality_country_model.dart'; @@ -74,6 +75,7 @@ class AuthenticationViewModel extends ChangeNotifier { dobController = TextEditingController(), nameController = TextEditingController(), emailController = TextEditingController(); + CountryEnum selectedCountrySignup = CountryEnum.saudiArabia; MaritalStatusTypeEnum? maritalStatus; GenderTypeEnum? genderType; @@ -279,9 +281,10 @@ class AuthenticationViewModel extends ChangeNotifier { loginTypeEnum = otpTypeEnum == OTPTypeEnum.sms ? LoginTypeEnum.sms : LoginTypeEnum.whatsapp; - if (phoneNumberController.text.isEmpty) { - phoneNumberController.text = "504278212"; - } + // if (phoneNumberController.text.isEmpty) { + // phoneNumberController.text = "504278212"; + // } + bool isValidated = ValidationUtils.isValidatePhoneAndId(phoneNumber: phoneNumberController.text, nationalId: nationalIdController.text); if (!isValidated) { @@ -404,7 +407,6 @@ class AuthenticationViewModel extends ChangeNotifier { return; } patientShareRequestID = apiResponse.data['PatientShareRequestID']; - print("PatientShareRequestID ==============: $patientShareRequestID"); _appState.setFamilyFileTokenID = apiResponse.data['LogInTokenID']; } if (apiResponse.messageStatus == 2) { @@ -432,6 +434,15 @@ class AuthenticationViewModel extends ChangeNotifier { } else { // TODO: Handle isSMSSent false // navigateToOTPScreen(otpTypeEnum: otpTypeEnum, phoneNumber: phoneNumber); + if (isFormFamilyFile) { + LoaderBottomSheet.hideLoader(); + await _dialogService.showCommonBottomSheetWithoutH( + message: LocaleKeys.unableToSendOTP.tr(), + label: LocaleKeys.notice.tr(), + onOkPressed: () { + _navigationService.pop(); + }); + } } } }, @@ -462,7 +473,7 @@ class AuthenticationViewModel extends ChangeNotifier { if (isSwitchUser && _appState.getSuperUserID == null) { nationalIdController.text = responseID.toString(); - }else if( isSwitchUser && _appState.getSuperUserID != null){ + } else if (isSwitchUser && _appState.getSuperUserID != null) { nationalIdController.text = _appState.getSuperUserID.toString(); } @@ -571,8 +582,13 @@ class AuthenticationViewModel extends ChangeNotifier { MedicalFileViewModel medicalVm = getIt(); if (!_appState.getIsChildLoggedIn) { await medicalVm.getFamilyFiles(status: 0); - await medicalVm.getAllPendingRecordsByResponseId(); - _navigationService.replaceAllRoutesAndNavigateToLanding(); + if (isFormFamilyFile) { + await medicalVm.getAllPendingRecordsByResponseId(); + _navigationService.pushAndReplace(AppRoutes.medicalFilePage); + } else { + _navigationService.replaceAllRoutesAndNavigateToLanding(); + } + // await medicalVm.getAllPendingRecordsByResponseId(); } } else { if (activation.list != null && activation.list!.isNotEmpty) { @@ -596,7 +612,7 @@ class AuthenticationViewModel extends ChangeNotifier { } // _appState.setUserBloodGroup = (activation.patientBlodType ?? ""); _appState.setAppAuthToken = activation.authenticationTokenId; - myAppointmentsVM.getActiveAppointmentsCount(); + // myAppointmentsVM.getActiveAppointmentsCount(); final request = RequestUtils.getAuthanticatedCommonRequest().toJson(); bool isUserAgreedBefore = await checkIfUserAgreedBefore(request: request); @@ -610,7 +626,7 @@ class AuthenticationViewModel extends ChangeNotifier { MedicalFileViewModel medicalVm = getIt(); insertPatientIMEIData(loginTypeEnum.toInt); medicalVm.getFamilyFiles(status: 0); //TODO: Remove status: 1 by Aamir Need to Discuss With Sultan - medicalVm.getAllPendingRecordsByResponseId(); + // medicalVm.getAllPendingRecordsByResponseId(); } await clearDefaultInputValues(); if (isUserAgreedBefore) { @@ -732,7 +748,12 @@ class AuthenticationViewModel extends ChangeNotifier { Future onWrongActivationCode({String? message}) async { otpScreenNotifier.value = true; - await _dialogService.showErrorBottomSheet(message: message ?? "Something went wrong. ", onOkPressed: () {}); + await _dialogService.showErrorBottomSheet( + message: message ?? "Something went wrong. ", + onOkPressed: () { + LoaderBottomSheet.hideLoader(); + _navigationService.pushAndReplace(AppRoutes.loginScreen); + }); } loginWithFingerPrintFace(Function success) async { @@ -1035,6 +1056,7 @@ class AuthenticationViewModel extends ChangeNotifier { } Future getServicePrivilege() async { + ApiConsts.setBackendURLs(); final resultEither = await _authenticationRepo.getServicePrivilege(); List privilegeModelList = []; List vidaPlusProjectListModel = []; @@ -1078,4 +1100,30 @@ class AuthenticationViewModel extends ChangeNotifier { return null; } } + + Future logout() async { + try { + // Clear user data from AppState + _appState.setAuthenticatedUser(null); + _appState.setAuthenticatedUser(null, isFamily: true); + _appState.setIsAuthenticated = false; + _appState.setAppAuthToken = ""; + _appState.setUserBloodGroup = ""; + _appState.setSuperUserID = null; + _appState.setIsChildLoggedIn = false; + _appState.setFamilyFileTokenID = ""; + _appState.setSelectDeviceByImeiRespModelElement(null); + _appState.setNHICUserData = CheckUserStatusResponseNHIC(); + _appState.setUserRegistrationPayload = RegistrationDataModelPayload(); + // Clear privilege lists + _appState.setPrivilegeModelList([]); + _appState.setVidaPlusProjectList([]); + _appState.setHMCProjectList([]); + _appState.setProjectsDetailList([]); + await clearDefaultInputValues(); + _navigationService.pushAndReplace(AppRoutes.landingScreen); + } catch (e) { + _navigationService.pushAndReplace(AppRoutes.landingScreen); + } + } } diff --git a/lib/features/authentication/widgets/otp_verification_screen.dart b/lib/features/authentication/widgets/otp_verification_screen.dart index e33222c0..7ddccda8 100644 --- a/lib/features/authentication/widgets/otp_verification_screen.dart +++ b/lib/features/authentication/widgets/otp_verification_screen.dart @@ -545,20 +545,20 @@ class _OTPVerificationScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 10.h), - LocaleKeys.otpVerification.tr().toText24(isBold: true), + LocaleKeys.otpVerification.tr(context: context).toText24(isBold: true), SizedBox(height: 20.h), Wrap( spacing: 2.h, runSpacing: 2.h, children: [ - LocaleKeys.weHaveSendOTP.tr().toText15(color: AppColors.inputLabelTextColor, letterSpacing: -0.4), + LocaleKeys.weHaveSendOTP.tr(context: context).toText15(color: AppColors.inputLabelTextColor, letterSpacing: -0.4), _getMaskedPhoneNumber().toText15(color: AppColors.inputLabelTextColor, isBold: true), - LocaleKeys.via.tr().toText15(color: AppColors.inputLabelTextColor, letterSpacing: -0.4), + LocaleKeys.via.tr(context: context).toText15(color: AppColors.inputLabelTextColor, letterSpacing: -0.4), (widget.isFormFamilyFile ? LoginTypeEnum.sms.displayName : authVM.loginTypeEnum.displayName) .toText15(color: AppColors.inputLabelTextColor, isBold: true, letterSpacing: -0.4), appState.getUserRegistrationPayload.isRegister != null && appState.getUserRegistrationPayload.isRegister == true - ? LocaleKeys.forRegistrationVerification.tr().toText15(color: AppColors.inputLabelTextColor, letterSpacing: -0.4) - : LocaleKeys.forLoginVerification.tr().toText15(color: AppColors.inputLabelTextColor, letterSpacing: -0.4), + ? LocaleKeys.forRegistrationVerification.tr(context: context).toText15(color: AppColors.inputLabelTextColor, letterSpacing: -0.4) + : LocaleKeys.forLoginVerification.tr(context: context).toText15(color: AppColors.inputLabelTextColor, letterSpacing: -0.4), ], ), @@ -590,7 +590,7 @@ class _OTPVerificationScreenState extends State { Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - LocaleKeys.didntReceiveIt.tr().toText16(color: AppColors.inputLabelTextColor), + LocaleKeys.didntReceiveIt.tr(context: context).toText16(color: AppColors.inputLabelTextColor), SizedBox(width: 5.h), if (_resendTime > 0) Builder( @@ -599,7 +599,7 @@ class _OTPVerificationScreenState extends State { final seconds = (_resendTime % 60).toString().padLeft(2, '0'); return Row( children: [ - LocaleKeys.resendIn.tr().toText16(color: AppColors.inputLabelTextColor), + LocaleKeys.resendIn.tr(context: context).toText16(color: AppColors.inputLabelTextColor), SizedBox(width: 2.h), ' ($minutes:$seconds). '.toText16(color: AppColors.inputLabelTextColor) ], @@ -609,7 +609,7 @@ class _OTPVerificationScreenState extends State { else GestureDetector( onTap: _resendOtp, - child: LocaleKeys.resendOTP.tr().toText16(color: AppColors.primaryRedColor), + child: LocaleKeys.resendOTP.tr(context: context).toText16(color: AppColors.primaryRedColor), ), ], ), diff --git a/lib/features/blood_donation/blood_donation_repo.dart b/lib/features/blood_donation/blood_donation_repo.dart index 56436356..c709b038 100644 --- a/lib/features/blood_donation/blood_donation_repo.dart +++ b/lib/features/blood_donation/blood_donation_repo.dart @@ -23,6 +23,8 @@ abstract class BloodDonationRepo { Future>> getFreeBloodDonationSlots({required Map request}); Future>> addUserAgreementForBloodDonation({required Map request}); + + Future>> getUserAgreementForBloodDonation(); } class BloodDonationRepoImp implements BloodDonationRepo { @@ -76,7 +78,7 @@ class BloodDonationRepoImp implements BloodDonationRepo { GenericApiModel? apiResponse; Failure? failure; await apiClient.post( - GET_BLOOD_REQUEST, + ApiConsts.getPatientBloodGroup, body: mapDevice, isAllowAny: true, onFailure: (error, statusCode, {messageStatus, failureType}) { @@ -287,4 +289,36 @@ class BloodDonationRepoImp implements BloodDonationRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>>> getUserAgreementForBloodDonation() async { + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + ApiConsts.getPatientBloodAgreement, + body: {}, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: response, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/blood_donation/blood_donation_view_model.dart b/lib/features/blood_donation/blood_donation_view_model.dart index 8325cf78..8ad5bdec 100644 --- a/lib/features/blood_donation/blood_donation_view_model.dart +++ b/lib/features/blood_donation/blood_donation_view_model.dart @@ -4,6 +4,7 @@ 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/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_repo.dart'; @@ -233,7 +234,7 @@ class BloodDonationViewModel extends ChangeNotifier { return false; } - if (selectedBloodGroup == null) { + if (selectedGender == null) { await dialogService.showErrorBottomSheet( message: "Please choose Gender", ); @@ -258,29 +259,28 @@ class BloodDonationViewModel extends ChangeNotifier { Future updateBloodGroup() async { LoaderBottomSheet.showLoader(); - // body['City'] = detailsModel.city; - // body['cityCode'] = detailsModel.cityCode; - // body['Gender'] = detailsModel.gender; - // body['BloodGroup'] = detailsModel.bloodGroup; - // body['CellNumber'] = user.mobileNumber; - // body['LanguageID'] = languageID; - // body['NationalID'] = user.nationalityID; - // body['ZipCode'] = user.zipCode ?? "+966"; - // body['isDentalAllowedBackend'] = false; - Map payload = { - "City": selectedCity?.description, - "cityCode": selectedCity?.iD, - "Gender": selectedGender?.value, - "isDentalAllowedBackend": false - // "Gender": selectedGender?.value, - }; + Map payload = {"City": selectedCity?.description, "cityCode": selectedCity?.iD, "Gender": selectedGender?.value, "isDentalAllowedBackend": false}; await bloodDonationRepo.updateBloodGroup(request: payload); await addUserAgreementForBloodDonation(); LoaderBottomSheet.hideLoader(); + dialogService.showSuccessBottomSheetWithoutH(message: "Blood Group Updated Successfully", onOkPressed: () {}); } Future addUserAgreementForBloodDonation() async { Map payload = {"IsAgreed": true}; await bloodDonationRepo.addUserAgreementForBloodDonation(request: payload); } + + Future getUserAgreementForBloodDonation() async { + await bloodDonationRepo.getUserAgreementForBloodDonation(); + } + + Future fetchAllData() async { + LoaderBottomSheet.showLoader(loadingText: "Fetching Data..."); + await getRegionSelectedClinics(); + if (isUserAuthanticated()) { + // await getPatientBloodGroupDetails(); + } + LoaderBottomSheet.hideLoader(); + } } diff --git a/lib/features/blood_donation/widgets/hospital_selection.dart b/lib/features/blood_donation/widgets/hospital_selection.dart index 288ac34a..9889410c 100644 --- a/lib/features/blood_donation/widgets/hospital_selection.dart +++ b/lib/features/blood_donation/widgets/hospital_selection.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; @@ -8,13 +9,15 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_view_model.dart'; import 'package:hmg_patient_app_new/features/blood_donation/models/blood_group_hospitals_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart' show AppColors; import 'package:provider/provider.dart'; class HospitalBottomSheetBodySelection extends StatelessWidget { final Function(BdGetProjectsHaveBdClinic userSelection) onUserHospitalSelection; + final bool isHideTitle; - const HospitalBottomSheetBodySelection({super.key, required this.onUserHospitalSelection(BdGetProjectsHaveBdClinic userSelection)}); + const HospitalBottomSheetBodySelection({super.key, required this.onUserHospitalSelection(BdGetProjectsHaveBdClinic userSelection), this.isHideTitle = false}); @override Widget build(BuildContext context) { @@ -23,14 +26,7 @@ class HospitalBottomSheetBodySelection extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - "Please select the hospital you want to make an appointment.".needTranslation, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: AppColors.greyTextColor, - ), - ), + if(!isHideTitle) LocaleKeys.selectHospital.tr(context: context).toText16(weight: FontWeight.w500, color: AppColors.greyTextColor), SizedBox(height: 16.h), SizedBox( height: MediaQuery.sizeOf(context).height * .4, diff --git a/lib/features/book_appointments/book_appointments_repo.dart b/lib/features/book_appointments/book_appointments_repo.dart index cfd473ee..fc541d2e 100644 --- a/lib/features/book_appointments/book_appointments_repo.dart +++ b/lib/features/book_appointments/book_appointments_repo.dart @@ -30,6 +30,8 @@ abstract class BookAppointmentsRepo { Future>> getDoctorProfile(int clinicID, int projectID, int doctorId, {Function(dynamic)? onSuccess, Function(String)? onError}); + Future>> getDoctorRatingDetails(int doctorId, {Function(dynamic)? onSuccess, Function(String)? onError}); + Future>> getDoctorFreeSlots(int clinicID, int projectID, int doctorId, bool isBookingForLiveCare, {bool continueDentalPlan = false, Function(dynamic)? onSuccess, Function(String)? onError}); @@ -183,7 +185,7 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo { body: mapDevice, onFailure: (error, statusCode, {messageStatus, failureType}) { failure = failureType; - onError!(error); + // onError!(error); }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { @@ -298,6 +300,52 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo { } } + @override + Future>> getDoctorRatingDetails(int doctorId, {Function(dynamic)? onSuccess, Function(String)? onError}) async { + Map mapDevice = { + "DoctorID": doctorId, + "PatientID": 0, + "License": true, + "IsRegistered": true, + "isDentalAllowedBackend": false, + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + GET_DOCTOR_RATING_DETAILS, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + if (onError != null) { + onError(error); + } + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: response, + ); + if (onSuccess != null) { + onSuccess(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())); + } + } + //TODO: Implement the logic for Dental & laser clinics @override Future>> getDoctorFreeSlots(int clinicID, int projectID, int doctorId, bool isBookingForLiveCare, diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index 3f5517a0..9b53a133 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -17,6 +17,7 @@ import 'package:hmg_patient_app_new/features/book_appointments/models/free_slot. import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/appointment_nearest_gate_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/dental_chief_complaints_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctor_profile_response_model.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctor_rating_details_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_patient_dental_plan_response_model.dart'; @@ -92,6 +93,9 @@ class BookAppointmentsViewModel extends ChangeNotifier { late DoctorsProfileResponseModel doctorsProfileResponseModel; + bool isDoctorRatingDetailsLoading = false; + List doctorDetailsList = []; + List slotsList = []; List docFreeSlots = []; List dayEvents = []; @@ -99,6 +103,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { String selectedAppointmentDate = ""; String selectedAppointmentTime = ""; + String selectedAppointmentDisplayDate = ""; dynamic freeSlotsResponse; @@ -340,6 +345,14 @@ class BookAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } + setIsLiveCareDoctorsListLoading(bool value) { + if (value) { + liveCareDoctorsList.clear(); + } + isDoctorsListLoading = value; + notifyListeners(); + } + setIsClinicsListLoading(bool value) { if (value) { clinicsList.clear(); @@ -373,9 +386,10 @@ class BookAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } - setSelectedAppointmentDateTime(String date, String time) { + setSelectedAppointmentDateTime(String date, String time, String displayDate) { selectedAppointmentDate = date; selectedAppointmentTime = time; + selectedAppointmentDisplayDate = displayDate; notifyListeners(); } @@ -467,13 +481,13 @@ class BookAppointmentsViewModel extends ChangeNotifier { } Future getLiveCareDoctorsList({Function(dynamic)? onSuccess, Function(String)? onError}) async { - doctorsList.clear(); + liveCareDoctorsList.clear(); + notifyListeners(); final result = await bookAppointmentsRepo.getLiveCareDoctorsList(selectedLiveCareClinic.serviceID!, _appState.getAuthenticatedUser()!.age!, _appState.getAuthenticatedUser()!.gender!, onError: onError); - result.fold( (failure) async { - onError!("No doctors found for the search criteria".needTranslation); + onError!(LocaleKeys.noDoctorFound.tr()); }, (apiResponse) { if (apiResponse.messageStatus == 2) { @@ -501,7 +515,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { result.fold( (failure) async { isDoctorsListLoading = false; - if (onError != null) onError("No doctors found for the search criteria".needTranslation); + if (onError != null) onError(LocaleKeys.noDoctorFound.tr()); notifyListeners(); }, @@ -533,7 +547,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { result.fold( (failure) async { isDoctorsListLoading = false; - if (onError != null) onError("No doctors found for the search criteria".needTranslation); + if (onError != null) onError(LocaleKeys.noDoctorFound.tr()); notifyListeners(); }, @@ -569,7 +583,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { result.fold( (failure) async { - onError?.call("No doctors found for the search criteria".needTranslation); + onError?.call(selectedClinic.clinicID == 23 ? failure.message : LocaleKeys.noDoctorFound.tr()); }, (apiResponse) async { if (apiResponse.messageStatus == 2) { @@ -614,6 +628,46 @@ class BookAppointmentsViewModel extends ChangeNotifier { ); } + Future getDoctorRatingDetails({Function(dynamic)? onSuccess, Function(String)? onError}) async { + isDoctorRatingDetailsLoading = true; + doctorDetailsList.clear(); + notifyListeners(); + + final result = await bookAppointmentsRepo.getDoctorRatingDetails(selectedDoctor.doctorID ?? 0, onSuccess: onSuccess, onError: onError); + + result.fold( + (failure) async { + if (onError != null) { + onError("Failed to load doctor rating details"); + } + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + if (onError != null) { + onError(apiResponse.errorMessage ?? "Unknown error occurred"); + } + isDoctorRatingDetailsLoading = false; + } else if (apiResponse.messageStatus == 1) { + try { + apiResponse.data['DoctorRatingDetailsList'].forEach((v) { + doctorDetailsList.add(DoctorRateDetails.fromJson(v)); + }); + isDoctorRatingDetailsLoading = false; + // doctorRatingDetails = DoctorRateDetails.fromJson(apiResponse.data); + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse.data); + } + } catch (e) { + if (onError != null) { + onError("Failed to parse rating details: ${e.toString()}"); + } + } + } + }, + ); + } + Future getDoctorFreeSlots({bool isBookingForLiveCare = false, Function(dynamic)? onSuccess, Function(String)? onError}) async { docFreeSlots.clear(); DateTime date; @@ -669,7 +723,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { Future getLiveCareDoctorFreeSlots({bool isBookingForLiveCare = false, Function(dynamic)? onSuccess, Function(String)? onError}) async { docFreeSlots.clear(); DateTime date; - final DateFormat formatter = DateFormat('HH:mm'); + final DateFormat formatter = DateFormat('HH:mm', "en-US"); final DateFormat dateFormatter = DateFormat('yyyy-MM-dd'); Map _eventsParsed; @@ -754,6 +808,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { result.fold( (failure) async { print(failure); + onError!(failure.message); }, (apiResponse) { if (apiResponse.messageStatus == 2) { @@ -784,7 +839,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { ); showCommonBottomSheet(navigationService.navigatorKey.currentContext!, - child: Utils.getLoadingWidget(loadingText: "Cancelling your previous appointment....".needTranslation), + child: Utils.getLoadingWidget(loadingText: LocaleKeys.cancellingAppointmentPleaseWait.tr()), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.3, @@ -794,7 +849,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { await cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel).then((val) async { navigationService.pop(); Future.delayed(Duration(milliseconds: 50)).then((value) async {}); - LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: "Booking your appointment...".needTranslation); + LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: LocaleKeys.bookingYourAppointment.tr()); await insertSpecificAppointment( onError: (err) {}, onSuccess: (apiResp) async { @@ -880,7 +935,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { ); showCommonBottomSheet(navigationService.navigatorKey.currentContext!, - child: Utils.getLoadingWidget(loadingText: "Cancelling your previous appointment....".needTranslation), + child: Utils.getLoadingWidget(loadingText: LocaleKeys.cancellingAppointmentPleaseWait.tr()), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.3, @@ -890,7 +945,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { await cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel).then((val) async { navigationService.pop(); Future.delayed(Duration(milliseconds: 50)).then((value) async {}); - LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: "Booking your appointment...".needTranslation); + LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: LocaleKeys.bookingYourAppointment.tr()); await insertSpecificAppointment( onError: (err) {}, onSuccess: (apiResp) async { @@ -1204,7 +1259,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { result.fold( (failure) async { - onError!("No doctors found for the search criteria...".needTranslation); + onError!(LocaleKeys.noDoctorFound.tr()); }, (apiResponse) { if (apiResponse.messageStatus == 2) { @@ -1291,18 +1346,18 @@ class BookAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } else { - if (this.duration == 90) { - dialogService.showErrorBottomSheet( - message: "Duration can not exceed 90 min".needTranslation, - ); - return; - } + // if (this.duration == 90) { + // dialogService.showErrorBottomSheet( + // message: "Duration can not exceed 90 min".needTranslation, + // ); + // return; + // } selectedBodyPartList.add(part); var duration = getDuration(); if (duration > 90) { selectedBodyPartList.remove(part); dialogService.showErrorBottomSheet( - message: "Duration Exceeds 90 min".needTranslation, + message: LocaleKeys.durationCannotExceed90.tr(), ); return; } @@ -1336,7 +1391,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { result.fold( (failure) async { - onError!("Invalid verification point scanned.".needTranslation); + onError!("Invalid verification point scanned."); }, (apiResponse) { // if (apiResponse.data['returnValue'] == 0) { @@ -1410,7 +1465,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { ); } else if (apiResponse.messageStatus == 1) { if (apiResponse.data == null || apiResponse.data!.isEmpty) { - onError!("Unexpected Error Occurred".needTranslation); + onError!(LocaleKeys.unexpectedError.tr()); return; } notifyListeners(); diff --git a/lib/features/book_appointments/models/resp_models/doctor_rating_details_response_model.dart b/lib/features/book_appointments/models/resp_models/doctor_rating_details_response_model.dart new file mode 100644 index 00000000..58668856 --- /dev/null +++ b/lib/features/book_appointments/models/resp_models/doctor_rating_details_response_model.dart @@ -0,0 +1,36 @@ +class DoctorRateDetails { + dynamic doctorID; + dynamic projectID; + dynamic clinicID; + dynamic rate; + dynamic patientNumber; + dynamic ratio; + + DoctorRateDetails( + {this.doctorID, + this.projectID, + this.clinicID, + this.rate, + this.patientNumber, + this.ratio}); + + DoctorRateDetails.fromJson(Map json) { + doctorID = json['DoctorID']; + projectID = json['ProjectID']; + clinicID = json['ClinicID']; + rate = json['Rate']; + patientNumber = json['PatientNumber']; + ratio = json['Ratio']; + } + + Map toJson() { + final Map data = new Map(); + data['DoctorID'] = this.doctorID; + data['ProjectID'] = this.projectID; + data['ClinicID'] = this.clinicID; + data['Rate'] = this.rate; + data['PatientNumber'] = this.patientNumber; + data['Ratio'] = this.ratio; + return data; + } +} diff --git a/lib/features/emergency_services/emergency_services_view_model.dart b/lib/features/emergency_services/emergency_services_view_model.dart index 400eb047..ec211793 100644 --- a/lib/features/emergency_services/emergency_services_view_model.dart +++ b/lib/features/emergency_services/emergency_services_view_model.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart' as GMSMapServices; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -32,6 +33,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/facility_sel import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/RRT/rrt_request_type_select.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/RRT/terms_and_condition.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/call_ambulance_page.dart'; @@ -152,7 +154,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { print("the app state is ${appState.isAuthenticated}"); if (!appState.isAuthenticated) { dialogService.showErrorBottomSheet( - message: "You Need To Login First To Continue".needTranslation, + message: LocaleKeys.loginToUseService.tr(), onOkPressed: () { navServices.pop(); getIt().onLoginPressed(); @@ -196,7 +198,6 @@ class EmergencyServicesViewModel extends ChangeNotifier { } void filterErList(String query) { - print("the query is $query"); if (query.isEmpty) { nearestERFilteredList = nearestERList; } else { @@ -277,7 +278,6 @@ class EmergencyServicesViewModel extends ChangeNotifier { flushData(); selectedFacility = FacilitySelection.ALL; - print("the app state is ${appState.isAuthenticated}"); if (appState.isAuthenticated) { locationUtils!.getLocation( isShowConfirmDialog: true, @@ -289,7 +289,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { }); } else { dialogService.showErrorBottomSheet( - message: "You Need To Login First To Continue".needTranslation, + message: LocaleKeys.loginToUseService.tr(), onOkPressed: () { navServices.pop(); navServices.pushAndReplace(AppRoutes.loginScreen); @@ -311,7 +311,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { void updateBottomSheetState(BottomSheetType sheetType) { if (sheetType == BottomSheetType.EXPANDED && selectedHospital == null) { - dialogService.showErrorBottomSheet(message: "Kindly Select Hospital".needTranslation); + dialogService.showErrorBottomSheet(message: LocaleKeys.selectHospital.tr()); return; } bottomSheetType = sheetType; @@ -481,21 +481,18 @@ class EmergencyServicesViewModel extends ChangeNotifier { Future getTransportationOption() async { //handle the cache if the data is present then dont fetch it in the authenticated lifecycle - - print("the app state is ${appState.isAuthenticated}"); if (appState.isAuthenticated == false) { dialogService.showErrorBottomSheet( - message: "You Need To Login First To Continue".needTranslation, + message: LocaleKeys.loginToUseService.tr(), onOkPressed: () { navServices.pop(); - print("inside the ok button"); getIt().onLoginPressed(); }); return; } int? id = appState.getAuthenticatedUser()?.patientId; - LoaderBottomSheet.showLoader(loadingText: "Getting Ambulance Transport Option".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.gettingAmbulanceTransportOption.tr()); notifyListeners(); var response = await emergencyServicesRepo.getTransportationMethods(id: id); @@ -514,7 +511,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { Future getTransportationMethods() async { int? id = appState.getAuthenticatedUser()?.patientId; - LoaderBottomSheet.showLoader(loadingText: "Getting Ambulance Transport Option".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.gettingAmbulanceTransportOption.tr()); notifyListeners(); var response = await emergencyServicesRepo.getTransportationMethods(id: id); @@ -703,7 +700,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { } Future getAppointments() async { - LoaderBottomSheet.showLoader(loadingText: "Fetching Appointment".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingAppointment.tr()); var result = await appointmentRepo.getPatientAppointments(isActiveAppointment: true, isArrivedAppointments: false); LoaderBottomSheet.hideLoader(); @@ -860,10 +857,10 @@ class EmergencyServicesViewModel extends ChangeNotifier { Future cancelOrder(AmbulanceRequestOrdersModel? order, {bool shouldPop = false}) async { dialogService.showCommonBottomSheetWithoutH( - message: "Do you want to cancel the request".needTranslation, + message: LocaleKeys.doYouWantToCancelTheRequest.tr(), onOkPressed: () async { navServices.pop(); - LoaderBottomSheet.showLoader(loadingText: "Cancelling request".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.cancellingRequest.tr()); var response = await emergencyServicesRepo.cancelOrder(order?.iD, appState.getAuthenticatedUser()?.patientId ?? 0); LoaderBottomSheet.hideLoader(); response.fold((failure) => errorHandlerService.handleError(failure: failure), (success) { @@ -968,10 +965,10 @@ class EmergencyServicesViewModel extends ChangeNotifier { FutureOr cancelRRTOrder(int? orderID, {bool shouldPop = false}) async { dialogService.showCommonBottomSheetWithoutH( - message: "Do you want to cancel the request".needTranslation, + message: LocaleKeys.doYouWantToCancelTheRequest.tr(), onOkPressed: () async { navServices.pop(); - LoaderBottomSheet.showLoader(loadingText: "Cancelling request".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.cancellingRequest.tr()); var response = await emergencyServicesRepo.cancelRRTOrder(orderID); LoaderBottomSheet.hideLoader(); response.fold((failure) => errorHandlerService.handleError(failure: failure), (success) { @@ -1001,11 +998,10 @@ class EmergencyServicesViewModel extends ChangeNotifier { } void openRRT() { - print("the app state is ${appState.isAuthenticated}"); if (appState.isAuthenticated) { if (agreedToTermsAndCondition == false) { dialogService.showErrorBottomSheet( - message: "You Need To Agree To Terms And Conditions".needTranslation, + message: LocaleKeys.pleaseAcceptTermsConditions.tr(), onOkPressed: () { if (navServices.context == null) return; showCommonBottomSheetWithoutHeight( @@ -1042,9 +1038,9 @@ class EmergencyServicesViewModel extends ChangeNotifier { bool result = await navServices.push( CustomPageRoute( page: MapUtilityScreen( - confirmButtonString: "Submit Request".needTranslation, - titleString: "Select Location".needTranslation, - subTitleString: "Please select the location".needTranslation, + confirmButtonString: LocaleKeys.submitRequest.tr(), + titleString: LocaleKeys.selectLocation.tr(), + subTitleString: LocaleKeys.pleaseSelectTheLocation.tr(), isGmsAvailable: appState.isGMSAvailable, ), direction: AxisDirection.down), @@ -1059,7 +1055,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { }); } else { dialogService.showErrorBottomSheet( - message: "You Need To Login First To Continue".needTranslation, + message: LocaleKeys.loginToUseService.tr(), onOkPressed: () { navServices.pop(); getIt().onLoginPressed(); @@ -1072,12 +1068,11 @@ class EmergencyServicesViewModel extends ChangeNotifier { } FutureOr getTermsAndConditions() async { - LoaderBottomSheet.showLoader(loadingText: "Fetching Terms And Conditions".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingTermsAndConditions.tr()); var response = await emergencyServicesRepo.getTermsAndCondition(); LoaderBottomSheet.hideLoader(); response.fold((failure) => errorHandlerService.handleError(failure: failure), (success) { termsAndConditions = success.data; - print("the response terms are $termsAndConditions"); notifyListeners(); navServices.push( CustomPageRoute(page: TermsAndCondition(termsAndCondition: success.data ?? ""), direction: AxisDirection.down), diff --git a/lib/features/habib_wallet/habib_wallet_view_model.dart b/lib/features/habib_wallet/habib_wallet_view_model.dart index 52916465..2f338957 100644 --- a/lib/features/habib_wallet/habib_wallet_view_model.dart +++ b/lib/features/habib_wallet/habib_wallet_view_model.dart @@ -9,6 +9,7 @@ class HabibWalletViewModel extends ChangeNotifier { bool isWalletAmountLoading = false; num habibWalletAmount = 0; num walletRechargeAmount = 0; + String notesText = ""; bool isBottomSheetContentLoading = false; @@ -60,6 +61,11 @@ class HabibWalletViewModel extends ChangeNotifier { notifyListeners(); } + setNotesText(String notes) { + notesText = notes; + notifyListeners(); + } + setDepositorDetails(String fileNum, String depositor, String mobile) { fileNumber = fileNum; depositorName = depositor; diff --git a/lib/presentation/health_trackers/health_trackers_view_model.dart b/lib/features/health_trackers/health_trackers_view_model.dart similarity index 98% rename from lib/presentation/health_trackers/health_trackers_view_model.dart rename to lib/features/health_trackers/health_trackers_view_model.dart index ce1bdf26..ab54f595 100644 --- a/lib/presentation/health_trackers/health_trackers_view_model.dart +++ b/lib/features/health_trackers/health_trackers_view_model.dart @@ -215,7 +215,8 @@ class HealthTrackersViewModel extends ChangeNotifier { final averageResult = await healthTrackersRepo.getWeightMeasurementResultAverage(); averageResult.fold( - (failure) => errorHandlerService.handleError(failure: failure), + // (failure) => errorHandlerService.handleError(failure: failure), + (failure) => {}, (apiModel) { final data = apiModel.data; if (data is Map) { @@ -258,7 +259,8 @@ class HealthTrackersViewModel extends ChangeNotifier { final resultsResponse = await healthTrackersRepo.getWeightMeasurementResults(); resultsResponse.fold( - (failure) => errorHandlerService.handleError(failure: failure), + // (failure) => errorHandlerService.handleError(failure: failure), + (failure) => {}, (apiModel) { final data = apiModel.data; if (data is Map) { @@ -435,7 +437,8 @@ class HealthTrackersViewModel extends ChangeNotifier { final averageResult = await healthTrackersRepo.getBloodPressureResultAverage(); averageResult.fold( - (failure) => errorHandlerService.handleError(failure: failure), + // (failure) => errorHandlerService.handleError(failure: failure), + (failure) => {}, (apiModel) { final data = apiModel.data; @@ -479,7 +482,8 @@ class HealthTrackersViewModel extends ChangeNotifier { final resultsResponse = await healthTrackersRepo.getBloodPressureResults(); resultsResponse.fold( - (failure) => errorHandlerService.handleError(failure: failure), + // (failure) => errorHandlerService.handleError(failure: failure), + (failure) => {}, (apiModel) { final data = apiModel.data; if (data is Map) { @@ -636,7 +640,7 @@ class HealthTrackersViewModel extends ChangeNotifier { final averageResult = await healthTrackersRepo.getDiabeticResultAverage(); averageResult.fold( - (failure) => errorHandlerService.handleError(failure: failure), + (failure) {}, (apiModel) { final data = apiModel.data; if (data is Map) { @@ -679,7 +683,8 @@ class HealthTrackersViewModel extends ChangeNotifier { final resultsResponse = await healthTrackersRepo.getDiabeticResults(); resultsResponse.fold( - (failure) => errorHandlerService.handleError(failure: failure), + // (failure) => errorHandlerService.handleError(failure: failure), + (failure) {}, (apiModel) { final data = apiModel.data; if (data is Map) { diff --git a/lib/features/hmg_services/hmg_services_view_model.dart b/lib/features/hmg_services/hmg_services_view_model.dart index daddbff4..5bf450ba 100644 --- a/lib/features/hmg_services/hmg_services_view_model.dart +++ b/lib/features/hmg_services/hmg_services_view_model.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:flutter/material.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/features/book_appointments/book_appointments_repo.dart'; import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_repo.dart'; @@ -17,6 +18,7 @@ import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/search_e_referral_resp_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/vital_sign_respo_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; +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'; @@ -31,8 +33,7 @@ class HmgServicesViewModel extends ChangeNotifier { final ErrorHandlerService errorHandlerService; final NavigationService navigationService; - HmgServicesViewModel( - {required this.bookAppointmentsRepo, required this.hmgServicesRepo, required this.errorHandlerService, required this.navigationService}); + HmgServicesViewModel({required this.bookAppointmentsRepo, required this.hmgServicesRepo, required this.errorHandlerService, required this.navigationService}); bool isCmcOrdersLoading = false; bool isCmcServicesLoading = false; @@ -55,9 +56,11 @@ class HmgServicesViewModel extends ChangeNotifier { // Vital Sign PageView Controller PageController _vitalSignPageController = PageController(); + PageController get vitalSignPageController => _vitalSignPageController; int _vitalSignCurrentPage = 0; + int get vitalSignCurrentPage => _vitalSignCurrentPage; void setVitalSignCurrentPage(int page) { @@ -65,7 +68,6 @@ class HmgServicesViewModel extends ChangeNotifier { notifyListeners(); } - // HHC specific lists List hhcOrdersList = []; List hhcServicesList = []; @@ -82,9 +84,8 @@ class HmgServicesViewModel extends ChangeNotifier { List searchReferralList = []; List covidTestProcedureList = []; Covid19GetPaymentInfo? covidPaymentInfo; - Future getOrdersList() async {} - + Future getOrdersList() async {} // HHC multiple services selection List selectedHhcServices = []; @@ -792,22 +793,20 @@ class HmgServicesViewModel extends ChangeNotifier { }, ); } + List getQuestionsFromJson() { - final String questionsJson = '''[ { "id": 1, "questionEN": "Is the test intended for travel?", "questionAR": "هل تجري التحليل بغرض السفر؟", "ans": 2 }, { "id": 2, "questionEN": "Coming from outside KSA within last 2 weeks?", "questionAR": "هل قدمت من خارج المملكة خلال الأسبوعين الماضيين؟", "ans": 2 }, { "id": 3, "questionEN": "Do you currently have fever?", "questionAR": "هل تعاني حاليا من حرارة؟", "ans": 2 }, { "id": 4, "questionEN": "Did you have fever in last 2 weeks?", "questionAR": "هل عانيت من حرارة في الأسبوعين الماضيين؟", "ans": 2 }, { "id": 5, "questionEN": "Do you have a sore throat?", "questionAR": "هل لديك التهاب في الحلق؟", "ans": 2 }, { "id": 6, "questionEN": "Do you have a runny nose?", "questionAR": "هل لديك سيلان بالأنف؟" }, { "id": 7, "questionEN": "Do you have a cough?", "questionAR": "هل لديك سعال؟", "ans": 2 }, { "id": 8, "questionEN": "Do you have shortness of breath?", "questionAR": "هل تعاني من ضيق في التنفس؟", "ans": 2 }, { "id": 9, "questionEN": "Do you have nausea?", "questionAR": "هل تعاني من غثيان؟", "ans": 2 }, { "id": 10, "questionEN": "Do you have vomiting?", "questionAR": "هل تعاني من القيء؟", "ans": 2 }, { "id": 11, "questionEN": "Do you have a headache?", "questionAR": "هل تعاني من صداع في الرأس؟", "ans": 2 }, { "id": 12, "questionEN": "Do you have muscle pain?", "questionAR": "هل تعانين من آلام عضلية؟", "ans": 2 }, { "id": 13, "questionEN": "Do you have joint pain?", "questionAR": "هل تعاني من آلام المفاصل؟", "ans": 2 }, { "id": 14, "questionEN": "Do you have diarrhea?", "questionAR": "هل لديك اسهال؟", "ans": 2 } ]'''; + final String questionsJson = + '''[ { "id": 1, "questionEN": "Is the test intended for travel?", "questionAR": "هل تجري التحليل بغرض السفر؟", "ans": 2 }, { "id": 2, "questionEN": "Coming from outside KSA within last 2 weeks?", "questionAR": "هل قدمت من خارج المملكة خلال الأسبوعين الماضيين؟", "ans": 2 }, { "id": 3, "questionEN": "Do you currently have fever?", "questionAR": "هل تعاني حاليا من حرارة؟", "ans": 2 }, { "id": 4, "questionEN": "Did you have fever in last 2 weeks?", "questionAR": "هل عانيت من حرارة في الأسبوعين الماضيين؟", "ans": 2 }, { "id": 5, "questionEN": "Do you have a sore throat?", "questionAR": "هل لديك التهاب في الحلق؟", "ans": 2 }, { "id": 6, "questionEN": "Do you have a runny nose?", "questionAR": "هل لديك سيلان بالأنف؟" }, { "id": 7, "questionEN": "Do you have a cough?", "questionAR": "هل لديك سعال؟", "ans": 2 }, { "id": 8, "questionEN": "Do you have shortness of breath?", "questionAR": "هل تعاني من ضيق في التنفس؟", "ans": 2 }, { "id": 9, "questionEN": "Do you have nausea?", "questionAR": "هل تعاني من غثيان؟", "ans": 2 }, { "id": 10, "questionEN": "Do you have vomiting?", "questionAR": "هل تعاني من القيء؟", "ans": 2 }, { "id": 11, "questionEN": "Do you have a headache?", "questionAR": "هل تعاني من صداع في الرأس؟", "ans": 2 }, { "id": 12, "questionEN": "Do you have muscle pain?", "questionAR": "هل تعانين من آلام عضلية؟", "ans": 2 }, { "id": 13, "questionEN": "Do you have joint pain?", "questionAR": "هل تعاني من آلام المفاصل؟", "ans": 2 }, { "id": 14, "questionEN": "Do you have diarrhea?", "questionAR": "هل لديك اسهال؟", "ans": 2 } ]'''; try { final parsed = json.decode(questionsJson) as List; - return parsed - .map((e) => CovidQuestionnaireModel.fromJson(Map.from(e))) - .toList(); + return parsed.map((e) => CovidQuestionnaireModel.fromJson(Map.from(e))).toList(); } catch (_) { return []; } - } - + } Future getCovidProcedureList({ - Function(dynamic)? onSuccess, Function(String)? onError, }) async { @@ -816,14 +815,14 @@ class HmgServicesViewModel extends ChangeNotifier { final result = await hmgServicesRepo.getCovidTestProcedures(); result.fold( - (failure) async { + (failure) async { notifyListeners(); await errorHandlerService.handleError(failure: failure); if (onError != null) { onError(failure.toString()); } }, - (apiResponse) { + (apiResponse) { if (apiResponse.messageStatus == 1) { covidTestProcedureList = apiResponse.data ?? []; notifyListeners(); @@ -840,7 +839,6 @@ class HmgServicesViewModel extends ChangeNotifier { ); } - Future getPaymentInfo({ String? procedureID, int? projectID, @@ -852,14 +850,14 @@ class HmgServicesViewModel extends ChangeNotifier { final result = await hmgServicesRepo.getCovidPaymentInfo(procedureID!, projectID!); result.fold( - (failure) async { + (failure) async { notifyListeners(); await errorHandlerService.handleError(failure: failure); if (onError != null) { onError(failure.toString()); } }, - (apiResponse) { + (apiResponse) { if (apiResponse.messageStatus == 1) { covidPaymentInfo = apiResponse.data; notifyListeners(); @@ -900,6 +898,8 @@ class HmgServicesViewModel extends ChangeNotifier { if (apiResponse.messageStatus == 1) { vitalSignList = apiResponse.data ?? []; hasVitalSignDataLoaded = true; + getIt.get().setSelectedHeight(vitalSignList.first.heightCm); + getIt.get().setSelectedWeight(vitalSignList.first.weightKg); notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); diff --git a/lib/features/hospital/hospital_selection_view_model.dart b/lib/features/hospital/hospital_selection_view_model.dart index c0cab2ad..24c7b9b0 100644 --- a/lib/features/hospital/hospital_selection_view_model.dart +++ b/lib/features/hospital/hospital_selection_view_model.dart @@ -101,6 +101,7 @@ class HospitalSelectionBottomSheetViewModel extends ChangeNotifier { NavigationClinicDetails data = NavigationClinicDetails(); data.projectId = projectID.toString(); data.clinicId = clinicID; + // data.clinicId = "1"; final bool permited = await AppPermission.askPenguinPermissions(); if (!permited) { Map statuses = await [ @@ -110,7 +111,7 @@ class HospitalSelectionBottomSheetViewModel extends ChangeNotifier { Permission.bluetoothScan, Permission.activityRecognition, ].request().whenComplete(() { - PenguinMethodChannel().launch("penguin", appState.isArabic() ? "ar" : "en", appState.getAuthenticatedUser()?.patientId?.toString()??"", details: data); + PenguinMethodChannel().launch("penguin", appState.isArabic() ? "ar" : "en", appState.getAuthenticatedUser()?.patientId?.toString() ?? "", false, details: data); }); } } diff --git a/lib/features/immediate_livecare/immediate_livecare_view_model.dart b/lib/features/immediate_livecare/immediate_livecare_view_model.dart index 1baa81bd..ffa0b00b 100644 --- a/lib/features/immediate_livecare/immediate_livecare_view_model.dart +++ b/lib/features/immediate_livecare/immediate_livecare_view_model.dart @@ -45,9 +45,7 @@ class ImmediateLiveCareViewModel extends ChangeNotifier { initImmediateLiveCare() { _appState = getIt(); immediateLiveCareClinicsList = []; - patientLiveCareHistoryList = []; isImmediateLiveCareClinicsLoading = true; - patientHasPendingLiveCareRequest = false; liveCareSelectedCallType = 0; // 1- Video, 2- Audio, 3- Phone immediateLiveCareSelectedClinic = GetLiveCareClinicListResponseModel(); liveCareImmediateAppointmentFeesList = LiveCareImmediateAppointmentFeesList(); @@ -136,6 +134,10 @@ class ImmediateLiveCareViewModel extends ChangeNotifier { } Future getPatientLiveCareHistory({Function(dynamic)? onSuccess, Function(String)? onError}) async { + patientLiveCareHistoryList = []; + patientHasPendingLiveCareRequest = false; + notifyListeners(); + final result = await immediateLiveCareRepo.getPatientLiveCareHistory(); result.fold( diff --git a/lib/features/insurance/insurance_repo.dart b/lib/features/insurance/insurance_repo.dart index 1aeb7696..2719035b 100644 --- a/lib/features/insurance/insurance_repo.dart +++ b/lib/features/insurance/insurance_repo.dart @@ -7,6 +7,7 @@ import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patien import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patient_insurance_card_history.dart'; import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patient_insurance_details_response_model.dart'; import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patient_insurance_update_response_model.dart'; +import 'package:hmg_patient_app_new/features/insurance/models/resp_models/upload_insurance_card_response_model.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class InsuranceRepo { @@ -17,6 +18,14 @@ abstract class InsuranceRepo { Future>> getPatientInsuranceDetailsForUpdate({required String patientId, required String identificationNo}); Future>>> getPatientInsuranceApprovalsList(); + + Future>> updatePatientInsuranceCard({ + required int patientID, + required int patientType, + required String mobileNo, + required String patientIdentificationID, + required String insuranceCardImage, + }); } class InsuranceRepoImp implements InsuranceRepo { @@ -183,4 +192,52 @@ class InsuranceRepoImp implements InsuranceRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>> updatePatientInsuranceCard({ + required int patientID, + required int patientType, + required String mobileNo, + required String patientIdentificationID, + required String insuranceCardImage, + }) async { + Map mapDevice = { + "PatientID": patientID, + "PatientType": patientType, + "MobileNo": mobileNo, + "PatientIdentificationID": patientIdentificationID, + "InsuranceCardImage": insuranceCardImage, + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + UPLOAD_INSURANCE_CARD, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + // final uploadResponse = UploadInsuranceCardResponseModel.fromJson(response); + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: apiResponse, + ); + } 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())); + } + } } diff --git a/lib/features/insurance/insurance_view_model.dart b/lib/features/insurance/insurance_view_model.dart index 8573ae9d..bbee760c 100644 --- a/lib/features/insurance/insurance_view_model.dart +++ b/lib/features/insurance/insurance_view_model.dart @@ -5,6 +5,7 @@ import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patien import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patient_insurance_card_history.dart'; import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patient_insurance_details_response_model.dart'; import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patient_insurance_update_response_model.dart'; +import 'package:hmg_patient_app_new/features/insurance/models/resp_models/upload_insurance_card_response_model.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; class InsuranceViewModel extends ChangeNotifier { @@ -185,4 +186,44 @@ class InsuranceViewModel extends ChangeNotifier { }, ); } + + Future updatePatientInsuranceCard({ + required int patientID, + required int patientType, + required String mobileNo, + required String patientIdentificationID, + required String insuranceCardImage, + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + final result = await insuranceRepo.updatePatientInsuranceCard( + patientID: patientID, + patientType: patientType, + mobileNo: mobileNo, + patientIdentificationID: patientIdentificationID, + insuranceCardImage: insuranceCardImage, + ); + + result.fold( + (failure) async { + notifyListeners(); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? "Error updating insurance card"); + } + } else if (apiResponse.messageStatus == 1) { + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } } diff --git a/lib/features/insurance/models/resp_models/upload_insurance_card_response_model.dart b/lib/features/insurance/models/resp_models/upload_insurance_card_response_model.dart new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/lib/features/insurance/models/resp_models/upload_insurance_card_response_model.dart @@ -0,0 +1 @@ + diff --git a/lib/features/lab/lab_repo.dart b/lib/features/lab/lab_repo.dart index e661157d..a4cdcf1c 100644 --- a/lib/features/lab/lab_repo.dart +++ b/lib/features/lab/lab_repo.dart @@ -5,6 +5,8 @@ import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:dartz/dartz.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/lab/models/resp_models/ai_overview_labresults_by_ai_model.dart'; +import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_order_response_by_ai_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_special_result.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; @@ -13,6 +15,7 @@ import 'models/resp_models/lab_result.dart' show LabResult; abstract class LabRepo { Future>>> getPatientLabOrders(); + Future>>> getPatientLabResults(PatientLabOrdersResponseModel laborder, bool isVidaPlus, String procedureName); Future>>> getPatientLabResultsByHospitals(PatientLabOrdersResponseModel laborder, bool isVidaPlus); @@ -22,6 +25,10 @@ abstract class LabRepo { Future>> getLabResultReportPDF({required PatientLabOrdersResponseModel labOrder}); Future>> getLabResultsByAppointmentNo({required num appointmentNo, required num projectID, required num clinicID}); + + Future>> getPatientAiOverViewLabOrders(var payload); + + Future>> getPatientAiOverViewLabOrder(var payload); } class LabRepoImp implements LabRepo { @@ -71,10 +78,7 @@ class LabRepoImp implements LabRepo { } @override - Future>>> getPatientLabResults( - PatientLabOrdersResponseModel laborder, bool isVidaPlus, String procedureName - ) async { - + Future>>> getPatientLabResults(PatientLabOrdersResponseModel laborder, bool isVidaPlus, String procedureName) async { Map request = Map(); request['InvoiceNo_VP'] = isVidaPlus ? laborder!.invoiceNo : "0"; request['InvoiceNo'] = isVidaPlus ? "0" : laborder!.invoiceNo; @@ -100,10 +104,7 @@ class LabRepoImp implements LabRepo { throw Exception("lab list is empty"); } - final labOrders = list - .map((item) => LabResult.fromJson(item as Map)) - .toList() - .cast(); + final labOrders = list.map((item) => LabResult.fromJson(item as Map)).toList().cast(); apiResponse = GenericApiModel>( messageStatus: messageStatus, @@ -125,9 +126,7 @@ class LabRepoImp implements LabRepo { } @override - Future>>> - getPatientLabResultsByHospitals( - PatientLabOrdersResponseModel laborder, bool isVidaPlus) async { + Future>>> getPatientLabResultsByHospitals(PatientLabOrdersResponseModel laborder, bool isVidaPlus) async { Map request = Map(); request['InvoiceNo_VP'] = isVidaPlus ? laborder!.invoiceNo : "0"; request['InvoiceNo'] = isVidaPlus ? "0" : laborder!.invoiceNo; @@ -175,9 +174,7 @@ class LabRepoImp implements LabRepo { } @override - Future>>> - getSpecialLabResult( - PatientLabOrdersResponseModel laborder, bool isVidaPlus) async { + Future>>> getSpecialLabResult(PatientLabOrdersResponseModel laborder, bool isVidaPlus) async { Map request = Map(); request['InvoiceNo_VP'] = isVidaPlus ? laborder!.invoiceNo : "0"; request['InvoiceNo'] = isVidaPlus ? "0" : laborder!.invoiceNo; @@ -203,11 +200,7 @@ class LabRepoImp implements LabRepo { throw Exception("lab list is empty"); } - final labOrders = list - .map((item) => PatientLabSpecialResult.fromJson( - item as Map)) - .toList() - .cast(); + final labOrders = list.map((item) => PatientLabSpecialResult.fromJson(item as Map)).toList().cast(); apiResponse = GenericApiModel>( messageStatus: messageStatus, @@ -318,4 +311,76 @@ class LabRepoImp implements LabRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>> getPatientAiOverViewLabOrder(var labOrder) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + ApiConsts.getAiOverViewLabOrder, + body: labOrder, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + var res; + if (response["analysis_result"] != null) { + res = response["analysis_result"]; + } + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: LabOrderResponseByAi.fromJson(res), + ); + } 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>> getPatientAiOverViewLabOrders(var labOrder) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + ApiConsts.getAiOverViewLabOrders, + body: {"lab_results": labOrder}, + 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"]; + } + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: LabOrdersResponseByAi.fromJson(res), + ); + } 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())); + } + } } diff --git a/lib/features/lab/lab_view_model.dart b/lib/features/lab/lab_view_model.dart index 118c54a0..22e48595 100644 --- a/lib/features/lab/lab_view_model.dart +++ b/lib/features/lab/lab_view_model.dart @@ -1,16 +1,24 @@ import 'dart:collection'; +import 'dart:convert'; import 'dart:core'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/common_models/data_points.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/loading_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart' show Utils; import 'package:hmg_patient_app_new/features/lab/lab_repo.dart'; +import 'package:hmg_patient_app_new/features/lab/models/ai_overview_labresult_model_payload.dart'; +import 'package:hmg_patient_app_new/features/lab/models/resp_models/ai_overview_labresults_by_ai_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/lab_result.dart'; +import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_order_response_by_ai_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_results/lab_result_details.dart'; +import 'package:hmg_patient_app_new/presentation/labAiAnalysis/lab_ai_analysis_detailed_page.dart'; +import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -53,20 +61,7 @@ class LabViewModel extends ChangeNotifier { List mainLabResults = []; List mainGraphPoints = []; List filteredGraphValues = []; - List months = [ - 'Jan', - 'Feb', - 'Mar', - 'April', - 'May', - 'Jun', - 'July', - 'Aug', - 'Sep', - 'Oct', - 'Nov', - 'Dec' - ]; + List months = ['Jan', 'Feb', 'Mar', 'April', 'May', 'Jun', 'July', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; late List _labSuggestionsList = []; @@ -86,22 +81,22 @@ class LabViewModel extends ChangeNotifier { List> patientLabOrdersByHospital = []; List> patientLabOrdersViewList = []; - LabViewModel( - {required this.labRepo, - required this.errorHandlerService, - required this.navigationService}); + LabOrderResponseByAi? labOrderResponseByAi; + LabOrdersResponseByAi? labOrdersResponseByAi; + + LabViewModel({required this.labRepo, required this.errorHandlerService, required this.navigationService}); initLabProvider() { // if (isLabNeedToLoad) { - patientLabOrders.clear(); - filteredLabOrders.clear(); - labOrderTests.clear(); - isLabOrdersLoading = true; - isLabResultsLoading = true; - patientLabOrdersByClinic.clear(); - patientLabOrdersByHospital.clear(); - patientLabOrdersViewList.clear(); - getPatientLabOrders(); + patientLabOrders.clear(); + filteredLabOrders.clear(); + labOrderTests.clear(); + isLabOrdersLoading = true; + isLabResultsLoading = true; + patientLabOrdersByClinic.clear(); + patientLabOrdersByHospital.clear(); + patientLabOrdersViewList.clear(); + getPatientLabOrders(); // } notifyListeners(); } @@ -133,13 +128,13 @@ class LabViewModel extends ChangeNotifier { final result = await labRepo.getPatientLabOrders(); result.fold( - (failure) async { - isLabOrdersLoading = false; - isLabResultsLoading = false; - notifyListeners(); - }, - // => await errorHandler_service.handleError(failure: failure), - (apiResponse) { + (failure) async { + isLabOrdersLoading = false; + isLabResultsLoading = false; + notifyListeners(); + }, + // => await errorHandler_service.handleError(failure: failure), + (apiResponse) { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { @@ -203,11 +198,7 @@ class LabViewModel extends ChangeNotifier { } filterSuggestions() { - final List labels = patientLabOrders - .expand((order) => order.testDetails ?? []) - .map((detail) => detail.description) - .whereType() - .toList(); + final List labels = patientLabOrders.expand((order) => order.testDetails ?? []).map((detail) => detail.description).whereType().toList(); _labSuggestionsList = labels.toSet().toList(); notifyListeners(); @@ -240,6 +231,9 @@ class LabViewModel extends ChangeNotifier { model: item)) }; uniqueTestsList = uniqueTests.toList(); + + uniqueTestsList = Utils.uniqueBy(uniqueTestsList, (p) => p.testCode); + uniqueTestsList.sort((a, b) { return a.description!.toLowerCase().compareTo(b.description!.toLowerCase()); }); @@ -319,14 +313,12 @@ class LabViewModel extends ChangeNotifier { ); } - Future getPatientLabResultByHospital( - PatientLabOrdersResponseModel laborder) async { + Future getPatientLabResultByHospital(PatientLabOrdersResponseModel laborder) async { isLabResultByHospitalLoading = true; notifyListeners(); mainLabResultsByHospitals.clear(); - final result = await labRepo.getPatientLabResultsByHospitals(laborder, - Utils.isVidaPlusProject(int.parse(laborder.projectID ?? "0"))); + final result = await labRepo.getPatientLabResultsByHospitals(laborder, Utils.isVidaPlusProject(int.parse(laborder.projectID ?? "0"))); result.fold( (failure) async { @@ -350,12 +342,9 @@ class LabViewModel extends ChangeNotifier { mainLabResults.clear(); filteredGraphValues.clear(); maxY = double.negativeInfinity; - minY = double.infinity; - maxX = double.infinity; - final result = await labRepo.getPatientLabResults( - laborder, - Utils.isVidaPlusProject(int.parse(laborder.projectID ?? "0")), - procedureName); + minY = double.infinity; + maxX = double.infinity; + final result = await labRepo.getPatientLabResults(laborder, Utils.isVidaPlusProject(int.parse(laborder.projectID ?? "0")), procedureName); result.fold( (failure) async { @@ -369,7 +358,6 @@ class LabViewModel extends ChangeNotifier { var recentThree = sort(apiResponse.data!); mainLabResults = recentThree; - double highRefrenceValue = double.negativeInfinity; String? flagForHighReferenceRange; double lowRefenceValue = double.infinity; @@ -377,11 +365,10 @@ class LabViewModel extends ChangeNotifier { recentThree.reversed.forEach((element) { try { - var dateTime = - DateUtil.convertStringToDate(element.verifiedOnDateTime!); + var dateTime = DateUtil.convertStringToDate(element.verifiedOnDateTime!); var resultValue = double.parse(element.resultValue!); - var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??""); - if (resultValue>maxY) { + var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag ?? ""); + if (resultValue > maxY) { maxY = resultValue; maxX = maxY; } @@ -398,14 +385,13 @@ class LabViewModel extends ChangeNotifier { } filteredGraphValues.add(DataPoint( - value: transformedValue, - actualValue:element.resultValue!, - label: formatDateAsMMYY(dateTime), - unitOfMeasurement: element.uOM, - displayTime: resultDate(dateTime), - time: DateUtil.convertStringToDate(element.verifiedOnDateTime), - referenceValue: element.calculatedResultFlag ?? "", - + value: transformedValue, + actualValue: element.resultValue!, + label: formatDateAsMMYY(dateTime), + unitOfMeasurement: element.uOM, + displayTime: resultDate(dateTime), + time: DateUtil.convertStringToDate(element.verifiedOnDateTime), + referenceValue: element.calculatedResultFlag ?? "", )); } catch (e) {} }); @@ -414,6 +400,10 @@ class LabViewModel extends ChangeNotifier { lowRefenceValue = minY; } + if (minY > lowRefenceValue) { + minY = lowRefenceValue - 25; + } + this.flagForHighReferenceRange = flagForHighReferenceRange; this.flagForLowReferenceRange = flagForLowReferenceRange; highTransformedReferenceValue = double.parse(transformValueInRange(highRefrenceValue, flagForHighReferenceRange ?? "").toStringAsFixed(1)); @@ -421,7 +411,7 @@ class LabViewModel extends ChangeNotifier { this.highRefrenceValue = double.parse(highRefrenceValue.toStringAsFixed(1)); this.lowRefenceValue = double.parse(lowRefenceValue.toStringAsFixed(1)); - if(maxY< highRefrenceValue) { + if (maxY < highRefrenceValue) { maxY = highRefrenceValue; } maxY += 25; @@ -431,7 +421,9 @@ class LabViewModel extends ChangeNotifier { checkIfGraphShouldBeDisplayed(recentResult); recentResult.verifiedOn = resultDate(DateUtil.convertStringToDate(recentResult.verifiedOnDateTime!)); // filteredGraphValues = [filteredGraphValues.first]; - navigationService.push(MaterialPageRoute( + labOrderResponseByAi = null; + navigationService.push( + MaterialPageRoute( builder: (_) => LabResultDetails(recentLabResult: recentResult, testDescription: testDescription), ), ); @@ -441,14 +433,13 @@ class LabViewModel extends ChangeNotifier { ); } - void checkIfGraphShouldBeDisplayed(LabResult recentResult){ + void checkIfGraphShouldBeDisplayed(LabResult recentResult) { shouldShowGraph = recentResult.checkIfGraphShouldBeDisplayed(); isGraphVisible = shouldShowGraph; notifyListeners(); } - Future getPatientSpecialResult( - PatientLabOrdersResponseModel laborder) async { + Future getPatientSpecialResult(PatientLabOrdersResponseModel laborder) async { isSpecialResultsLoading = true; labSpecialResult = ""; notifyListeners(); @@ -470,8 +461,7 @@ class LabViewModel extends ChangeNotifier { StringBuffer htmlbuffer = StringBuffer(""); apiResponse.data?.forEach((element) { - if(element.resultDataHTML != null && element.resultDataHTML?.isNotEmpty == true) - htmlbuffer.write("${element.resultDataHTML}

"); + if (element.resultDataHTML != null && element.resultDataHTML?.isNotEmpty == true) htmlbuffer.write("${element.resultDataHTML}

"); }); labSpecialResult = htmlbuffer.toString(); @@ -483,10 +473,8 @@ class LabViewModel extends ChangeNotifier { ); } - String resultDate(DateTime date){ - - - return '${date.day} ${months[date.month-1]},${date.year}'; + String resultDate(DateTime date) { + return '${date.day} ${months[date.month - 1]},${date.year}'; } double transformValueInRange(double inputValue, String flag) { @@ -494,7 +482,7 @@ class LabViewModel extends ChangeNotifier { double rangeStart, rangeEnd; switch (flag) { - case'LCL': + case 'LCL': case 'CL': rangeStart = 0.0; rangeEnd = 19.0; @@ -531,16 +519,17 @@ class LabViewModel extends ChangeNotifier { debugPrint("the transformed value is $transformedValue"); return transformedValue; } + void getSelectedDateRange(DateTime? start, DateTime? end) { maxY = double.negativeInfinity; - if(start == null && end == null) { + if (start == null && end == null) { mainLabResults.forEach((element) { final time = DateUtil.convertStringToDate(element.verifiedOnDateTime!); - try{ + try { var resultValue = double.parse(element.resultValue!); - var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??""); + var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag ?? ""); if (resultValue > maxY) { maxY = resultValue; } @@ -549,54 +538,43 @@ class LabViewModel extends ChangeNotifier { actualValue: element.resultValue!, label: formatDateAsMMYY(time), unitOfMeasurement: element.uOM, - displayTime: resultDate(time), time: DateUtil.convertStringToDate(element.verifiedOnDateTime), referenceValue: element.calculatedResultFlag ?? "", )); - }catch(e){ - - } + } catch (e) {} }); - - }else { + } else { filteredGraphValues.clear(); mainLabResults.forEach((element) { try { - var dateTime = - DateUtil.convertStringToDate(element.verifiedOnDateTime!); + var dateTime = DateUtil.convertStringToDate(element.verifiedOnDateTime!); var resultValue = double.parse(element.resultValue!); - var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??""); + var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag ?? ""); if (resultValue > maxY) { maxY = resultValue; } if (start != null && end == null) { if (dateTime.isAtSameMomentAs(start)) { - filteredGraphValues.add(DataPoint( value: transformedValue, actualValue: element.resultValue!, unitOfMeasurement: element.uOM, - label: formatDateAsMMYY(dateTime), displayTime: resultDate(dateTime), - time: - DateUtil.convertStringToDate(element.verifiedOnDateTime), + time: DateUtil.convertStringToDate(element.verifiedOnDateTime), referenceValue: element.calculatedResultFlag ?? "")); } } else if (start != null && end != null) { if ((dateTime.isAfter(start)) && (dateTime.isBefore(end))) { - filteredGraphValues.add(DataPoint( value: transformedValue, actualValue: element.resultValue!, label: formatDateAsMMYY(dateTime), unitOfMeasurement: element.uOM, - displayTime: resultDate(dateTime), - time: - DateUtil.convertStringToDate(element.verifiedOnDateTime), + time: DateUtil.convertStringToDate(element.verifiedOnDateTime), referenceValue: element.calculatedResultFlag ?? "")); } } @@ -605,16 +583,13 @@ class LabViewModel extends ChangeNotifier { } filteredGraphValues = sortFilteredList(filteredGraphValues).reversed.toList(); - notifyListeners(); } String formatDateAsMMYY(DateTime date) { - - return '${months[date.month-1]}, ${date.year}'; + return '${months[date.month - 1]}, ${date.year}'; } - List sortByFlagAndValue(List original) { const priorityOrder = ['LCL', 'CL', 'L', 'N', 'H', 'CH', 'HCH']; @@ -648,7 +623,6 @@ class LabViewModel extends ChangeNotifier { } List sort(List original) { - final copy = List.from(original); copy.sort((a, b) { final aDate = DateUtil.convertStringToDate(a.verifiedOnDateTime); @@ -663,8 +637,6 @@ class LabViewModel extends ChangeNotifier { } List sortFilteredList(List original) { - - final copy = List.from(original); copy.sort((a, b) { final aDate = a.time; @@ -699,7 +671,7 @@ class LabViewModel extends ChangeNotifier { } } - String getFormattedDate(DateTime date){ + String getFormattedDate(DateTime date) { return DateFormat('EEEE, dd MMMM. yyyy').format(date); } @@ -713,7 +685,7 @@ class LabViewModel extends ChangeNotifier { case 'N': return AppAssets.ic_normal_result; case 'H': - return AppAssets.ic_low_result; + return AppAssets.ic_low_result; case 'CH': case 'HCH': return AppAssets.ic_critical_low_result; @@ -755,8 +727,81 @@ class LabViewModel extends ChangeNotifier { } } - alterGraphVisibility(){ + alterGraphVisibility() { isGraphVisible = !isGraphVisible; notifyListeners(); } + + Future 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); + List> results = []; + Map orderData = {"order_date": labOrder.orderDate ?? "", "clinic": labOrder.clinicDescription ?? "", "doctor": labOrder.doctorName ?? "", "results": []}; + List> testResults = []; + if (labOrder.testDetails != null && labOrder.testDetails!.isNotEmpty) { + for (var testDetail in labOrder.testDetails!) { + LabResult? matchingResult = + mainLabResultsByHospitals.firstWhere((labResult) => labResult.testCode == testDetail.testCode || labResult.description == testDetail.description, orElse: () => LabResult()); + testResults.add({ + "test_name": testDetail.description ?? "", + "result_value": matchingResult.resultValue ?? "", + "reference_range": matchingResult.referanceRange ?? "", + "unit": matchingResult.uOM ?? "", + "flag": matchingResult.calculatedResultFlag ?? "N" + }); + } + } + orderData["results"] = testResults; + results.add(orderData); + final result = await labRepo.getPatientAiOverViewLabOrders(results); + result.fold( + (failure) async { + LoadingUtils.hideFullScreenLoader(); + await errorHandlerService.handleError(failure: failure); + }, + (apiResponse) { + LoadingUtils.hideFullScreenLoader(); + if (apiResponse.messageStatus == 2) { + } else if (apiResponse.messageStatus == 1) { + labOrdersResponseByAi = apiResponse.data; + navigationService.push( + MaterialPageRoute(builder: (_) => LabAiAnalysisDetailedPage()), + ); + } + }, + ); + + LoadingUtils.hideFullScreenLoader(); + } + + Future getAiOverviewSingleLabResult({required String langId, required LabResult recentLabResult, required String loadingText}) async { + LoaderBottomSheet.showLoader(loadingText: loadingText); + List> results = []; + results.add({ + "Description": recentLabResult.description ?? '', + "ResultValue": recentLabResult.resultValue ?? '', + "CalculatedResultFlag": recentLabResult.calculatedResultFlag ?? '', + "UOM": recentLabResult.uOM ?? '', + "ReferanceRange": recentLabResult.referanceRange ?? '', + }); + + var payload = {"patient_id": currentlySelectedPatientOrder!.patientID, "language_id": langId, "lab_results": results}; + final result = await labRepo.getPatientAiOverViewLabOrder(payload); + result.fold( + (failure) async { + LoaderBottomSheet.hideLoader(); + await errorHandlerService.handleError(failure: failure); + }, + (apiResponse) { + LoaderBottomSheet.hideLoader(); + if (apiResponse.messageStatus == 2) { + } else if (apiResponse.messageStatus == 1) { + labOrderResponseByAi = apiResponse.data; + notifyListeners(); + } + }, + ); + + LoaderBottomSheet.hideLoader(); + } } diff --git a/lib/features/lab/models/ai_overview_labresult_model_payload.dart b/lib/features/lab/models/ai_overview_labresult_model_payload.dart new file mode 100644 index 00000000..c8765929 --- /dev/null +++ b/lib/features/lab/models/ai_overview_labresult_model_payload.dart @@ -0,0 +1,69 @@ +import 'dart:convert'; + +class AiLabOrdersOverView { + String? orderDate; + String? clinic; + String? doctor; + List? results; + + AiLabOrdersOverView({ + this.orderDate, + this.clinic, + this.doctor, + this.results, + }); + + factory AiLabOrdersOverView.fromRawJson(String str) => AiLabOrdersOverView.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory AiLabOrdersOverView.fromJson(Map json) => AiLabOrdersOverView( + orderDate: json["order_date"] == null ? null : json["order_date"], + clinic: json["clinic"], + doctor: json["doctor"], + results: json["results"] == null ? [] : List.from(json["results"]!.map((x) => Result.fromJson(x))), + ); + + Map toJson() => { + "order_date": orderDate, + "clinic": clinic, + "doctor": doctor, + "results": results == null ? [] : List.from(results!.map((x) => x.toJson())), + }; +} + +class Result { + String? testName; + String? resultValue; + String? referenceRange; + String? unit; + String? flag; + + Result({ + this.testName, + this.resultValue, + this.referenceRange, + this.unit, + this.flag, + }); + + factory Result.fromRawJson(String str) => Result.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Result.fromJson(Map json) => Result( + testName: json["test_name"], + resultValue: json["result_value"], + referenceRange: json["reference_range"], + unit: json["unit"], + flag: json["flag"], + ); + + Map toJson() => { + "test_name": testName, + "result_value": resultValue, + "reference_range": referenceRange, + "unit": unit, + "flag": flag, + }; +} diff --git a/lib/features/lab/models/resp_models/ai_overview_labresults_by_ai_model.dart b/lib/features/lab/models/resp_models/ai_overview_labresults_by_ai_model.dart new file mode 100644 index 00000000..2353cf82 --- /dev/null +++ b/lib/features/lab/models/resp_models/ai_overview_labresults_by_ai_model.dart @@ -0,0 +1,177 @@ +import 'dart:convert'; + +class LabOrdersResponseByAi { + Analysis? analysis; + String? patientId; + int? totalOrders; + int? totalResults; + + LabOrdersResponseByAi({ + this.analysis, + this.patientId, + this.totalOrders, + this.totalResults, + }); + + factory LabOrdersResponseByAi.fromRawJson(String str) => LabOrdersResponseByAi.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory LabOrdersResponseByAi.fromJson(Map json) => LabOrdersResponseByAi( + analysis: json["analysis"] == null ? null : Analysis.fromJson(json["analysis"]), + patientId: json["patient_id"], + totalOrders: json["total_orders"], + totalResults: json["total_results"], + ); + + Map toJson() => { + "analysis": analysis?.toJson(), + "patient_id": patientId, + "total_orders": totalOrders, + "total_results": totalResults, + }; +} + +class Analysis { + List? aiGeneratedAttributes; + List? aiPredictions; + List? aiRecommendations; + List? prevention; + String? rawMessage; + List? riskFactors; + List? suggestedDoctors; + String? summary; + + Analysis({ + this.aiGeneratedAttributes, + this.aiPredictions, + this.aiRecommendations, + this.prevention, + this.rawMessage, + this.riskFactors, + this.suggestedDoctors, + this.summary, + }); + + factory Analysis.fromRawJson(String str) => Analysis.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Analysis.fromJson(Map json) => Analysis( + aiGeneratedAttributes: json["ai_generated_attributes"] == null ? [] : List.from(json["ai_generated_attributes"]!.map((x) => AiGeneratedAttribute.fromJson(x))), + aiPredictions: json["ai_predictions"] == null ? [] : List.from(json["ai_predictions"]!.map((x) => x)), + aiRecommendations: json["ai_recommendations"] == null ? [] : List.from(json["ai_recommendations"]!.map((x) => AiRecommendation.fromJson(x))), + prevention: json["prevention"] == null ? [] : List.from(json["prevention"]!.map((x) => x)), + rawMessage: json["raw_message"], + riskFactors: json["riskFactors"] == null ? [] : List.from(json["riskFactors"]!.map((x) => RiskFactor.fromJson(x))), + suggestedDoctors: json["suggested_doctors"] == null ? [] : List.from(json["suggested_doctors"]!.map((x) => x)), + summary: json["summary"], + ); + + Map toJson() => { + "ai_generated_attributes": aiGeneratedAttributes == null ? [] : List.from(aiGeneratedAttributes!.map((x) => x.toJson())), + "ai_predictions": aiPredictions == null ? [] : List.from(aiPredictions!.map((x) => x)), + "ai_recommendations": aiRecommendations == null ? [] : List.from(aiRecommendations!.map((x) => x.toJson())), + "prevention": prevention == null ? [] : List.from(prevention!.map((x) => x)), + "raw_message": rawMessage, + "riskFactors": riskFactors == null ? [] : List.from(riskFactors!.map((x) => x.toJson())), + "suggested_doctors": suggestedDoctors == null ? [] : List.from(suggestedDoctors!.map((x) => x)), + "summary": summary, + }; +} + +class AiGeneratedAttribute { + String? calculatedResultFlag; + String? description; + String? referanceRange; + String? resultValue; + String? testShortDescription; + String? uom; + String? calculationFormula; + + AiGeneratedAttribute({ + this.calculatedResultFlag, + this.description, + this.referanceRange, + this.resultValue, + this.testShortDescription, + this.uom, + this.calculationFormula, + }); + + factory AiGeneratedAttribute.fromRawJson(String str) => AiGeneratedAttribute.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory AiGeneratedAttribute.fromJson(Map json) => AiGeneratedAttribute( + calculatedResultFlag: json["CalculatedResultFlag"], + description: json["Description"], + referanceRange: json["ReferanceRange"], + resultValue: json["ResultValue"], + testShortDescription: json["TestShortDescription"], + uom: json["UOM"], + calculationFormula: json["calculationFormula"], + ); + + Map toJson() => { + "CalculatedResultFlag": calculatedResultFlag, + "Description": description, + "ReferanceRange": referanceRange, + "ResultValue": resultValue, + "TestShortDescription": testShortDescription, + "UOM": uom, + "calculationFormula": calculationFormula, + }; +} + +class AiRecommendation { + String? priority; + String? recommendation; + + AiRecommendation({ + this.priority, + this.recommendation, + }); + + factory AiRecommendation.fromRawJson(String str) => AiRecommendation.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory AiRecommendation.fromJson(Map json) => AiRecommendation( + priority: json["priority"], + recommendation: json["recommendation"], + ); + + Map toJson() => { + "priority": priority, + "recommendation": recommendation, + }; +} + +class RiskFactor { + String? description; + String? factor; + String? risk; + + RiskFactor({ + this.description, + this.factor, + this.risk, + }); + + factory RiskFactor.fromRawJson(String str) => RiskFactor.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory RiskFactor.fromJson(Map json) => RiskFactor( + description: json["description"], + factor: json["factor"], + risk: json["risk"], + ); + + Map toJson() => { + "description": description, + "factor": factor, + "risk": risk, + }; +} diff --git a/lib/features/lab/models/resp_models/lab_result.dart b/lib/features/lab/models/resp_models/lab_result.dart index a075be3d..23146849 100644 --- a/lib/features/lab/models/resp_models/lab_result.dart +++ b/lib/features/lab/models/resp_models/lab_result.dart @@ -26,6 +26,7 @@ class LabResult { String? referenceLow; num? resultTypeID; String? packageShortDescription; + int? testId; LabResult( {this.description, @@ -52,6 +53,7 @@ class LabResult { this.referenceHigh, this.criticalLow, this.referenceLow, + this.testId, }); LabResult.fromJson(Map json) { @@ -82,6 +84,7 @@ class LabResult { referenceLow = json['ReferenceLow']; packageShortDescription = json['PackageShortDescription']; resultTypeID = json['ResultTypeID']; + testId = json['TestID']; } Map toJson() { @@ -110,6 +113,7 @@ class LabResult { data['ReferenceHigh'] = this.referenceHigh; data['CriticalLow'] = this.criticalLow; data['ReferenceLow'] = this.referenceLow; + data['TestID'] = this.testId; return data; } diff --git a/lib/features/lab/models/resp_models/patient_lab_order_response_by_ai_model.dart b/lib/features/lab/models/resp_models/patient_lab_order_response_by_ai_model.dart new file mode 100644 index 00000000..7ad3d543 --- /dev/null +++ b/lib/features/lab/models/resp_models/patient_lab_order_response_by_ai_model.dart @@ -0,0 +1,145 @@ +import 'dart:convert'; + +class LabOrderResponseByAi { + String? summary; + List? aiGeneratedAttributes; + List? aiPredictions; + List? aiRecommendations; + List? riskFactors; + List? prevention; + List? suggestedDoctors; + String? rawMessage; + + LabOrderResponseByAi({ + this.summary, + this.aiGeneratedAttributes, + this.aiPredictions, + this.aiRecommendations, + this.riskFactors, + this.prevention, + this.suggestedDoctors, + this.rawMessage, + }); + + factory LabOrderResponseByAi.fromRawJson(String str) => LabOrderResponseByAi.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory LabOrderResponseByAi.fromJson(Map json) => LabOrderResponseByAi( + summary: json["summary"], + aiGeneratedAttributes: json["ai_generated_attributes"] == null ? [] : List.from(json["ai_generated_attributes"]!.map((x) => AiGeneratedAttribute.fromJson(x))), + aiPredictions: json["ai_predictions"] == null ? [] : List.from(json["ai_predictions"]!.map((x) => x)), + aiRecommendations: json["ai_recommendations"] == null ? [] : List.from(json["ai_recommendations"]!.map((x) => AiRecommendation.fromJson(x))), + riskFactors: json["riskFactors"] == null ? [] : List.from(json["riskFactors"]!.map((x) => RiskFactor.fromJson(x))), + prevention: json["prevention"] == null ? [] : List.from(json["prevention"]!.map((x) => x)), + suggestedDoctors: json["suggested_doctors"] == null ? [] : List.from(json["suggested_doctors"]!.map((x) => x)), + rawMessage: json["raw_message"], + ); + + Map toJson() => { + "summary": summary, + "ai_generated_attributes": aiGeneratedAttributes == null ? [] : List.from(aiGeneratedAttributes!.map((x) => x.toJson())), + "ai_predictions": aiPredictions == null ? [] : List.from(aiPredictions!.map((x) => x)), + "ai_recommendations": aiRecommendations == null ? [] : List.from(aiRecommendations!.map((x) => x.toJson())), + "riskFactors": riskFactors == null ? [] : List.from(riskFactors!.map((x) => x.toJson())), + "prevention": prevention == null ? [] : List.from(prevention!.map((x) => x)), + "suggested_doctors": suggestedDoctors == null ? [] : List.from(suggestedDoctors!.map((x) => x)), + "raw_message": rawMessage, + }; +} + +class AiGeneratedAttribute { + String? description; + String? resultValue; + String? referanceRange; + String? calculatedResultFlag; + String? uom; + String? calculationFormula; + String? testShortDescription; + + AiGeneratedAttribute({ + this.description, + this.resultValue, + this.referanceRange, + this.calculatedResultFlag, + this.uom, + this.calculationFormula, + this.testShortDescription, + }); + + factory AiGeneratedAttribute.fromRawJson(String str) => AiGeneratedAttribute.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory AiGeneratedAttribute.fromJson(Map json) => AiGeneratedAttribute( + description: json["Description"], + resultValue: json["ResultValue"], + referanceRange: json["ReferanceRange"], + calculatedResultFlag: json["CalculatedResultFlag"], + uom: json["UOM"], + calculationFormula: json["calculationFormula"], + testShortDescription: json["TestShortDescription"], + ); + + Map toJson() => { + "Description": description, + "ResultValue": resultValue, + "ReferanceRange": referanceRange, + "CalculatedResultFlag": calculatedResultFlag, + "UOM": uom, + "calculationFormula": calculationFormula, + "TestShortDescription": testShortDescription, + }; +} + +class AiRecommendation { + String? recommendation; + String? priority; + + AiRecommendation({ + this.recommendation, + this.priority, + }); + + factory AiRecommendation.fromRawJson(String str) => AiRecommendation.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory AiRecommendation.fromJson(Map json) => AiRecommendation( + recommendation: json["recommendation"], + priority: json["priority"], + ); + + Map toJson() => { + "recommendation": recommendation, + "priority": priority, + }; +} + +class RiskFactor { + String? factor; + String? risk; + String? description; + + RiskFactor({ + this.factor, + this.risk, + this.description, + }); + + factory RiskFactor.fromRawJson(String str) => RiskFactor.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory RiskFactor.fromJson(Map json) => RiskFactor( + factor: json["factor"], + risk: json["risk"], + description: json["description"], + ); + + Map toJson() => { + "factor": factor, + "risk": risk, + "description": description, + }; +} diff --git a/lib/features/medical_file/medical_file_view_model.dart b/lib/features/medical_file/medical_file_view_model.dart index d67e5de8..9e50075f 100644 --- a/lib/features/medical_file/medical_file_view_model.dart +++ b/lib/features/medical_file/medical_file_view_model.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/core/utils/request_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; @@ -40,6 +41,7 @@ class MedicalFileViewModel extends ChangeNotifier { List patientSickLeavesViewList = []; bool isSickLeavesSortByClinic = true; + bool isSickLeavesDataNeedsReloading = true; List patientAllergiesList = []; @@ -162,15 +164,15 @@ class MedicalFileViewModel extends ChangeNotifier { } setIsPatientSickLeaveListLoading(bool val) { - if (val) { + if (val && isSickLeavesDataNeedsReloading) { patientSickLeaveList.clear(); patientSickLeavesByClinic.clear(); patientSickLeavesByHospital.clear(); patientSickLeavesViewList.clear(); patientSickLeavePDFBase64 = ""; isSickLeavesSortByClinic = true; + isPatientSickLeaveListLoading = val; } - isPatientSickLeaveListLoading = val; notifyListeners(); } @@ -266,6 +268,10 @@ class MedicalFileViewModel extends ChangeNotifier { } Future getPatientSickLeaveList({Function(dynamic)? onSuccess, Function(String)? onError}) async { + if (!isSickLeavesDataNeedsReloading) { + return; + } + patientSickLeaveList.clear(); final result = await medicalFileRepo.getPatientSickLeavesList(); @@ -308,6 +314,7 @@ class MedicalFileViewModel extends ChangeNotifier { } } patientSickLeavesViewList = patientSickLeavesByClinic; + isSickLeavesDataNeedsReloading = false; notifyListeners(); if (onSuccess != null) { @@ -432,11 +439,10 @@ class MedicalFileViewModel extends ChangeNotifier { ); // Clear and start fresh with current user - patientFamilyFiles.clear(); - patientFamilyFiles.add(currentUserFamilyFile); + final List newPatientFamilyFiles = [currentUserFamilyFile]; final List activeFamilyFiles = []; - final List pendingFamilyFiles = []; + final List newPendingFamilyFiles = []; for (var element in apiResponse.data!) { if (element.status == null) { @@ -477,7 +483,7 @@ class MedicalFileViewModel extends ChangeNotifier { if (isPending) { familyFile.isRequestFromMySide = true; - pendingFamilyFiles.add(familyFile); + newPendingFamilyFiles.add(familyFile); } if (isActive) { activeFamilyFiles.add(familyFile); @@ -485,13 +491,14 @@ class MedicalFileViewModel extends ChangeNotifier { } for (var activeFile in activeFamilyFiles) { - if (!patientFamilyFiles.any((e) => e.responseId == activeFile.responseId)) { - patientFamilyFiles.add(activeFile); + if (!newPatientFamilyFiles.any((e) => e.responseId == activeFile.responseId)) { + newPatientFamilyFiles.add(activeFile); } } - this.pendingFamilyFiles.clear(); - this.pendingFamilyFiles.addAll(pendingFamilyFiles); + // Assign new list instances + patientFamilyFiles = newPatientFamilyFiles; + pendingFamilyFiles = newPendingFamilyFiles; } notifyListeners(); @@ -546,7 +553,9 @@ class MedicalFileViewModel extends ChangeNotifier { } } // pendingFamilyFiles.addAll(tempPendingFamilyFiles.where((element) => !pendingFamilyFiles.any((e) => e.responseId == element.responseId))); - pendingFamilyFiles.addAll(tempPendingFamilyFiles.where((element) => !pendingFamilyFiles.any((e) => e.patientId == element.patientId))); + final existingIds = pendingFamilyFiles.map((e) => e.patientId).toSet(); + final newItems = tempPendingFamilyFiles.where((element) => !existingIds.contains(element.patientId)).toList(); + pendingFamilyFiles = [...pendingFamilyFiles, ...newItems]; } notifyListeners(); } @@ -576,9 +585,18 @@ class MedicalFileViewModel extends ChangeNotifier { await RequestUtils.getAddFamilyRequest(nationalIDorFile: authVM.nationalIdController.text, mobileNo: authVM.phoneNumberController.text, countryCode: authVM.selectedCountrySignup.countryCode); final resultEither = await medicalFileRepo.addFamilyFile(request: request.toJson()); - resultEither.fold((failure) async => await errorHandlerService.handleError(failure: failure), (apiResponse) async { + resultEither.fold( + (failure) async => await errorHandlerService.handleError( + failure: failure, + onUnHandledFailure: (failure) { + LoaderBottomSheet.hideLoader(); + _dialogService.showErrorBottomSheet( + message: failure.message, + onOkPressed: () { + navigationService.pop(); + }); + }), (apiResponse) async { if (apiResponse.messageStatus == 2) { - print("======="); LoaderBottomSheet.hideLoader(); _dialogService.showErrorBottomSheet( message: apiResponse.errorMessage!, @@ -647,7 +665,7 @@ class MedicalFileViewModel extends ChangeNotifier { navigationService.pop(); }); } else if (apiResponse.messageStatus == 1) { - patientFamilyFiles.removeWhere((element) => element.id == id); + patientFamilyFiles = patientFamilyFiles.where((element) => element.id != id).toList(); getFamilyFiles(); getAllPendingRecordsByResponseId(); LoaderBottomSheet.hideLoader(); @@ -725,11 +743,12 @@ class MedicalFileViewModel extends ChangeNotifier { // moveProfile.status = 3; // moveProfile.statusDescription = "Approved"; // patientFamilyFiles.add(moveProfile); - pendingFamilyFiles.removeWhere((element) => element.id == id); + pendingFamilyFiles = pendingFamilyFiles.where((element) => element.id != id).toList(); //TODO: Call Api Here To Load Family Members getFamilyFiles(status: 0); getAllPendingRecordsByResponseId(); LoaderBottomSheet.hideLoader(); + notifyListeners(); // onFamilyFileTabChange(0); } }, diff --git a/lib/features/my_appointments/models/resp_models/patient_appointment_share_response_model.dart b/lib/features/my_appointments/models/resp_models/patient_appointment_share_response_model.dart index 1dc1cbe7..b412a2e4 100644 --- a/lib/features/my_appointments/models/resp_models/patient_appointment_share_response_model.dart +++ b/lib/features/my_appointments/models/resp_models/patient_appointment_share_response_model.dart @@ -5,14 +5,14 @@ class PatientAppointmentShareResponseModel { int? appointmentNo; dynamic cardNumber; dynamic cardType; - int? cashPrice; - int? cashPriceTax; - int? cashPriceWithTax; + num? cashPrice; + num? cashPriceTax; + num? cashPriceWithTax; int? clinicID; String? clinicName; int? companyId; String? companyName; - int? companyShareWithTax; + num? companyShareWithTax; int? doctorID; String? doctorImageURL; String? doctorNameObj; @@ -39,7 +39,7 @@ class PatientAppointmentShareResponseModel { int? patientStatusType; num? patientTaxAmount; String? patientType; - int? paymentAmount; + num? paymentAmount; String? paymentDate; dynamic paymentMethodName; dynamic paymentReferenceNumber; @@ -57,7 +57,7 @@ class PatientAppointmentShareResponseModel { int? statusCode; dynamic statusDesc; String? subPolicyNo; - int? tax; + num? tax; int? userID; PatientAppointmentShareResponseModel({ diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index 96340a7b..ce2b7b91 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -220,8 +220,23 @@ class MyAppointmentsViewModel extends ChangeNotifier { } Future getPatientAppointments(bool isActiveAppointment, bool isArrivedAppointments, {Function(dynamic)? onSuccess, Function(String)? onError}) async { + 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 (!isAppointmentDataToBeLoaded) return; + debugPrint('getPatientAppointments CALLED!!!'); + + patientAppointmentsByClinic.clear(); + patientAppointmentsByHospital.clear(); + patientAppointmentsViewList.clear(); + + patientTimelineAppointmentsList.clear(); filteredAppointmentList.clear(); patientAppointmentsHistoryList.clear(); patientUpcomingAppointmentsHistoryList.clear(); @@ -275,6 +290,8 @@ class MyAppointmentsViewModel extends ChangeNotifier { _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(); @@ -493,7 +510,7 @@ class MyAppointmentsViewModel extends ChangeNotifier { } Future getPatientMyDoctors({Function(dynamic)? onSuccess, Function(String)? onError}) async { - if (!isAppointmentDataToBeLoaded) return; + // if (!isAppointmentDataToBeLoaded) return; isPatientMyDoctorsLoading = true; notifyListeners(); @@ -727,6 +744,7 @@ class MyAppointmentsViewModel extends ChangeNotifier { Future getPatientAppointmentQueueDetails({Function(dynamic)? onSuccess, Function(String)? onError}) async { //TODO: Discuss With Haroon, Is the User Has no data it return No Element Bad State; + isPatientHasQueueAppointment = false; isAppointmentQueueDetailsLoading = true; notifyListeners(); final result = await myAppointmentsRepo.getPatientAppointmentQueueDetails( diff --git a/lib/features/my_appointments/utils/appointment_type.dart b/lib/features/my_appointments/utils/appointment_type.dart index abc23dc0..ae3c7516 100644 --- a/lib/features/my_appointments/utils/appointment_type.dart +++ b/lib/features/my_appointments/utils/appointment_type.dart @@ -69,7 +69,7 @@ class AppointmentType { case 10: return AppColors.successColor; case 15: - return AppColors.greyColor; + return AppColors.greyTextColor; case 20: return AppColors.infoColor; case 50: @@ -84,7 +84,7 @@ class AppointmentType { static String getNextActionText(nextAction) { switch (nextAction) { case 0: - return "No Action".needTranslation; + return LocaleKeys.upcomingNoAction.tr(); case 10: return LocaleKeys.confirm.tr(); case 15: @@ -96,7 +96,7 @@ class AppointmentType { case 90: return LocaleKeys.checkinOption.tr(); default: - return "No Action".needTranslation; + return LocaleKeys.upcomingNoAction.tr(); } } diff --git a/lib/features/prescriptions/prescriptions_repo.dart b/lib/features/prescriptions/prescriptions_repo.dart index c6e7150b..dcc9de92 100644 --- a/lib/features/prescriptions/prescriptions_repo.dart +++ b/lib/features/prescriptions/prescriptions_repo.dart @@ -167,29 +167,44 @@ class PrescriptionsRepoImp implements PrescriptionsRepo { @override Future> getPrescriptionPDF( {required PatientPrescriptionsResponseModel prescriptionsResponseModel, required List prescriptionDetailsList}) async { + // Map mapDevice = { + // "AppointmentDate": prescriptionsResponseModel.appointmentDate, + // "ClinicName": prescriptionsResponseModel.clinicDescription, + // "DoctorName": prescriptionsResponseModel.doctorName, + // "ProjectID": prescriptionsResponseModel.projectID, + // "DoctorID": prescriptionsResponseModel.doctorID, + // "ClinicID": prescriptionsResponseModel.clinicID, + // "DateofBirth": Utils.appState.getAuthenticatedUser()!.dateofBirth, + // "ListPrescriptions": prescriptionDetailsList, + // "PatientIditificationNum": Utils.appState.getAuthenticatedUser()!.patientIdentificationNo, + // "PatientMobileNumber": Utils.appState.getAuthenticatedUser()!.mobileNumber, + // "PatientName": "${Utils.appState.getAuthenticatedUser()!.firstName!} ${Utils.appState.getAuthenticatedUser()!.lastName!}", + // "To": Utils.appState.getAuthenticatedUser()!.emailAddress, + // "SetupID": prescriptionsResponseModel.setupID, + // "IsDownload": true, + // "isDentalAllowedBackend": false, + // }; + Map mapDevice = { - "AppointmentDate": prescriptionsResponseModel.appointmentDate, - "ClinicName": prescriptionsResponseModel.clinicDescription, - "DoctorName": prescriptionsResponseModel.doctorName, - "ProjectID": prescriptionsResponseModel.projectID, - "DoctorID": prescriptionsResponseModel.doctorID, - "ClinicID": prescriptionsResponseModel.clinicID, - "DateofBirth": Utils.appState.getAuthenticatedUser()!.dateofBirth, - "ListPrescriptions": prescriptionDetailsList, - "PatientIditificationNum": Utils.appState.getAuthenticatedUser()!.patientIdentificationNo, - "PatientMobileNumber": Utils.appState.getAuthenticatedUser()!.mobileNumber, - "PatientName": "${Utils.appState.getAuthenticatedUser()!.firstName!} ${Utils.appState.getAuthenticatedUser()!.lastName!}", - "To": Utils.appState.getAuthenticatedUser()!.emailAddress, + "AppointmentNo": prescriptionsResponseModel.appointmentNo, "SetupID": prescriptionsResponseModel.setupID, + "EpisodeID": prescriptionsResponseModel.episodeID, + "ClinicID": prescriptionsResponseModel.clinicID, + "ProjectID": prescriptionsResponseModel.projectID, + "DischargeNo": prescriptionsResponseModel.dischargeNo, "IsDownload": true, - "isDentalAllowedBackend": false, + "To": Utils.appState.getAuthenticatedUser()!.emailAddress, + // "To": "amjad.haroon@cloudsolutions.com.sa", + "ClinicName": prescriptionsResponseModel.clinicDescription, + "DoctorName": prescriptionsResponseModel.doctorName, }; try { GenericApiModel? apiResponse; Failure? failure; await apiClient.post( - SEND_PRESCRIPTION_EMAIL, + // SEND_PRESCRIPTION_EMAIL, + SEND_PRESCRIPTION_EMAIL_NEW, body: mapDevice, onFailure: (error, statusCode, {messageStatus, failureType}) { failure = failureType; diff --git a/lib/features/prescriptions/prescriptions_view_model.dart b/lib/features/prescriptions/prescriptions_view_model.dart index ff864061..319e5fc1 100644 --- a/lib/features/prescriptions/prescriptions_view_model.dart +++ b/lib/features/prescriptions/prescriptions_view_model.dart @@ -47,20 +47,24 @@ class PrescriptionsViewModel extends ChangeNotifier { late GeocodeResponse locationGeocodeResponse; bool isPrescriptionsDeliveryOrdersLoading = false; + + bool isPrescriptionsDataNeedsReloading = true; List prescriptionsOrderList = []; PrescriptionsViewModel({required this.prescriptionsRepo, required this.errorHandlerService, required this.navServices}); initPrescriptionsViewModel() { - patientPrescriptionOrders.clear(); - patientPrescriptionOrdersByClinic.clear(); - patientPrescriptionOrdersByHospital.clear(); - patientPrescriptionOrdersViewList.clear(); - prescriptionsOrderList.clear(); - isPrescriptionsOrdersLoading = true; + // if (isPrescriptionsDataNeedsReloading) { + patientPrescriptionOrders.clear(); + patientPrescriptionOrdersByClinic.clear(); + patientPrescriptionOrdersByHospital.clear(); + patientPrescriptionOrdersViewList.clear(); + prescriptionsOrderList.clear(); + isPrescriptionsOrdersLoading = true; + getPatientPrescriptionOrders(); + // } isSortByClinic = true; isPrescriptionsDeliveryOrdersLoading = true; - getPatientPrescriptionOrders(); notifyListeners(); } @@ -98,6 +102,10 @@ class PrescriptionsViewModel extends ChangeNotifier { } Future getPatientPrescriptionOrders({Function(dynamic)? onSuccess, Function(String)? onError}) async { + // if (!isPrescriptionsDataNeedsReloading) { + // return; + // } + final result = await prescriptionsRepo.getPatientPrescriptionOrders(patientId: "1231755"); result.fold( @@ -131,6 +139,7 @@ class PrescriptionsViewModel extends ChangeNotifier { } } patientPrescriptionOrdersViewList = patientPrescriptionOrdersByClinic; + isPrescriptionsDataNeedsReloading = false; notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); @@ -245,8 +254,8 @@ class PrescriptionsViewModel extends ChangeNotifier { CustomPageRoute( page: MapUtilityScreen( confirmButtonString: LocaleKeys.next.tr(), - titleString: "Select Location".needTranslation, - subTitleString: "Please select the location for prescription delivery".needTranslation, + titleString: LocaleKeys.selectLocation.tr(), + subTitleString: LocaleKeys.selectLocationPrescriptionDelivery.tr(), isGmsAvailable: getIt.get().isGMSAvailable, ), direction: AxisDirection.down), diff --git a/lib/features/radiology/radiology_view_model.dart b/lib/features/radiology/radiology_view_model.dart index 986945e6..c8150c12 100644 --- a/lib/features/radiology/radiology_view_model.dart +++ b/lib/features/radiology/radiology_view_model.dart @@ -1,7 +1,9 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.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/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'; @@ -97,7 +99,7 @@ class RadiologyViewModel extends ChangeNotifier { ); } else { if (onError != null) { - onError("No Radiology Orders Found".needTranslation); + onError(LocaleKeys.noRadiologyOrders.tr()); } } } @@ -110,7 +112,8 @@ class RadiologyViewModel extends ChangeNotifier { final result = await radiologyRepo.getRadiologyImage(patientRadiologyResponseModel: patientRadiologyResponseModel); result.fold( - (failure) async => await errorHandlerService.handleError(failure: failure), + // (failure) async => await errorHandlerService.handleError(failure: failure), + (failure) async {}, (apiResponse) { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); diff --git a/lib/features/services_price_list/models/resp_models/services_price_list_response_model.dart b/lib/features/services_price_list/models/resp_models/services_price_list_response_model.dart new file mode 100644 index 00000000..bb60094a --- /dev/null +++ b/lib/features/services_price_list/models/resp_models/services_price_list_response_model.dart @@ -0,0 +1,54 @@ +class ServicesPriceListResponseModel { + int? createdBy; + String? createdOn; + int? editedBy; + String? editedOn; + int? id; + bool? isEnabled; + String? nameAR; + String? nameEN; + num? price; + int? rowID; + + ServicesPriceListResponseModel({ + this.createdBy, + this.createdOn, + this.editedBy, + this.editedOn, + this.id, + this.isEnabled, + this.nameAR, + this.nameEN, + this.price, + this.rowID, + }); + + ServicesPriceListResponseModel.fromJson(Map json) { + createdBy = json['CreatedBy']; + createdOn = json['CreatedOn']; + editedBy = json['EditedBy']; + editedOn = json['EditedOn']; + id = json['ID']; + isEnabled = json['IsEnabled']; + nameAR = json['NameAR']; + nameEN = json['NameEN']; + price = json['Price']; + rowID = json['RowID']; + } + + Map toJson() { + final Map data = {}; + data['CreatedBy'] = createdBy; + data['CreatedOn'] = createdOn; + data['EditedBy'] = editedBy; + data['EditedOn'] = editedOn; + data['ID'] = id; + data['IsEnabled'] = isEnabled; + data['NameAR'] = nameAR; + data['NameEN'] = nameEN; + data['Price'] = price; + data['RowID'] = rowID; + return data; + } +} + diff --git a/lib/features/services_price_list/services_price_list_repo.dart b/lib/features/services_price_list/services_price_list_repo.dart new file mode 100644 index 00000000..4267d77f --- /dev/null +++ b/lib/features/services_price_list/services_price_list_repo.dart @@ -0,0 +1,80 @@ +import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/core/api/api_client.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; +import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; +import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/features/services_price_list/models/resp_models/services_price_list_response_model.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; + +abstract class ServicesPriceListRepo { + Future>>> getServicesPriceList({ + String searchKey = "", + int pageIndex = 0, + int pageSize = 0, + }); +} + +class ServicesPriceListRepoImp implements ServicesPriceListRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + ServicesPriceListRepoImp({required this.loggerService, required this.apiClient}); + + @override + Future>>> getServicesPriceList({ + String searchKey = "", + int pageIndex = 0, + int pageSize = 0, + }) async { + Map mapDevice = { + "ID": 1, + "SearchKey": searchKey, + "PageIndex": pageIndex, + "PageSize": pageSize, + "RowCount": 0, + "TokenID": "@dm!n" + }; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + + await apiClient.post( + GET_SERVICES_PRICE_LIST, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['getServicesPriceList']; + if (list == null || list.isEmpty) { + throw Exception("Services price list is empty"); + } + + final servicesList = list + .map((item) => ServicesPriceListResponseModel.fromJson(item as Map)) + .toList() + .cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: servicesList, + ); + } 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())); + } + } +} + diff --git a/lib/features/services_price_list/services_price_list_view_model.dart b/lib/features/services_price_list/services_price_list_view_model.dart new file mode 100644 index 00000000..85b96802 --- /dev/null +++ b/lib/features/services_price_list/services_price_list_view_model.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/features/services_price_list/models/resp_models/services_price_list_response_model.dart'; +import 'package:hmg_patient_app_new/features/services_price_list/services_price_list_repo.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; + +class ServicesPriceListViewModel extends ChangeNotifier { + bool isServicesPriceListLoading = false; + + ServicesPriceListRepo servicesPriceListRepo; + ErrorHandlerService errorHandlerService; + + List servicesPriceList = []; + List filteredServicesPriceList = []; + + String searchKey = ""; + + ServicesPriceListViewModel({ + required this.servicesPriceListRepo, + required this.errorHandlerService, + }); + + initServicesPriceListProvider() { + servicesPriceList.clear(); + filteredServicesPriceList.clear(); + isServicesPriceListLoading = true; + getServicesPriceList(); + notifyListeners(); + } + + setIsServicesPriceListLoading(bool val) { + isServicesPriceListLoading = val; + notifyListeners(); + } + + setSearchKey(String val) { + searchKey = val; + filterServicesList(val); + } + + void filterServicesList(String query) { + if (query.isEmpty) { + filteredServicesPriceList = List.from(servicesPriceList); + } else { + filteredServicesPriceList = servicesPriceList.where((service) { + final nameEN = service.nameEN?.toLowerCase() ?? ''; + final nameAR = service.nameAR?.toLowerCase() ?? ''; + final searchLower = query.toLowerCase(); + return nameEN.contains(searchLower) || nameAR.contains(searchLower); + }).toList(); + } + notifyListeners(); + } + + Future getServicesPriceList({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await servicesPriceListRepo.getServicesPriceList( + searchKey: searchKey, + ); + + result.fold( + (failure) async { + isServicesPriceListLoading = false; + notifyListeners(); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + isServicesPriceListLoading = false; + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? "Error loading services price list"); + } + } else if (apiResponse.messageStatus == 1) { + servicesPriceList = apiResponse.data!; + servicesPriceList.removeWhere((element) => element.isEnabled == false); + filteredServicesPriceList = List.from(servicesPriceList); + isServicesPriceListLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } +} diff --git a/lib/features/smartwatch_health_data/health_provider.dart b/lib/features/smartwatch_health_data/health_provider.dart index fc9dacc5..0acb25bb 100644 --- a/lib/features/smartwatch_health_data/health_provider.dart +++ b/lib/features/smartwatch_health_data/health_provider.dart @@ -10,6 +10,15 @@ class HealthProvider with ChangeNotifier { String selectedTimeRange = '7D'; int selectedTabIndex = 0; + String selectedWatchType = 'apple'; + String selectedWatchURL = 'assets/images/png/smartwatches/apple-watch-5.jpg'; + + setSelectedWatchType(String type, String imageURL) { + selectedWatchType = type; + selectedWatchURL = imageURL; + notifyListeners(); + } + void onTabChanged(int index) { selectedTabIndex = index; notifyListeners(); diff --git a/lib/features/symptoms_checker/data/organ_mapping_data.dart b/lib/features/symptoms_checker/data/organ_mapping_data.dart index 6b50b254..b0c2ffea 100644 --- a/lib/features/symptoms_checker/data/organ_mapping_data.dart +++ b/lib/features/symptoms_checker/data/organ_mapping_data.dart @@ -1,35 +1,45 @@ +import 'dart:developer'; + import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/organ_model.dart'; class OrganData { // ==================== FRONT VIEW ==================== - static const List frontViewOrgans = [ + static const List frontViewOrgansForMale = [ // ========== HEAD & FACE ========== OrganModel( id: 'head', name: 'whole_head', + nameAr: 'الرأس_كامل', description: 'Head', + descriptionAr: 'الرأس', bodyView: BodyView.front, position: OrganPosition(x: 0.50, y: 0.03), ), OrganModel( id: 'left_eye', name: 'eyes', + nameAr: 'العيون', description: 'Left Eye', + descriptionAr: 'العين اليسرى', bodyView: BodyView.front, position: OrganPosition(x: 0.43, y: 0.07), ), OrganModel( id: 'right_eye', name: 'eyes', + nameAr: 'العيون', description: 'Right Eye', + descriptionAr: 'العين اليمنى', bodyView: BodyView.front, position: OrganPosition(x: 0.57, y: 0.07), ), OrganModel( id: 'nose_mouth', name: 'oral_cavity', + nameAr: 'تجويف_الفم', description: 'Nose/Mouth', + descriptionAr: 'الأنف/الفم', bodyView: BodyView.front, position: OrganPosition(x: 0.50, y: 0.09), ), @@ -38,7 +48,9 @@ class OrganData { OrganModel( id: 'throat', name: 'neck_or_throat', + nameAr: 'الرقبة_أو_الحلق', description: 'Throat', + descriptionAr: 'الحلق', bodyView: BodyView.front, position: OrganPosition(x: 0.50, y: 0.15), ), @@ -47,14 +59,18 @@ class OrganData { OrganModel( id: 'left_shoulder', name: 'upper_arm', + nameAr: 'الذراع_العلوي', description: 'Left Shoulder', + descriptionAr: 'الكتف الأيسر', bodyView: BodyView.front, position: OrganPosition(x: 0.22, y: 0.22), ), OrganModel( id: 'right_shoulder', name: 'upper_arm', + nameAr: 'الذراع_العلوي', description: 'Right Shoulder', + descriptionAr: 'الكتف الأيمن', bodyView: BodyView.front, position: OrganPosition(x: 0.8, y: 0.22), ), @@ -63,21 +79,27 @@ class OrganData { OrganModel( id: 'left_chest', name: 'chest', + nameAr: 'الصدر', description: 'Left Chest', + descriptionAr: 'الصدر الأيسر', bodyView: BodyView.front, position: OrganPosition(x: 0.38, y: 0.22), ), OrganModel( id: 'center_chest', name: 'chest', + nameAr: 'الصدر', description: 'Center Chest', + descriptionAr: 'وسط الصدر', bodyView: BodyView.front, position: OrganPosition(x: 0.52, y: 0.25), ), OrganModel( id: 'right_chest', name: 'chest', + nameAr: 'الصدر', description: 'Right Chest', + descriptionAr: 'الصدر الأيمن', bodyView: BodyView.front, position: OrganPosition(x: 0.65, y: 0.22), ), @@ -86,14 +108,18 @@ class OrganData { OrganModel( id: 'left_ribs', name: 'chest', + nameAr: 'الصدر', description: 'Left Ribs', + descriptionAr: 'الأضلاع اليسرى', bodyView: BodyView.front, position: OrganPosition(x: 0.38, y: 0.35), ), OrganModel( id: 'right_ribs', name: 'chest', + nameAr: 'الصدر', description: 'Right Ribs', + descriptionAr: 'الأضلاع اليمنى', bodyView: BodyView.front, position: OrganPosition(x: 0.64, y: 0.35), ), @@ -102,21 +128,27 @@ class OrganData { OrganModel( id: 'upper_abdomen', name: 'upper_abdomen', + nameAr: 'البطن_العلوي', description: 'Upper Abdomen', + descriptionAr: 'أعلى البطن', bodyView: BodyView.front, position: OrganPosition(x: 0.51, y: 0.31), ), OrganModel( id: 'navel', name: 'mid_abdomen', + nameAr: 'البطن_الأوسط', description: 'Navel', + descriptionAr: 'السرة', bodyView: BodyView.front, position: OrganPosition(x: 0.51, y: 0.38), ), OrganModel( id: 'lower_abdomen', name: 'lower_abdomen', + nameAr: 'البطن_السفلي', description: 'Lower Abdomen', + descriptionAr: 'أسفل البطن', bodyView: BodyView.front, position: OrganPosition(x: 0.51, y: 0.44), ), @@ -125,14 +157,18 @@ class OrganData { OrganModel( id: 'left_groin', name: 'sexual_organs', + nameAr: 'الأعضاء_التناسلية', description: 'Left Groin', + descriptionAr: 'الفخذ الأيسر', bodyView: BodyView.front, position: OrganPosition(x: 0.42, y: 0.49), ), OrganModel( id: 'right_groin', name: 'sexual_organs', + nameAr: 'الأعضاء_التناسلية', description: 'Right Groin', + descriptionAr: 'الفخذ الأيمن', bodyView: BodyView.front, position: OrganPosition(x: 0.59, y: 0.49), ), @@ -142,21 +178,27 @@ class OrganData { OrganModel( id: 'left_elbow', name: 'elbow', + nameAr: 'الكوع', description: 'Left Elbow', + descriptionAr: 'الكوع الأيسر', bodyView: BodyView.front, position: OrganPosition(x: 0.21, y: 0.35), ), OrganModel( id: 'left_forearm', name: 'forearm', + nameAr: 'الساعد', description: 'Left Forearm', + descriptionAr: 'الساعد الأيسر', bodyView: BodyView.front, position: OrganPosition(x: 0.19, y: 0.43), ), OrganModel( id: 'left_wrist', name: 'hand', + nameAr: 'اليد', description: 'Left Wrist', + descriptionAr: 'المعصم الأيسر', bodyView: BodyView.front, position: OrganPosition(x: 0.14, y: 0.5), ), @@ -166,21 +208,27 @@ class OrganData { OrganModel( id: 'right_elbow', name: 'elbow', + nameAr: 'الكوع', description: 'Right Elbow', + descriptionAr: 'الكوع الأيمن', bodyView: BodyView.front, position: OrganPosition(x: 0.79, y: 0.35), ), OrganModel( id: 'right_forearm', name: 'forearm', + nameAr: 'الساعد', description: 'Right Forearm', + descriptionAr: 'الساعد الأيمن', bodyView: BodyView.front, position: OrganPosition(x: 0.81, y: 0.43), ), OrganModel( id: 'right_wrist', name: 'hand', + nameAr: 'اليد', description: 'Right Wrist', + descriptionAr: 'المعصم الأيمن', bodyView: BodyView.front, position: OrganPosition(x: 0.85, y: 0.5), ), @@ -189,28 +237,36 @@ class OrganData { OrganModel( id: 'left_thigh', name: 'thigh', + nameAr: 'الفخذ', description: 'Left Thigh', + descriptionAr: 'الفخذ الأيسر', bodyView: BodyView.front, position: OrganPosition(x: 0.4, y: 0.60), ), OrganModel( id: 'left_knee', name: 'knee', + nameAr: 'الركبة', description: 'Left Knee', + descriptionAr: 'الركبة اليسرى', bodyView: BodyView.front, position: OrganPosition(x: 0.4, y: 0.72), ), OrganModel( id: 'left_shin', name: 'lower_leg', + nameAr: 'الساق_السفلي', description: 'Left Shin', + descriptionAr: 'الساق اليسرى', bodyView: BodyView.front, position: OrganPosition(x: 0.41, y: 0.82), ), OrganModel( id: 'left_ankle', name: 'foot', + nameAr: 'القدم', description: 'Left Ankle', + descriptionAr: 'الكاحل الأيسر', bodyView: BodyView.front, position: OrganPosition(x: 0.43, y: 0.91), ), @@ -219,47 +275,363 @@ class OrganData { OrganModel( id: 'right_thigh', name: 'thigh', + nameAr: 'الفخذ', description: 'Right Thigh', + descriptionAr: 'الفخذ الأيمن', bodyView: BodyView.front, position: OrganPosition(x: 0.65, y: 0.60), ), OrganModel( id: 'right_knee', name: 'knee', + nameAr: 'الركبة', description: 'Right Knee', + descriptionAr: 'الركبة اليمنى', bodyView: BodyView.front, position: OrganPosition(x: 0.6, y: 0.72), ), OrganModel( id: 'right_shin', name: 'lower_leg', + nameAr: 'الساق_السفلي', description: 'Right Shin', + descriptionAr: 'الساق اليمنى', bodyView: BodyView.front, position: OrganPosition(x: 0.57, y: 0.82), ), OrganModel( id: 'right_ankle', name: 'foot', + nameAr: 'القدم', description: 'Right Ankle', + descriptionAr: 'الكاحل الأيمن', + bodyView: BodyView.front, + position: OrganPosition(x: 0.57, y: 0.91), + ), + ]; + static const List frontViewOrgansForFemale = [ + // ========== HEAD & FACE ========== + OrganModel( + id: 'head', + name: 'whole_head', + nameAr: 'الرأس_كامل', + description: 'Head', + descriptionAr: 'الرأس', + bodyView: BodyView.front, + position: OrganPosition(x: 0.54, y: 0.05), + ), + OrganModel( + id: 'left_eye', + name: 'eyes', + nameAr: 'العيون', + description: 'Left Eye', + descriptionAr: 'العين اليسرى', + bodyView: BodyView.front, + position: OrganPosition(x: 0.49, y: 0.095), + ), + OrganModel( + id: 'right_eye', + name: 'eyes', + nameAr: 'العيون', + description: 'Right Eye', + descriptionAr: 'العين اليمنى', + bodyView: BodyView.front, + position: OrganPosition(x: 0.6, y: 0.095), + ), + OrganModel( + id: 'nose_mouth', + name: 'oral_cavity', + nameAr: 'تجويف_الفم', + description: 'Nose/Mouth', + descriptionAr: 'الأنف/الفم', + bodyView: BodyView.front, + position: OrganPosition(x: 0.54, y: 0.12), + ), + + // ========== NECK ========== + OrganModel( + id: 'throat', + name: 'neck_or_throat', + nameAr: 'الرقبة_أو_الحلق', + description: 'Throat', + descriptionAr: 'الحلق', + bodyView: BodyView.front, + position: OrganPosition(x: 0.54, y: 0.17), + ), + + // ========== SHOULDERS ========== + OrganModel( + id: 'left_shoulder', + name: 'upper_arm', + nameAr: 'الذراع_العلوي', + description: 'Left Shoulder', + descriptionAr: 'الكتف الأيسر', + bodyView: BodyView.front, + position: OrganPosition(x: 0.29, y: 0.22), + ), + OrganModel( + id: 'right_shoulder', + name: 'upper_arm', + nameAr: 'الذراع_العلوي', + description: 'Right Shoulder', + descriptionAr: 'الكتف الأيمن', + bodyView: BodyView.front, + position: OrganPosition(x: 0.75, y: 0.22), + ), + + // ========== CHEST ========== + OrganModel( + id: 'left_chest', + name: 'chest', + nameAr: 'الصدر', + description: 'Left Chest', + descriptionAr: 'الصدر الأيسر', + bodyView: BodyView.front, + position: OrganPosition(x: 0.4, y: 0.22), + ), + OrganModel( + id: 'center_chest', + name: 'chest', + nameAr: 'الصدر', + description: 'Center Chest', + descriptionAr: 'وسط الصدر', + bodyView: BodyView.front, + position: OrganPosition(x: 0.52, y: 0.25), + ), + OrganModel( + id: 'right_chest', + name: 'chest', + nameAr: 'الصدر', + description: 'Right Chest', + descriptionAr: 'الصدر الأيمن', + bodyView: BodyView.front, + position: OrganPosition(x: 0.65, y: 0.22), + ), + + // ========== RIBS ========== + OrganModel( + id: 'left_ribs', + name: 'chest', + nameAr: 'الصدر', + description: 'Left Ribs', + descriptionAr: 'الأضلاع اليسرى', + bodyView: BodyView.front, + position: OrganPosition(x: 0.38, y: 0.35), + ), + OrganModel( + id: 'right_ribs', + name: 'chest', + nameAr: 'الصدر', + description: 'Right Ribs', + descriptionAr: 'الأضلاع اليمنى', + bodyView: BodyView.front, + position: OrganPosition(x: 0.64, y: 0.35), + ), + + // ========== ABDOMEN ========== + OrganModel( + id: 'upper_abdomen', + name: 'upper_abdomen', + nameAr: 'البطن_العلوي', + description: 'Upper Abdomen', + descriptionAr: 'أعلى البطن', + bodyView: BodyView.front, + position: OrganPosition(x: 0.51, y: 0.31), + ), + OrganModel( + id: 'navel', + name: 'mid_abdomen', + nameAr: 'البطن_الأوسط', + description: 'Navel', + descriptionAr: 'السرة', + bodyView: BodyView.front, + position: OrganPosition(x: 0.51, y: 0.38), + ), + OrganModel( + id: 'lower_abdomen', + name: 'lower_abdomen', + nameAr: 'البطن_السفلي', + description: 'Lower Abdomen', + descriptionAr: 'أسفل البطن', + bodyView: BodyView.front, + position: OrganPosition(x: 0.51, y: 0.44), + ), + + // ========== PELVIS ========== + OrganModel( + id: 'left_groin', + name: 'sexual_organs', + nameAr: 'الأعضاء_التناسلية', + description: 'Left Groin', + descriptionAr: 'الفخذ الأيسر', + bodyView: BodyView.front, + position: OrganPosition(x: 0.42, y: 0.49), + ), + OrganModel( + id: 'right_groin', + name: 'sexual_organs', + nameAr: 'الأعضاء_التناسلية', + description: 'Right Groin', + descriptionAr: 'الفخذ الأيمن', + bodyView: BodyView.front, + position: OrganPosition(x: 0.59, y: 0.49), + ), + + // ========== LEFT ARM ========== + + OrganModel( + id: 'left_elbow', + name: 'elbow', + nameAr: 'الكوع', + description: 'Left Elbow', + descriptionAr: 'الكوع الأيسر', + bodyView: BodyView.front, + position: OrganPosition(x: 0.24, y: 0.35), + ), + OrganModel( + id: 'left_forearm', + name: 'forearm', + nameAr: 'الساعد', + description: 'Left Forearm', + descriptionAr: 'الساعد الأيسر', + bodyView: BodyView.front, + position: OrganPosition(x: 0.24, y: 0.43), + ), + OrganModel( + id: 'left_wrist', + name: 'hand', + nameAr: 'اليد', + description: 'Left Wrist', + descriptionAr: 'المعصم الأيسر', + bodyView: BodyView.front, + position: OrganPosition(x: 0.17, y: 0.49), + ), + + // ========== RIGHT ARM ========== + + OrganModel( + id: 'right_elbow', + name: 'elbow', + nameAr: 'الكوع', + description: 'Right Elbow', + descriptionAr: 'الكوع الأيمن', + bodyView: BodyView.front, + position: OrganPosition(x: 0.76, y: 0.35), + ), + OrganModel( + id: 'right_forearm', + name: 'forearm', + nameAr: 'الساعد', + description: 'Right Forearm', + descriptionAr: 'الساعد الأيمن', + bodyView: BodyView.front, + position: OrganPosition(x: 0.76, y: 0.43), + ), + OrganModel( + id: 'right_wrist', + name: 'hand', + nameAr: 'اليد', + description: 'Right Wrist', + descriptionAr: 'المعصم الأيمن', + bodyView: BodyView.front, + position: OrganPosition(x: 0.83, y: 0.5), + ), + + // ========== LEFT LEG ========== + OrganModel( + id: 'left_thigh', + name: 'thigh', + nameAr: 'الفخذ', + description: 'Left Thigh', + descriptionAr: 'الفخذ الأيسر', + bodyView: BodyView.front, + position: OrganPosition(x: 0.4, y: 0.60), + ), + OrganModel( + id: 'left_knee', + name: 'knee', + nameAr: 'الركبة', + description: 'Left Knee', + descriptionAr: 'الركبة اليسرى', + bodyView: BodyView.front, + position: OrganPosition(x: 0.4, y: 0.72), + ), + OrganModel( + id: 'left_shin', + name: 'lower_leg', + nameAr: 'الساق_السفلي', + description: 'Left Shin', + descriptionAr: 'الساق اليسرى', + bodyView: BodyView.front, + position: OrganPosition(x: 0.41, y: 0.82), + ), + OrganModel( + id: 'left_ankle', + name: 'foot', + nameAr: 'القدم', + description: 'Left Ankle', + descriptionAr: 'الكاحل الأيسر', + bodyView: BodyView.front, + position: OrganPosition(x: 0.43, y: 0.91), + ), + + // ========== RIGHT LEG ========== + OrganModel( + id: 'right_thigh', + name: 'thigh', + nameAr: 'الفخذ', + description: 'Right Thigh', + descriptionAr: 'الفخذ الأيمن', + bodyView: BodyView.front, + position: OrganPosition(x: 0.65, y: 0.60), + ), + OrganModel( + id: 'right_knee', + name: 'knee', + nameAr: 'الركبة', + description: 'Right Knee', + descriptionAr: 'الركبة اليمنى', + bodyView: BodyView.front, + position: OrganPosition(x: 0.6, y: 0.72), + ), + OrganModel( + id: 'right_shin', + name: 'lower_leg', + nameAr: 'الساق_السفلي', + description: 'Right Shin', + descriptionAr: 'الساق اليمنى', + bodyView: BodyView.front, + position: OrganPosition(x: 0.59, y: 0.82), + ), + OrganModel( + id: 'right_ankle', + name: 'foot', + nameAr: 'القدم', + description: 'Right Ankle', + descriptionAr: 'الكاحل الأيمن', bodyView: BodyView.front, position: OrganPosition(x: 0.57, y: 0.91), ), ]; // ==================== BACK VIEW ==================== - static const List backViewOrgans = [ + static const List backViewOrgansForMale = [ // ========== HEAD & NECK ========== OrganModel( id: 'back_of_head', name: 'whole_head', + nameAr: 'الرأس_كامل', description: 'Back of Head', + descriptionAr: 'مؤخرة الرأس', bodyView: BodyView.back, position: OrganPosition(x: 0.50, y: 0.04), ), OrganModel( id: 'neck_back', name: 'nape_of_neck', + nameAr: 'مؤخرة_الرقبة', description: 'Neck', + descriptionAr: 'الرقبة', bodyView: BodyView.back, position: OrganPosition(x: 0.50, y: 0.14), ), @@ -268,14 +640,18 @@ class OrganData { OrganModel( id: 'left_shoulder_back', name: 'upper_arm', + nameAr: 'الذراع_العلوي', description: 'Left Shoulder', + descriptionAr: 'الكتف الأيسر', bodyView: BodyView.back, position: OrganPosition(x: 0.33, y: 0.19), ), OrganModel( id: 'right_shoulder_back', name: 'upper_arm', + nameAr: 'الذراع_العلوي', description: 'Right Shoulder', + descriptionAr: 'الكتف الأيمن', bodyView: BodyView.back, position: OrganPosition(x: 0.67, y: 0.19), ), @@ -284,21 +660,27 @@ class OrganData { OrganModel( id: 'upper_spine', name: 'back', + nameAr: 'الظهر', description: 'Upper Spine', + descriptionAr: 'العمود الفقري العلوي', bodyView: BodyView.back, position: OrganPosition(x: 0.50, y: 0.23), ), OrganModel( id: 'left_upper_back', name: 'back', + nameAr: 'الظهر', description: 'Left Upper Back', + descriptionAr: 'أعلى الظهر الأيسر', bodyView: BodyView.back, position: OrganPosition(x: 0.38, y: 0.28), ), OrganModel( id: 'right_upper_back', name: 'back', + nameAr: 'الظهر', description: 'Right Upper Back', + descriptionAr: 'أعلى الظهر الأيمن', bodyView: BodyView.back, position: OrganPosition(x: 0.62, y: 0.28), ), @@ -307,21 +689,27 @@ class OrganData { OrganModel( id: 'mid_spine', name: 'back', + nameAr: 'الظهر', description: 'Mid Spine', + descriptionAr: 'العمود الفقري الأوسط', bodyView: BodyView.back, position: OrganPosition(x: 0.50, y: 0.35), ), OrganModel( id: 'left_mid_back', name: 'back', + nameAr: 'الظهر', description: 'Left Mid Back', + descriptionAr: 'وسط الظهر الأيسر', bodyView: BodyView.back, position: OrganPosition(x: 0.38, y: 0.35), ), OrganModel( id: 'right_mid_back', name: 'back', + nameAr: 'الظهر', description: 'Right Mid Back', + descriptionAr: 'وسط الظهر الأيمن', bodyView: BodyView.back, position: OrganPosition(x: 0.62, y: 0.35), ), @@ -330,21 +718,27 @@ class OrganData { OrganModel( id: 'lower_spine', name: 'lower_back', + nameAr: 'أسفل_الظهر', description: 'Lower Back', + descriptionAr: 'أسفل الظهر', bodyView: BodyView.back, position: OrganPosition(x: 0.50, y: 0.43), ), OrganModel( id: 'left_lower_back', name: 'lower_back', + nameAr: 'أسفل_الظهر', description: 'Left Lower Back', + descriptionAr: 'أسفل الظهر الأيسر', bodyView: BodyView.back, position: OrganPosition(x: 0.40, y: 0.43), ), OrganModel( id: 'right_lower_back', name: 'lower_back', + nameAr: 'أسفل_الظهر', description: 'Right Lower Back', + descriptionAr: 'أسفل الظهر الأيمن', bodyView: BodyView.back, position: OrganPosition(x: 0.60, y: 0.43), ), @@ -353,21 +747,27 @@ class OrganData { OrganModel( id: 'sacrum', name: 'lower_back', + nameAr: 'أسفل_الظهر', description: 'Sacrum', + descriptionAr: 'العجز', bodyView: BodyView.back, position: OrganPosition(x: 0.50, y: 0.52), ), OrganModel( id: 'left_glute', name: 'buttocks', + nameAr: 'الأرداف', description: 'Left Glute', + descriptionAr: 'الأرداف اليسرى', bodyView: BodyView.back, position: OrganPosition(x: 0.45, y: 0.56), ), OrganModel( id: 'right_glute', name: 'buttocks', + nameAr: 'الأرداف', description: 'Right Glute', + descriptionAr: 'الأرداف اليمنى', bodyView: BodyView.back, position: OrganPosition(x: 0.55, y: 0.56), ), @@ -376,45 +776,266 @@ class OrganData { OrganModel( id: 'left_hamstring', name: 'thigh', + nameAr: 'الفخذ', description: 'Left Hamstring', + descriptionAr: 'عضلة الفخذ الخلفية اليسرى', bodyView: BodyView.back, position: OrganPosition(x: 0.43, y: 0.65), ), OrganModel( id: 'right_hamstring', name: 'thigh', + nameAr: 'الفخذ', description: 'Right Hamstring', + descriptionAr: 'عضلة الفخذ الخلفية اليمنى', bodyView: BodyView.back, position: OrganPosition(x: 0.57, y: 0.65), ), OrganModel( id: 'left_calf', name: 'lower_leg', + nameAr: 'الساق_السفلي', + description: 'Left Calf', + descriptionAr: 'ربلة الساق اليسرى', + bodyView: BodyView.back, + position: OrganPosition(x: 0.43, y: 0.79), + ), + OrganModel( + id: 'right_calf', + name: 'lower_leg', + nameAr: 'الساق_السفلي', + description: 'Right Calf', + descriptionAr: 'ربلة الساق اليمنى', + bodyView: BodyView.back, + position: OrganPosition(x: 0.57, y: 0.79), + ), + ]; + static const List backViewOrgansForFemale = [ + // ========== HEAD & NECK ========== + OrganModel( + id: 'back_of_head', + name: 'whole_head', + nameAr: 'الرأس_كامل', + description: 'Back of Head', + descriptionAr: 'مؤخرة الرأس', + bodyView: BodyView.back, + position: OrganPosition(x: 0.48, y: 0.055), + ), + OrganModel( + id: 'neck_back', + name: 'nape_of_neck', + nameAr: 'مؤخرة_الرقبة', + description: 'Neck', + descriptionAr: 'الرقبة', + bodyView: BodyView.back, + position: OrganPosition(x: 0.50, y: 0.14), + ), + + // ========== SHOULDERS ========== + OrganModel( + id: 'left_shoulder_back', + name: 'upper_arm', + nameAr: 'الذراع_العلوي', + description: 'Left Shoulder', + descriptionAr: 'الكتف الأيسر', + bodyView: BodyView.back, + position: OrganPosition(x: 0.3, y: 0.2), + ), + OrganModel( + id: 'right_shoulder_back', + name: 'upper_arm', + nameAr: 'الذراع_العلوي', + description: 'Right Shoulder', + descriptionAr: 'الكتف الأيمن', + bodyView: BodyView.back, + position: OrganPosition(x: 0.69, y: 0.2), + ), + + // ========== UPPER BACK ========== + OrganModel( + id: 'upper_spine', + name: 'back', + nameAr: 'الظهر', + description: 'Upper Spine', + descriptionAr: 'العمود الفقري العلوي', + bodyView: BodyView.back, + position: OrganPosition(x: 0.50, y: 0.23), + ), + OrganModel( + id: 'left_upper_back', + name: 'back', + nameAr: 'الظهر', + description: 'Left Upper Back', + descriptionAr: 'أعلى الظهر الأيسر', + bodyView: BodyView.back, + position: OrganPosition(x: 0.38, y: 0.28), + ), + OrganModel( + id: 'right_upper_back', + name: 'back', + nameAr: 'الظهر', + description: 'Right Upper Back', + descriptionAr: 'أعلى الظهر الأيمن', + bodyView: BodyView.back, + position: OrganPosition(x: 0.62, y: 0.28), + ), + + // ========== MID BACK ========== + OrganModel( + id: 'mid_spine', + name: 'back', + nameAr: 'الظهر', + description: 'Mid Spine', + descriptionAr: 'العمود الفقري الأوسط', + bodyView: BodyView.back, + position: OrganPosition(x: 0.50, y: 0.35), + ), + OrganModel( + id: 'left_mid_back', + name: 'back', + nameAr: 'الظهر', + description: 'Left Mid Back', + descriptionAr: 'وسط الظهر الأيسر', + bodyView: BodyView.back, + position: OrganPosition(x: 0.38, y: 0.35), + ), + OrganModel( + id: 'right_mid_back', + name: 'back', + nameAr: 'الظهر', + description: 'Right Mid Back', + descriptionAr: 'وسط الظهر الأيمن', + bodyView: BodyView.back, + position: OrganPosition(x: 0.62, y: 0.35), + ), + + // ========== LOWER BACK ========== + OrganModel( + id: 'lower_spine', + name: 'lower_back', + nameAr: 'أسفل_الظهر', + description: 'Lower Back', + descriptionAr: 'أسفل الظهر', + bodyView: BodyView.back, + position: OrganPosition(x: 0.50, y: 0.43), + ), + OrganModel( + id: 'left_lower_back', + name: 'lower_back', + nameAr: 'أسفل_الظهر', + description: 'Left Lower Back', + descriptionAr: 'أسفل الظهر الأيسر', + bodyView: BodyView.back, + position: OrganPosition(x: 0.36, y: 0.43), + ), + OrganModel( + id: 'right_lower_back', + name: 'lower_back', + nameAr: 'أسفل_الظهر', + description: 'Right Lower Back', + descriptionAr: 'أسفل الظهر الأيمن', + bodyView: BodyView.back, + position: OrganPosition(x: 0.65, y: 0.43), + ), + + // ========== GLUTES ========== + OrganModel( + id: 'sacrum', + name: 'lower_back', + nameAr: 'أسفل_الظهر', + description: 'Sacrum', + descriptionAr: 'العجز', + bodyView: BodyView.back, + position: OrganPosition(x: 0.50, y: 0.5), + ), + OrganModel( + id: 'left_glute', + name: 'buttocks', + nameAr: 'الأرداف', + description: 'Left Glute', + descriptionAr: 'الأرداف اليسرى', + bodyView: BodyView.back, + position: OrganPosition(x: 0.36, y: 0.52), + ), + OrganModel( + id: 'right_glute', + name: 'buttocks', + nameAr: 'الأرداف', + description: 'Right Glute', + descriptionAr: 'الأرداف اليمنى', + bodyView: BodyView.back, + position: OrganPosition(x: 0.61, y: 0.52), + ), + + // ========== LEGS ========== + OrganModel( + id: 'left_hamstring', + name: 'thigh', + nameAr: 'الفخذ', + description: 'Left Hamstring', + descriptionAr: 'عضلة الفخذ الخلفية اليسرى', + bodyView: BodyView.back, + position: OrganPosition(x: 0.41, y: 0.62), + ), + OrganModel( + id: 'right_hamstring', + name: 'thigh', + nameAr: 'الفخذ', + description: 'Right Hamstring', + descriptionAr: 'عضلة الفخذ الخلفية اليمنى', + bodyView: BodyView.back, + position: OrganPosition(x: 0.62, y: 0.62), + ), + OrganModel( + id: 'left_calf', + name: 'lower_leg', + nameAr: 'الساق_السفلي', description: 'Left Calf', + descriptionAr: 'ربلة الساق اليسرى', bodyView: BodyView.back, position: OrganPosition(x: 0.43, y: 0.79), ), OrganModel( id: 'right_calf', name: 'lower_leg', + nameAr: 'الساق_السفلي', description: 'Right Calf', + descriptionAr: 'ربلة الساق اليمنى', bodyView: BodyView.back, position: OrganPosition(x: 0.57, y: 0.79), ), ]; // Helper methods - static List getOrgansForView(BodyView view) { - return view == BodyView.front ? frontViewOrgans : backViewOrgans; + // Accept genderKey (e.g., 'male' / 'female' or Arabic 'ذكر' / 'أنثى') to return gender-specific organ sets + static List getOrgansForView(BodyView view, {String? genderKey}) { + final bool isFemale = genderKey != null && (genderKey.toLowerCase() == 'female' || genderKey == 'أنثى' || genderKey == '2'); + + + if (view == BodyView.front) { + return isFemale ? frontViewOrgansForFemale : frontViewOrgansForMale; + } else { + return isFemale ? backViewOrgansForFemale : backViewOrgansForMale; + } } - static List getAllOrgans() { - return [...frontViewOrgans, ...backViewOrgans]; + /// Return all organs (front + back) for the selected gender. + /// If genderKey is null, default to male to preserve previous behavior. + static List getAllOrgans({String? genderKey}) { + final bool isFemale = genderKey != null && (genderKey.toLowerCase() == 'female' || genderKey == 'أنثى' || genderKey == '2'); + + if (isFemale) { + return [...frontViewOrgansForFemale, ...backViewOrgansForFemale]; + } + + // default: male + return [...frontViewOrgansForMale, ...backViewOrgansForMale]; } - static OrganModel? getOrganById(String id) { + /// Find organ by id within the organs of the selected gender (or male if not specified) + static OrganModel? getOrganById(String id, {String? genderKey}) { try { - return getAllOrgans().firstWhere((organ) => organ.id == id); + return getAllOrgans(genderKey: genderKey).firstWhere((organ) => organ.id == id); } catch (e) { return null; } diff --git a/lib/features/symptoms_checker/models/conditions_model.dart b/lib/features/symptoms_checker/models/conditions_model.dart index 774d8c38..af66c215 100644 --- a/lib/features/symptoms_checker/models/conditions_model.dart +++ b/lib/features/symptoms_checker/models/conditions_model.dart @@ -1,97 +1,97 @@ -import 'package:flutter/material.dart'; -import 'package:hmg_patient_app_new/core/enums.dart'; - -class ConditionsModel { - final IconData icon; - final String title; - final int percentage; - final String tagText; - final String clinic; - final List symptoms; - final String description; - final String? monitorNote; - final String? appointmentLabel; - final PossibleConditionsSeverityEnum possibleConditionsSeverityEnum; - - ConditionsModel({ - required this.icon, - required this.title, - required this.percentage, - required this.tagText, - required this.clinic, - required this.symptoms, - required this.description, - required this.possibleConditionsSeverityEnum, - this.monitorNote, - this.appointmentLabel, - }); -} - -List dummyConditions = [ - ConditionsModel( - icon: Icons.psychology_alt, - possibleConditionsSeverityEnum: PossibleConditionsSeverityEnum.seekMedicalAdvice, - title: "Migraine", - percentage: 87, - tagText: "Seek Medical Advice", - clinic: "Internal Medicine Clinic", - symptoms: ["Headache", "Nausea", "Sensitivity to light"], - description: "A migraine is a type of headache that can cause severe throbbing pain, usually on one side of the head.", - appointmentLabel: "Book Appointment", - ), - ConditionsModel( - icon: Icons.deblur, - title: "Tension Headache", - percentage: 37, - tagText: "Monitor", - monitorNote: "No need to visit doctor", - clinic: "GP Clinic", - symptoms: ["Mild head pressure", "Scalp tenderness"], - description: "A tension-type headache is generally a mild to moderate pain that feels like a tight band around your head.", - possibleConditionsSeverityEnum: PossibleConditionsSeverityEnum.monitorOnly, - ), - ConditionsModel( - icon: Icons.medication_liquid, - title: "Meningitis", - percentage: 28, - tagText: "Emergency", - clinic: "Neurology Clinic", - symptoms: ["Mild head pressure", "Scalp tenderness"], - description: "A tension-type headache is generally a mild to moderate pain that feels like a tight band around your head.", - appointmentLabel: "Book Appointment", - possibleConditionsSeverityEnum: PossibleConditionsSeverityEnum.emergency, - ), - ConditionsModel( - icon: Icons.psychology_alt, - title: "Migraine", - percentage: 87, - tagText: "Seek Medical Advice", - clinic: "Internal Medicine Clinic", - symptoms: ["Headache", "Nausea", "Sensitivity to light"], - description: "A migraine is a type of headache that can cause severe throbbing pain, usually on one side of the head.", - appointmentLabel: "Book Appointment", - possibleConditionsSeverityEnum: PossibleConditionsSeverityEnum.seekMedicalAdvice, - ), - ConditionsModel( - icon: Icons.deblur, - title: "Tension Headache", - percentage: 37, - tagText: "Monitor", - monitorNote: "No need to visit doctor", - clinic: "GP Clinic", - symptoms: ["Mild head pressure", "Scalp tenderness"], - description: "A tension-type headache is generally a mild to moderate pain that feels like a tight band around your head.", - possibleConditionsSeverityEnum: PossibleConditionsSeverityEnum.monitorOnly, - ), - ConditionsModel( - icon: Icons.medication_liquid, - title: "Meningitis", - percentage: 28, - tagText: "Emergency", - clinic: "Neurology Clinic", - symptoms: ["Mild head pressure", "Scalp tenderness"], - description: "A tension-type headache is generally a mild to moderate pain that feels like a tight band around your head.", - appointmentLabel: "Book Appointment", - possibleConditionsSeverityEnum: PossibleConditionsSeverityEnum.emergency, - ), -]; +// import 'package:flutter/material.dart'; +// import 'package:hmg_patient_app_new/core/enums.dart'; +// +// class ConditionsModel { +// final IconData icon; +// final String title; +// final int percentage; +// final String tagText; +// final String clinic; +// final List symptoms; +// final String description; +// final String? monitorNote; +// final String? appointmentLabel; +// final PossibleConditionsSeverityEnum possibleConditionsSeverityEnum; +// +// ConditionsModel({ +// required this.icon, +// required this.title, +// required this.percentage, +// required this.tagText, +// required this.clinic, +// required this.symptoms, +// required this.description, +// required this.possibleConditionsSeverityEnum, +// this.monitorNote, +// this.appointmentLabel, +// }); +// } +// +// List dummyConditions = [ +// ConditionsModel( +// icon: Icons.psychology_alt, +// possibleConditionsSeverityEnum: PossibleConditionsSeverityEnum.seekMedicalAdvice, +// title: "Migraine", +// percentage: 87, +// tagText: "Seek Medical Advice", +// clinic: "Internal Medicine Clinic", +// symptoms: ["Headache", "Nausea", "Sensitivity to light"], +// description: "A migraine is a type of headache that can cause severe throbbing pain, usually on one side of the head.", +// appointmentLabel: "Book Appointment", +// ), +// ConditionsModel( +// icon: Icons.deblur, +// title: "Tension Headache", +// percentage: 37, +// tagText: "Monitor", +// monitorNote: "No need to visit doctor", +// clinic: "GP Clinic", +// symptoms: ["Mild head pressure", "Scalp tenderness"], +// description: "A tension-type headache is generally a mild to moderate pain that feels like a tight band around your head.", +// possibleConditionsSeverityEnum: PossibleConditionsSeverityEnum.monitorOnly, +// ), +// ConditionsModel( +// icon: Icons.medication_liquid, +// title: "Meningitis", +// percentage: 28, +// tagText: "Emergency", +// clinic: "Neurology Clinic", +// symptoms: ["Mild head pressure", "Scalp tenderness"], +// description: "A tension-type headache is generally a mild to moderate pain that feels like a tight band around your head.", +// appointmentLabel: "Book Appointment", +// possibleConditionsSeverityEnum: PossibleConditionsSeverityEnum.emergency, +// ), +// ConditionsModel( +// icon: Icons.psychology_alt, +// title: "Migraine", +// percentage: 87, +// tagText: "Seek Medical Advice", +// clinic: "Internal Medicine Clinic", +// symptoms: ["Headache", "Nausea", "Sensitivity to light"], +// description: "A migraine is a type of headache that can cause severe throbbing pain, usually on one side of the head.", +// appointmentLabel: "Book Appointment", +// possibleConditionsSeverityEnum: PossibleConditionsSeverityEnum.seekMedicalAdvice, +// ), +// ConditionsModel( +// icon: Icons.deblur, +// title: "Tension Headache", +// percentage: 37, +// tagText: "Monitor", +// monitorNote: "No need to visit doctor", +// clinic: "GP Clinic", +// symptoms: ["Mild head pressure", "Scalp tenderness"], +// description: "A tension-type headache is generally a mild to moderate pain that feels like a tight band around your head.", +// possibleConditionsSeverityEnum: PossibleConditionsSeverityEnum.monitorOnly, +// ), +// ConditionsModel( +// icon: Icons.medication_liquid, +// title: "Meningitis", +// percentage: 28, +// tagText: "Emergency", +// clinic: "Neurology Clinic", +// symptoms: ["Mild head pressure", "Scalp tenderness"], +// description: "A tension-type headache is generally a mild to moderate pain that feels like a tight band around your head.", +// appointmentLabel: "Book Appointment", +// possibleConditionsSeverityEnum: PossibleConditionsSeverityEnum.emergency, +// ), +// ]; diff --git a/lib/features/symptoms_checker/models/organ_model.dart b/lib/features/symptoms_checker/models/organ_model.dart index fab7dffd..9a871f39 100644 --- a/lib/features/symptoms_checker/models/organ_model.dart +++ b/lib/features/symptoms_checker/models/organ_model.dart @@ -3,29 +3,47 @@ import 'package:hmg_patient_app_new/core/enums.dart'; class OrganModel { final String id; final String name; + final String nameAr; final String description; + final String descriptionAr; final BodyView bodyView; final OrganPosition position; const OrganModel({ required this.id, required this.name, + required this.nameAr, required this.description, + required this.descriptionAr, required this.bodyView, required this.position, }); + /// Get localized name based on isArabic flag + String getLocalizedName(bool isArabic) { + return isArabic ? nameAr : name; + } + + /// Get localized description based on isArabic flag + String getLocalizedDescription(bool isArabic) { + return isArabic ? descriptionAr : description; + } + OrganModel copyWith({ String? id, String? name, + String? nameAr, String? description, + String? descriptionAr, BodyView? bodyView, OrganPosition? position, }) { return OrganModel( id: id ?? this.id, name: name ?? this.name, + nameAr: nameAr ?? this.nameAr, description: description ?? this.description, + descriptionAr: descriptionAr ?? this.descriptionAr, bodyView: bodyView ?? this.bodyView, position: position ?? this.position, ); diff --git a/lib/features/symptoms_checker/models/resp_models/risk_and_suggestions_response_model.dart b/lib/features/symptoms_checker/models/resp_models/risk_and_suggestions_response_model.dart index b2be4a27..9928d786 100644 --- a/lib/features/symptoms_checker/models/resp_models/risk_and_suggestions_response_model.dart +++ b/lib/features/symptoms_checker/models/resp_models/risk_and_suggestions_response_model.dart @@ -22,6 +22,8 @@ class RiskAndSuggestionsItemModel { final String? type; final String? name; final String? commonName; + final String? nameAr; + final String? commonNameAr; final String? language; RiskAndSuggestionsItemModel({ @@ -29,6 +31,8 @@ class RiskAndSuggestionsItemModel { this.type, this.name, this.commonName, + this.nameAr, + this.commonNameAr, this.language, }); @@ -38,6 +42,8 @@ class RiskAndSuggestionsItemModel { type: json['type'], name: json['name'], commonName: json['common_name'], + nameAr: json['nameAr'], + commonNameAr: json['common_nameAr'], language: json['language'], ); } @@ -48,12 +54,17 @@ class RiskAndSuggestionsItemModel { 'type': type, 'name': name, 'common_name': commonName, + 'nameAr': nameAr, + 'common_nameAr': commonNameAr, 'language': language, }; } - // Helper method to get display name - String getDisplayName() { + // Helper method to get display name based on locale + String getDisplayName({bool isArabic = false}) { + if (isArabic) { + return commonNameAr ?? nameAr ?? commonName ?? name ?? ''; + } return commonName ?? name ?? ''; } } diff --git a/lib/features/symptoms_checker/models/resp_models/triage_response_model.dart b/lib/features/symptoms_checker/models/resp_models/triage_response_model.dart index f0d7e904..da0f4b67 100644 --- a/lib/features/symptoms_checker/models/resp_models/triage_response_model.dart +++ b/lib/features/symptoms_checker/models/resp_models/triage_response_model.dart @@ -1,3 +1,5 @@ +import 'package:hmg_patient_app_new/core/enums.dart'; + class TriageDataDetails { final TriageQuestion? question; final List? conditions; @@ -170,13 +172,17 @@ class TriageCondition { final String? id; final String? name; final String? commonName; + final String? nameAr; + final String? commonNameAr; final double? probability; - final dynamic conditionDetails; + final ConditionDetails? conditionDetails; TriageCondition({ this.id, this.name, this.commonName, + this.nameAr, + this.commonNameAr, this.probability, this.conditionDetails, }); @@ -186,8 +192,10 @@ class TriageCondition { id: json['id'], name: json['name'], commonName: json['common_name'], + nameAr: json['nameAr'], + commonNameAr: json['common_nameAr'], probability: json['probability']?.toDouble(), - conditionDetails: json['condition_details'], + conditionDetails: json['condition_details'] != null ? ConditionDetails.fromJson(json['condition_details']) : null, ); } @@ -196,14 +204,96 @@ class TriageCondition { 'id': id, 'name': name, 'common_name': commonName, + 'nameAr': nameAr, + 'common_nameAr': commonNameAr, 'probability': probability, - 'condition_details': conditionDetails, + 'condition_details': conditionDetails?.toJson(), }; } + /// Get display name based on locale + String getDisplayName({bool isArabic = false}) { + if (isArabic) { + return commonNameAr ?? nameAr ?? commonName ?? name ?? ''; + } + return commonName ?? name ?? ''; + } + /// Get probability as percentage String getProbabilityPercentage() { if (probability == null) return '0%'; return '${(probability! * 100).toStringAsFixed(1)}%'; } } + +class ConditionDetails { + final String? icd10Code; + final ConditionCategory? category; + final SeverityEnum? severity; + final TriageLevelEnum? triageLevel; + final String? hint; + final bool? hasPatientEducation; + final String? prevalence; + final String? acuteness; + + ConditionDetails({ + this.icd10Code, + this.category, + this.severity, + this.triageLevel, + this.hint, + this.hasPatientEducation, + this.prevalence, + this.acuteness, + }); + + factory ConditionDetails.fromJson(Map json) { + return ConditionDetails( + icd10Code: json['icd10_code'], + category: json['category'] != null ? ConditionCategory.fromJson(json['category']) : null, + severity: json['severity'] != null ? SeverityEnumExtension.fromInt(json['severity']) : null, + triageLevel: json['triage_level'] != null ? TriageLevelEnumExtension.fromInt(json['triage_level']) : null, + hint: json['hint'], + hasPatientEducation: json['has_patient_education'], + prevalence: json['prevalence'], + acuteness: json['acuteness'], + ); + } + + Map toJson() { + return { + 'icd10_code': icd10Code, + 'category': category?.toJson(), + 'severity': severity?.toInt, + 'triage_level': triageLevel?.toInt, + 'hint': hint, + 'has_patient_education': hasPatientEducation, + 'prevalence': prevalence, + 'acuteness': acuteness, + }; + } +} + +class ConditionCategory { + final String? id; + final String? name; + + ConditionCategory({ + this.id, + this.name, + }); + + factory ConditionCategory.fromJson(Map json) { + return ConditionCategory( + id: json['id'], + name: json['name'], + ); + } + + Map toJson() { + return { + 'id': id, + 'name': name, + }; + } +} diff --git a/lib/features/symptoms_checker/symptoms_checker_repo.dart b/lib/features/symptoms_checker/symptoms_checker_repo.dart index c55c2f0c..c0214e18 100644 --- a/lib/features/symptoms_checker/symptoms_checker_repo.dart +++ b/lib/features/symptoms_checker/symptoms_checker_repo.dart @@ -12,50 +12,42 @@ import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class SymptomsCheckerRepo { - Future>> - getUserDetails({ + Future>> getUserDetails({ required String userName, required String password, }); - Future>> - getBodySymptomsByName({ + Future>> getBodySymptomsByName({ required List organNames, required String userSessionToken, required int gender, }); - Future>> - getRiskFactors({ + Future>> getRiskFactors({ required int age, - required String sex, + required int gender, required List evidenceIds, required String language, required String userSessionToken, - required int gender, required String sessionId, }); - Future>> - getSuggestions({ + Future>> getSuggestions({ required int age, - required String sex, + required int gender, required List evidenceIds, required String language, required String userSessionToken, required String sessionId, - required int gender, }); - Future>> - getDiagnosisForTriage({ + Future>> getDiagnosisForTriage({ required int age, - required String sex, + required int gender, required List evidenceIds, List>? triageEvidence, required String language, required String userSessionToken, - required int gender, required String sessionId, }); } @@ -64,16 +56,14 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { final ApiClient apiClient; final LoggerService loggerService; - SymptomsCheckerRepoImp( - {required this.apiClient, required this.loggerService}); + SymptomsCheckerRepoImp({required this.apiClient, required this.loggerService}); @override - Future>> - getUserDetails({ + Future>> getUserDetails({ required String userName, required String password, }) async { - Map body = {"userName": userName, "password": password}; + Map body = {"userName": userName, "password": password}; try { GenericApiModel? apiResponse; @@ -92,11 +82,9 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { // Parse response if it's a string - final Map responseData = - response is String ? jsonDecode(response) : response; + final Map responseData = response is String ? jsonDecode(response) : response; - SymptomsUserDetailsResponseModel symptomsUserDetailsResponseModel = - SymptomsUserDetailsResponseModel.fromJson(responseData); + SymptomsUserDetailsResponseModel symptomsUserDetailsResponseModel = SymptomsUserDetailsResponseModel.fromJson(responseData); apiResponse = GenericApiModel( messageStatus: messageStatus ?? 1, @@ -123,8 +111,7 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { } @override - Future>> - getBodySymptomsByName({ + Future>> getBodySymptomsByName({ required List organNames, required String userSessionToken, required int gender, @@ -154,8 +141,7 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { - BodySymptomResponseModel bodySymptomResponse = - BodySymptomResponseModel.fromJson(response); + BodySymptomResponseModel bodySymptomResponse = BodySymptomResponseModel.fromJson(response); apiResponse = GenericApiModel( messageStatus: messageStatus ?? 1, @@ -164,8 +150,7 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { data: bodySymptomResponse, ); } catch (e, stackTrace) { - loggerService - .logError("Error parsing GetBodySymptomsByName response: $e"); + loggerService.logError("Error parsing GetBodySymptomsByName response: $e"); loggerService.logError("StackTrace: $stackTrace"); failure = DataParsingFailure(e.toString()); } @@ -183,21 +168,19 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { } @override - Future>> - getRiskFactors({ + Future>> getRiskFactors({ required int age, - required String sex, + required int gender, required List evidenceIds, required String language, required String userSessionToken, - required int gender, required String sessionId, }) async { final Map body = { "age": { "value": age, }, - "sex": sex, + "gender": gender, "evidence": evidenceIds.map((id) => {"id": id}).toList(), "language": language, "generalId": sessionId, @@ -225,11 +208,9 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { // Parse response if it's a string - final Map responseData = - response is String ? jsonDecode(response) : response; + final Map responseData = response is String ? jsonDecode(response) : response; - RiskAndSuggestionsResponseModel riskFactorsResponse = - RiskAndSuggestionsResponseModel.fromJson(responseData); + RiskAndSuggestionsResponseModel riskFactorsResponse = RiskAndSuggestionsResponseModel.fromJson(responseData); apiResponse = GenericApiModel( messageStatus: messageStatus ?? 1, @@ -255,16 +236,14 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { } } - Future>> - getDiagnosisForTriage({ + @override + Future>> getDiagnosisForTriage({ required int age, - required String sex, + required int gender, required List evidenceIds, - List>? - triageEvidence, // Additional triage-specific evidence + List>? triageEvidence, // Additional triage-specific evidence required String language, required String userSessionToken, - required int gender, required String sessionId, }) async { // Build evidence list: combine initial symptoms with triage evidence @@ -284,7 +263,7 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { "age": { "value": age, }, - "sex": sex, + "gender": gender, "evidence": evidenceList, "language": language, "suggest_method": "diagnosis", @@ -313,13 +292,11 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { // Parse response if it's a string - final Map responseData = - response is String ? jsonDecode(response) : response; + final Map responseData = response is String ? jsonDecode(response) : response; final updatedResponseData = responseData['dataDetails']; - TriageDataDetails riskFactorsResponse = - TriageDataDetails.fromJson(updatedResponseData); + TriageDataDetails riskFactorsResponse = TriageDataDetails.fromJson(updatedResponseData); apiResponse = GenericApiModel( messageStatus: messageStatus ?? 1, @@ -328,8 +305,7 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { data: riskFactorsResponse, ); } catch (e, stackTrace) { - loggerService - .logError("Error parsing getDiagnosisForTriage response: $e"); + loggerService.logError("Error parsing getDiagnosisForTriage response: $e"); loggerService.logError("StackTrace: $stackTrace"); failure = DataParsingFailure(e.toString()); } @@ -347,21 +323,19 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { } @override - Future>> - getSuggestions({ + Future>> getSuggestions({ required int age, - required String sex, + required int gender, required List evidenceIds, required String language, required String userSessionToken, required String sessionId, - required int gender, }) async { final Map body = { "age": { "value": age, }, - "sex": sex, + "gender": gender, "evidence": evidenceIds.map((id) => {"id": id}).toList(), "language": language, "generalId": sessionId, @@ -389,11 +363,9 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { // Parse response if it's a string - final Map responseData = - response is String ? jsonDecode(response) : response; + final Map responseData = response is String ? jsonDecode(response) : response; - RiskAndSuggestionsResponseModel riskFactorsResponse = - RiskAndSuggestionsResponseModel.fromJson(responseData); + RiskAndSuggestionsResponseModel riskFactorsResponse = RiskAndSuggestionsResponseModel.fromJson(responseData); apiResponse = GenericApiModel( messageStatus: messageStatus ?? 1, diff --git a/lib/features/symptoms_checker/symptoms_checker_view_model.dart b/lib/features/symptoms_checker/symptoms_checker_view_model.dart index 73768fe0..66706709 100644 --- a/lib/features/symptoms_checker/symptoms_checker_view_model.dart +++ b/lib/features/symptoms_checker/symptoms_checker_view_model.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/data/organ_mapping_data.dart'; @@ -23,6 +23,23 @@ class SymptomsCheckerViewModel extends ChangeNotifier { required this.appState, }); + // Gender constants for both English and Arabic + static const String _maleEn = "male"; + static const String _femaleEn = "female"; + static const String _maleAr = "ذكر"; + static const String _femaleAr = "أنثى"; + + /// Convert gender string to integer (1 for male, 2 for female) + /// Handles both English and Arabic values + int _getGenderAsInt(String? gender) { + if (gender == null) return 1; // Default to male + final lowerGender = gender.toLowerCase(); + if (lowerGender == _maleEn || gender == _maleAr) { + return 1; + } + return 2; // Female or any other value + } + // State variables bool isBodyHidden = false; BodyView _currentView = BodyView.front; @@ -47,12 +64,9 @@ class SymptomsCheckerViewModel extends ChangeNotifier { TriageDataDetails? triageDataDetails; // Triage state - int? - _selectedTriageChoiceIndex; // Deprecated - keeping for backward compatibility - final Map _selectedTriageChoicesByItemId = - {}; // Map of itemId -> choiceIndex for multi-item questions - final List> _triageEvidenceList = - []; // Store triage evidence with proper format + int? _selectedTriageChoiceIndex; // Deprecated - keeping for backward compatibility + final Map _selectedTriageChoicesByItemId = {}; // Map of itemId -> choiceIndex for multi-item questions + final List> _triageEvidenceList = []; // Store triage evidence with proper format int _triageQuestionCount = 0; // Track number of triage questions answered // Selected risk factors tracking @@ -66,14 +80,13 @@ class SymptomsCheckerViewModel extends ChangeNotifier { // User Info Flow State int _userInfoCurrentPage = 0; - bool _isSinglePageEditMode = - false; // Track if editing single page or full flow + bool _isSinglePageEditMode = false; // Track if editing single page or full flow String? _selectedGender; DateTime? _dateOfBirth; int? _selectedAge; - double _selectedHeight = 170; + num _selectedHeight = 170; bool _isHeightCm = true; - double _selectedWeight = 60; + num _selectedWeight = 60; bool _isWeightKg = true; // Getters @@ -91,11 +104,25 @@ class SymptomsCheckerViewModel extends ChangeNotifier { int? get selectedAge => _selectedAge; - double? get selectedHeight => _selectedHeight; + setSelectedAge(int age) { + _selectedAge = age; + } + + setSelectedHeight(num height) { + _selectedHeight = height; + notifyListeners(); + } + + setSelectedWeight(num weight) { + _selectedWeight = weight; + notifyListeners(); + } + + num? get selectedHeight => _selectedHeight; bool get isHeightCm => _isHeightCm; - double? get selectedWeight => _selectedWeight; + num? get selectedWeight => _selectedWeight; bool get isWeightKg => _isWeightKg; @@ -107,17 +134,14 @@ class SymptomsCheckerViewModel extends ChangeNotifier { String? get tooltipOrganId => _tooltipOrganId; - String get currentSessionAuthToken => - symptomsUserDetailsResponseModel?.tokenDetails?.authToken ?? ""; + String get currentSessionAuthToken => symptomsUserDetailsResponseModel?.tokenDetails?.authToken ?? ""; - String get currentSessionId => - symptomsUserDetailsResponseModel?.sessionId ?? ""; + String get currentSessionId => symptomsUserDetailsResponseModel?.sessionId ?? ""; // Triage-related getters bool get shouldStopTriage => triageDataDetails?.shouldStop ?? false; - bool get hasEmergencyEvidence => - triageDataDetails?.hasEmergencyEvidence ?? false; + bool get hasEmergencyEvidence => triageDataDetails?.hasEmergencyEvidence ?? false; String? get currentInterviewToken => triageDataDetails?.interviewToken; @@ -137,15 +161,13 @@ class SymptomsCheckerViewModel extends ChangeNotifier { /// Check if all items in current question have been answered bool get areAllTriageItemsAnswered { - if (currentTriageQuestion?.items == null || - currentTriageQuestion!.items!.isEmpty) { + if (currentTriageQuestion?.items == null || currentTriageQuestion!.items!.isEmpty) { return false; } // Check if we have an answer for each item for (var item in currentTriageQuestion!.items!) { - if (item.id != null && - !_selectedTriageChoicesByItemId.containsKey(item.id)) { + if (item.id != null && !_selectedTriageChoicesByItemId.containsKey(item.id)) { return false; } } @@ -153,18 +175,12 @@ class SymptomsCheckerViewModel extends ChangeNotifier { } /// Get organs for current view - List get currentOrgans => - OrganData.getOrgansForView(_currentView); + List get currentOrgans => OrganData.getOrgansForView(_currentView, genderKey: _selectedGender); - /// Get all selected organs from both views + /// Get all selected organs from both views (for the selected gender) List get selectedOrgans { - final allOrgans = [ - ...OrganData.frontViewOrgans, - ...OrganData.backViewOrgans, - ]; - return allOrgans - .where((organ) => _selectedOrganIds.contains(organ.id)) - .toList(); + final allOrgans = OrganData.getAllOrgans(genderKey: _selectedGender); + return allOrgans.where((organ) => _selectedOrganIds.contains(organ.id)).toList(); } /// Check if any organs are selected @@ -181,13 +197,11 @@ class SymptomsCheckerViewModel extends ChangeNotifier { } int get totalSelectedSymptomsCount { - return _selectedSymptomsByOrgan.values - .fold(0, (sum, symptomIds) => sum + symptomIds.length); + return _selectedSymptomsByOrgan.values.fold(0, (sum, symptomIds) => sum + symptomIds.length); } bool get hasSelectedSymptoms { - return _selectedSymptomsByOrgan.values - .any((symptomIds) => symptomIds.isNotEmpty); + return _selectedSymptomsByOrgan.values.any((symptomIds) => symptomIds.isNotEmpty); } /// Get risk factors list @@ -213,8 +227,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier { } void toggleView() { - _currentView = - _currentView == BodyView.front ? BodyView.back : BodyView.front; + _currentView = _currentView == BodyView.front ? BodyView.back : BodyView.front; notifyListeners(); } @@ -317,8 +330,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier { return; } - List organNames = - selectedOrgans.map((organ) => organ.name).toList(); + List organNames = selectedOrgans.map((organ) => organ.name).toList(); await getBodySymptomsByName( organNames: organNames, @@ -368,8 +380,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier { } } - if (matchingOrganId != null && - _selectedSymptomsByOrgan.containsKey(matchingOrganId)) { + if (matchingOrganId != null && _selectedSymptomsByOrgan.containsKey(matchingOrganId)) { final selectedIds = _selectedSymptomsByOrgan[matchingOrganId]!; if (organResult.bodySymptoms != null) { @@ -420,10 +431,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier { /// Get all selected risk factors List getAllSelectedRiskFactors() { - return riskFactorsList - .where((factor) => - factor.id != null && _selectedRiskFactorIds.contains(factor.id)) - .toList(); + return riskFactorsList.where((factor) => factor.id != null && _selectedRiskFactorIds.contains(factor.id)).toList(); } /// Clear all risk factor selections @@ -456,12 +464,11 @@ class SymptomsCheckerViewModel extends ChangeNotifier { } // Extract symptom IDs - List evidenceIds = - selectedSymptoms.where((s) => s.id != null).map((s) => s.id!).toList(); + List evidenceIds = selectedSymptoms.where((s) => s.id != null).map((s) => s.id!).toList(); await getRiskFactors( age: _selectedAge!, - sex: _selectedGender!.toLowerCase(), + gender: _getGenderAsInt(_selectedGender), evidenceIds: evidenceIds, sessionId: currentSessionId, language: appState.isArabic() ? 'ar' : 'en', @@ -481,7 +488,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier { /// Call Risk Factors API Future getRiskFactors({ required int age, - required String sex, + required int gender, required String sessionId, required List evidenceIds, required String language, @@ -493,12 +500,11 @@ class SymptomsCheckerViewModel extends ChangeNotifier { final result = await symptomsCheckerRepo.getRiskFactors( age: age, - sex: sex, + gender: gender, evidenceIds: evidenceIds, language: language, sessionId: sessionId, userSessionToken: currentSessionAuthToken, - gender: (selectedGender ?? "Male").toLowerCase() == "male" ? 1 : 2, ); result.fold( @@ -515,13 +521,13 @@ class SymptomsCheckerViewModel extends ChangeNotifier { if (apiResponse.messageStatus == 1 && apiResponse.data != null) { riskFactorsResponse = apiResponse.data; - if (riskFactorsResponse != null && - riskFactorsResponse!.dataDetails != null) { - RiskAndSuggestionsItemModel riskFactorItem = - RiskAndSuggestionsItemModel( + if (riskFactorsResponse != null && riskFactorsResponse!.dataDetails != null) { + RiskAndSuggestionsItemModel riskFactorItem = RiskAndSuggestionsItemModel( id: "not_applicable", commonName: "Not Applicable", name: "Not Applicable", + commonNameAr: "لا ينطبق", + nameAr: "لا ينطبق", language: appState.isArabic() ? 'ar' : 'en', type: null, ); @@ -572,10 +578,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier { /// Get all selected risk factors List getAllSelectedSuggestions() { - return suggestionsList - .where((factor) => - factor.id != null && _selectedSuggestionsIds.contains(factor.id)) - .toList(); + return suggestionsList.where((factor) => factor.id != null && _selectedSuggestionsIds.contains(factor.id)).toList(); } /// Clear all risk factor selections @@ -590,20 +593,15 @@ class SymptomsCheckerViewModel extends ChangeNotifier { // Add selected symptoms final selectedSymptoms = getAllSelectedSymptoms(); - evidenceIds - .addAll(selectedSymptoms.where((s) => s.id != null).map((s) => s.id!)); + evidenceIds.addAll(selectedSymptoms.where((s) => s.id != null).map((s) => s.id!)); // Add selected risk factors (excluding "not_applicable") final selectedRiskFactors = getAllSelectedRiskFactors(); - evidenceIds.addAll(selectedRiskFactors - .where((rf) => rf.id != null && rf.id != "not_applicable") - .map((rf) => rf.id!)); + evidenceIds.addAll(selectedRiskFactors.where((rf) => rf.id != null && rf.id != "not_applicable").map((rf) => rf.id!)); // Add selected suggestions (excluding "not_applicable") final selectedSuggestions = getAllSelectedSuggestions(); - evidenceIds.addAll(selectedSuggestions - .where((s) => s.id != null && s.id != "not_applicable") - .map((s) => s.id!)); + evidenceIds.addAll(selectedSuggestions.where((s) => s.id != null && s.id != "not_applicable").map((s) => s.id!)); return evidenceIds; } @@ -632,23 +630,19 @@ class SymptomsCheckerViewModel extends ChangeNotifier { } // Extract symptom IDs - List evidenceIds = - selectedSymptoms.where((s) => s.id != null).map((s) => s.id!).toList(); + List evidenceIds = selectedSymptoms.where((s) => s.id != null).map((s) => s.id!).toList(); // Get all selected symptoms final selectedRisks = getAllSelectedRiskFactors(); if (selectedRisks.isNotEmpty) { - List evidenceRisksIds = selectedRisks - .where((s) => s.id != null && s.id != "not_applicable") - .map((s) => s.id!) - .toList(); + List evidenceRisksIds = selectedRisks.where((s) => s.id != null && s.id != "not_applicable").map((s) => s.id!).toList(); evidenceIds.addAll(evidenceRisksIds); } await getSuggestions( age: _selectedAge!, - sex: _selectedGender!.toLowerCase(), + gender: _getGenderAsInt(_selectedGender), evidenceIds: evidenceIds, language: appState.isArabic() ? 'ar' : 'en', onSuccess: (response) { @@ -667,7 +661,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier { /// Call Suggestions API Future getSuggestions({ required int age, - required String sex, + required int gender, required List evidenceIds, required String language, Function(RiskAndSuggestionsResponseModel)? onSuccess, @@ -678,12 +672,11 @@ class SymptomsCheckerViewModel extends ChangeNotifier { final result = await symptomsCheckerRepo.getSuggestions( age: age, - sex: sex, + gender: gender, evidenceIds: evidenceIds, language: language, sessionId: currentSessionId, userSessionToken: currentSessionAuthToken, - gender: (selectedGender ?? "Male").toLowerCase() == "male" ? 1 : 2, ); result.fold( @@ -700,13 +693,13 @@ class SymptomsCheckerViewModel extends ChangeNotifier { if (apiResponse.messageStatus == 1 && apiResponse.data != null) { suggestionsResponse = apiResponse.data; - if (suggestionsResponse != null && - suggestionsResponse!.dataDetails != null) { - RiskAndSuggestionsItemModel riskFactorItem = - RiskAndSuggestionsItemModel( + if (suggestionsResponse != null && suggestionsResponse!.dataDetails != null) { + RiskAndSuggestionsItemModel riskFactorItem = RiskAndSuggestionsItemModel( id: "not_applicable", commonName: "Not Applicable", name: "Not Applicable", + commonNameAr: "لا ينطبق", + nameAr: "لا ينطبق", language: appState.isArabic() ? 'ar' : 'en', type: null, ); @@ -730,7 +723,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier { /// Call Diagnosis API for Triage - This is called iteratively until shouldStop is true Future getDiagnosisForTriage({ required int age, - required String sex, + required int gender, required List evidenceIds, List>? triageEvidence, required String language, @@ -742,13 +735,12 @@ class SymptomsCheckerViewModel extends ChangeNotifier { final result = await symptomsCheckerRepo.getDiagnosisForTriage( age: age, - sex: sex, + gender: gender, evidenceIds: evidenceIds, triageEvidence: triageEvidence, language: language, sessionId: currentSessionId, userSessionToken: currentSessionAuthToken, - gender: (selectedGender ?? "Male").toLowerCase() == "male" ? 1 : 2, ); result.fold( @@ -804,7 +796,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier { await getDiagnosisForTriage( age: _selectedAge!, - sex: _selectedGender!.toLowerCase(), + gender: _getGenderAsInt(_selectedGender), evidenceIds: evidenceIds, language: appState.isArabic() ? 'ar' : 'en', onSuccess: (response) { @@ -941,8 +933,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier { // Calculate age from date of birth final now = DateTime.now(); int age = now.year - dateOfBirth.year; - if (now.month < dateOfBirth.month || - (now.month == dateOfBirth.month && now.day < dateOfBirth.day)) { + if (now.month < dateOfBirth.month || (now.month == dateOfBirth.month && now.day < dateOfBirth.day)) { age--; } _selectedAge = age; @@ -968,6 +959,22 @@ class SymptomsCheckerViewModel extends ChangeNotifier { bool get isUserInfoFirstPage => _userInfoCurrentPage == 0; + /// Check if current user info page has valid selection + bool get isCurrentUserInfoPageValid { + switch (_userInfoCurrentPage) { + case 0: // Gender page + return _selectedGender != null && _selectedGender!.isNotEmpty; + case 1: // Age/Birth date page + return _dateOfBirth != null || (_selectedAge != null && _selectedAge! > 0); + case 2: // Height page + return _selectedHeight > 0; + case 3: // Weight page + return _selectedWeight > 0; + default: + return false; + } + } + /// Validate and submit user info Map getUserInfoData() { return { @@ -989,8 +996,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier { }) async { isBodySymptomsLoading = true; notifyListeners(); - final result = await symptomsCheckerRepo.getUserDetails( - userName: userName, password: password); + final result = await symptomsCheckerRepo.getUserDetails(userName: userName, password: password); result.fold( (failure) async { @@ -1030,7 +1036,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier { final result = await symptomsCheckerRepo.getBodySymptomsByName( organNames: organNames, userSessionToken: currentSessionAuthToken, - gender: (selectedGender ?? "Male").toLowerCase() == "male" ? 1 : 2, + gender: _getGenderAsInt(selectedGender ?? "Male"), ); result.fold( diff --git a/lib/features/todo_section/todo_section_view_model.dart b/lib/features/todo_section/todo_section_view_model.dart index 92d893ad..0f6cc873 100644 --- a/lib/features/todo_section/todo_section_view_model.dart +++ b/lib/features/todo_section/todo_section_view_model.dart @@ -13,17 +13,20 @@ class TodoSectionViewModel extends ChangeNotifier { String? notificationsCount = "0"; initializeTodoSectionViewModel() async { - patientAncillaryOrdersList.clear(); - isAncillaryOrdersLoading = true; - isAncillaryDetailsProceduresLoading = true; - notificationsCount = "0"; - getPatientOnlineAncillaryOrderList(); + if (isAncillaryOrdersNeedReloading) { + patientAncillaryOrdersList.clear(); + isAncillaryOrdersLoading = true; + isAncillaryDetailsProceduresLoading = true; + notificationsCount = "0"; + getPatientOnlineAncillaryOrderList(); + } getPatientDashboard(); } bool isAncillaryOrdersLoading = false; bool isAncillaryDetailsProceduresLoading = false; bool isProcessingPayment = false; + bool isAncillaryOrdersNeedReloading = true; List patientAncillaryOrdersList = []; List patientAncillaryOrderProceduresList = []; @@ -32,6 +35,11 @@ class TodoSectionViewModel extends ChangeNotifier { notifyListeners(); } + void setIsAncillaryOrdersNeedReloading(bool value) { + isAncillaryOrdersNeedReloading = value; + notifyListeners(); + } + Future getPatientDashboard({Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = await todoSectionRepo.getPatientDashboard(); @@ -55,6 +63,10 @@ class TodoSectionViewModel extends ChangeNotifier { } Future getPatientOnlineAncillaryOrderList({Function(dynamic)? onSuccess, Function(String)? onError}) async { + if (!isAncillaryOrdersNeedReloading) { + return; + } + patientAncillaryOrdersList.clear(); isAncillaryOrdersLoading = true; notifyListeners(); @@ -71,6 +83,7 @@ class TodoSectionViewModel extends ChangeNotifier { } else if (apiResponse.messageStatus == 1) { patientAncillaryOrdersList = apiResponse.data!; isAncillaryOrdersLoading = false; + isAncillaryOrdersNeedReloading = false; notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); diff --git a/lib/features/water_monitor/water_monitor_view_model.dart b/lib/features/water_monitor/water_monitor_view_model.dart index d82712a1..1ef2ef65 100644 --- a/lib/features/water_monitor/water_monitor_view_model.dart +++ b/lib/features/water_monitor/water_monitor_view_model.dart @@ -1,5 +1,6 @@ import 'dart:developer'; +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'; @@ -14,6 +15,7 @@ import 'package:hmg_patient_app_new/features/water_monitor/models/update_user_de import 'package:hmg_patient_app_new/features/water_monitor/models/user_progress_models.dart'; import 'package:hmg_patient_app_new/features/water_monitor/models/water_cup_model.dart'; import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_repo.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; @@ -598,36 +600,36 @@ class WaterMonitorViewModel extends ChangeNotifier { String? validateAge() { if (ageController.text.trim().isEmpty) { - return 'Age is required'.needTranslation; + return LocaleKeys.ageIsRequired.tr(); } final age = int.tryParse(ageController.text.trim()); if (age == null) { - return 'Invalid age'.needTranslation; + return LocaleKeys.invalidAge.tr(); } if (age < 11 || age > 120) { - return 'Age must be between 11 and 120'.needTranslation; + return LocaleKeys.ageMustBeBetween11And120.tr(); } return null; } String? validateHeight() { if (heightController.text.trim().isEmpty) { - return 'Height is required'.needTranslation; + return LocaleKeys.heightIsRequired.tr(); } final height = double.tryParse(heightController.text.trim()); if (height == null || height <= 0) { - return 'Invalid height'.needTranslation; + return LocaleKeys.invalidHeight.tr(); } return null; } String? validateWeight() { if (weightController.text.trim().isEmpty) { - return 'Weight is required'.needTranslation; + return LocaleKeys.weightIsRequired.tr(); } final weight = double.tryParse(weightController.text.trim()); if (weight == null || weight <= 0) { - return 'Invalid weight'.needTranslation; + return LocaleKeys.invalidWeight.tr(); } return null; } @@ -1212,8 +1214,8 @@ class WaterMonitorViewModel extends ChangeNotifier { // Schedule water reminders await notificationService.scheduleWaterReminders( reminderTimes: reminderTimes, - title: 'Time to Drink Water! 💧'.needTranslation, - body: 'Stay hydrated! Drink ${selectedCupCapacityMl}ml of water.'.needTranslation, + title: LocaleKeys.timeToDrinkWater.tr(), + body: LocaleKeys.stayHydratedDrinkWater.tr(namedArgs: {'amount': selectedCupCapacityMl.toString()}), ); // Save reminder enabled state to cache @@ -1334,8 +1336,8 @@ class WaterMonitorViewModel extends ChangeNotifier { await notificationService.scheduleNotification( id: 9999, // Use a unique ID for test notifications - title: 'Time to Drink Water! 💧'.needTranslation, - body: 'Stay hydrated! Drink ${selectedCupCapacityMl}ml of water.'.needTranslation, + title: LocaleKeys.timeToDrinkWater.tr(), + body: LocaleKeys.stayHydratedDrinkWater.tr(namedArgs: {'amount': selectedCupCapacityMl.toString()}), scheduledDate: scheduledTime, payload: 'test_notification', ); diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 689587c4..7bafadf2 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -1208,5 +1208,360 @@ abstract class LocaleKeys { static const carParking = 'carParking'; static const latestNews = 'latestNews'; static const hmgContact = 'hmgContact'; + static const durationCannotExceed90 = 'durationCannotExceed90'; + static const unexpectedError = 'unexpectedError'; + static const gettingAmbulanceTransportOption = 'gettingAmbulanceTransportOption'; + static const fetchingAppointment = 'fetchingAppointment'; + static const doYouWantToCancelTheRequest = 'doYouWantToCancelTheRequest'; + static const cancellingRequest = 'cancellingRequest'; + static const fetchingTermsAndConditions = 'fetchingTermsAndConditions'; + static const selectLocationPrescriptionDelivery = 'selectLocationPrescriptionDelivery'; + static const noRadiologyOrders = 'noRadiologyOrders'; + static const ageIsRequired = 'ageIsRequired'; + static const invalidAge = 'invalidAge'; + static const ageMustBeBetween11And120 = 'ageMustBeBetween11And120'; + static const heightIsRequired = 'heightIsRequired'; + static const invalidHeight = 'invalidHeight'; + static const weightIsRequired = 'weightIsRequired'; + static const invalidWeight = 'invalidWeight'; + static const timeToDrinkWater = 'timeToDrinkWater'; + static const stayHydratedDrinkWater = 'stayHydratedDrinkWater'; + static const visitPharmacyOnline = 'visitPharmacyOnline'; + static const howAreYouFeelingToday = 'howAreYouFeelingToday'; + static const checkYourSymptomsWithScale = 'checkYourSymptomsWithScale'; + static const checkYourSymptoms = 'checkYourSymptoms'; + static const noUpcomingAppointmentPleaseBook = 'noUpcomingAppointmentPleaseBook'; + static const youHaveEROnlineCheckInRequest = 'youHaveEROnlineCheckInRequest'; + static const quickLinks = 'quickLinks'; + static const viewMedicalFileLandingPage = 'viewMedicalFileLandingPage'; + static const immediateLiveCareRequest = 'immediateLiveCareRequest'; + static const yourTurnIsAfterPatients = 'yourTurnIsAfterPatients'; + static const dontHaveHHCOrders = 'dontHaveHHCOrders'; + static const hhcOrders = 'hhcOrders'; + static const requestedServices = 'requestedServices'; + static const selectServices = 'selectServices'; + static const selectedServices = 'selectedServices'; + static const createNewRequest = 'createNewRequest'; + static const youHaveNoPendingRequests = 'youHaveNoPendingRequests'; + static const noInsuranceDataFound = 'noInsuranceDataFound'; + static const noInsuranceUpdateRequest = 'noInsuranceUpdateRequest'; + static const policyNumberInsurancePage = 'policyNumberInsurancePage'; + static const insuranceExpired = 'insuranceExpired'; + static const insuranceActive = 'insuranceActive'; + static const patientCardID = 'patientCardID'; + static const noInsuranceApprovals = 'noInsuranceApprovals'; + static const noInsuranceWithHMG = 'noInsuranceWithHMG'; + static const referenceRange = 'referenceRange'; + static const downloadReport = 'downloadReport'; + static const generatingReport = 'generatingReport'; + static const noLabResults = 'noLabResults'; + static const labResultDetails = 'labResultDetails'; + static const resultOf = 'resultOf'; + static const whatIsThisResult = 'whatIsThisResult'; + static const lastTested = 'lastTested'; + static const byVisit = 'byVisit'; + static const byTest = 'byTest'; + static const results = 'results'; + static const viewResults = 'viewResults'; + static const rebook = 'rebook'; + static const noOphthalmologyAppointments = 'noOphthalmologyAppointments'; + static const noVitalSignsRecordedYet = 'noVitalSignsRecordedYet'; + static const appointmentsAndVisits = 'appointmentsAndVisits'; + static const labAndRadiology = 'labAndRadiology'; + static const activeMedicationsAndPrescriptions = 'activeMedicationsAndPrescriptions'; + static const allPrescriptions = 'allPrescriptions'; + static const allMedications = 'allMedications'; + static const youDontHaveAnyPrescriptionsYet = 'youDontHaveAnyPrescriptionsYet'; + static const youDontHaveAnyCompletedVisitsYet = 'youDontHaveAnyCompletedVisitsYet'; + static const others = 'others'; + static const allergyInfo = 'allergyInfo'; + static const vaccineInfo = 'vaccineInfo'; + static const updateInsuranceInfo = 'updateInsuranceInfo'; + static const myInvoicesList = 'myInvoicesList'; + static const ancillaryOrdersList = 'ancillaryOrdersList'; + static const youDontHaveAnySickLeavesYet = 'youDontHaveAnySickLeavesYet'; + static const medicalReports = 'medicalReports'; + static const sickLeaveReport = 'sickLeaveReport'; + static const weightTracker = 'weightTracker'; + static const askYourDoctor = 'askYourDoctor'; + static const internetPairing = 'internetPairing'; + static const requested = 'requested'; + static const youDontHaveAnyMedicalReportsYet = 'youDontHaveAnyMedicalReportsYet'; + static const requestMedicalReport = 'requestMedicalReport'; + static const youDoNotHaveAnyAppointmentsToRequestMedicalReport = 'youDoNotHaveAnyAppointmentsToRequestMedicalReport'; + static const areYouSureYouWantToRequestMedicalReport = 'areYouSureYouWantToRequestMedicalReport'; + static const yourMedicalReportRequestSubmittedSuccessfully = 'yourMedicalReportRequestSubmittedSuccessfully'; + static const monthlyHealthSummaryReportDisclaimer = 'monthlyHealthSummaryReportDisclaimer'; + static const updatingMonthlyReportStatus = 'updatingMonthlyReportStatus'; + static const monthlyReportStatusUpdatedSuccessfully = 'monthlyReportStatusUpdatedSuccessfully'; + static const whoCanViewMyMedicalFile = 'whoCanViewMyMedicalFile'; + static const acceptedYourRequestToBeYourFamilyMember = 'acceptedYourRequestToBeYourFamilyMember'; + static const canViewYourFile = 'canViewYourFile'; + static const hasARequestPendingToBeYourFamilyMember = 'hasARequestPendingToBeYourFamilyMember'; + static const wantsToAddYouAsTheirFamilyMember = 'wantsToAddYouAsTheirFamilyMember'; + static const rejectedYourRequestToBeYourFamilyMember = 'rejectedYourRequestToBeYourFamilyMember'; + static const rejectedYourFamilyMemberRequest = 'rejectedYourFamilyMemberRequest'; + static const notAvailable = 'notAvailable'; + static const selectAProfile = 'selectAProfile'; + static const switchFamilyFile = 'switchFamilyFile'; + static const medicalFiles = 'medicalFiles'; + static const addANewFamilyMember = 'addANewFamilyMember'; + static const viewInvoiceDetails = 'viewInvoiceDetails'; + static const outPatient = 'outPatient'; + static const invoiceDetails = 'invoiceDetails'; + static const sendingEmailPleaseWait = 'sendingEmailPleaseWait'; + static const emailSentSuccessfullyMessage = 'emailSentSuccessfullyMessage'; + static const discount = 'discount'; + static const paid = 'paid'; + static const fetchingInvoiceDetails = 'fetchingInvoiceDetails'; + static const scanQRCode = 'scanQRCode'; + static const parkingSlotDetails = 'parkingSlotDetails'; + static const slotNumber = 'slotNumber'; + static const basement = 'basement'; + static const parkingDate = 'parkingDate'; + static const parkedSince = 'parkedSince'; + static const resetDirection = 'resetDirection'; + static const noPrescriptionOrdersYet = 'noPrescriptionOrdersYet'; + static const fetchingPrescriptionPDFPleaseWait = 'fetchingPrescriptionPDFPleaseWait'; + static const ratingValue = 'ratingValue'; + static const downloadPrescription = 'downloadPrescription'; + static const fetchingPrescriptionDetails = 'fetchingPrescriptionDetails'; + static const switchBackFamilyFile = 'switchBackFamilyFile'; + static const profileAndSettings = 'profileAndSettings'; + static const quickActions = 'quickActions'; + static const notificationsSettings = 'notificationsSettings'; + static const touchIDFaceIDServices = 'touchIDFaceIDServices'; + static const personalInformation = 'personalInformation'; + static const updateEmailAddress = 'updateEmailAddress'; + static const helpAndSupport = 'helpAndSupport'; + static const permissionsProfile = 'permissionsProfile'; + static const privacyPolicy = 'privacyPolicy'; + static const deactivateAccount = 'deactivateAccount'; + static const ageYearsOld = 'ageYearsOld'; + static const youDontHaveRadiologyOrders = 'youDontHaveRadiologyOrders'; + static const radiologyResult = 'radiologyResult'; + static const viewRadiologyImage = 'viewRadiologyImage'; + static const rateClinic = 'rateClinic'; + static const back = 'back'; + static const rateDoctor = 'rateDoctor'; + static const howWasYourLastVisitWithDoctor = 'howWasYourLastVisitWithDoctor'; + static const dateOfBirthSymptoms = 'dateOfBirthSymptoms'; + static const genderSymptoms = 'genderSymptoms'; + static const heightSymptoms = 'heightSymptoms'; + static const weightSymptoms = 'weightSymptoms'; + static const femaleGender = 'femaleGender'; + static const previous = 'previous'; + static const selectedOrgans = 'selectedOrgans'; + static const noOrgansSelected = 'noOrgansSelected'; + static const organSelector = 'organSelector'; + static const noPredictionsAvailable = 'noPredictionsAvailable'; + static const areYouSureYouWantToRestartOrganSelection = 'areYouSureYouWantToRestartOrganSelection'; + static const possibleConditions = 'possibleConditions'; + static const pleaseSelectAtLeastOneRiskBeforeProceeding = 'pleaseSelectAtLeastOneRiskBeforeProceeding'; + static const aboveYouSeeCommonRiskFactors = 'aboveYouSeeCommonRiskFactors'; + static const readMore = 'readMore'; + static const riskFactors = 'riskFactors'; + static const noRiskFactorsFound = 'noRiskFactorsFound'; + static const basedOnYourSelectedSymptomsNoRiskFactors = 'basedOnYourSelectedSymptomsNoRiskFactors'; + static const messageNotification = 'messageNotification'; + static const attachedImage = 'attachedImage'; + static const failedToLoadImage = 'failedToLoadImage'; + static const typeNotification = 'typeNotification'; + static const pleaseSelectAtLeastOneOptionBeforeProceeding = 'pleaseSelectAtLeastOneOptionBeforeProceeding'; + static const suggestions = 'suggestions'; + static const pleaseGoBackAndSelectOrgansFirst = 'pleaseGoBackAndSelectOrgansFirst'; + static const symptomsSelector = 'symptomsSelector'; + static const possibleSymptomsRelatedTo = 'possibleSymptomsRelatedTo'; + static const suggestionAIListDescription = 'suggestionAIListDescription'; + static const analyzing = 'analyzing'; + static const seekMedicalAdvice = 'seekMedicalAdvice'; + static const monitorDescription = 'monitorDescription'; + static const seekMedicalAdviceDescription = 'seekMedicalAdviceDescription'; + static const emergencyDescription = 'emergencyDescription'; + static const colorScience = 'colorScience'; + static const noNeedToVisitDoctor = 'noNeedToVisitDoctor'; + static const commonSymptom = 'commonSymptom'; + static const notApplicable = 'notApplicable'; + static const emergencyTriage = 'emergencyTriage'; + static const emergencyEvidenceDetected = 'emergencyEvidenceDetected'; + static const noQuestionItemsAvailable = 'noQuestionItemsAvailable'; + static const pleaseAnswerAllQuestionsBeforeProceeding = 'pleaseAnswerAllQuestionsBeforeProceeding'; + static const triage = 'triage'; + static const areYouSureYouWantToExitProgress = 'areYouSureYouWantToExitProgress'; + static const noQuestionAvailable = 'noQuestionAvailable'; + static const possibleSymptom = 'possibleSymptom'; + static const symptomsCheckerFindingScore = 'symptomsCheckerFindingScore'; + static const notSet = 'notSet'; + static const years = 'years'; + static const symptomsChecker = 'symptomsChecker'; + static const helloIsYourInformationUpToDate = 'helloIsYourInformationUpToDate'; + static const noEditAll = 'noEditAll'; + static const yesItIs = 'yesItIs'; + static const age = 'age'; + static const youDontHaveAnyAncillaryOrdersYet = 'youDontHaveAnyAncillaryOrdersYet'; + static const invoiceWithNumber = 'invoiceWithNumber'; + static const queued = 'queued'; + static const checkInReady = 'checkInReady'; + static const checkIn = 'checkIn'; + static const viewDetails = 'viewDetails'; + static const selectPaymentMethod = 'selectPaymentMethod'; + static const processingPaymentPleaseWait = 'processingPaymentPleaseWait'; + static const finalizingPaymentPleaseWait = 'finalizingPaymentPleaseWait'; + static const generatingInvoicePleaseWait = 'generatingInvoicePleaseWait'; + static const hereIsYourInvoiceNumber = 'hereIsYourInvoiceNumber'; + static const paymentCompletedSuccessfully = 'paymentCompletedSuccessfully'; + static const failedToInitializeApplePay = 'failedToInitializeApplePay'; + static const cash = 'cash'; + static const approved = 'approved'; + static const approvalRejectedPleaseVisitReceptionist = 'approvalRejectedPleaseVisitReceptionist'; + static const sentForApproval = 'sentForApproval'; + static const ancillaryOrderDetails = 'ancillaryOrderDetails'; + static const noProceduresAvailableForSelectedOrder = 'noProceduresAvailableForSelectedOrder'; + static const procedures = 'procedures'; + static const totalAmount = 'totalAmount'; + static const covered = 'covered'; + static const vatPercent = 'vatPercent'; + static const proceedToPayment = 'proceedToPayment'; + static const supportedSmartWatches = 'supportedSmartWatches'; + static const pleaseMakeSureSamsungWatchConnected = 'pleaseMakeSureSamsungWatchConnected'; + static const beforeSyncingDataFollowInstructions = 'beforeSyncingDataFollowInstructions'; + static const viewWatchInstructions = 'viewWatchInstructions'; + static const healthConnectAppNotInstalled = 'healthConnectAppNotInstalled'; + static const setTimerOfReminder = 'setTimerOfReminder'; + static const youHaveAppointmentWithDr = 'youHaveAppointmentWithDr'; + static const hours = 'hours'; + static const secs = 'secs'; + static const noAllergiesDataFound = 'noAllergiesDataFound'; + static const heartRateDescription = 'heartRateDescription'; + static const bloodOxygenDescription = 'bloodOxygenDescription'; + static const stepsDescription = 'stepsDescription'; + static const caloriesDescription = 'caloriesDescription'; + static const distanceDescription = 'distanceDescription'; + static const overview = 'overview'; + static const details = 'details'; + static const healthy = 'healthy'; + static const warning = 'warning'; + static const vitalSignDetails = 'vitalSignDetails'; + static const resultOfNoDate = 'resultOfNoDate'; + static const referenceRangeBetween = 'referenceRangeBetween'; + static const referenceRangeMin = 'referenceRangeMin'; + static const referenceRangeMax = 'referenceRangeMax'; + static const noHistoryAvailable = 'noHistoryAvailable'; + static const bmiDescription = 'bmiDescription'; + static const heightDescription = 'heightDescription'; + static const weightDescription = 'weightDescription'; + static const bloodPressureDescription = 'bloodPressureDescription'; + static const temperatureDescription = 'temperatureDescription'; + static const heartRateDescriptionVital = 'heartRateDescriptionVital'; + static const respiratoryRateDescription = 'respiratoryRateDescription'; + static const bmiAdvice = 'bmiAdvice'; + static const heightAdvice = 'heightAdvice'; + static const weightAdvice = 'weightAdvice'; + static const bloodPressureAdvice = 'bloodPressureAdvice'; + static const temperatureAdvice = 'temperatureAdvice'; + static const heartRateAdvice = 'heartRateAdvice'; + static const respiratoryRateAdvice = 'respiratoryRateAdvice'; + static const whatShouldIDoNext = 'whatShouldIDoNext'; + static const customizeDrinkCup = 'customizeDrinkCup'; + static const tipsToStayHydrated = 'tipsToStayHydrated'; + static const drinkBeforeYouFeelThirsty = 'drinkBeforeYouFeelThirsty'; + static const keepRefillableBottleNextToYou = 'keepRefillableBottleNextToYou'; + static const trackYourDailyIntakeToStayMotivated = 'trackYourDailyIntakeToStayMotivated'; + static const chooseSparklingWaterInsteadOfSoda = 'chooseSparklingWaterInsteadOfSoda'; + static const switchCup = 'switchCup'; + static const plainWater = 'plainWater'; + static const yourGoal = 'yourGoal'; + static const remaining = 'remaining'; + static const hydrationStatus = 'hydrationStatus'; + static const areYouSureYouWantToCancelAllWaterReminders = 'areYouSureYouWantToCancelAllWaterReminders'; + static const remindersSet = 'remindersSet'; + static const dailyWaterRemindersScheduledAt = 'dailyWaterRemindersScheduledAt'; + static const waterConsumption = 'waterConsumption'; + static const selectNumberOfReminders = 'selectNumberOfReminders'; + static const h2oSettings = 'h2oSettings'; + static const settingsSavedSuccessfully = 'settingsSavedSuccessfully'; + static const yourName = 'yourName'; + static const ageYears = 'ageYears'; + static const numberOfRemindersInADay = 'numberOfRemindersInADay'; + static const medications = 'medications'; + static const someRemarksAboutPrescription = 'someRemarksAboutPrescription'; + static const notifyMeBeforeConsumptionTime = 'notifyMeBeforeConsumptionTime'; + static const noMedicationsToday = 'noMedicationsToday'; + static const reminders = 'reminders'; + static const reminderAddedToCalendar = 'reminderAddedToCalendar'; + static const errorWhileSettingCalendar = 'errorWhileSettingCalendar'; + static const instructions = 'instructions'; + static const requests = 'requests'; + static const thisWeek = 'thisWeek'; + static const lastMonth = 'lastMonth'; + static const lastSixMonths = 'lastSixMonths'; + static const selectTime = 'selectTime'; + static const pleaseWaitYouWillBeCalledForVitalSigns = 'pleaseWaitYouWillBeCalledForVitalSigns'; + static const pleaseVisitRoomForVitalSigns = 'pleaseVisitRoomForVitalSigns'; + static const pleaseVisitRoomToTheDoctor = 'pleaseVisitRoomToTheDoctor'; + static const erOnlineCheckInRequest = 'erOnlineCheckInRequest'; + static const indoor = 'indoor'; + static const navigation = 'navigation'; + static const health = 'health'; + static const calculators = 'calculators'; + static const converters = 'converters'; + static const guide = 'guide'; + static const liveCareServiceDesc = 'liveCareServiceDesc'; + static const bloodGroup = 'bloodGroup'; + static const medicalServices = 'medicalServices'; + static const insuranceAndPayments = 'insuranceAndPayments'; + static const trackerAndOthers = 'trackerAndOthers'; + static const bmi = 'bmi'; + static const yesPleaseINeedHelp = 'yesPleaseINeedHelp'; + static const aiOverView = 'aiOverView'; + static const generateAiAnalysis = 'generateAiAnalysis'; + static const aiAnalysis = 'aiAnalysis'; + static const prediction = 'prediction'; + static const prevention = 'prevention'; + static const disclaimer = 'disclaimer'; + static const thisAboveInfo = 'thisAboveInfo'; + static const aiDisclaimer = 'aiDisclaimer'; + static const consent = 'consent'; + static const bmiNormalDescription = 'bmiNormalDescription'; + static const bmiUnderweightDescription = 'bmiUnderweightDescription'; + static const bmiOverweightDescription = 'bmiOverweightDescription'; + static const caloriesMaintenanceDescription = 'caloriesMaintenanceDescription'; + static const caloriesModerateDeficitDescription = 'caloriesModerateDeficitDescription'; + static const caloriesAggressiveDeficitDescription = 'caloriesAggressiveDeficitDescription'; + static const bodyFatEssentialDescription = 'bodyFatEssentialDescription'; + static const bodyFatFitnessDescription = 'bodyFatFitnessDescription'; + static const bodyFatAverageDescription = 'bodyFatAverageDescription'; + static const bodyFatObeseDescription = 'bodyFatObeseDescription'; + static const bmrNormalDescription = 'bmrNormalDescription'; + static const bmrLowDescription = 'bmrLowDescription'; + static const bmrHighDescription = 'bmrHighDescription'; + static const ibwAtTargetDescription = 'ibwAtTargetDescription'; + static const ibwBelowTargetDescription = 'ibwBelowTargetDescription'; + static const ibwAboveTargetDescription = 'ibwAboveTargetDescription'; + static const ibwCloseToRangeDescription = 'ibwCloseToRangeDescription'; + static const ibwUnderweightDescription = 'ibwUnderweightDescription'; + static const ibwUnderweightGenericDescription = 'ibwUnderweightGenericDescription'; + static const ibwOverweightDescription = 'ibwOverweightDescription'; + static const ibwOverweightGenericDescription = 'ibwOverweightGenericDescription'; + static const dietVeryLowCarbsDescription = 'dietVeryLowCarbsDescription'; + static const dietLowCarbsDescription = 'dietLowCarbsDescription'; + static const dietModerateCarbsDescription = 'dietModerateCarbsDescription'; + static const dietUSDAGuidelinesDescription = 'dietUSDAGuidelinesDescription'; + static const dietZoneDietDescription = 'dietZoneDietDescription'; + static const contextDependent = 'contextDependent'; + static const calculatedValue = 'calculatedValue'; + static const calculationFormula = 'calculationFormula'; + static const confirmYourLocation = 'confirmYourLocation'; + static const confirmYourLocationDesc = 'confirmYourLocationDesc'; + static const insideHospital = 'insideHospital'; + static const outsideHospital = 'outsideHospital'; + static const servicePriceList = 'servicePriceList'; + static const servicePriceListDesc = 'servicePriceListDesc'; + static const servicePriceListRights = 'servicePriceListRights'; + static const serviceName = 'serviceName'; + static const noFreeSlot = 'noFreeSlot'; + static const noThanksIKnowTheClinic = 'noThanksIKnowTheClinic'; + static const unableToSendOTP = 'unableToSendOTP'; + static const loadingAIAnalysis = 'loadingAIAnalysis'; } diff --git a/lib/main.dart b/lib/main.dart index 51587068..e034cf92 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,11 +3,13 @@ import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/features/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'; import 'package:hmg_patient_app_new/features/active_prescriptions/active_prescriptions_view_model.dart'; @@ -32,13 +34,15 @@ import 'package:hmg_patient_app_new/features/notifications/notifications_view_mo import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; +import 'package:hmg_patient_app_new/features/qr_parking/qr_parking_view_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; +import 'package:hmg_patient_app_new/features/services_price_list/services_price_list_view_model.dart'; 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/water_monitor/water_monitor_view_model.dart'; import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; -import 'package:hmg_patient_app_new/presentation/health_trackers/health_trackers_view_model.dart'; +import 'package:hmg_patient_app_new/features/health_trackers/health_trackers_view_model.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; @@ -191,11 +195,20 @@ void main() async { ChangeNotifierProvider( create: (_) => getIt.get(), ), + ChangeNotifierProvider( + create: (_) => getIt.get(), + ), ChangeNotifierProvider( create: (_) => getIt.get(), + ), + ChangeNotifierProvider( + create: (_) => getIt.get(), + ), + ChangeNotifierProvider( + create: (_) => getIt.get(), ) ], child: MyApp()), - ), + ), // Wrap your app ); } @@ -215,7 +228,7 @@ class MyApp extends StatelessWidget { return MaterialApp( title: 'Dr. AlHabib', builder: (context, mchild) { - return MediaQuery(data: MediaQuery.of(context).copyWith(textScaler: TextScaler.linear(1.0)), child: mchild!); + return MediaQuery(data: MediaQuery.of(context).copyWith(textScaler: TextScaler.noScaling, alwaysUse24HourFormat: true,), child: mchild!, ); }, showSemanticsDebugger: false, debugShowCheckedModeBanner: false, @@ -235,4 +248,3 @@ class MyApp extends StatelessWidget { } } // flutter pub run easy_localization:generate -S assets/langs -f keys -o locale_keys.g.dart - diff --git a/lib/presentation/active_medication/active_medication_page.dart b/lib/presentation/active_medication/active_medication_page.dart index d0720fb2..14da5b01 100644 --- a/lib/presentation/active_medication/active_medication_page.dart +++ b/lib/presentation/active_medication/active_medication_page.dart @@ -134,7 +134,7 @@ class _ActiveMedicationPageState extends State { body: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Active Medications".needTranslation, + Text(LocaleKeys.activeMedications.tr(), style: TextStyle( color: AppColors.textColor, fontSize: 27.f, @@ -191,7 +191,7 @@ class _ActiveMedicationPageState extends State { ], ), ), - Text("Medications".needTranslation, + Text(LocaleKeys.medications.tr(), style: TextStyle( color: AppColors.primaryRedBorderColor, fontSize: 12.f, @@ -253,8 +253,7 @@ class _ActiveMedicationPageState extends State { text: TextSpan( children: [ TextSpan( - text: "Remarks: " - .needTranslation, + text: LocaleKeys.remarks.tr(), style: TextStyle( color: AppColors.textColor, @@ -265,8 +264,7 @@ class _ActiveMedicationPageState extends State { ), TextSpan( text: - "some remarks about the prescription will be here" - .needTranslation, + LocaleKeys.someRemarksAboutPrescription.tr(), style: TextStyle( color: AppColors .lightGreyTextColor, @@ -312,8 +310,7 @@ class _ActiveMedicationPageState extends State { CrossAxisAlignment.start, children: [ Text( - "Set Reminder" - .needTranslation, + LocaleKeys.setReminder.tr(), style: TextStyle( fontSize: 14.f, fontWeight: @@ -321,8 +318,7 @@ class _ActiveMedicationPageState extends State { color: AppColors .textColor)), Text( - "Notify me before the consumption time" - .needTranslation, + LocaleKeys.notifyMeBeforeConsumptionTime.tr(), style: TextStyle( fontSize: 12.f, color: AppColors @@ -346,7 +342,7 @@ class _ActiveMedicationPageState extends State { : Utils.getNoDataWidget( context, noDataText: - "No medications today".needTranslation, + LocaleKeys.noMedicationsToday.tr(), ), ), ), @@ -397,17 +393,16 @@ class _ActiveMedicationPageState extends State { children: [ AppCustomChipWidget( labelText: - "Route: ${med.route}".needTranslation), + LocaleKeys.route.tr(namedArgs: {'route': med.route ?? ''})), AppCustomChipWidget( labelText: - "Frequency: ${med.frequency}".needTranslation), + LocaleKeys.frequency.tr(namedArgs: {'frequency': med.frequency ?? ''})), AppCustomChipWidget( labelText: - "Daily Dose: ${med.doseDailyQuantity}" - .needTranslation), + LocaleKeys.instruction.tr(namedArgs: {'instruction': med.doseDailyQuantity?.toString() ?? ''})), AppCustomChipWidget( labelText: - "Duration: ${med.days}".needTranslation), + LocaleKeys.duration.tr(namedArgs: {'days': med.days.toString() ?? ''})), ], ), ], @@ -419,7 +414,7 @@ class _ActiveMedicationPageState extends State { child: Row(children: [ Expanded( child: CustomButton( - text: "Check Availability".needTranslation, + text: LocaleKeys.checkAvailability.tr(), fontSize: 13.f, onPressed: () {}, backgroundColor: AppColors.secondaryLightRedColor, @@ -430,7 +425,7 @@ class _ActiveMedicationPageState extends State { SizedBox(width: 12.h), Expanded( child: CustomButton( - text: "Read Instructions".needTranslation, + text: LocaleKeys.readInstructions.tr(), fontSize: 13.f, onPressed: () {})), ]), @@ -519,7 +514,7 @@ class _ActiveMedicationPageState extends State { MainAxisAlignment.spaceBetween, children: [ Text( - "Reminders".needTranslation, + LocaleKeys.reminders.tr(), style: TextStyle( fontSize: 20.f, fontWeight: FontWeight.w600, @@ -988,11 +983,11 @@ class _ReminderTimerDialogState extends State { route: widget.med.route ?? "", ); ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Reminder added to calendar ✅".needTranslation)), + SnackBar(content: Text(LocaleKeys.reminderAddedToCalendar.tr())), ); } catch (e) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Error while setting calendar: $e".needTranslation)), + SnackBar(content: Text(LocaleKeys.errorWhileSettingCalendar.tr(namedArgs: {'error': e.toString()}))), ); } Navigator.pop(context); diff --git a/lib/presentation/allergies/allergies_list_page.dart b/lib/presentation/allergies/allergies_list_page.dart index efcdd0ac..adb63f02 100644 --- a/lib/presentation/allergies/allergies_list_page.dart +++ b/lib/presentation/allergies/allergies_list_page.dart @@ -123,7 +123,7 @@ class AllergiesListPage extends StatelessWidget { ), ), ) - : Utils.getNoDataWidget(context, noDataText: "No allergies data found...".needTranslation); + : Utils.getNoDataWidget(context, noDataText: LocaleKeys.noAllergiesDataFound.tr()); }, separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), ), diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index 0b8066e1..0e0194b5 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -8,6 +9,7 @@ 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/calender_utils_new.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/penguin_method_channel.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'; @@ -15,6 +17,8 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart'; import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart'; +import 'package:hmg_patient_app_new/features/contact_us/models/feedback_type.dart'; +import 'package:hmg_patient_app_new/features/hospital/AppPermission.dart'; import 'package:hmg_patient_app_new/features/lab/lab_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'; @@ -37,7 +41,8 @@ import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; -import 'package:maps_launcher/maps_launcher.dart'; +import 'package:map_launcher/map_launcher.dart'; +import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; import '../medical_file/widgets/medical_file_card.dart'; @@ -59,6 +64,8 @@ class _AppointmentDetailsPageState extends State { late LabViewModel labViewModel; late RadiologyViewModel radiologyViewModel; + String mapURL = ""; + @override void initState() { scheduleMicrotask(() async { @@ -87,9 +94,10 @@ class _AppointmentDetailsPageState extends State { children: [ Expanded( child: CollapsingListView( - title: LocaleKeys.appointmentDetails.tr(), + title: LocaleKeys.appointmentDetails.tr(context: context), report: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? () { + contactUsViewModel.setSelectedFeedbackType(FeedbackType(id: 1, nameEN: "Complaint for appointment", nameAR: 'شكوى على موعد')); contactUsViewModel.setPatientFeedbackSelectedAppointment(widget.patientAppointmentHistoryResponseModel); Navigator.of(context).push( CustomPageRoute( @@ -103,9 +111,12 @@ class _AppointmentDetailsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ AppointmentDoctorCard( + renderWidgetForERDisplay: ((widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) || + (widget.patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) || + !Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID)), patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, onAskDoctorTap: () async { - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingDoctorAvailability.tr()); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingDoctorAvailability.tr(context: context)); await myAppointmentsViewModel.isDoctorAvailable( projectID: widget.patientAppointmentHistoryResponseModel.projectID, doctorId: widget.patientAppointmentHistoryResponseModel.doctorID, @@ -135,7 +146,7 @@ class _AppointmentDetailsPageState extends State { onCancelTap: () async { myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.cancellingAppointmentPleaseWait.tr()); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.cancellingAppointmentPleaseWait.tr(context: context)); await myAppointmentsViewModel.cancelAppointment( patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, onSuccess: (apiResponse) { @@ -144,7 +155,7 @@ class _AppointmentDetailsPageState extends State { myAppointmentsViewModel.getPatientAppointments(true, false); showCommonBottomSheetWithoutHeight( context, - child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentCancelledSuccessfully.tr()), + child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentCancelledSuccessfully.tr(context: context)), callBackFunc: () { Navigator.of(context).pop(); }, @@ -180,15 +191,57 @@ class _AppointmentDetailsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - LocaleKeys.appointmentStatus.tr().toText16(isBold: true), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 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)), + 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.of(context).pop(); + }, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }, + backgroundColor: AppColors.successColor, + borderColor: AppColors.successColor, + textColor: AppColors.whiteColor, + fontSize: 14.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + height: 40.h, + icon: AppAssets.confirm_appointment_icon, + iconColor: AppColors.whiteColor, + iconSize: 16.h, + ) + : SizedBox.shrink()) ], ), - SizedBox(height: 4.h), - (!AppointmentType.isConfirmed(widget.patientAppointmentHistoryResponseModel) - ? LocaleKeys.notConfirmed.tr().toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500) - : LocaleKeys.confirmed.tr().toText12(color: AppColors.successColor, fontWeight: FontWeight.w500)), - SizedBox(height: 16.h), //TODO Add countdown timer in case of LiveCare Appointment widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false ? Row( @@ -199,7 +252,7 @@ class _AppointmentDetailsPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.doctorWillCallYou.tr().toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + LocaleKeys.doctorWillCallYou.tr(context: context).toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), ], ), ), @@ -212,7 +265,7 @@ class _AppointmentDetailsPageState extends State { borderRadius: BorderRadius.circular(24.r), // Todo: what is this???? Api Key??? 😲 child: Image.network( - "https://maps.googleapis.com/maps/api/staticmap?center=${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&zoom=14&size=350x165&maptype=roadmap&markers=color:red%7C${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&key=${ApiKeyConstants.googleMapsApiKey}", + "https://maps.googleapis.com/maps/api/staticmap?center=${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&zoom=14&size=${(MediaQuery.of(context).size.width * 1.5).toInt()}x${(MediaQuery.of(context).size.height * 0.35).toInt()}&maptype=roadmap&markers=color:red%7C${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&key=${ApiKeyConstants.googleMapsApiKey}", fit: BoxFit.contain, ), ), @@ -221,11 +274,26 @@ class _AppointmentDetailsPageState extends State { child: SizedBox( width: MediaQuery.of(context).size.width * 0.785, child: CustomButton( - onPressed: () { - MapsLauncher.launchCoordinates(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), - double.parse(widget.patientAppointmentHistoryResponseModel.longitude!), widget.patientAppointmentHistoryResponseModel.projectName); + onPressed: () async { + if (widget.patientAppointmentHistoryResponseModel.projectID == 130 || widget.patientAppointmentHistoryResponseModel.projectID == 120) { + showDirectionsBottomSheet(); + } else { + await MapLauncher.showMarker( + mapType: MapType.google, + coords: Coords(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), + double.parse(widget.patientAppointmentHistoryResponseModel.longitude!)), + title: widget.patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital", + ).catchError((err) { + MapLauncher.showMarker( + mapType: Platform.isIOS ? MapType.apple : MapType.google, + coords: Coords(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), + double.parse(widget.patientAppointmentHistoryResponseModel.longitude!)), + title: widget.patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital", + ); + }); + } }, - text: LocaleKeys.getDirections.tr(), + text: LocaleKeys.getDirections.tr(context: context), backgroundColor: AppColors.textColor.withValues(alpha: 0.8), borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.01), textColor: AppColors.whiteColor, @@ -280,7 +348,7 @@ class _AppointmentDetailsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.setReminder.tr(context: context).toText13(isBold: true), - LocaleKeys.notifyMeBeforeAppointment.tr().toText11(color: AppColors.textColorLight, weight: FontWeight.w500), + LocaleKeys.notifyMeBeforeAppointment.tr(context: context).toText11(color: AppColors.textColorLight, weight: FontWeight.w500), ], ), const Spacer(), @@ -365,7 +433,7 @@ class _AppointmentDetailsPageState extends State { isLargeText: true, iconSize: 36.w, ).onPress(() async { - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingLabResults.tr()); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingLabResults.tr(context: context)); await labViewModel.getLabResultsByAppointmentNo( appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo, projectID: widget.patientAppointmentHistoryResponseModel.projectID, @@ -398,7 +466,7 @@ class _AppointmentDetailsPageState extends State { isLargeText: true, iconSize: 36.w, ).onPress(() async { - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingRadiologyResults.tr()); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingRadiologyResults.tr(context: context)); await radiologyViewModel.getPatientRadiologyOrdersByAppointment( appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo, projectID: widget.patientAppointmentHistoryResponseModel.projectID, @@ -425,7 +493,7 @@ class _AppointmentDetailsPageState extends State { isLargeText: true, iconSize: 36.w, ).onPress(() async { - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingAppointmentPrescriptions.tr()); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingAppointmentPrescriptions.tr(context: context)); await prescriptionsViewModel.getPrescriptionDetails( getPrescriptionRequestModel(), onSuccess: (val) { @@ -453,7 +521,7 @@ class _AppointmentDetailsPageState extends State { } else { showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: LocaleKeys.noPrescriptionsForAppointment.tr()), + child: Utils.getErrorWidget(loadingText: LocaleKeys.noPrescriptionsForAppointment.tr(context: context)), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, @@ -522,7 +590,7 @@ class _AppointmentDetailsPageState extends State { // ], // ), // SizedBox(height: 16.h), - // LocaleKeys.prescriptions.tr().toText18(isBold: true), + // LocaleKeys.prescriptions.tr(context: context).toText18(isBold: true), // SizedBox(height: 16.h), // Consumer(builder: (context, prescriptionVM, child) { // return prescriptionVM.isPrescriptionsDetailsLoading @@ -708,7 +776,7 @@ class _AppointmentDetailsPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - LocaleKeys.amountBeforeTax.tr().toText18(isBold: true), + LocaleKeys.amountBeforeTax.tr(context: context).toText18(isBold: true), Utils.getPaymentAmountWithSymbol( widget.patientAppointmentHistoryResponseModel.patientShare!.toString().toText16(isBold: true), AppColors.blackColor, @@ -751,11 +819,15 @@ class _AppointmentDetailsPageState extends State { ], ).paddingOnly(left: 16.h, top: 24.h, right: 16.h, bottom: 0.h), AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) - ? CustomButton( - text: LocaleKeys.rebookAppointment.tr(), - onPressed: () { - openDoctorScheduleCalendar(); - }, + ? + (widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! || + widget.patientAppointmentHistoryResponseModel.isExecludeDoctor! || + Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID)) + ? CustomButton( + text: LocaleKeys.rebookAppointment.tr(context: context), + onPressed: () { + openDoctorScheduleCalendar(); + }, backgroundColor: AppColors.successColor, borderColor: AppColors.successColor, textColor: AppColors.whiteColor, @@ -767,11 +839,11 @@ class _AppointmentDetailsPageState extends State { icon: AppAssets.add_icon, iconColor: AppColors.whiteColor, iconSize: 18.h, - ).paddingSymmetrical(16.h, 24.h) + ).paddingSymmetrical(16.h, 24.h) + : SizedBox.shrink() : CustomButton( text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction), onPressed: () { - myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); handleAppointmentNextAction(widget.patientAppointmentHistoryResponseModel.nextAction); }, backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction), @@ -796,6 +868,89 @@ class _AppointmentDetailsPageState extends State { ); } + void showDirectionsBottomSheet() { + showCommonBottomSheetWithoutHeight( + context, + title: "", + isDismissible: true, + isCloseButtonVisible: false, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.confirmYourLocation.tr(context: context).toText28(color: AppColors.textColor, isBold: true, height: 1.5), + SizedBox(height: 4.h), + LocaleKeys.confirmYourLocationDesc.tr(context: context).toText12( + color: AppColors.greyTextColor, + fontWeight: FontWeight.w500, + ), + SizedBox(height: 24.h), + CustomButton( + text: LocaleKeys.insideHospital.tr(context: context), + onPressed: () { + Navigator.pop(context); + initPenguinSDK(widget.patientAppointmentHistoryResponseModel.projectID == 130 ? 1 : (widget.patientAppointmentHistoryResponseModel.projectID == 120 ? 3 : -1), + clinicID: widget.patientAppointmentHistoryResponseModel.clinicID.toString()); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 56.h, + ), + SizedBox(height: 8.h), + CustomButton( + text: LocaleKeys.outsideHospital.tr(context: context), + onPressed: () async { + Navigator.pop(context); + await MapLauncher.showMarker( + mapType: MapType.google, + coords: Coords(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), double.parse(widget.patientAppointmentHistoryResponseModel.longitude!)), + title: widget.patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital", + ).catchError((err) { + MapLauncher.showMarker( + mapType: Platform.isIOS ? MapType.apple : MapType.google, + coords: Coords(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), double.parse(widget.patientAppointmentHistoryResponseModel.longitude!)), + title: widget.patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital", + ); + }); + }, + backgroundColor: AppColors.chipSecondaryLightRedColor, + borderColor: AppColors.chipSecondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 16.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 56.h, + ), + ], + ).paddingSymmetrical(24.w, 20.h), + callBackFunc: () {}, + ); + } + + initPenguinSDK(int projectID, {String clinicID = ""}) async { + NavigationClinicDetails data = NavigationClinicDetails(); + data.projectId = projectID.toString(); + data.clinicId = clinicID; + // data.clinicId = "1"; + final bool permited = await AppPermission.askPenguinPermissions(); + if (!permited) { + Map statuses = await [ + Permission.location, + Permission.bluetooth, + Permission.bluetoothConnect, + Permission.bluetoothScan, + Permission.activityRecognition, + ].request().whenComplete(() { + PenguinMethodChannel().launch("penguin", getIt.get().isArabic() ? "ar" : "en", getIt.get().getAuthenticatedUser()?.patientId?.toString() ?? "", true, details: data); + }); + } + } + openDoctorScheduleCalendar() async { DoctorsListResponseModel doctor = DoctorsListResponseModel( clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, @@ -810,13 +965,13 @@ class _AppointmentDetailsPageState extends State { projectName: widget.patientAppointmentHistoryResponseModel.projectName, ); bookAppointmentsViewModel.setSelectedDoctor(doctor); - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingDoctorSchedulePleaseWait.tr()); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingDoctorSchedulePleaseWait.tr(context: context)); await bookAppointmentsViewModel.getDoctorFreeSlots( isBookingForLiveCare: false, onSuccess: (dynamic respData) async { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( - title: LocaleKeys.pickADate.tr(), + title: LocaleKeys.pickADate.tr(context: context), context, child: AppointmentCalendar(), isFullScreen: false, @@ -841,14 +996,17 @@ class _AppointmentDetailsPageState extends State { case 0: break; case 10: - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.confirmingAppointmentPleaseWait.tr()); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.confirmingAppointmentPleaseWait.tr(context: context)); await myAppointmentsViewModel.confirmAppointment( patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, onSuccess: (apiResponse) { LoaderBottomSheet.hideLoader(); myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); myAppointmentsViewModel.getPatientAppointments(true, false); - showCommonBottomSheet(context, child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentConfirmedSuccessfully.tr()), callBackFunc: (str) { + showCommonBottomSheet(context, child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentConfirmedSuccessfully.tr(context: context)), callBackFunc: (str) { + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); Navigator.of(context).pop(); }, title: "", @@ -869,10 +1027,10 @@ class _AppointmentDetailsPageState extends State { ), ); case 50: - // return LocaleKeys.confirmLiveCare.tr(); + // return LocaleKeys.confirmLiveCare.tr(context: context); case 90: showCommonBottomSheetWithoutHeight(context, - title: LocaleKeys.onlineCheckIn.tr(), + title: LocaleKeys.onlineCheckIn.tr(context: context), child: AppointmentCheckinBottomSheet( patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, myAppointmentsViewModel: myAppointmentsViewModel, diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index a38a9f1f..e70ddf49 100644 --- a/lib/presentation/appointments/appointment_payment_page.dart +++ b/lib/presentation/appointments/appointment_payment_page.dart @@ -90,7 +90,7 @@ class _AppointmentPaymentPageState extends State { children: [ Expanded( child: CollapsingListView( - title: LocaleKeys.appointmentPayment.tr(), + title: LocaleKeys.appointmentPayment.tr(context: context), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -248,7 +248,7 @@ class _AppointmentPaymentPageState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - LocaleKeys.insuranceExpiredOrInactive.tr() + LocaleKeys.insuranceExpiredOrInactive.tr(context: context) .toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500) .paddingSymmetrical(24.h, 0.h), CustomButton( @@ -274,12 +274,12 @@ class _AppointmentPaymentPageState extends State { ) : const SizedBox(), SizedBox(height: 24.h), - LocaleKeys.totalAmountToPay.tr().toText18(isBold: true).paddingSymmetrical(24.h, 0.h), + LocaleKeys.totalAmountToPay.tr(context: context).toText18(isBold: true).paddingSymmetrical(24.h, 0.h), SizedBox(height: 17.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - LocaleKeys.amountBeforeTax.tr().toText14(isBold: true), + LocaleKeys.amountBeforeTax.tr(context: context).toText14(isBold: true), Utils.getPaymentAmountWithSymbol( myAppointmentsVM.patientAppointmentShareResponseModel!.patientShare!.toString().toText16(isBold: true), AppColors.blackColor, @@ -380,7 +380,7 @@ class _AppointmentPaymentPageState extends State { } void checkPaymentStatus() async { - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr()); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context)); if (selectedPaymentMethod == "TAMARA") { await payfortViewModel.checkTamaraPaymentStatus( transactionID: transID, @@ -403,6 +403,9 @@ class _AppointmentPaymentPageState extends State { patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, onSuccess: (apiResponse) { Future.delayed(Duration(milliseconds: 500), () { + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); LoaderBottomSheet.hideLoader(); Navigator.pushAndRemoveUntil( context, @@ -421,6 +424,9 @@ class _AppointmentPaymentPageState extends State { isFollowUp: myAppointmentsViewModel.patientAppointmentShareResponseModel!.isFollowup!, onSuccess: (apiResponse) { Future.delayed(Duration(milliseconds: 500), () { + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); LoaderBottomSheet.hideLoader(); Navigator.pushAndRemoveUntil( context, @@ -438,7 +444,7 @@ class _AppointmentPaymentPageState extends State { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: LocaleKeys.paymentFailedPleaseTryAgain.tr()), + child: Utils.getErrorWidget(loadingText: LocaleKeys.paymentFailedPleaseTryAgain.tr(context: context)), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, @@ -459,7 +465,6 @@ class _AppointmentPaymentPageState extends State { await payfortViewModel.checkPaymentStatus( transactionID: transID, onSuccess: (apiResponse) async { - print(apiResponse.data); if (payfortViewModel.payfortCheckPaymentStatusResponseModel!.responseMessage!.toLowerCase() == "success") { await myAppointmentsViewModel.createAdvancePayment( paymentMethodName: selectedPaymentMethod, @@ -486,6 +491,9 @@ class _AppointmentPaymentPageState extends State { patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, onSuccess: (apiResponse) { Future.delayed(Duration(milliseconds: 500), () { + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); LoaderBottomSheet.hideLoader(); Navigator.pushAndRemoveUntil( context, @@ -504,6 +512,9 @@ class _AppointmentPaymentPageState extends State { isFollowUp: myAppointmentsViewModel.patientAppointmentShareResponseModel!.isFollowup!, onSuccess: (apiResponse) { Future.delayed(Duration(milliseconds: 500), () { + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); LoaderBottomSheet.hideLoader(); Navigator.pushAndRemoveUntil( context, @@ -517,9 +528,10 @@ class _AppointmentPaymentPageState extends State { }); }); } else { + LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: LocaleKeys.paymentFailedPleaseTryAgain.tr()), + child: Utils.getErrorWidget(loadingText: LocaleKeys.paymentFailedPleaseTryAgain.tr(context: context)), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, @@ -564,14 +576,7 @@ class _AppointmentPaymentPageState extends State { } startApplePay() async { - showCommonBottomSheet(context, - child: Utils.getLoadingWidget(), - callBackFunc: (str) {}, - title: "", - height: ResponsiveExtension.screenHeight * 0.3, - isCloseButtonVisible: false, - isDismissible: false, - isFullScreen: false); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr(context: context)); transID = Utils.getAppointmentTransID( widget.patientAppointmentHistoryResponseModel.projectID, widget.patientAppointmentHistoryResponseModel.clinicID, @@ -642,7 +647,7 @@ class _AppointmentPaymentPageState extends State { currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED", onFailed: (failureResult) async { log("failureResult: ${failureResult.message.toString()}"); - Navigator.of(context).pop(); + LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( context, child: Utils.getErrorWidget(loadingText: failureResult.message.toString()), @@ -652,6 +657,7 @@ class _AppointmentPaymentPageState extends State { ); }, onSucceeded: (successResult) async { + LoaderBottomSheet.hideLoader(); log("successResult: ${successResult.responseMessage.toString()}"); selectedPaymentMethod = successResult.paymentOption ?? "VISA"; checkPaymentStatus(); diff --git a/lib/presentation/appointments/my_appointments_page.dart b/lib/presentation/appointments/my_appointments_page.dart index 12094674..c9e70487 100644 --- a/lib/presentation/appointments/my_appointments_page.dart +++ b/lib/presentation/appointments/my_appointments_page.dart @@ -155,7 +155,7 @@ class _MyAppointmentsPageState extends State { isLoading: true, isFromHomePage: false, ), - ).paddingSymmetrical(24.h, 0.h) + ) : filteredAppointmentList.isNotEmpty ? AnimationConfiguration.staggeredList( position: index, diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index 6b0439f1..971e9ca8 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -26,6 +26,7 @@ import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'dart:ui' as ui; class AppointmentCard extends StatelessWidget { final PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel; @@ -39,20 +40,20 @@ class AppointmentCard extends StatelessWidget { final ContactUsViewModel? contactUsViewModel; final BookAppointmentsViewModel bookAppointmentsViewModel; final bool isForRate; - const AppointmentCard({ - super.key, - required this.patientAppointmentHistoryResponseModel, - required this.myAppointmentsViewModel, - required this.bookAppointmentsViewModel, - this.isLoading = false, - this.isFromHomePage = false, - this.isFromMedicalReport = false, - this.isForEyeMeasurements = false, - this.isForFeedback = false, - this.medicalFileViewModel, - this.contactUsViewModel, - this.isForRate =false - }); + + const AppointmentCard( + {super.key, + required this.patientAppointmentHistoryResponseModel, + required this.myAppointmentsViewModel, + required this.bookAppointmentsViewModel, + this.isLoading = false, + this.isFromHomePage = false, + this.isFromMedicalReport = false, + this.isForEyeMeasurements = false, + this.isForFeedback = false, + this.medicalFileViewModel, + this.contactUsViewModel, + this.isForRate = false}); @override Widget build(BuildContext context) { @@ -64,11 +65,11 @@ class AppointmentCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - isForRate ? SizedBox(): _buildHeader(context, appState), + isForRate ? SizedBox() : _buildHeader(context, appState), SizedBox(height: 16.h), _buildDoctorRow(context), SizedBox(height: 16.h), - isForRate ? SizedBox(): _buildActionArea(context, appState), + isForRate ? SizedBox() : _buildActionArea(context, appState), ], ), ), @@ -101,18 +102,13 @@ class AppointmentCard extends StatelessWidget { textColor: isLoading ? AppColors.textColor : (isLiveCare ? AppColors.whiteColor : AppColors.textColor), ).toShimmer2(isShow: isLoading), AppCustomChipWidget( - labelText: isLoading - ? 'OutPatient' - : (appState.isArabic() - ? patientAppointmentHistoryResponseModel.isInOutPatientDescriptionN! - : patientAppointmentHistoryResponseModel.isInOutPatientDescription!), + labelText: + isLoading ? 'OutPatient' : (appState.isArabic() ? patientAppointmentHistoryResponseModel.isInOutPatientDescriptionN! : patientAppointmentHistoryResponseModel.isInOutPatientDescription!), backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.1), textColor: AppColors.primaryRedColor, ).toShimmer2(isShow: isLoading), AppCustomChipWidget( - labelText: isLoading - ? 'Booked' - : AppointmentType.getAppointmentStatusType(patientAppointmentHistoryResponseModel.patientStatusType!), + labelText: isLoading ? 'Booked' : AppointmentType.getAppointmentStatusType(patientAppointmentHistoryResponseModel.patientStatusType!), backgroundColor: AppColors.successColor.withValues(alpha: 0.1), textColor: AppColors.successColor, ).toShimmer2(isShow: isLoading), @@ -151,7 +147,9 @@ class AppointmentCard extends StatelessWidget { children: [ Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 15.w, height: 15.h), SizedBox(height: 2.h), - "${patientAppointmentHistoryResponseModel.decimalDoctorRate}".toText11(isBold: true, color: AppColors.textColor), + (isFoldable || isTablet) + ? "${patientAppointmentHistoryResponseModel.decimalDoctorRate}".toText9(isBold: true, color: AppColors.textColor) + : "${patientAppointmentHistoryResponseModel.decimalDoctorRate ?? "0.0"}".toText11(isBold: true, color: AppColors.textColor), ], ), ).circle(100).toShimmer2(isShow: isLoading), @@ -175,24 +173,28 @@ class AppointmentCard extends StatelessWidget { AppCustomChipWidget( labelText: isLoading ? 'Cardiology' - : (patientAppointmentHistoryResponseModel.clinicName!.length > 15 - ? '${patientAppointmentHistoryResponseModel.clinicName!.substring(0, 12)}...' + : (patientAppointmentHistoryResponseModel.clinicName!.length > 20 + ? '${patientAppointmentHistoryResponseModel.clinicName!.substring(0, 20)}...' : patientAppointmentHistoryResponseModel.clinicName!), ).toShimmer2(isShow: isLoading), AppCustomChipWidget( labelText: isLoading ? 'Olaya' - : patientAppointmentHistoryResponseModel.projectName!.length > 15 - ? '${patientAppointmentHistoryResponseModel.projectName!.substring(0, 12)}...' - : patientAppointmentHistoryResponseModel.projectName!) + : (patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital").length > 15 + ? '${(patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital").substring(0, 15)}...' + : patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital") .toShimmer2(isShow: isLoading), - AppCustomChipWidget( - labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), - icon: AppAssets.appointment_calendar_icon, - labelText: isLoading - ? 'Cardiology' - : "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)}", - ).toShimmer2(isShow: isLoading), + Directionality( + textDirection: ui.TextDirection.ltr, + child: AppCustomChipWidget( + labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), + icon: AppAssets.appointment_calendar_icon, + richText: isLoading + ? 'Cardiology'.toText10().toShimmer2(isShow: isLoading) + : "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)}" + .toText10(isEnglishOnly: true), + ).toShimmer2(isShow: isLoading), + ), // AppCustomChipWidget( // labelPadding: EdgeInsetsDirectional.only(start: -2.w, end: 6.w), @@ -224,83 +226,123 @@ class AppointmentCard extends StatelessWidget { } Widget _buildActionArea(BuildContext context, AppState appState) { - if (isFromMedicalReport) { - if (isForEyeMeasurements) { - return SizedBox.shrink(); - } else { - return CustomButton( - text: LocaleKeys.selectAppointment.tr(context: context), - onPressed: () { + if ((((patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) || + (patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) || + !Utils.isClinicAllowedForRebook(patientAppointmentHistoryResponseModel.clinicID ?? 0))) && + AppointmentType.isArrived(patientAppointmentHistoryResponseModel)) { + return CustomButton( + text: isFromMedicalReport ? LocaleKeys.selectAppointment.tr(context: context) : LocaleKeys.viewDetails.tr(context: context), + onPressed: () { + if (isFromMedicalReport) { if (isForFeedback) { contactUsViewModel!.setPatientFeedbackSelectedAppointment(patientAppointmentHistoryResponseModel); } else { medicalFileViewModel!.setSelectedMedicalReportAppointment(patientAppointmentHistoryResponseModel); } Navigator.pop(context, false); - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 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.checkmark_icon, - iconColor: AppColors.primaryRedColor, - iconSize: 16.h, - ); + } 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, + ); + } else { + if (isFromMedicalReport) { + if (isForEyeMeasurements) { + return SizedBox.shrink(); + } else { + return CustomButton( + text: LocaleKeys.selectAppointment.tr(context: context), + onPressed: () { + if (isForFeedback) { + contactUsViewModel!.setPatientFeedbackSelectedAppointment(patientAppointmentHistoryResponseModel); + } else { + medicalFileViewModel!.setSelectedMedicalReportAppointment(patientAppointmentHistoryResponseModel); + } + Navigator.pop(context, 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: AppAssets.checkmark_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + ); + } } - } - return Row( - children: [ - Expanded( - flex: 6, - child: (AppointmentType.isArrived(patientAppointmentHistoryResponseModel) - ? _getArrivedButton(context) - : CustomButton( - text: AppointmentType.getNextActionText(patientAppointmentHistoryResponseModel.nextAction), - onPressed: () => _goToDetails(context), - backgroundColor: - AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.15), - borderColor: - AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.01), - textColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction), - fontSize: 14.f, - fontWeight: FontWeight.w500, - borderRadius: 12.r, - padding: EdgeInsets.symmetric(horizontal: 10.w), - height: 40.h, - icon: AppointmentType.getNextActionIcon(patientAppointmentHistoryResponseModel.nextAction), - iconColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction), - iconSize: 15.h, - )) - .toShimmer2(isShow: isLoading), - ), - SizedBox(width: 8.h), - Expanded( - flex: 1, - child: Container( - height: (isFoldable || isTablet) ? 50.h : 40.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.textColor, borderRadius: 10.h), - 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, + return Row( + children: [ + Expanded( + flex: 6, + child: (AppointmentType.isArrived(patientAppointmentHistoryResponseModel) + ? _getArrivedButton(context) + : CustomButton( + text: AppointmentType.getNextActionText(patientAppointmentHistoryResponseModel.nextAction), + onPressed: () => _goToDetails(context), + backgroundColor: AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.15), + borderColor: AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.01), + textColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction), + fontSize: (isFoldable || isTablet) ? 12.f : 14.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: 40.h, + icon: AppointmentType.getNextActionIcon(patientAppointmentHistoryResponseModel.nextAction), + iconColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction), + iconSize: 15.h, + )) + .toShimmer2(isShow: isLoading), + ), + SizedBox(width: 8.h), + Expanded( + flex: 1, + child: Container( + height: (isFoldable || isTablet) ? 50.h : 40.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.textColor, borderRadius: 10.h), + 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); - }), - ), - ], - ); + ).toShimmer2(isShow: isLoading).onPress(() { + _goToDetails(context); + }), + ), + ], + ); + } } Widget _getArrivedButton(BuildContext context) { @@ -340,7 +382,7 @@ class AppointmentCard extends StatelessWidget { backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, - fontSize: 14.f, + fontSize: (isFoldable || isTablet) ? 12.f : 14.f, fontWeight: FontWeight.w500, borderRadius: 12.r, padding: EdgeInsets.symmetric(horizontal: 10.w), @@ -358,7 +400,7 @@ class AppointmentCard extends StatelessWidget { backgroundColor: AppColors.greyColor, borderColor: AppColors.greyColor, textColor: AppColors.blackColor, - fontSize: 14.f, + fontSize: (isFoldable || isTablet) ? 12.f : 14.f, fontWeight: FontWeight.w500, borderRadius: 12.r, padding: EdgeInsets.symmetric(horizontal: 10.w), @@ -386,8 +428,8 @@ class AppointmentCard extends StatelessWidget { ), ) .then((_) { - myAppointmentsViewModel.initAppointmentsViewModel(); - myAppointmentsViewModel.getPatientAppointments(true, false); + // myAppointmentsViewModel.initAppointmentsViewModel(); + // myAppointmentsViewModel.getPatientAppointments(true, false); }); } } diff --git a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart index c5139f17..f07b9a4f 100644 --- a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart +++ b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart @@ -36,6 +36,7 @@ class AppointmentCheckinBottomSheet extends StatelessWidget { @override Widget build(BuildContext context) { + locationUtils = getIt.get(); appState = getIt.get(); FlutterNfcKit.nfcAvailability.then((value) { _supportsNFC = (value == NFCAvailability.available); @@ -149,6 +150,9 @@ class AppointmentCheckinBottomSheet extends StatelessWidget { scannedCode: scannedCode, checkInType: checkInType, onSuccess: (apiResponse) { + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.success.tr(context: context), child: Utils.getSuccessWidget(loadingText: LocaleKeys.success.tr()), callBackFunc: () async { await myAppointmentsViewModel.getPatientAppointmentQueueDetails(); diff --git a/lib/presentation/appointments/widgets/appointment_doctor_card.dart b/lib/presentation/appointments/widgets/appointment_doctor_card.dart index 0c4aec14..7e430694 100644 --- a/lib/presentation/appointments/widgets/appointment_doctor_card.dart +++ b/lib/presentation/appointments/widgets/appointment_doctor_card.dart @@ -12,6 +12,7 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'dart:ui' as ui; class AppointmentDoctorCard extends StatelessWidget { const AppointmentDoctorCard( @@ -89,25 +90,28 @@ class AppointmentDoctorCard extends StatelessWidget { runSpacing: 6.h, children: [ AppCustomChipWidget( - labelText: (patientAppointmentHistoryResponseModel.clinicName!.length > 15 - ? '${patientAppointmentHistoryResponseModel.clinicName!.substring(0, 12)}...' + labelText: (patientAppointmentHistoryResponseModel.clinicName!.length > 20 + ? '${patientAppointmentHistoryResponseModel.clinicName!.substring(0, 20)}...' : patientAppointmentHistoryResponseModel.clinicName!), labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w), ), AppCustomChipWidget( - labelText: patientAppointmentHistoryResponseModel.projectName!, + labelText: patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital", labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w), ), - AppCustomChipWidget( - labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), - icon: AppAssets.doctor_calendar_icon, - labelText: "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang( - DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), - false, - )}", + Directionality( + textDirection: ui.TextDirection.ltr, + child: AppCustomChipWidget( + labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w), + icon: AppAssets.doctor_calendar_icon, + richText: "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang( + DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), + false, + )}".toText10(isEnglishOnly: true), + ), ), AppCustomChipWidget( - labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), + labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w), icon: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppAssets.walkin_appointment_icon : AppAssets.small_livecare_icon, @@ -159,8 +163,9 @@ class AppointmentDoctorCard extends StatelessWidget { icon: AppAssets.ask_doctor_icon, iconColor: AppColors.primaryRedColor, ) - : CustomButton( - text: LocaleKeys.rebookSameDoctor.tr(), + : !patientAppointmentHistoryResponseModel.isLiveCareAppointment! + ? CustomButton( + text: LocaleKeys.rebookSameDoctor.tr(), onPressed: () { onRescheduleTap(); }, @@ -175,7 +180,8 @@ class AppointmentDoctorCard extends StatelessWidget { icon: AppAssets.rebook_appointment_icon, iconColor: AppColors.blackColor, iconSize: 14.h, - ); + ) + : SizedBox.shrink(); } else { return patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false ? CustomButton( diff --git a/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart b/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart index eed45dfb..f3301f0c 100644 --- a/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart +++ b/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart @@ -32,7 +32,7 @@ import '../../../emergency_services/call_ambulance/widgets/type_selection_widget // crossAxisAlignment: CrossAxisAlignment.start, // children: [ // Text( -// LocaleKeys.selectHospital.tr(), +// LocaleKeys.selectHospital.tr(context: context), // style: TextStyle( // fontSize: 21, // fontWeight: FontWeight.w600, @@ -40,7 +40,7 @@ import '../../../emergency_services/call_ambulance/widgets/type_selection_widget // ), // ), // Text( -// LocaleKeys.selectHospitalSubTitle.tr(), +// LocaleKeys.selectHospitalSubTitle.tr(context: context), // style: TextStyle( // fontSize: 16, // fontWeight: FontWeight.w500, @@ -49,8 +49,8 @@ import '../../../emergency_services/call_ambulance/widgets/type_selection_widget // ), // SizedBox(height: 16.h), // TextInputWidget( -// labelText: LocaleKeys.search.tr(), -// hintText: LocaleKeys.searchHospital.tr(), +// labelText: LocaleKeys.search.tr(context: context), +// hintText: LocaleKeys.searchHospital.tr(context: context), // controller: searchText, // onChange: (value) { // appointmentsViewModel.filterHospitalListByString( @@ -135,8 +135,8 @@ class HospitalBottomSheetBody extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ TextInputWidget( - labelText: LocaleKeys.search.tr(), - hintText: LocaleKeys.searchHospital.tr(), + labelText: LocaleKeys.search.tr(context: context), + hintText: LocaleKeys.searchHospital.tr(context: context), controller: searchText, onChange: (value) { debouncer.run((){ diff --git a/lib/presentation/ask_doctor/ask_doctor_page.dart b/lib/presentation/ask_doctor/ask_doctor_page.dart new file mode 100644 index 00000000..4c2c267f --- /dev/null +++ b/lib/presentation/ask_doctor/ask_doctor_page.dart @@ -0,0 +1,104 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/ask_doctor/ask_doctor_view_model.dart'; +import 'package:hmg_patient_app_new/features/medical_file/models/patient_sickleave_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/appointment_card.dart'; +import 'package:hmg_patient_app_new/presentation/ask_doctor/doctor_response_page.dart'; +import 'package:hmg_patient_app_new/presentation/ask_doctor/widgets/ask_doctor_appointment_card.dart'; +import 'package:hmg_patient_app_new/presentation/medical_file/widgets/patient_sick_leave_card.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'package:provider/provider.dart'; + +class AskDoctorPage extends StatefulWidget { + const AskDoctorPage({super.key}); + + @override + State createState() => _AskDoctorPageState(); +} + +class _AskDoctorPageState extends State { + late MyAppointmentsViewModel myAppointmentsViewModel; + + @override + Widget build(BuildContext context) { + myAppointmentsViewModel = Provider.of(context, listen: false); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: Consumer( + builder: (context, askDoctorVM, child) { + return CollapsingListView( + title: LocaleKeys.askDoctor.tr(context: context), + doctorResponse: () { + askDoctorVM.getDoctorResponses(); + Navigator.of(context).push( + CustomPageRoute( + page: DoctorResponsePage(), + ), + ); + }, + child: SingleChildScrollView( + child: Column(children: [ + SizedBox(height: 16.h), + ListView.builder( + padding: EdgeInsets.only(top: 8.h), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: askDoctorVM.isAskDoctorAppointmentsLoading + ? 4 + : askDoctorVM.askDoctorAppointmentsList.isNotEmpty + ? askDoctorVM.askDoctorAppointmentsList.length + : 1, + itemBuilder: (context, index) { + return askDoctorVM.isAskDoctorAppointmentsLoading + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: PatientSickLeaveCard( + patientSickLeavesResponseModel: PatientSickLeavesResponseModel(), + isLoading: true, + )).paddingSymmetrical(0.w, 12.h) + : askDoctorVM.askDoctorAppointmentsList.isNotEmpty + ? AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + margin: EdgeInsets.symmetric(vertical: 8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AskDoctorAppointmentCard( + askDoctorAppointmentHistoryList: askDoctorVM.askDoctorAppointmentsList[index], + myAppointmentsViewModel: myAppointmentsViewModel, + ) + ], + ), + ), + ), + ), + ) + : Utils.getNoDataWidget(context, noDataText: LocaleKeys.noAppointmentsYet.tr(context: context)); + }, + ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 24.h), + ]), + ), + ); + }, + ), + ); + } +} diff --git a/lib/presentation/ask_doctor/doctor_response_page.dart b/lib/presentation/ask_doctor/doctor_response_page.dart new file mode 100644 index 00000000..9cd19ef6 --- /dev/null +++ b/lib/presentation/ask_doctor/doctor_response_page.dart @@ -0,0 +1,157 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/ask_doctor/ask_doctor_view_model.dart'; +import 'package:hmg_patient_app_new/features/medical_file/models/patient_sickleave_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/appointment_card.dart'; +import 'package:hmg_patient_app_new/presentation/ask_doctor/widgets/ask_doctor_appointment_card.dart'; +import 'package:hmg_patient_app_new/presentation/ask_doctor/widgets/doctor_response_transactions.dart'; +import 'package:hmg_patient_app_new/presentation/medical_file/widgets/patient_sick_leave_card.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:provider/provider.dart'; + +class DoctorResponsePage extends StatefulWidget { + const DoctorResponsePage({super.key}); + + @override + State createState() => _DoctorResponsePageState(); +} + +class _DoctorResponsePageState extends State { + late MyAppointmentsViewModel myAppointmentsViewModel; + + @override + Widget build(BuildContext context) { + myAppointmentsViewModel = Provider.of(context, listen: false); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: LocaleKeys.doctorResponses.tr(context: context), + child: SingleChildScrollView( + child: Column( + children: [ + SizedBox(height: 16.h), + Consumer(builder: (context, askDoctorVM, child) { + return ListView.builder( + padding: EdgeInsets.zero, + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: askDoctorVM.isDoctorResponsesLoading + ? 4 + : askDoctorVM.doctorResponsesList.isNotEmpty + ? askDoctorVM.doctorResponsesList.length + : 1, + itemBuilder: (context, index) { + return askDoctorVM.isDoctorResponsesLoading + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: PatientSickLeaveCard( + patientSickLeavesResponseModel: PatientSickLeavesResponseModel(), + isLoading: true, + )).paddingSymmetrical(0.w, 12.h) + : askDoctorVM.doctorResponsesList.isNotEmpty + ? AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + margin: EdgeInsets.symmetric(vertical: 8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), + child: Padding( + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + askDoctorVM.doctorResponsesList[index].requestTypeDescription!.toText18(isBold: true), + SizedBox(height: 24.h), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.network( + "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown.png", + width: 50.w, + height: 50.h, + fit: BoxFit.cover, + ).circle(100.r), + ], + ), + SizedBox(width: 16.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + askDoctorVM.doctorResponsesList[index].doctorName!.toText16(isBold: true, maxlines: 1), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget( + labelText: (askDoctorVM.doctorResponsesList[index].clinicName ?? "").length > 15 + ? '${askDoctorVM.doctorResponsesList[index].clinicName!.substring(0, 12)}...' + : askDoctorVM.doctorResponsesList[index].clinicName ?? "", + ), + AppCustomChipWidget( + labelText: (askDoctorVM.doctorResponsesList[index].projectName ?? "").length > 15 + ? '${askDoctorVM.doctorResponsesList[index].projectName!.substring(0, 12)}...' + : askDoctorVM.doctorResponsesList[index].projectName ?? ""), + AppCustomChipWidget( + labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), + icon: AppAssets.appointment_calendar_icon, + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(askDoctorVM.doctorResponsesList[index].createdOn), false), + ), + ], + ), + ], + ), + ), + ], + ), + ], + ), + ), + ).onPress(() { + showCommonBottomSheetWithoutHeight( + context, + title: askDoctorVM.doctorResponsesList[index].requestTypeDescription!, + child: DoctorResponseTransactions(doctorResponseModel: askDoctorVM.doctorResponsesList[index],), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }), + ), + ), + ) + : Utils.getNoDataWidget(context, noDataText: LocaleKeys.noDataAvailable.tr(context: context)); + }, + ).paddingSymmetrical(24.h, 0.h); + }), + SizedBox(height: 24.h), + ], + ), + ), + ), + ); + } +} diff --git a/lib/presentation/ask_doctor/widgets/ask_doctor_appointment_card.dart b/lib/presentation/ask_doctor/widgets/ask_doctor_appointment_card.dart new file mode 100644 index 00000000..4ff24c7a --- /dev/null +++ b/lib/presentation/ask_doctor/widgets/ask_doctor_appointment_card.dart @@ -0,0 +1,205 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/ask_doctor/models/ask_doctor_appointments_list.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/ask_doctor_request_type_select.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; + +class AskDoctorAppointmentCard extends StatelessWidget { + final AskDoctorAppointmentHistoryList askDoctorAppointmentHistoryList; + final MyAppointmentsViewModel myAppointmentsViewModel; + + const AskDoctorAppointmentCard({super.key, required this.askDoctorAppointmentHistoryList, required this.myAppointmentsViewModel}); + + @override + Widget build(BuildContext context) { + final appState = getIt.get(); + return Padding( + padding: EdgeInsets.all(14.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildHeader(context, appState), + SizedBox(height: 16.h), + _buildDoctorRow(context), + SizedBox(height: 16.h), + _buildActionArea(context, appState), + ], + ), + ); + } + + Widget _buildHeader(BuildContext context, AppState appState) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded(child: _buildChips(context, appState)), + ], + ); + } + + Widget _buildChips(BuildContext context, AppState appState) { + return Wrap( + alignment: WrapAlignment.start, + direction: Axis.horizontal, + spacing: 6.w, + runSpacing: 6.h, + children: [ + AppCustomChipWidget( + icon: AppAssets.walkin_appointment_icon, + iconColor: AppColors.textColor, + labelText: LocaleKeys.walkin.tr(context: context), + backgroundColor: AppColors.greyColor, + textColor: AppColors.textColor, + ), + AppCustomChipWidget( + labelText: LocaleKeys.outPatient.tr(context: context), + backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.1), + textColor: AppColors.primaryRedColor, + ), + AppCustomChipWidget( + labelText: LocaleKeys.booked.tr(context: context), + backgroundColor: AppColors.successColor.withValues(alpha: 0.1), + textColor: AppColors.successColor, + ), + ], + ); + } + + Widget _buildDoctorRow(BuildContext context) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.network( + askDoctorAppointmentHistoryList.doctorImageURL!, + width: 63.h, + height: 63.h, + fit: BoxFit.cover, + ).circle(100.r), + Transform.translate( + offset: Offset(0.0, -20.h), + child: Container( + width: 40.w, + height: 40.h, + decoration: BoxDecoration( + color: AppColors.whiteColor, + shape: BoxShape.circle, // Makes the container circular + border: Border.all( + color: AppColors.scaffoldBgColor, // Color of the border + width: 1.5.w, // Width of the border + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 15.w, height: 15.h), + SizedBox(height: 2.h), + "${askDoctorAppointmentHistoryList.decimalDoctorRate}".toText11(isBold: true, color: AppColors.textColor), + ], + ), + ).circle(100), + ), + ], + ), + SizedBox(width: 16.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ("${askDoctorAppointmentHistoryList.doctorTitle} ${askDoctorAppointmentHistoryList.doctorName!}").toText16(isBold: true, maxlines: 1), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget( + labelText: + askDoctorAppointmentHistoryList.clinicName!.length > 15 ? '${askDoctorAppointmentHistoryList.clinicName!.substring(0, 12)}...' : askDoctorAppointmentHistoryList.clinicName!, + ), + AppCustomChipWidget( + labelText: askDoctorAppointmentHistoryList.projectName!.length > 15 + ? '${askDoctorAppointmentHistoryList.projectName!.substring(0, 12)}...' + : askDoctorAppointmentHistoryList.projectName!), + AppCustomChipWidget( + labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), + icon: AppAssets.appointment_calendar_icon, + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(askDoctorAppointmentHistoryList.appointmentDate), false), + ), + ], + ), + ], + ), + ), + ], + ); + } + + Widget _buildActionArea(BuildContext context, AppState appState) { + return CustomButton( + text: LocaleKeys.askDoctor.tr(context: context), + onPressed: () async { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingDoctorAvailability.tr(context: context)); + await myAppointmentsViewModel.isDoctorAvailable( + projectID: askDoctorAppointmentHistoryList.projectID!, + doctorId: askDoctorAppointmentHistoryList.doctorID!, + clinicId: askDoctorAppointmentHistoryList.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() + // myAppointmentsVM.patientAppointmentsHistoryList[index], + ), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + } else { + LoaderBottomSheet.hideLoader(); + } + }, + onError: (_) { + LoaderBottomSheet.hideLoader(); + }, + ); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 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, + ); + } +} diff --git a/lib/presentation/ask_doctor/widgets/doctor_response_transactions.dart b/lib/presentation/ask_doctor/widgets/doctor_response_transactions.dart new file mode 100644 index 00000000..33b544d7 --- /dev/null +++ b/lib/presentation/ask_doctor/widgets/doctor_response_transactions.dart @@ -0,0 +1,131 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/ask_doctor/models/doctor_response_model.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; + +class DoctorResponseTransactions extends StatelessWidget { + const DoctorResponseTransactions({super.key, required this.doctorResponseModel}); + + final DoctorResponseModel doctorResponseModel; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.network( + "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown.png", + width: 50.w, + height: 50.h, + fit: BoxFit.cover, + ).circle(100.r), + ], + ), + SizedBox(width: 16.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + doctorResponseModel.doctorName!.toText16(isBold: true, maxlines: 1), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget( + labelText: (doctorResponseModel.clinicName ?? "").length > 15 ? '${doctorResponseModel.clinicName!.substring(0, 12)}...' : doctorResponseModel.clinicName ?? "", + ), + AppCustomChipWidget( + labelText: (doctorResponseModel.projectName ?? "").length > 15 ? '${doctorResponseModel.projectName!.substring(0, 12)}...' : doctorResponseModel.projectName ?? ""), + AppCustomChipWidget( + labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), + icon: AppAssets.appointment_calendar_icon, + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(doctorResponseModel.createdOn), false), + ), + ], + ), + ], + ), + ), + ], + ), + SizedBox(height: 16.h), + Divider(color: AppColors.dividerColor, height: 1.h), + SizedBox(height: 16.h), + Padding( + padding: EdgeInsets.symmetric(horizontal: 72.w), + child: CustomButton( + text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(doctorResponseModel.createdOn), false), + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.textColor, + fontSize: 14.f, + fontWeight: FontWeight.w500, + borderRadius: 10.r, + height: 40.h, + ).paddingSymmetrical(16.w, 0.h), + ), + SizedBox(height: 16.h), + Row( + children: [ + Image.asset(getIt.get().getAuthenticatedUser()?.gender == 1 ? AppAssets.maleImg : AppAssets.femaleImg, width: 42.w, height: 42.h), + SizedBox(width: 8.w), + Container( + padding: EdgeInsets.all(8.w), + decoration: BoxDecoration( + color: AppColors.infoColor.withAlpha(20), + borderRadius: BorderRadius.circular(8.r), + ), + child: doctorResponseModel.requestTypeDescription!.toText12(color: AppColors.infoColor, fontWeight: FontWeight.w500), + ), + ], + ), + SizedBox(height: 24.h), + ListView.builder( + padding: EdgeInsets.zero, + itemCount: doctorResponseModel.transactions!.length, + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Container( + padding: EdgeInsets.all(8.w), + decoration: BoxDecoration( + color: AppColors.infoColor.withAlpha(20), + borderRadius: BorderRadius.circular(8.r), + ), + child: doctorResponseModel.transactions![index].doctorResponse!.toText12(color: AppColors.textColor, fontWeight: FontWeight.w500), + ), + SizedBox(width: 8.w), + Image.network("https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown.png", fit: BoxFit.cover, width: 42.w, height: 42.h).circle(100.r), + ], + ), + ], + ); + }, + ), + ], + ); + } +} diff --git a/lib/presentation/authentication/login.dart b/lib/presentation/authentication/login.dart index a59062d6..9f64bde4 100644 --- a/lib/presentation/authentication/login.dart +++ b/lib/presentation/authentication/login.dart @@ -71,10 +71,11 @@ class LoginScreenState extends State { children: [ Utils.showLottie(context: context, assetPath: AppAnimations.login, width: 200.h, height: 200.h, repeat: true, fit: BoxFit.cover), SizedBox(height: 130.h), // Adjusted to sizer unit - LocaleKeys.welcomeToDrSulaiman.tr().toText32(isBold: true, color: AppColors.textColor), + LocaleKeys.welcomeToDrSulaiman.tr(context: context).toText32(isBold: true, color: AppColors.textColor), SizedBox(height: 32.h), + Localizations.override(context: context, locale: Locale('en', 'US'), child: Container()), // Force English locale for this widget TextInputWidget( - labelText: "${LocaleKeys.nationalId.tr()} / ${LocaleKeys.fileNo.tr()}", + labelText: "${LocaleKeys.nationalId.tr(context: context)} / ${LocaleKeys.fileNo.tr(context: context)}", hintText: "xxxxxxxxx", controller: authVm.nationalIdController, focusNode: _nationalIdFocusNode, @@ -87,13 +88,14 @@ class LoginScreenState extends State { isAllowLeadingIcon: true, padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 10.h), leadingIcon: AppAssets.student_card, - errorMessage: LocaleKeys.enterValidIDorIqama.tr(), + errorMessage: LocaleKeys.enterValidIDorIqama.tr(context: context), hasError: false, + fontFamily: "Poppins", ), SizedBox(height: 16.h), CustomButton( height: 50.h, - text: LocaleKeys.login.tr(), + text: LocaleKeys.login.tr(context: context), icon: AppAssets.login1, iconColor: Colors.white, onPressed: () { @@ -116,10 +118,10 @@ class LoginScreenState extends State { text: TextSpan( style: context.dynamicTextStyle(color: Colors.black, fontSize: 14.f, height: 26 / 16, fontWeight: FontWeight.w500), children: [ - TextSpan(text: LocaleKeys.dontHaveAccount.tr(), style: context.dynamicTextStyle()), + TextSpan(text: LocaleKeys.dontHaveAccount.tr(context: context), style: context.dynamicTextStyle()), TextSpan(text: " "), TextSpan( - text: LocaleKeys.registernow.tr(), + text: LocaleKeys.registernow.tr(context: context), style: context.dynamicTextStyle( color: AppColors.primaryRedColor, fontSize: 14.f, // Adjusted to sizer unit @@ -172,7 +174,7 @@ class LoginScreenState extends State { Padding( padding: EdgeInsets.only(bottom: 10.h), child: CustomButton( - text: LocaleKeys.sendOTPSMS.tr(), + text: LocaleKeys.sendOTPSMS.tr(context: context), onPressed: () async { if (ValidationUtils.isValidatePhone( phoneNumber: phoneNumberController!.text, @@ -196,14 +198,14 @@ class LoginScreenState extends State { children: [ Padding( padding: EdgeInsets.symmetric(horizontal: 8.h), - child: LocaleKeys.oR.tr().toText16(color: AppColors.textColor), + child: LocaleKeys.oR.tr(context: context).toText16(color: AppColors.textColor), ), ], ), Padding( padding: EdgeInsets.only(bottom: 10.h, top: 10.h), child: CustomButton( - text: LocaleKeys.sendOTPWHATSAPP.tr(), + text: LocaleKeys.sendOTPWHATSAPP.tr(context: context), onPressed: () async { if (ValidationUtils.isValidatePhone( phoneNumber: phoneNumberController!.text, diff --git a/lib/presentation/authentication/quick_login.dart b/lib/presentation/authentication/quick_login.dart index bdeb0fff..fbcb17d8 100644 --- a/lib/presentation/authentication/quick_login.dart +++ b/lib/presentation/authentication/quick_login.dart @@ -49,7 +49,7 @@ class QuickLoginState extends State { ], ), Utils.showLottie(context: context, assetPath: AppAnimations.checkmark, width: 120, height: 120, repeat: true), - LocaleKeys.allSet.tr().toText16(textAlign: TextAlign.center, weight: FontWeight.w500) + LocaleKeys.allSet.tr(context: context).toText16(textAlign: TextAlign.center, weight: FontWeight.w500) // Text( // ' TranslationBase.of(context).allSet', // textAlign: TextAlign.center, @@ -67,7 +67,7 @@ class QuickLoginState extends State { children: [ Image.asset(AppAssets.lockIcon, height: 100), SizedBox(height: 10.h), - LocaleKeys.enableQuickLogin.tr().toText26(isBold: true), + LocaleKeys.enableQuickLogin.tr(context: context).toText26(isBold: true), // Text( // ' TranslationBase.of(context).enableQuickLogin', // style: context.dynamicTextStyle( @@ -77,7 +77,7 @@ class QuickLoginState extends State { // ), // ), SizedBox(height: 5.h), - LocaleKeys.enableQuickLogin.tr().toText16(color: AppColors.quickLoginColor), + LocaleKeys.enableQuickLogin.tr(context: context).toText16(color: AppColors.quickLoginColor), // Description // Text( // 'TranslationBase.of(context).enableMsg', @@ -94,7 +94,7 @@ class QuickLoginState extends State { children: [ Expanded( child: CustomButton( - text: LocaleKeys.enableQuickLogin.tr(), + text: LocaleKeys.enableQuickLogin.tr(context: context), onPressed: () { widget.onPressed(); }, @@ -114,7 +114,7 @@ class QuickLoginState extends State { children: [ Expanded( child: CustomButton( - text: LocaleKeys.notNow.tr(), + text: LocaleKeys.notNow.tr(context: context), onPressed: () { cacheService.saveBool(key: CacheConst.quickLoginEnabled, value: false); Navigator.pop(context, "true"); diff --git a/lib/presentation/authentication/register.dart b/lib/presentation/authentication/register.dart index fa10b95d..2a26a726 100644 --- a/lib/presentation/authentication/register.dart +++ b/lib/presentation/authentication/register.dart @@ -80,7 +80,7 @@ class _RegisterNew extends State { children: [ Utils.showLottie(context: context, assetPath: 'assets/animations/lottie/register.json', width: 200.w, height: 200.h, fit: BoxFit.cover, repeat: true), SizedBox(height: 16.h), - LocaleKeys.prepareToElevate.tr().toText32(isBold: true), + LocaleKeys.prepareToElevate.tr(context: context).toText32(isBold: true), SizedBox(height: 24.h), Directionality( textDirection: Directionality.of(context), @@ -96,10 +96,11 @@ class _RegisterNew extends State { ).withVerticalPadding(8.h), Divider(height: 1.h), TextInputWidget( - labelText: LocaleKeys.nationalIdNumber.tr(), + labelText: LocaleKeys.nationalIdNumber.tr(context: context), hintText: "xxxxxxxxx", controller: authVm.nationalIdController, focusNode: _nationalIdFocusNode, + keyboardType: TextInputType.number, isEnable: true, prefix: null, isAllowRadius: true, @@ -108,10 +109,11 @@ class _RegisterNew extends State { autoFocus: true, padding: EdgeInsets.symmetric(vertical: 8.h), leadingIcon: AppAssets.student_card, + fontFamily: "Poppins", ).withVerticalPadding(8), Divider(height: 1), TextInputWidget( - labelText: LocaleKeys.dob.tr(), + labelText: LocaleKeys.dob.tr(context: context), hintText: "11 July, 1994", controller: authVm.dobController, focusNode: _dobFocusNode, @@ -125,6 +127,7 @@ class _RegisterNew extends State { selectionType: SelectionTypeEnum.calendar, onCalendarTypeChanged: authVm.onCalenderTypeChange, onChange: authVm.onDobChange, + fontFamily: "Poppins", ).withVerticalPadding(8), ], ), @@ -156,16 +159,18 @@ class _RegisterNew extends State { Row( children: [ Text( - LocaleKeys.iAcceptThe.tr(), + LocaleKeys.iAcceptThe.tr(context: context), style: context.dynamicTextStyle(fontSize: 14.f, fontWeight: FontWeight.w500, color: Color(0xFF2E3039)), ), GestureDetector( onTap: () { // Navigate to terms and conditions page - Navigator.of(context).pushNamed('/terms'); + Utils.openWebView( + url: 'https://hmg.com/en/Pages/Terms.aspx', + ); }, child: Text( - LocaleKeys.termsConditoins.tr(), + " ${LocaleKeys.termsConditoins.tr(context: context)}", style: context.dynamicTextStyle( fontSize: 14.f, fontWeight: FontWeight.w500, @@ -179,7 +184,7 @@ class _RegisterNew extends State { ), // Expanded( // child: Text( - // LocaleKeys.iAcceptTermsConditions.tr().split("the").first, + // LocaleKeys.iAcceptTermsConditions.tr(context: context).split("the").first, // style: context.dynamicTextStyle(fontSize: 14.fSize, fontWeight: FontWeight.w500, color: Color(0xFF2E3039)), // ), // ), @@ -188,7 +193,7 @@ class _RegisterNew extends State { ), SizedBox(height: 25.h), CustomButton( - text: LocaleKeys.registernow.tr(), + text: LocaleKeys.registernow.tr(context: context), icon: AppAssets.note_edit, onPressed: () { // Dismiss keyboard before proceeding @@ -220,10 +225,10 @@ class _RegisterNew extends State { fontWeight: FontWeight.w500, ), children: [ - TextSpan(text: LocaleKeys.alreadyHaveAccount.tr(), style: context.dynamicTextStyle()), + TextSpan(text: LocaleKeys.alreadyHaveAccount.tr(context: context), style: context.dynamicTextStyle()), TextSpan(text: " "), TextSpan( - text: LocaleKeys.loginNow.tr(), + text: LocaleKeys.loginNow.tr(context: context), style: context.dynamicTextStyle( color: AppColors.primaryRedColor, fontSize: 16.f, @@ -271,7 +276,7 @@ class _RegisterNew extends State { Padding( padding: const EdgeInsets.only(bottom: 10), child: CustomButton( - text: LocaleKeys.sendOTPSMS.tr(), + text: LocaleKeys.sendOTPSMS.tr(context: context), onPressed: () async { // Dismiss keyboard before validation FocusScope.of(context).unfocus(); @@ -298,14 +303,14 @@ class _RegisterNew extends State { children: [ Padding( padding: EdgeInsets.symmetric(horizontal: 8.h), - child: LocaleKeys.oR.tr().toText16(color: AppColors.textColor), + child: LocaleKeys.oR.tr(context: context).toText16(color: AppColors.textColor), ), ], ), Padding( padding: EdgeInsets.only(bottom: 10.h, top: 10.h), child: CustomButton( - text: LocaleKeys.sendOTPWHATSAPP.tr(), + text: LocaleKeys.sendOTPWHATSAPP.tr(context: context), onPressed: () async { FocusScope.of(context).unfocus(); if (ValidationUtils.isValidatePhone( diff --git a/lib/presentation/authentication/register_step2.dart b/lib/presentation/authentication/register_step2.dart index fdb0f42e..c83226ca 100644 --- a/lib/presentation/authentication/register_step2.dart +++ b/lib/presentation/authentication/register_step2.dart @@ -140,7 +140,7 @@ class _RegisterNew extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.personalDetailsVerification.tr().toText26(color: AppColors.textColor, weight: FontWeight.w600, letterSpacing: -2), + LocaleKeys.personalDetailsVerification.tr(context: context).toText26(color: AppColors.textColor, weight: FontWeight.w600, letterSpacing: -2), SizedBox(height: 24.h), // Insurance Card Section @@ -171,8 +171,8 @@ class _RegisterNew extends State { child: Column( children: [ TextInputWidget( - labelText: authVM!.isUserFromUAE() ? LocaleKeys.fullName.tr() : LocaleKeys.name.tr(), - hintText: authVM!.isUserFromUAE() ? LocaleKeys.enterNameHere.tr() : (name), + labelText: authVM!.isUserFromUAE() ? LocaleKeys.fullName.tr(context: context) : LocaleKeys.name.tr(context: context), + hintText: authVM!.isUserFromUAE() ? LocaleKeys.enterNameHere.tr(context: context) : (name), controller: authVM!.isUserFromUAE() ? authVM!.nameController : null, isEnable: true, prefix: null, @@ -190,7 +190,7 @@ class _RegisterNew extends State { ).paddingSymmetrical(0.h, 8.h), Divider(height: 1.h, color: AppColors.greyColor), TextInputWidget( - labelText: LocaleKeys.nationalIdNumber.tr(), + labelText: LocaleKeys.nationalIdNumber.tr(context: context), hintText: authVM!.isUserFromUAE() ? appState.getUserRegistrationPayload.patientIdentificationId.toString() : (appState.getNHICUserData.idNumber ?? ""), controller: null, isEnable: true, @@ -210,8 +210,8 @@ class _RegisterNew extends State { builder: (context, genderType, child) { final authVM = context.read(); return DropdownWidget( - labelText: LocaleKeys.gender.tr(), - hintText: LocaleKeys.malE.tr(), + labelText: LocaleKeys.gender.tr(context: context), + hintText: LocaleKeys.malE.tr(context: context), isEnable: true, dropdownItems: GenderTypeEnum.values.map((e) => appState.isArabic() ? e.typeAr : e.type).toList(), selectedValue: genderType != null ? (appState.isArabic() ? genderType.typeAr : genderType.type) : "", @@ -226,7 +226,7 @@ class _RegisterNew extends State { ).withVerticalPadding(8); }) : TextInputWidget( - labelText: LocaleKeys.gender.tr(), + labelText: LocaleKeys.gender.tr(context: context), hintText: (appState.getNHICUserData.gender ?? ""), controller: null, isEnable: true, @@ -247,8 +247,8 @@ class _RegisterNew extends State { builder: (context, maritalStatus, child) { final authVM = context.read(); // For onChange return DropdownWidget( - labelText: LocaleKeys.maritalStatus.tr(), - hintText: LocaleKeys.married.tr(), + labelText: LocaleKeys.maritalStatus.tr(context: context), + hintText: LocaleKeys.married.tr(context: context), isEnable: true, dropdownItems: MaritalStatusTypeEnum.values.map((e) => appState.isArabic() ? e.typeAr : e.type).toList(), selectedValue: maritalStatus != null ? (appState.isArabic() ? maritalStatus.typeAr : maritalStatus.type) : "", @@ -264,7 +264,7 @@ class _RegisterNew extends State { }, ) : TextInputWidget( - labelText: LocaleKeys.maritalStatus.tr(), + labelText: LocaleKeys.maritalStatus.tr(context: context), hintText: appState.isArabic() ? (MaritalStatusTypeExtension.fromValue(appState.getNHICUserData.maritalStatusCode)!.typeAr) : (MaritalStatusTypeExtension.fromValue(appState.getNHICUserData.maritalStatusCode)!.type), @@ -293,8 +293,8 @@ class _RegisterNew extends State { builder: (context, data, child) { final authVM = context.read(); return DropdownWidget( - labelText: LocaleKeys.country.tr(), - hintText: LocaleKeys.uae.tr(), + labelText: LocaleKeys.country.tr(context: context), + hintText: LocaleKeys.uae.tr(context: context), isEnable: true, dropdownItems: (data.countriesList ?? []).map((e) => data.isArabic ? e.nameN ?? "" : e.name ?? "").toList(), selectedValue: data.selectedCountry != null @@ -314,7 +314,7 @@ class _RegisterNew extends State { }, ) : TextInputWidget( - labelText: LocaleKeys.nationality.tr(), + labelText: LocaleKeys.nationality.tr(context: context), hintText: appState.isArabic() ? (authVM!.countriesList!.firstWhere((e) => e.id == (appState.getNHICUserData.nationalityCode ?? ""), orElse: () => NationalityCountries()).nameN ?? "") : (authVM!.countriesList!.firstWhere((e) => e.id == (appState.getNHICUserData.nationalityCode ?? ""), orElse: () => NationalityCountries()).name ?? ""), @@ -333,7 +333,7 @@ class _RegisterNew extends State { color: AppColors.greyColor, ), TextInputWidget( - labelText: LocaleKeys.mobileNumber.tr(), + labelText: LocaleKeys.mobileNumber.tr(context: context), hintText: appState.getUserRegistrationPayload.patientMobileNumber.toString(), controller: null, isEnable: false, @@ -350,7 +350,7 @@ class _RegisterNew extends State { color: AppColors.greyColor, ), TextInputWidget( - labelText: LocaleKeys.dob.tr(), + labelText: LocaleKeys.dob.tr(context: context), hintText: authVM!.isUserFromUAE() ? appState.getUserRegistrationPayload.dob! : appState.getNHICUserData.dateOfBirth ?? "", controller: authVM!.isUserFromUAE() ? authVM!.dobController : null, isEnable: false, @@ -365,8 +365,52 @@ class _RegisterNew extends State { ], ), ), - SizedBox(height: 120.h), - + SizedBox(height: 50.h), + Row( + children: [ + Expanded( + child: CustomButton( + text: LocaleKeys.cancel.tr(context: context), + icon: AppAssets.cancel, + onPressed: () { + Navigator.of(context).pop(); + // authVM!.clearDefaultInputValues(); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + iconColor: AppColors.primaryRedColor, + ), + ), + SizedBox(width: 16.w), + Expanded( + child: CustomButton( + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + text: LocaleKeys.confirm.tr(context: context), + icon: AppAssets.confirm, + iconColor: AppColors.whiteColor, + onPressed: () { + if (appState.getUserRegistrationPayload.zipCode != CountryEnum.saudiArabia.countryCode) { + if (ValidationUtils.validateUaeRegistration( + name: authVM!.nameController.text, + gender: authVM!.genderType, + country: authVM!.pickedCountryByUAEUser, + maritalStatus: authVM!.maritalStatus, + onOkPress: () { + Navigator.of(context).pop(); + })) { + showModel(context: context); + } + } else { + showModel(context: context); + } + }, + ), + ) + ], + ), ], ), ), @@ -391,7 +435,7 @@ class _RegisterNew extends State { Padding( padding: const EdgeInsets.only(bottom: 10), child: CustomButton( - text: LocaleKeys.submit.tr(), + text: LocaleKeys.submit.tr(context: context), onPressed: () { if (ValidationUtils.isValidateEmail( email: authVM!.emailController.text, diff --git a/lib/presentation/authentication/saved_login_screen.dart b/lib/presentation/authentication/saved_login_screen.dart index f0fd8cb8..231d7d14 100644 --- a/lib/presentation/authentication/saved_login_screen.dart +++ b/lib/presentation/authentication/saved_login_screen.dart @@ -18,6 +18,7 @@ import 'package:hmg_patient_app_new/widgets/bottomsheet/generic_bottom_sheet.dar import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; +import 'dart:ui' as ui; class SavedLogin extends StatefulWidget { const SavedLogin({super.key}); @@ -93,14 +94,15 @@ class _SavedLogin extends State { ("${LocaleKeys.lastLoginBy.tr()} ${loginType.displayName}") .toText14(isBold: true, color: AppColors.greyTextColor, letterSpacing: -1), - appState.getSelectDeviceByImeiRespModelElement != null - ? (appState.getSelectDeviceByImeiRespModelElement!.createdOn != null - ? DateUtil.getFormattedDate( - DateUtil.convertStringToDate(appState.getSelectDeviceByImeiRespModelElement!.createdOn!), - "d MMMM, y 'at' HH:mm") - : '--') - .toText16(isBold: true, color: AppColors.textColor) - : SizedBox(), + Directionality( + textDirection: ui.TextDirection.ltr, + child: appState.getSelectDeviceByImeiRespModelElement != null + ? (appState.getSelectDeviceByImeiRespModelElement!.createdOn != null + ? DateUtil.getFormattedDate(DateUtil.convertStringToDate(appState.getSelectDeviceByImeiRespModelElement!.createdOn!), "d MMMM, y 'at' HH:mm") + : '--') + .toText16(isBold: true, color: AppColors.textColor, isEnglishOnly: true) + : SizedBox(), + ), appState.getSelectDeviceByImeiRespModelElement != null ? Container( @@ -115,6 +117,7 @@ class _SavedLogin extends State { CustomButton( text: "${LocaleKeys.loginBy.tr()} ${loginType.displayName}", onPressed: () { + if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) { authVm.loginWithFingerPrintFace(() {}); } else { diff --git a/lib/presentation/blood_donation/blood_donation_page.dart b/lib/presentation/blood_donation/blood_donation_page.dart index a987220a..7c6c625f 100644 --- a/lib/presentation/blood_donation/blood_donation_page.dart +++ b/lib/presentation/blood_donation/blood_donation_page.dart @@ -28,11 +28,22 @@ import 'package:lottie/lottie.dart'; import 'package:provider/provider.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart'; -class BloodDonationPage extends StatelessWidget { +class BloodDonationPage extends StatefulWidget { BloodDonationPage({super.key}); + @override + State createState() => _BloodDonationPageState(); +} + +class _BloodDonationPageState extends State { late AppState appState; + @override + void initState() { + super.initState(); + Future.microtask(() => getIt.get().fetchAllData()); + } + @override Widget build(BuildContext context) { appState = getIt(); @@ -47,70 +58,69 @@ class BloodDonationPage extends StatelessWidget { trailing: CustomButton( text: "Book", onPressed: () { - // if (bloodDonationVM.isUserAuthanticated()) { - bloodDonationVM.fetchHospitalsList().then((value) { - showCommonBottomSheetWithoutHeight(context, title: "Select Hospital", isDismissible: false, child: Consumer(builder: (_, data, __) { - return HospitalBottomSheetBodySelection( - onUserHospitalSelection: (BdGetProjectsHaveBdClinic userChoice) { - print("============User Choice==============="); - - bloodDonationVM.getFreeBloodDonationSlots(request: {"ClinicID": 134, "ProjectID": userChoice.projectId}); - }, - ); - }), callBackFunc: () {}); - }); - // } else { - // return showCommonBottomSheetWithoutHeight( - // context, - // title: LocaleKeys.notice.tr(context: context), - // child: Column( - // mainAxisAlignment: MainAxisAlignment.center, - // crossAxisAlignment: CrossAxisAlignment.center, - // children: [ - // Lottie.asset(AppAnimations.errorAnimation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill), - // SizedBox(height: 8.h), - // (LocaleKeys.loginToUseService.tr()).toText16(color: AppColors.blackColor), - // SizedBox(height: 16.h), - // Row( - // children: [ - // Expanded( - // child: CustomButton( - // text: LocaleKeys.cancel.tr(), - // onPressed: () { - // Navigator.of(context).pop(); - // }, - // backgroundColor: AppColors.secondaryLightRedColor, - // borderColor: AppColors.secondaryLightRedColor, - // textColor: AppColors.primaryRedColor, - // icon: AppAssets.cancel, - // iconColor: AppColors.primaryRedColor, - // ), - // ), - // SizedBox(width: 8.h), - // Expanded( - // child: CustomButton( - // text: LocaleKeys.confirm.tr(), - // onPressed: () async { - // Navigator.of(context).pop(); - // // Navigator.pushAndRemoveUntil(context, CustomPageRoute(page: LandingNavigation()), (r) => false); - // await getIt().onLoginPressed(); - // }, - // backgroundColor: AppColors.bgGreenColor, - // borderColor: AppColors.bgGreenColor, - // textColor: Colors.white, - // icon: AppAssets.confirm, - // ), - // ), - // ], - // ), - // SizedBox(height: 16.h), - // ], - // ).center, - // callBackFunc: () {}, - // isFullScreen: false, - // isCloseButtonVisible: true, - // ); - // } + if (bloodDonationVM.isUserAuthanticated()) { + bloodDonationVM.fetchHospitalsList().then((value) { + showCommonBottomSheetWithoutHeight(context, title: "Select Hospital", isDismissible: false, child: Consumer(builder: (_, data, __) { + return HospitalBottomSheetBodySelection( + isHideTitle: true, + onUserHospitalSelection: (BdGetProjectsHaveBdClinic userChoice) { + bloodDonationVM.getFreeBloodDonationSlots(request: {"ClinicID": 134, "ProjectID": userChoice.projectId}); + }, + ); + }), callBackFunc: () {}); + }); + } else { + return showCommonBottomSheetWithoutHeight( + context, + title: LocaleKeys.notice.tr(context: context), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Lottie.asset(AppAnimations.errorAnimation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill), + SizedBox(height: 8.h), + (LocaleKeys.loginToUseService.tr()).toText16(color: AppColors.blackColor), + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: CustomButton( + text: LocaleKeys.cancel.tr(), + onPressed: () { + Navigator.of(context).pop(); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + icon: AppAssets.cancel, + iconColor: AppColors.primaryRedColor, + ), + ), + SizedBox(width: 8.h), + Expanded( + child: CustomButton( + text: LocaleKeys.confirm.tr(), + onPressed: () async { + Navigator.of(context).pop(); + // Navigator.pushAndRemoveUntil(context, CustomPageRoute(page: LandingNavigation()), (r) => false); + await getIt().onLoginPressed(); + }, + backgroundColor: AppColors.bgGreenColor, + borderColor: AppColors.bgGreenColor, + textColor: Colors.white, + icon: AppAssets.confirm, + ), + ), + ], + ), + SizedBox(height: 16.h), + ], + ).center, + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } }, backgroundColor: AppColors.bgRedLightColor, borderColor: AppColors.bgRedLightColor, @@ -253,13 +263,20 @@ class BloodDonationPage extends StatelessWidget { Row( children: [ Text( - LocaleKeys.iAcceptThe.tr(), + "${LocaleKeys.iAcceptThe.tr()} ", style: context.dynamicTextStyle(fontSize: 14.f, fontWeight: FontWeight.w500, color: Color(0xFF2E3039)), ), GestureDetector( onTap: () { // Navigate to terms and conditions page - Navigator.of(context).pushNamed('/terms'); + // Navigator.of(context).pushNamed('/terms'); + appState.isArabic() + ? Utils.openWebView( + url: 'https://hmg.com/ar/Pages/Terms.aspx', + ) + : Utils.openWebView( + url: 'https://hmg.com/en/Pages/Terms.aspx', + ); }, child: Text( LocaleKeys.termsConditoins.tr(), @@ -288,7 +305,60 @@ class BloodDonationPage extends StatelessWidget { onPressed: () async { DialogService dialogService = getIt.get(); if (await bloodDonationVM.validateSelections()) { - bloodDonationVM.updateBloodGroup(); + if (bloodDonationVM.isUserAuthanticated()) { + bloodDonationVM.updateBloodGroup(); + } else { + return showCommonBottomSheetWithoutHeight( + context, + title: LocaleKeys.notice.tr(context: context), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Lottie.asset(AppAnimations.errorAnimation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill), + SizedBox(height: 8.h), + (LocaleKeys.loginToUseService.tr()).toText16(color: AppColors.blackColor), + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: CustomButton( + text: LocaleKeys.cancel.tr(), + onPressed: () { + Navigator.of(context).pop(); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + icon: AppAssets.cancel, + iconColor: AppColors.primaryRedColor, + ), + ), + SizedBox(width: 8.h), + Expanded( + child: CustomButton( + text: LocaleKeys.confirm.tr(), + onPressed: () async { + Navigator.of(context).pop(); + // Navigator.pushAndRemoveUntil(context, CustomPageRoute(page: LandingNavigation()), (r) => false); + await getIt().onLoginPressed(); + }, + backgroundColor: AppColors.bgGreenColor, + borderColor: AppColors.bgGreenColor, + textColor: Colors.white, + icon: AppAssets.confirm, + ), + ), + ], + ), + SizedBox(height: 16.h), + ], + ).center, + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } } }, backgroundColor: AppColors.primaryRedColor, diff --git a/lib/presentation/book_appointment/book_appointment_page.dart b/lib/presentation/book_appointment/book_appointment_page.dart index 39d5eee5..fa239797 100644 --- a/lib/presentation/book_appointment/book_appointment_page.dart +++ b/lib/presentation/book_appointment/book_appointment_page.dart @@ -60,6 +60,9 @@ class _BookAppointmentPageState extends State { bookAppointmentsViewModel.getLocation(); immediateLiveCareViewModel.initImmediateLiveCare(); }); + WidgetsBinding.instance.addPostFrameCallback((_) { + showUnKnownClinicBottomSheet(); + }); super.initState(); } @@ -89,8 +92,8 @@ class _BookAppointmentPageState extends State { activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), initialIndex: bookAppointmentsVM.selectedTabIndex, tabs: [ - CustomTabBarModel(null, LocaleKeys.general.tr()), - CustomTabBarModel(null, LocaleKeys.liveCare.tr()), + CustomTabBarModel(null, LocaleKeys.general.tr(context: context)), + CustomTabBarModel(null, LocaleKeys.liveCare.tr(context: context)), ], onTabChange: (index) { bookAppointmentsVM.onTabChanged(index); @@ -121,7 +124,7 @@ class _BookAppointmentPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ if (appState.isAuthenticated) ...[], - LocaleKeys.recentVisits.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0.h), + LocaleKeys.recentVisits.tr(context: context).toText18(isBold: true).paddingSymmetrical(24.w, 0.h), SizedBox(height: 16.h), SizedBox( height: 110.h, @@ -232,8 +235,8 @@ class _BookAppointmentPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.searchByClinic.tr().toText14(color: AppColors.textColor, weight: FontWeight.w500), - LocaleKeys.tapToSelectClinic.tr().toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + LocaleKeys.searchByClinic.tr(context: context).toText14(color: AppColors.textColor, weight: FontWeight.w500), + LocaleKeys.tapToSelectClinic.tr(context: context).toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), ], ), ], @@ -264,8 +267,8 @@ class _BookAppointmentPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.searchByDoctor.tr().toText14(color: AppColors.textColor, weight: FontWeight.w500), - LocaleKeys.tapToSelect.tr().toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + LocaleKeys.searchByDoctor.tr(context: context).toText14(color: AppColors.textColor, weight: FontWeight.w500), + LocaleKeys.tapToSelect.tr(context: context).toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), ], ), ], @@ -294,8 +297,8 @@ class _BookAppointmentPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.searchByRegion.tr().toText14(color: AppColors.textColor, weight: FontWeight.w500), - LocaleKeys.centralRegion.tr().toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + LocaleKeys.searchByRegion.tr(context: context).toText14(color: AppColors.textColor, weight: FontWeight.w500), + LocaleKeys.centralRegion.tr(context: context).toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), ], ), ], @@ -313,11 +316,9 @@ class _BookAppointmentPageState extends State { ], ).paddingSymmetrical(24.h, 0.h); case 1: - //TODO: Get LiveCare type Select UI from Hussain - return - // appState.isAuthenticated - // ? - Column( + //TODO: Get LiveCare type Select UI from Hussain + return appState.isAuthenticated + ? Column( children: [ Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( @@ -340,8 +341,8 @@ class _BookAppointmentPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.immediateConsultation.tr().toText14(color: AppColors.textColor, weight: FontWeight.w500), - LocaleKeys.tapToSelectClinic.tr().toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + LocaleKeys.immediateConsultation.tr(context: context).toText14(color: AppColors.textColor, weight: FontWeight.w500), + LocaleKeys.tapToSelectClinic.tr(context: context).toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), ], ), ], @@ -382,8 +383,8 @@ class _BookAppointmentPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.scheduledConsultation.tr().toText14(color: AppColors.textColor, weight: FontWeight.w500), - LocaleKeys.tapToSelectClinic.tr().toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + LocaleKeys.scheduledConsultation.tr(context: context).toText14(color: AppColors.textColor, weight: FontWeight.w500), + LocaleKeys.tapToSelectClinic.tr(context: context).toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), ], ), ], @@ -412,7 +413,7 @@ class _BookAppointmentPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.pharmaLiveCare.tr().toText14(color: AppColors.textColor, weight: FontWeight.w500), + LocaleKeys.pharmaLiveCare.tr(context: context).toText14(color: AppColors.textColor, weight: FontWeight.w500), "".toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), ], ), @@ -429,8 +430,7 @@ class _BookAppointmentPageState extends State { ), ], ).paddingSymmetrical(24.h, 0.h) - // : getLiveCareNotLoggedInUI() - ; + : getLiveCareNotLoggedInUI(); default: SizedBox.shrink(); } @@ -447,21 +447,22 @@ class _BookAppointmentPageState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.notSureHelpMeChooseClinic.tr().toText16(weight: FontWeight.w600, color: AppColors.textColor), + LocaleKeys.notSureHelpMeChooseClinic.tr(context: context).toText16(weight: FontWeight.w600, color: AppColors.textColor), SizedBox(height: 8.h), - LocaleKeys.mentionYourSymptomsAndFindDoctors.tr().toText12( - fontWeight: FontWeight.w500, - color: AppColors.greyTextColor, - ), + LocaleKeys.mentionYourSymptomsAndFindDoctors.tr(context: context).toText12( + fontWeight: FontWeight.w500, + color: AppColors.greyTextColor, + ), ], ), ), SizedBox(width: 16.w), CustomButton( - height: 40.h, + height: 42.h, + width: 42.w, text: "", onPressed: () => context.navigateWithName(AppRoutes.userInfoSelection), - icon: AppAssets.arrow_forward, + icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward, ) ], ).paddingAll(24.w), @@ -472,10 +473,13 @@ class _BookAppointmentPageState extends State { regionalViewModel.flush(); regionalViewModel.setBottomSheetType(type); // AppointmentViaRegionViewmodel? viewmodel = null; - showCommonBottomSheetWithoutHeight(context, title: "", titleWidget: Consumer(builder: (_, data, __) => getTitle(data)), isDismissible: false, + showCommonBottomSheetWithoutHeight(context, title: "", + titleWidget: Consumer(builder: (_, data, __) => getTitle(data)), + isDismissible: false, child: Consumer(builder: (_, data, __) { - return getRegionalSelectionWidget(data); - }), callBackFunc: () {}); + return getRegionalSelectionWidget(data); + }), + callBackFunc: () {}); } Widget getRegionalSelectionWidget(AppointmentViaRegionViewmodel data) { @@ -531,7 +535,7 @@ class _BookAppointmentPageState extends State { getTitle(AppointmentViaRegionViewmodel data) { if (data.selectedRegionId == null) { - return LocaleKeys.selectRegion.tr().toText20(weight: FontWeight.w600); + return LocaleKeys.selectRegion.tr(context: context).toText20(weight: FontWeight.w600); } else { return Transform.flip( flipX: data.isArabic ? true : false, @@ -558,8 +562,8 @@ class _BookAppointmentPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.immediateService.tr().toText18(color: AppColors.textColor, isBold: true), - LocaleKeys.noNeedToWaitGetMedicalConsultation.tr().toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + LocaleKeys.immediateService.tr(context: context).toText18(color: AppColors.textColor, isBold: true), + LocaleKeys.noNeedToWaitGetMedicalConsultation.tr(context: context).toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), ], ), ), @@ -574,7 +578,7 @@ class _BookAppointmentPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.noVisitRequired.tr().toText18(color: AppColors.textColor, isBold: true), + LocaleKeys.noVisitRequired.tr(context: context).toText18(color: AppColors.textColor, isBold: true), LocaleKeys.livecarePoint5.tr(context: context).toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), ], ), @@ -590,8 +594,8 @@ class _BookAppointmentPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.doctorWillContact.tr().toText18(color: AppColors.textColor, isBold: true), - LocaleKeys.specialisedDoctorWillContactYou.tr().toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + LocaleKeys.doctorWillContact.tr(context: context).toText18(color: AppColors.textColor, isBold: true), + LocaleKeys.specialisedDoctorWillContactYou.tr(context: context).toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), ], ), ), @@ -606,8 +610,8 @@ class _BookAppointmentPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.freeMedicineDelivery.tr().toText18(color: AppColors.textColor, isBold: true), - LocaleKeys.offersFreeMedicineDelivery.tr().toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + LocaleKeys.freeMedicineDelivery.tr(context: context).toText18(color: AppColors.textColor, isBold: true), + LocaleKeys.offersFreeMedicineDelivery.tr(context: context).toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), ], ), ), @@ -634,4 +638,53 @@ class _BookAppointmentPageState extends State { ], ).paddingSymmetrical(24.h, 0.h); } + + void showUnKnownClinicBottomSheet() { + showCommonBottomSheetWithoutHeight( + context, + title: "", + isDismissible: true, + isCloseButtonVisible: false, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.notSureHelpMeChooseClinic.tr(context: context).toText28(color: AppColors.textColor, isBold: true, height: 1.5), + SizedBox(height: 4.h), + LocaleKeys.mentionYourSymptomsAndFindDoctors.tr(context: context).toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500,), + SizedBox(height: 24.h), + CustomButton( + text: LocaleKeys.yesPleaseINeedHelp.tr(context: context), + onPressed: () { + context.pop(); + context.navigateWithName(AppRoutes.userInfoSelection); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 56.h, + ), + SizedBox(height: 8.h), + CustomButton( + text: LocaleKeys.noThanksIKnowTheClinic.tr(context: context), + onPressed: () { + context.pop(); + }, + backgroundColor: AppColors.chipSecondaryLightRedColor, + borderColor: AppColors.chipSecondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 16.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 56.h, + ), + ], + ).paddingSymmetrical(24.w, 20.h), + callBackFunc: () {}, + ); + } } diff --git a/lib/presentation/book_appointment/doctor_profile_page.dart b/lib/presentation/book_appointment/doctor_profile_page.dart index 72f242d2..c62404ab 100644 --- a/lib/presentation/book_appointment/doctor_profile_page.dart +++ b/lib/presentation/book_appointment/doctor_profile_page.dart @@ -11,6 +11,7 @@ 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/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/doctor_rating_details.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -108,9 +109,21 @@ class DoctorProfilePage extends StatelessWidget { ), SizedBox(height: 16.h), "Ratings".toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), - bookAppointmentsViewModel.doctorsProfileResponseModel.decimalDoctorRate.toString().toText16(isBold: true, color: AppColors.textColor), + bookAppointmentsViewModel.doctorsProfileResponseModel.decimalDoctorRate + .toString() + .toText16(isBold: true, color: AppColors.textColor, isUnderLine: true, decorationColor: AppColors.textColor), ], - ), + ).onPress(() { + bookAppointmentsViewModel.getDoctorRatingDetails(); + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.doctorRating.tr(context: context), + context, + child: DoctorRatingDetails(), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }), SizedBox(width: 36.w), Column( children: [ @@ -122,15 +135,28 @@ class DoctorProfilePage extends StatelessWidget { ), SizedBox(height: 16.h), "Reviews".toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), - bookAppointmentsViewModel.doctorsProfileResponseModel.noOfPatientsRate.toString().toText16(isBold: true, color: AppColors.textColor), + bookAppointmentsViewModel.doctorsProfileResponseModel.noOfPatientsRate + .toString() + .toText16(isBold: true, color: AppColors.textColor, isUnderLine: true, decorationColor: AppColors.textColor), ], - ), + ).onPress(() { + bookAppointmentsViewModel.getDoctorRatingDetails(); + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.doctorRating.tr(context: context), + context, + child: DoctorRatingDetails(), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }), ], ), SizedBox(height: 16.h), Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), SizedBox(height: 16.h), - "Biography".toText14(weight: FontWeight.w600, color: AppColors.textColor), + LocaleKeys.docInfo.tr(context: context).toText14(weight: FontWeight.w600, color: AppColors.textColor), + SizedBox(height: 6.h), bookAppointmentsViewModel.doctorsProfileResponseModel.doctorProfileInfo!.toText12(fontWeight: FontWeight.w600, color: AppColors.greyTextColor), ], ).paddingSymmetrical(24.h, 0.h), diff --git a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart index 3e48f8bc..4992d850 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart @@ -1,4 +1,3 @@ - import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; @@ -18,6 +17,7 @@ import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/immed import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/widgets/select_livecare_call_type.dart'; import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; +import 'package:hmg_patient_app_new/services/livecare_permission_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -32,71 +32,30 @@ import 'package:smooth_corner/smooth_corner.dart'; class ImmediateLiveCarePaymentDetails extends StatelessWidget { ImmediateLiveCarePaymentDetails({super.key}); - late ImmediateLiveCareViewModel immediateLiveCareViewModel; + // late ImmediateLiveCareViewModel immediateLiveCareViewModel; late AppState appState; @override Widget build(BuildContext context) { - immediateLiveCareViewModel = Provider.of(context, listen: false); + // immediateLiveCareViewModel = Provider.of(context, listen: false); appState = getIt.get(); return Scaffold( backgroundColor: AppColors.scaffoldBgColor, - body: Column( - children: [ - Expanded( - child: CollapsingListView( - title: LocaleKeys.reviewLiveCareRequest.tr(context: context), - child: SingleChildScrollView( - padding: EdgeInsets.symmetric(horizontal: 24.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 24.h), - LocaleKeys.patientInfo.tr(context: context).toText16(isBold: true), - SizedBox(height: 16.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.h, - hasShadow: false, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Row( - children: [ - Image.asset( - appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.maleImg : AppAssets.femaleImg, - width: 52.h, - height: 52.h, - ), - SizedBox(width: 8.h), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}".toText16(isBold: true), - SizedBox(height: 8.h), - Wrap( - direction: Axis.horizontal, - spacing: 3.h, - runSpacing: 4.h, - children: [ - AppCustomChipWidget(labelText: "${appState.getAuthenticatedUser()!.age} ${LocaleKeys.yearsOld.tr(context: context)}"), - AppCustomChipWidget( - labelText: - "${LocaleKeys.clinic.tr()}: ${(appState.isArabic() ? immediateLiveCareViewModel.immediateLiveCareSelectedClinic.serviceNameN : immediateLiveCareViewModel.immediateLiveCareSelectedClinic.serviceName)!}"), - ], - ), - ], - ), - ], - ), - ), - ), - SizedBox(height: 24.h), - LocaleKeys.selectedLiveCareType.tr(context: context).toText16(isBold: true), - SizedBox(height: 16.h), - Consumer(builder: (context, bookAppointmentsVM, child) { - return Container( + body: Consumer(builder: (context, immediateLiveCareVM, child) { + return Column( + children: [ + Expanded( + child: CollapsingListView( + title: LocaleKeys.reviewLiveCareRequest.tr(context: context), + child: SingleChildScrollView( + padding: EdgeInsets.symmetric(horizontal: 24.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24.h), + LocaleKeys.patientInfo.tr(context: context).toText16(isBold: true), + SizedBox(height: 16.h), + Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, borderRadius: 24.h, @@ -105,248 +64,371 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { child: Padding( padding: EdgeInsets.all(16.h), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( + Image.asset( + appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.maleImg : AppAssets.femaleImg, + width: 52.h, + height: 52.h, + ), + SizedBox(width: 8.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Utils.buildSvgWithAssets(icon: AppAssets.livecare_clinic_icon, width: 32.h, height: 32.h, fit: BoxFit.contain), - SizedBox(width: 8.h), - getLiveCareType(context, immediateLiveCareViewModel.liveCareSelectedCallType).toText16(isBold: true), + "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}".toText16(isBold: true), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget(labelText: "${appState.getAuthenticatedUser()!.age} ${LocaleKeys.yearsOld.tr(context: context)}"), + AppCustomChipWidget( + labelText: + "${LocaleKeys.clinic.tr()}: ${(appState.isArabic() ? immediateLiveCareVM.immediateLiveCareSelectedClinic.serviceNameN : immediateLiveCareVM.immediateLiveCareSelectedClinic.serviceName)!}"), + ], + ), ], ), - Utils.buildSvgWithAssets(icon: AppAssets.edit_icon, width: 24.h, height: 24.h, fit: BoxFit.contain), ], ), ), - ).onPress(() { - showCommonBottomSheetWithoutHeight(context, child: SelectLiveCareCallType(immediateLiveCareViewModel: immediateLiveCareViewModel), callBackFunc: () async { - debugPrint("Selected Call Type: ${immediateLiveCareViewModel.liveCareSelectedCallType}"); - }, title: LocaleKeys.selectLiveCareCallType.tr(context: context), isCloseButtonVisible: true, isFullScreen: false); - }); - }), - SizedBox(height: 24.h) - ], + ), + SizedBox(height: 24.h), + LocaleKeys.selectedLiveCareType.tr(context: context).toText16(isBold: true), + SizedBox(height: 16.h), + Consumer(builder: (context, bookAppointmentsVM, child) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.livecare_clinic_icon, width: 32.h, height: 32.h, fit: BoxFit.contain), + SizedBox(width: 8.h), + getLiveCareType(context, immediateLiveCareVM.liveCareSelectedCallType).toText16(isBold: true), + ], + ), + Utils.buildSvgWithAssets(icon: AppAssets.edit_icon, width: 24.h, height: 24.h, fit: BoxFit.contain), + ], + ), + ), + ).onPress(() { + showCommonBottomSheetWithoutHeight(context, child: SelectLiveCareCallType(immediateLiveCareViewModel: immediateLiveCareVM), callBackFunc: () async { + debugPrint("Selected Call Type: ${immediateLiveCareVM.liveCareSelectedCallType}"); + }, title: LocaleKeys.selectLiveCareCallType.tr(context: context), isCloseButtonVisible: true, isFullScreen: false); + }); + }), + SizedBox(height: 24.h) + ], + ), ), ), ), - ), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.h, - hasShadow: false, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - (immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.isCash ?? true) - ? Container( - height: 50.h, - decoration: ShapeDecoration( - color: AppColors.secondaryLightRedBorderColor, - shape: SmoothRectangleBorder( - borderRadius: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)), - smoothness: 1, + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.isCash ?? true) + ? Container( + height: 50.h, + decoration: ShapeDecoration( + color: AppColors.secondaryLightRedBorderColor, + shape: SmoothRectangleBorder( + borderRadius: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)), + smoothness: 1, + ), ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - LocaleKeys.insuranceExpiredOrInactive.tr(context: context).toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500).paddingSymmetrical(24.h, 0.h), - CustomButton( - text: LocaleKeys.updateInsurance.tr(context: context), - onPressed: () { - Navigator.of(context).push( - CustomPageRoute( - page: InsuranceHomePage(), - ), - ); - }, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.secondaryLightRedBorderColor, - textColor: AppColors.whiteColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(15, 0, 15, 0), - height: 30.h, - ).paddingSymmetrical(24.h, 0.h), - ], - ), - ) - : const SizedBox(), - SizedBox(height: 24.h), - LocaleKeys.totalAmountToPay.tr(context: context).toText18(isBold: true).paddingSymmetrical(24.h, 0.h), - SizedBox(height: 17.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - LocaleKeys.amountBeforeTax.tr(context: context).toText14(isBold: true), - Utils.getPaymentAmountWithSymbol(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.amount!.toText16(isBold: true), AppColors.blackColor, 13, - isSaudiCurrency: immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar"), - ], - ).paddingSymmetrical(24.h, 0.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - LocaleKeys.vat15.tr(context: context).toText14(isBold: true, color: AppColors.greyTextColor), - Utils.getPaymentAmountWithSymbol( - immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.tax!.toText14(isBold: true, color: AppColors.greyTextColor), AppColors.greyTextColor, 13, - isSaudiCurrency: immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar"), - ], - ).paddingSymmetrical(24.h, 0.h), - SizedBox(height: 17.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox(width: 150.h, child: Utils.getPaymentMethods()), - Utils.getPaymentAmountWithSymbol(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total!.toText24(isBold: true), AppColors.blackColor, 17, - isSaudiCurrency: immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar"), - ], - ).paddingSymmetrical(24.h, 0.h), - (immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total == "0" || immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total == "0.0") - ? CustomButton( - text: LocaleKeys.confirmLiveCare.tr(context: context), - onPressed: () async { - await askVideoCallPermission().then((val) async { - if (val) { - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.confirmingLiveCareRequest.tr(context: context)); + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.insuranceExpiredOrInactive.tr(context: context).toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500).paddingSymmetrical(24.h, 0.h), + CustomButton( + text: LocaleKeys.updateInsurance.tr(context: context), + onPressed: () { + Navigator.of(context).push( + CustomPageRoute( + page: InsuranceHomePage(), + ), + ); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.secondaryLightRedBorderColor, + textColor: AppColors.whiteColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(15, 0, 15, 0), + height: 30.h, + ).paddingSymmetrical(24.h, 0.h), + ], + ), + ) + : const SizedBox(), + SizedBox(height: 24.h), + LocaleKeys.totalAmountToPay.tr(context: context).toText18(isBold: true).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 17.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.amountBeforeTax.tr(context: context).toText14(isBold: true), + Utils.getPaymentAmountWithSymbol(immediateLiveCareVM.liveCareImmediateAppointmentFeesList.amount!.toText16(isBold: true), AppColors.blackColor, 13, + isSaudiCurrency: immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar" || + immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "ريال"), + ], + ).paddingSymmetrical(24.h, 0.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.vat15.tr(context: context).toText14(isBold: true, color: AppColors.greyTextColor), + Utils.getPaymentAmountWithSymbol( + immediateLiveCareVM.liveCareImmediateAppointmentFeesList.tax!.toText14(isBold: true, color: AppColors.greyTextColor), AppColors.greyTextColor, 13, + isSaudiCurrency: (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar" || + immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "ريال")), + ], + ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 17.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(width: 150.h, child: Utils.getPaymentMethods()), + Utils.getPaymentAmountWithSymbol(immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total!.toText24(isBold: true), AppColors.blackColor, 17, + isSaudiCurrency: (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar" || + immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "ريال")), + ], + ).paddingSymmetrical(24.h, 0.h), + (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0" || immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0.0") + ? CustomButton( + text: LocaleKeys.confirmLiveCare.tr(context: context), + onPressed: () async { + await askVideoCallPermission(context).then((val) async { + if (val) { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.confirmingLiveCareRequest.tr(context: context)); - await immediateLiveCareViewModel.addNewCallRequestForImmediateLiveCare("${appState.getAuthenticatedUser()!.patientId}${DateTime.now().millisecondsSinceEpoch}"); - await immediateLiveCareViewModel.getPatientLiveCareHistory(); - LoaderBottomSheet.hideLoader(); - if (immediateLiveCareViewModel.patientHasPendingLiveCareRequest) { - Navigator.pushAndRemoveUntil( - context, + await immediateLiveCareVM.addNewCallRequestForImmediateLiveCare("${appState.getAuthenticatedUser()!.patientId}${DateTime.now().millisecondsSinceEpoch}"); + await immediateLiveCareVM.getPatientLiveCareHistory(); + LoaderBottomSheet.hideLoader(); + if (immediateLiveCareVM.patientHasPendingLiveCareRequest) { + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); + Navigator.of(context).push( CustomPageRoute( - page: LandingNavigation(), + page: ImmediateLiveCarePendingRequestPage(), ), - (r) => false); - Navigator.of(context).push( - CustomPageRoute( - page: ImmediateLiveCarePendingRequestPage(), - ), - ); + ); + } else { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: LocaleKeys.unknownErrorOccurred.tr(context: context)), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } } else { showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), context, - child: Utils.getErrorWidget(loadingText: LocaleKeys.unknownErrorOccurred.tr(context: context)), + child: Utils.getWarningWidget( + loadingText: LocaleKeys.liveCarePermissionsMessage.tr(context: context), + isShowActionButtons: true, + onCancelTap: () { + Navigator.pop(context); + }, + onConfirmTap: () async { + openAppSettings(); + }), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, ); } - } else { - showCommonBottomSheetWithoutHeight( - title: LocaleKeys.notice.tr(context: context), - context, - child: Utils.getWarningWidget( - loadingText: LocaleKeys.liveCarePermissionsMessage.tr(context: context), - isShowActionButtons: true, - onCancelTap: () { - Navigator.pop(context); - }, - onConfirmTap: () async { - openAppSettings(); - }), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - } - }); - }, - backgroundColor: AppColors.successColor, - borderColor: AppColors.successColor, - textColor: AppColors.whiteColor, - fontSize: 16, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 50.h, - icon: AppAssets.livecare_book_icon, - iconColor: AppColors.whiteColor, - iconSize: 18.h, - ).paddingSymmetrical(24.h, 24.h) - : CustomButton( - text: LocaleKeys.payNow.tr(context: context), - onPressed: () async { - await askVideoCallPermission().then((val) { - if (val) { - Navigator.of(context).push( - CustomPageRoute( - page: ImmediateLiveCarePaymentPage(), - ), - ); - } else { - showCommonBottomSheetWithoutHeight( - title: LocaleKeys.notice.tr(context: context), - context, - child: Utils.getWarningWidget( - loadingText: LocaleKeys.liveCarePermissionsMessage.tr(context: context), - isShowActionButtons: true, - onCancelTap: () { - Navigator.pop(context); - }, - onConfirmTap: () async { - openAppSettings(); - }), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - } - }); - }, - backgroundColor: AppColors.infoColor, - borderColor: AppColors.infoColor, - textColor: AppColors.whiteColor, - fontSize: 16, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 50.h, - icon: AppAssets.appointment_pay_icon, - iconColor: AppColors.whiteColor, - iconSize: 18.h, - ).paddingSymmetrical(24.h, 24.h), - ], + }); + }, + backgroundColor: AppColors.successColor, + borderColor: AppColors.successColor, + textColor: AppColors.whiteColor, + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, + icon: AppAssets.livecare_book_icon, + iconColor: AppColors.whiteColor, + iconSize: 18.h, + ).paddingSymmetrical(24.h, 24.h) + : CustomButton( + text: LocaleKeys.payNow.tr(context: context), + onPressed: () async { + await askVideoCallPermission(context).then((val) { + if (val) { + Navigator.of(context).push( + CustomPageRoute( + page: ImmediateLiveCarePaymentPage(), + ), + ); + } + // else { + // showCommonBottomSheetWithoutHeight( + // title: LocaleKeys.notice.tr(context: context), + // context, + // child: Utils.getWarningWidget( + // loadingText: LocaleKeys.liveCarePermissionsMessage.tr(context: context), + // isShowActionButtons: true, + // onCancelTap: () { + // Navigator.pop(context); + // }, + // onConfirmTap: () async { + // openAppSettings(); + // }), + // callBackFunc: () {}, + // isFullScreen: false, + // isCloseButtonVisible: true, + // ); + // } + }); + }, + backgroundColor: AppColors.infoColor, + borderColor: AppColors.infoColor, + textColor: AppColors.whiteColor, + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, + icon: AppAssets.appointment_pay_icon, + iconColor: AppColors.whiteColor, + iconSize: 18.h, + ).paddingSymmetrical(24.h, 24.h), + ], + ), ), - ), - ], - ), + ], + ); + }), ); } - Future askVideoCallPermission() async { - bool hasPermission = false; - if (Platform.isIOS) { - Map statuses = await [Permission.camera, Permission.microphone].request(); + Future askVideoCallPermission(BuildContext context) async { + final statuses = await LiveCarePermissionService.instance.requestCameraMicAndNotification(context); + + // If service returned nothing (error), treat as not granted + if (statuses.isEmpty) return false; + + bool cameraGranted = statuses[Permission.camera]?.isGranted ?? false; + bool micGranted = statuses[Permission.microphone]?.isGranted ?? false; + bool notifGranted = statuses[Permission.notification]?.isGranted ?? false; + + // If all required permissions are already granted + if (cameraGranted && micGranted && notifGranted) return true; + + // Collect only the missing permissions + final missing = []; + if (!cameraGranted) missing.add(Permission.camera); + if (!micGranted) missing.add(Permission.microphone); + if (!notifGranted) missing.add(Permission.notification); + + // If any of the missing permissions are permanently denied/restricted -> open settings (single dialog) + final permanent = missing.where((p) => (statuses[p]?.isPermanentlyDenied ?? false) || (statuses[p]?.isRestricted ?? false)).toList(); + if (permanent.isNotEmpty) { + final names = permanent.map((p) => LiveCarePermissionService.instance.friendlyName(p)).join(' and '); + final message = permanent.length == 1 ? '$names permission is permanently denied. Open app settings to allow it.' : '$names permissions are permanently denied. Open app settings to allow them.'; + await LiveCarePermissionService.instance.showOpenSettingsDialog( + context, + title: "Permissions Required", + message: message, + ); + return false; + } + + // Show one combined retry dialog for all missing permissions + final missingNames = missing.map((p) => LiveCarePermissionService.instance.friendlyName(p)).join(' and '); + final retry = await showDialog( + context: context, + builder: (c) => AlertDialog( + title: const Text('Permissions required'), + content: Text('$missingNames permission${missing.length > 1 ? 's are' : ' is'} required to start the video call. Retry now?'), + actions: [ + TextButton(onPressed: () => Navigator.of(c).pop(false), child: const Text('Cancel')), + TextButton(onPressed: () => Navigator.of(c).pop(true), child: const Text('Retry')), + ], + ), + ); - if (statuses[Permission.camera] == PermissionStatus.granted && statuses[Permission.microphone] == PermissionStatus.granted) { - // Camera permission granted - hasPermission = true; - } else { - hasPermission = false; - } - } else { - Map statuses = await [Permission.camera, Permission.microphone, Permission.notification].request(); + if (retry != true) return false; - if (statuses[Permission.camera] == PermissionStatus.granted && statuses[Permission.microphone] == PermissionStatus.granted && statuses[Permission.notification] == PermissionStatus.granted) { - // Camera permission granted - hasPermission = true; - } else { - hasPermission = false; - } + // Request only the missing permissions + final newStatuses = {}; + for (final p in missing) { + newStatuses[p] = await p.request(); } - return hasPermission; + // Update grant state (preserve previously granted ones) + cameraGranted = newStatuses[Permission.camera]?.isGranted ?? cameraGranted; + micGranted = newStatuses[Permission.microphone]?.isGranted ?? micGranted; + notifGranted = newStatuses[Permission.notification]?.isGranted ?? notifGranted; + + // If any requested permission is now permanently denied -> open settings + final newlyPermanent = missing.where((p) => (newStatuses[p]?.isPermanentlyDenied ?? false) || (newStatuses[p]?.isRestricted ?? false)).toList(); + if (newlyPermanent.isNotEmpty) { + final names = newlyPermanent.map((p) => LiveCarePermissionService.instance.friendlyName(p)).join(' and '); + final message = + newlyPermanent.length == 1 ? '$names permission is permanently denied. Open app settings to allow it.' : '$names permissions are permanently denied. Open app settings to allow them.'; + await LiveCarePermissionService.instance.showOpenSettingsDialog( + context, + title: "Permissions Required", + message: message, + ); + return false; + } - // if (!(await Permission.camera.request().isGranted) || !(await Permission.microphone.request().isGranted)) { - // return false; - // } + return cameraGranted && micGranted && notifGranted; } + // Future askVideoCallPermission() async { + // bool hasPermission = false; + // if (Platform.isIOS) { + // Map statuses = await [Permission.camera, Permission.microphone].request(); + // + // if (statuses[Permission.camera] == PermissionStatus.granted && statuses[Permission.microphone] == PermissionStatus.granted) { + // // Camera permission granted + // hasPermission = true; + // } else { + // hasPermission = false; + // } + // } else { + // Map statuses = await [Permission.camera, Permission.microphone, Permission.notification].request(); + // + // if (statuses[Permission.camera] == PermissionStatus.granted && statuses[Permission.microphone] == PermissionStatus.granted && statuses[Permission.notification] == PermissionStatus.granted) { + // // Camera permission granted + // hasPermission = true; + // } else { + // hasPermission = false; + // } + // } + // + // return hasPermission; + // + // // if (!(await Permission.camera.request().isGranted) || !(await Permission.microphone.request().isGranted)) { + // // return false; + // // } + // } + String getLiveCareType(BuildContext context, int callType) { switch (callType) { case 1: diff --git a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart index 9ae7ee30..73da69f5 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart @@ -432,6 +432,7 @@ class _ImmediateLiveCarePaymentPageState extends State { ), AppCustomChipWidget( labelText: - "${LocaleKeys.date.tr(context: context)}: ${bookAppointmentsViewModel.isWaitingAppointmentSelected ? DateUtil.formatDateToDate(DateTime.now(), false) : bookAppointmentsViewModel.selectedAppointmentDate}", + "${LocaleKeys.date.tr(context: context)}: ${bookAppointmentsViewModel.isWaitingAppointmentSelected ? DateUtil.formatDateToDate(DateTime.now(), false) : bookAppointmentsViewModel.selectedAppointmentDisplayDate}", ), AppCustomChipWidget( labelText: @@ -178,7 +178,7 @@ class _ReviewAppointmentPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - bookAppointmentsViewModel.selectedDoctor.projectName!.toText16(isBold: true), + (bookAppointmentsViewModel.selectedDoctor.projectName ?? "Habib Hospital").toText16(isBold: true), SizedBox(height: 8.h), bookAppointmentsViewModel.appointmentNearestGateResponseModel != null ? Wrap( @@ -286,8 +286,20 @@ class _ReviewAppointmentPageState extends State { } else { //TODO: Add patient Derma package check API Here await bookAppointmentsViewModel.insertSpecificAppointment(onError: (err) { - print(err.data["ErrorEndUserMessage"]); + // print(err.data["ErrorEndUserMessage"]); LoadingUtils.hideFullScreenLoader(); + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getErrorWidget(loadingText: err.toString()), + callBackFunc: () { + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); + // Navigator.pop(context); + }, + isFullScreen: false, + isCloseButtonVisible: true, + ); }, onSuccess: (apiResp) async { LoadingUtils.hideFullScreenLoader(); await Future.delayed(Duration(milliseconds: 50)).then((value) async { diff --git a/lib/presentation/book_appointment/select_clinic_page.dart b/lib/presentation/book_appointment/select_clinic_page.dart index e76e1c9e..cad8e2e7 100644 --- a/lib/presentation/book_appointment/select_clinic_page.dart +++ b/lib/presentation/book_appointment/select_clinic_page.dart @@ -79,7 +79,7 @@ class _SelectClinicPageState extends State { decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.only(topLeft: Radius.circular(24.r), topRight: Radius.circular(24.r))), padding: EdgeInsets.symmetric(vertical: 20.h, horizontal: 20.h), child: CustomButton( - text: LocaleKeys.bookAppo.tr(), + text: LocaleKeys.bookAppo.tr(context: context), onPressed: () { if (widget.calculatedResult["clinicId"] == null) { bookAppointmentsViewModel.setIsDoctorsListLoading(true); @@ -261,8 +261,7 @@ class _SelectClinicPageState extends State { calorieWidget( title: calculatedResult["bmiCategory"], calories: calculatedResult["bmiResult"], - description: - 'Your BMI is within the healthy range. Continue a balanced diet, regular physical activity, and routine checkups to maintain good health. Monitor any significant changes over time.', + description: LocaleKeys.bmiNormalDescription.tr(), color: Colors.green, icon: Icons.scale, hide: true, @@ -272,8 +271,7 @@ class _SelectClinicPageState extends State { calorieWidget( title: calculatedResult["bmiCategory"], calories: calculatedResult["bmiResult"], - description: - 'BMI suggests you are underweight. Consider increasing calorie intake with nutrient-dense foods, include resistance training to build muscle, and consult a healthcare professional to check for underlying causes.', + description: LocaleKeys.bmiUnderweightDescription.tr(), color: Colors.orange, hide: true, icon: Icons.trending_down, @@ -283,8 +281,7 @@ class _SelectClinicPageState extends State { calorieWidget( title: calculatedResult["bmiCategory"], calories: calculatedResult["bmiResult"], - description: - 'BMI indicates overweight. Aim for gradual, sustainable weight loss through a modest calorie deficit, regular physical activity, and healthier food choices. Seek medical advice before major lifestyle changes.', + description: LocaleKeys.bmiOverweightDescription.tr(), color: Colors.red, hide: true, icon: Icons.warning_rounded, @@ -295,8 +292,7 @@ class _SelectClinicPageState extends State { calorieWidget( title: calculatedResult["calorieRange"], calories: calculatedResult["calories"], - description: - 'Estimated daily calories to maintain your current weight given your activity level and metabolism. Use this as a baseline: increase slightly for muscle gain, or create a small deficit for gradual weight loss. Track progress and adjust every 2–4 weeks.', + description: LocaleKeys.caloriesMaintenanceDescription.tr(), color: Colors.green, icon: Icons.scale, ), @@ -305,8 +301,7 @@ class _SelectClinicPageState extends State { calorieWidget( title: calculatedResult["calorieRange"], calories: calculatedResult["calories"], - description: - 'A moderate calorie reduction designed for steady, sustainable weight loss (typically 0.25–0.5 kg per week). Combine with resistance training and adequate protein to preserve muscle. Avoid extreme cuts and consult a professional if you have medical conditions.', + description: LocaleKeys.caloriesModerateDeficitDescription.tr(), color: Colors.orange, icon: Icons.trending_down, ), @@ -315,19 +310,59 @@ class _SelectClinicPageState extends State { calorieWidget( title: calculatedResult["calorieRange"], calories: calculatedResult["calories"], - description: - 'A large calorie reduction that may produce faster weight loss but can be hard to sustain and increase risk of nutrient deficiencies, fatigue, or muscle loss. Not recommended long-term; seek guidance from a healthcare or nutrition professional before continuing.', + description: LocaleKeys.caloriesAggressiveDeficitDescription.tr(), color: Colors.red, icon: Icons.warning_rounded, ), ] + ] else if (type == HealthCalculatorsTypeEnum.bodyFat) ...[ + if (calculatedResult["fatCategory"] == "Essential Fat" || calculatedResult["fatCategory"] == "Athletes") ...[ + calorieWidget( + title: calculatedResult["fatCategory"], + calories: calculatedResult["fatPercentage"], + description: LocaleKeys.bodyFatEssentialDescription.tr(), + color: Colors.green, + hide: true, + icon: Icons.fitness_center, + ), + SizedBox(height: 16.h), + ] else if (calculatedResult["fatCategory"] == "Fitness") ...[ + calorieWidget( + title: calculatedResult["fatCategory"], + calories: calculatedResult["fatPercentage"], + description: LocaleKeys.bodyFatFitnessDescription.tr(), + color: Colors.green, + hide: true, + icon: Icons.fitness_center, + ), + SizedBox(height: 16.h), + ] else if (calculatedResult["fatCategory"] == "Average" || calculatedResult["fatCategory"] == "Acceptable") ...[ + calorieWidget( + title: calculatedResult["fatCategory"], + calories: calculatedResult["fatPercentage"], + description: LocaleKeys.bodyFatAverageDescription.tr(), + color: Colors.orange, + hide: true, + icon: Icons.accessibility_new, + ), + SizedBox(height: 16.h), + ] else if (calculatedResult["fatCategory"] == "Obese" || calculatedResult["fatCategory"] == "Overweight") ...[ + calorieWidget( + title: calculatedResult["fatCategory"], + calories: calculatedResult["fatPercentage"], + description: LocaleKeys.bodyFatObeseDescription.tr(), + color: Colors.red, + hide: true, + icon: Icons.warning_rounded, + ), + SizedBox(height: 16.h), + ] ] else if (type == HealthCalculatorsTypeEnum.bmr) ...[ if (calculatedResult["bmrRange"] == "Normal") ...[ calorieWidget( title: calculatedResult["bmrRange"], calories: calculatedResult["bmr"], - description: - 'Your Basal Metabolic Rate (BMR) is within the expected range for your profile. BMR is the energy your body needs at rest to maintain vital functions (breathing, circulation, temperature). Use this value as the baseline for estimating total daily energy needs by applying an activity multiplier. Maintain a balanced diet and regular physical activity to support metabolic health.', + description: LocaleKeys.bmrNormalDescription.tr(), color: Colors.green, hide: true, icon: Icons.scale, @@ -337,8 +372,7 @@ class _SelectClinicPageState extends State { calorieWidget( title: calculatedResult["bmrRange"], calories: calculatedResult["bmr"], - description: - 'Your BMR is lower than typical for your profile. Possible causes include lower lean muscle mass, age-related metabolic decline, or metabolic adaptation from prolonged calorie restriction. Consider focusing on resistance training to build/maintain muscle, ensure adequate protein and micronutrient intake, and consult a healthcare professional if this is unexpected.', + description: LocaleKeys.low.tr(), color: Colors.orange, hide: true, icon: Icons.trending_down, @@ -348,8 +382,7 @@ class _SelectClinicPageState extends State { calorieWidget( title: calculatedResult["bmrRange"], calories: calculatedResult["bmr"], - description: - 'Your BMR is higher than average for your profile. This can reflect greater lean mass, younger age, or a naturally higher metabolic rate, meaning you burn more energy at rest. Use this information to tailor calorie targets: higher needs for maintenance or muscle gain, moderate deficit for weight loss. Seek medical advice if you notice rapid unexplained changes.', + description: LocaleKeys.bmrHighDescription.tr(), color: Colors.red, hide: true, icon: Icons.warning_rounded, @@ -915,9 +948,16 @@ class _SelectClinicPageState extends State { Widget _buildStatusIcon(String title) { final key = title.trim(); - if (key == 'Maintenance Calories' || key == 'Normal' || key == 'Ideal Weight' || key == 'Moderate Crabs' || key == 'USDA Guidelines') { + if (key == 'Maintenance Calories' || + key == 'Normal' || + key == 'Ideal Weight' || + key == 'Moderate Crabs' || + key == 'USDA Guidelines' || + key == 'Essential Fat' || + key == 'Athletes' || + key == 'Fitness') { return Utils.buildSvgWithAssets(icon: AppAssets.checkmark_icon, width: 24.w, height: 24.h); - } else if (key == 'Moderate Deficit' || key == 'Low' || key == 'Underweight' || key == 'Very Low Crabs' || key == 'Low Crabs' || key == 'Zone Diet') { + } else if (key == 'Moderate Deficit' || key == 'Low' || key == 'Underweight' || key == 'Very Low Crabs' || key == 'Low Crabs' || key == 'Zone Diet' || key == 'Average' || key == 'Acceptable') { return Utils.buildSvgWithAssets(icon: AppAssets.trade_down_yellow, width: 24.w, height: 24.h); } else if (key == 'Aggressive Deficit' || key == 'High' || key == 'Overweight' || key == "Obese") { return Utils.buildSvgWithAssets(icon: AppAssets.trade_down_red, width: 24.w, height: 24.h); @@ -928,9 +968,16 @@ class _SelectClinicPageState extends State { Color getTextColor(String title) { final key = title.trim(); - if (key == 'Maintenance Calories' || key == 'Normal' || key == 'Ideal Weight' || key == 'Moderate Crabs' || key == 'USDA Guidelines') { + if (key == 'Maintenance Calories' || + key == 'Normal' || + key == 'Ideal Weight' || + key == 'Moderate Crabs' || + key == 'USDA Guidelines' || + key == 'Essential Fat' || + key == 'Athletes' || + key == 'Fitness') { return AppColors.successColor; - } else if (key == 'Moderate Deficit' || key == 'Low' || key == 'Underweight' || key == 'Very Low Crabs' || key == 'Low Crabs' || key == 'Zone Diet') { + } else if (key == 'Moderate Deficit' || key == 'Low' || key == 'Underweight' || key == 'Very Low Crabs' || key == 'Low Crabs' || key == 'Zone Diet' || key == 'Average' || key == 'Acceptable') { return AppColors.warningColor; } else if (key == 'Aggressive Deficit' || key == 'High' || key == 'Overweight' || key == "Obese") { return AppColors.errorColor; @@ -941,7 +988,7 @@ class _SelectClinicPageState extends State { void onLiveCareClinicSelected(GetLiveCareClinicsResponseModel clinic) { bookAppointmentsViewModel.setLiveCareSelectedClinic(clinic); - bookAppointmentsViewModel.setIsDoctorsListLoading(true); + bookAppointmentsViewModel.setIsLiveCareDoctorsListLoading(true); Navigator.of(context).push( CustomPageRoute( page: SelectDoctorPage(), @@ -1097,7 +1144,7 @@ class _SelectClinicPageState extends State { getTitle(AppointmentViaRegionViewmodel data) { if (data.selectedRegionId == null) { - return LocaleKeys.selectRegion.tr().toText20(weight: FontWeight.w600); + return LocaleKeys.selectRegion.tr(context: context).toText20(weight: FontWeight.w600); } else { return Transform.flip( flipX: data.isArabic ? true : false, @@ -1156,7 +1203,8 @@ class _SelectClinicPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ bookAppointmentsViewModel.patientDentalPlanEstimationList[index].procedureName!.toText12(isBold: true), - AppCustomChipWidget(icon: AppAssets.appointment_time_icon, labelText: "${bookAppointmentsViewModel.totalTimeNeededForDentalProcedure} ${LocaleKeys.mins.tr(context: context)}"), + AppCustomChipWidget( + icon: AppAssets.appointment_time_icon, labelText: "${bookAppointmentsViewModel.totalTimeNeededForDentalProcedure} ${LocaleKeys.mins.tr(context: context)}"), ], ); }, @@ -1185,7 +1233,7 @@ class _SelectClinicPageState extends State { children: [ Expanded( child: CustomButton( - text: LocaleKeys.cancel.tr(), + text: LocaleKeys.cancel.tr(context: context), onPressed: () { bookAppointmentsViewModel.setIsContinueDentalPlan(false); bookAppointmentsViewModel.setIsChiefComplaintsListLoading(true); @@ -1206,7 +1254,7 @@ class _SelectClinicPageState extends State { SizedBox(width: 8.h), Expanded( child: CustomButton( - text: LocaleKeys.confirm.tr(), + text: LocaleKeys.confirm.tr(context: context), onPressed: () async { bookAppointmentsViewModel.setIsContinueDentalPlan(true); Navigator.of(context).pop(); diff --git a/lib/presentation/book_appointment/select_doctor_page.dart b/lib/presentation/book_appointment/select_doctor_page.dart index e57ed518..1fc9d9ad 100644 --- a/lib/presentation/book_appointment/select_doctor_page.dart +++ b/lib/presentation/book_appointment/select_doctor_page.dart @@ -75,7 +75,7 @@ class _SelectDoctorPageState extends State { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( - title: LocaleKeys.chooseDoctor.tr(), + title: LocaleKeys.chooseDoctor.tr(context: context), // bottomChild: Container( // decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.only(topLeft: Radius.circular(24.r), topRight: Radius.circular(24.r))), // padding: EdgeInsets.symmetric(vertical: 20.h, horizontal: 20.h), @@ -95,9 +95,59 @@ class _SelectDoctorPageState extends State { child: Padding( padding: EdgeInsets.symmetric(horizontal: 24.h), child: Consumer(builder: (context, bookAppointmentsVM, child) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ + return bookAppointmentsViewModel.isLiveCareSchedule + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ListView.separated( + padding: EdgeInsets.only(top: 16.h), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: bookAppointmentsVM.isDoctorsListLoading ? 5 : (bookAppointmentsVM.liveCareDoctorsList.isNotEmpty ? bookAppointmentsVM.liveCareDoctorsList.length : 1), + itemBuilder: (context, index) { + // final isExpanded = bookAppointmentsVM.expandedGroupIndex == index; + final isExpanded = true; + return bookAppointmentsVM.isDoctorsListLoading + ? DoctorCard( + doctorsListResponseModel: DoctorsListResponseModel(), + isLoading: true, + bookAppointmentsViewModel: bookAppointmentsViewModel, + ) + : bookAppointmentsVM.liveCareDoctorsList.isEmpty + ? Utils.getNoDataWidget(context, noDataText: LocaleKeys.noDoctorFound.tr()) + : AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: Padding( + padding: EdgeInsets.all(16.h), + child: DoctorCard( + isLoading: bookAppointmentsVM.isClinicsListLoading, + doctorsListResponseModel: bookAppointmentsVM.liveCareDoctorsList[index], + bookAppointmentsViewModel: bookAppointmentsViewModel, + ).onPress(() { + // onLiveCareClinicSelected(bookAppointmentsVM.liveCareClinicsList[index]); + }), + ), + ), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + SizedBox(height: 24.h), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ SizedBox(height: 16.h), Row( spacing: 8.h, @@ -178,7 +228,7 @@ class _SelectDoctorPageState extends State { children: [ LocaleKeys.nearestAppo.tr(context: context).toText13(isBold: true), SizedBox(height: 4.h), - LocaleKeys.viewNearestAppos.toText11(color: AppColors.textColorLight, weight: FontWeight.w500), + LocaleKeys.viewNearestAppos.tr(context: context).toText11(color: AppColors.textColorLight, weight: FontWeight.w500), ], ), const Spacer(), diff --git a/lib/presentation/book_appointment/widgets/appointment_calendar.dart b/lib/presentation/book_appointment/widgets/appointment_calendar.dart index c91e7a7a..3d12f033 100644 --- a/lib/presentation/book_appointment/widgets/appointment_calendar.dart +++ b/lib/presentation/book_appointment/widgets/appointment_calendar.dart @@ -7,6 +7,7 @@ import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/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'; @@ -144,13 +145,14 @@ class _AppointmentCalendarState extends State { child: selectedDateDisplay.toText16(weight: FontWeight.w500), ), //TODO: Add Next Day Span here - SizedBox( - height: 100.h, - child: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: Wrap( - direction: Axis.horizontal, - alignment: WrapAlignment.start, + dayEvents.isNotEmpty + ? SizedBox( + height: 100.h, + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: Wrap( + direction: Axis.horizontal, + alignment: WrapAlignment.start, spacing: 6.h, runSpacing: 6.h, children: List.generate( @@ -168,10 +170,13 @@ class _AppointmentCalendarState extends State { ), ), ), - ), + ) + : Utils.getNoDataWidget(context, noDataText: LocaleKeys.noFreeSlot.tr(context: context)), + SizedBox(height: 16.h), CustomButton( text: LocaleKeys.select.tr(context: context), + isDisabled: dayEvents.isEmpty, onPressed: () async { if (appState.isAuthenticated) { if(selectedTime == LocaleKeys.waitingAppointment.tr(context: context)){ @@ -186,7 +191,7 @@ class _AppointmentCalendarState extends State { ); } else { bookAppointmentsViewModel.getAppointmentNearestGate(projectID: bookAppointmentsViewModel.selectedDoctor.projectID!, clinicID: bookAppointmentsViewModel.selectedDoctor.clinicID!); - bookAppointmentsViewModel.setSelectedAppointmentDateTime(selectedDate, selectedTime); + bookAppointmentsViewModel.setSelectedAppointmentDateTime(selectedDate, selectedTime, selectedDateDisplay); Navigator.of(context).pop(); Navigator.of(context).push( CustomPageRoute( @@ -204,13 +209,13 @@ class _AppointmentCalendarState extends State { children: [ Lottie.asset(AppAnimations.errorAnimation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill), SizedBox(height: 8.h), - (LocaleKeys.loginToUseService.tr()).toText16(color: AppColors.blackColor), + (LocaleKeys.loginToUseService.tr(context: context)).toText16(color: AppColors.blackColor), SizedBox(height: 16.h), Row( children: [ Expanded( child: CustomButton( - text: LocaleKeys.cancel.tr(), + text: LocaleKeys.cancel.tr(context: context), onPressed: () { Navigator.of(context).pop(); }, @@ -224,7 +229,7 @@ class _AppointmentCalendarState extends State { SizedBox(width: 8.h), Expanded( child: CustomButton( - text: LocaleKeys.confirm.tr(), + text: LocaleKeys.confirm.tr(context: context), onPressed: () async { Navigator.of(context).pop(); Navigator.pushAndRemoveUntil( @@ -304,11 +309,14 @@ class _AppointmentCalendarState extends State { Map timeSlot = {"isoTime": dayEvents[i].isoTime, "start": dayEvents[i].start.toString(), "end": dayEvents[i].end.toString(), "vidaDate": dayEvents[i].vidaDate}; timeList.add(timeSlot); } - selectedTime = dayEvents[selectedButtonIndex].isoTime!; + if (dayEvents.isNotEmpty) { + selectedTime = dayEvents[selectedButtonIndex].isoTime!; + } + setState(() {}); } void _onDaySelected(DateTime day) { - final DateFormat formatter = DateFormat('yyyy-MM-dd'); + final DateFormat formatter = DateFormat('yyyy-MM-dd', "en-US"); setState(() { selectedDateDisplay = DateUtil.getMonthDayYearDateFormatted(day); selectedNextDate = DateUtil.getWeekDayMonthDayYearDateFormatted(day.add(Duration(days: 1)), "en"); diff --git a/lib/presentation/book_appointment/widgets/clinic_card.dart b/lib/presentation/book_appointment/widgets/clinic_card.dart index cab3b1a4..7e3c8df1 100644 --- a/lib/presentation/book_appointment/widgets/clinic_card.dart +++ b/lib/presentation/book_appointment/widgets/clinic_card.dart @@ -32,9 +32,9 @@ class ClinicCard extends StatelessWidget { child: Column( children: [ Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Utils.buildSvgWithAssets(icon: AppAssets.generic_clinic_icon, width: 24.h, height: 24.h, fit: BoxFit.contain).toShimmer2(isShow: isLoading), + Utils.buildSvgWithAssets(icon: "assets/images/clinicIcons/${clinicsListResponseModel.clinicID ?? 1}.svg", width: 24.w, height: 24.h, fit: BoxFit.contain).toShimmer2(isShow: isLoading), (clinicsListResponseModel.isLiveCareClinicAndOnline ?? true) - ? Utils.buildSvgWithAssets(icon: AppAssets.livecare_clinic_icon, width: 32.h, height: 32.h, fit: BoxFit.contain).toShimmer2(isShow: isLoading) + ? Utils.buildSvgWithAssets(icon: AppAssets.livecare_clinic_icon, width: 32.w, height: 32.h, fit: BoxFit.contain).toShimmer2(isShow: isLoading) : SizedBox.shrink(), ], ), diff --git a/lib/presentation/book_appointment/widgets/doctor_card.dart b/lib/presentation/book_appointment/widgets/doctor_card.dart index 97a941dd..ddb43e9d 100644 --- a/lib/presentation/book_appointment/widgets/doctor_card.dart +++ b/lib/presentation/book_appointment/widgets/doctor_card.dart @@ -38,7 +38,7 @@ class DoctorCard extends StatelessWidget { hasShadow: false, ), child: Padding( - padding: EdgeInsets.only(top: 14.h,bottom: 20.h), + padding: EdgeInsets.only(top: 14.h, bottom: 20.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -90,23 +90,21 @@ class DoctorCard extends StatelessWidget { Row( children: [ SizedBox( - width: MediaQuery.of(context).size.width * 0.49, - child: (isLoading ? "Dr John Smith" : "${doctorsListResponseModel.doctorTitle} ${doctorsListResponseModel.name}") - .toString() - .toText16(isBold: true, maxlines: 1), + width: MediaQuery.of(context).size.width * 0.55, + child: (isLoading ? "Dr John Smith" : "${doctorsListResponseModel.doctorTitle} ${doctorsListResponseModel.name}").toString().toText16(isBold: true, maxlines: 1), ).toShimmer2(isShow: isLoading), ], ), SizedBox(height: 2.h), Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ SizedBox( - width: MediaQuery.of(context).size.width * 0.45, child: (isLoading ? "Consultant Cardiologist" : doctorsListResponseModel.speciality!.isNotEmpty - ? doctorsListResponseModel.speciality!.first + ? (doctorsListResponseModel.speciality!.first.length > 32 + ? '${doctorsListResponseModel.speciality!.first.substring(0, 32)}...' + : doctorsListResponseModel.speciality!.first) : "") .toString() .toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor, maxLine: 2) @@ -121,38 +119,40 @@ class DoctorCard extends StatelessWidget { ).toShimmer2(isShow: isLoading), ], ), + SizedBox(height: 6.h), + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget( + labelText: "${isLoading ? "Cardiologist" : doctorsListResponseModel.clinicName}", + ).toShimmer2(isShow: isLoading), + AppCustomChipWidget( + labelText: "${isLoading ? "Olaya Hospital" : doctorsListResponseModel.projectName}", + ).toShimmer2(isShow: isLoading), + bookAppointmentsViewModel.isNearestAppointmentSelected + ? doctorsListResponseModel.nearestFreeSlot != null + ? AppCustomChipWidget( + // labelText: (isLoading ? "Cardiologist" : DateUtil.getDateStringForNearestSlot(doctorsListResponseModel.nearestFreeSlot)), + richText: (isLoading ? "Cardiologist" : DateUtil.getDateStringForNearestSlot(doctorsListResponseModel.nearestFreeSlot)) + .toText10(isEnglishOnly: true, color: AppColors.whiteColor), + backgroundColor: AppColors.successColor, + textColor: AppColors.whiteColor, + ).toShimmer2(isShow: isLoading) + : SizedBox.shrink() + : SizedBox.shrink(), + ], + ), ], ), ), Expanded( flex: 1, - child: Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown) - .toShimmer2(isShow: isLoading), + child: Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown).toShimmer2(isShow: isLoading), ), ], ), - Wrap( - direction: Axis.horizontal, - spacing: 3.h, - runSpacing: 4.h, - children: [ - AppCustomChipWidget( - labelText: "${isLoading ? "Cardiologist" : doctorsListResponseModel.clinicName}", - ).toShimmer2(isShow: isLoading), - AppCustomChipWidget( - labelText: "${isLoading ? "Olaya Hospital" : doctorsListResponseModel.projectName}", - ).toShimmer2(isShow: isLoading), - bookAppointmentsViewModel.isNearestAppointmentSelected - ? doctorsListResponseModel.nearestFreeSlot != null - ? AppCustomChipWidget( - labelText: (isLoading ? "Cardiologist" : DateUtil.getDateStringForNearestSlot(doctorsListResponseModel.nearestFreeSlot)), - backgroundColor: AppColors.successColor, - textColor: AppColors.whiteColor, - ).toShimmer2(isShow: isLoading) - : SizedBox.shrink() - : SizedBox.shrink(), - ], - ), SizedBox(height: 12.h), CustomButton( text: LocaleKeys.bookAppo.tr(context: context), diff --git a/lib/presentation/book_appointment/widgets/doctor_rating_details.dart b/lib/presentation/book_appointment/widgets/doctor_rating_details.dart new file mode 100644 index 00000000..0ed54862 --- /dev/null +++ b/lib/presentation/book_appointment/widgets/doctor_rating_details.dart @@ -0,0 +1,195 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_rating_bar/flutter_rating_bar.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:provider/provider.dart'; + +class DoctorRatingDetails extends StatelessWidget { + const DoctorRatingDetails({super.key}); + + @override + Widget build(BuildContext context) { + return Consumer(builder: (context, bookAppointmentsVM, child) { + return bookAppointmentsVM.isDoctorRatingDetailsLoading + ? Utils.getLoadingWidget() + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + bookAppointmentsVM.doctorsProfileResponseModel.actualDoctorRate!.ceilToDouble().toString().toText44(isBold: true), + SizedBox(height: 4.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "${bookAppointmentsVM.doctorsProfileResponseModel.noOfPatientsRate} ${LocaleKeys.reviews.tr(context: context)}" + .toText16(weight: FontWeight.w500, color: AppColors.greyInfoTextColor), + RatingBar( + initialRating: bookAppointmentsVM.doctorsProfileResponseModel.actualDoctorRate!.toDouble(), + direction: Axis.horizontal, + allowHalfRating: true, + itemCount: 5, + itemSize: 20.h, + ignoreGestures: true, + ratingWidget: RatingWidget( + full: Icon( + Icons.star, + color: AppColors.ratingColorYellow, + size: 24.h, + ), + half: Icon( + Icons.star_half, + color: AppColors.ratingColorYellow, + ), + empty: Icon( + Icons.star, + color: AppColors.ratingColorYellow, + ), + ), + tapOnlyMode: true, + unratedColor: Colors.grey[500], + itemPadding: EdgeInsets.symmetric(horizontal: 4.0), + onRatingUpdate: (rating) { + print(rating); + }, + ), + ], + ), + SizedBox(height: 8.h), + Container( + margin: EdgeInsets.only(top: 10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + width: 100.0, + margin: EdgeInsets.only(top: 10.0, left: 15.0, right: 15.0), + child: Text(LocaleKeys.excellent.tr(context: context), style: TextStyle(fontSize: 13.0, color: Colors.black, fontWeight: FontWeight.w600))), + getRatingLine(bookAppointmentsVM.doctorDetailsList[0].ratio, Colors.green[700]!), + ], + ), + Container( + margin: EdgeInsets.only(top: 10.0, left: 10.0, right: 10.0), + child: Text("${getRatingWidth(bookAppointmentsVM.doctorDetailsList[0].ratio).round()}%", style: TextStyle(fontSize: 14.0, color: Colors.black, fontWeight: FontWeight.w600)), + ), + ], + ), + ), + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + width: 100.0, + margin: EdgeInsets.only(top: 10.0, left: 15.0, right: 15.0), + child: Text(LocaleKeys.vGood.tr(context: context), style: TextStyle(fontSize: 13.0, color: Colors.black, fontWeight: FontWeight.w600))), + getRatingLine(bookAppointmentsVM.doctorDetailsList[1].ratio, Color(0xffB7B723)), + ], + ), + Container( + margin: EdgeInsets.only(top: 10.0, left: 10.0, right: 10.0), + child: Text("${bookAppointmentsVM.doctorDetailsList[1].ratio.round()}%", style: TextStyle(fontSize: 14.0, color: Colors.black, fontWeight: FontWeight.w600)), + ), + ], + ), + ), + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + width: 100.0, + margin: EdgeInsets.only(top: 10.0, left: 15.0, right: 15.0), + child: Text(LocaleKeys.good.tr(context: context), style: TextStyle(fontSize: 13.0, color: Colors.black, fontWeight: FontWeight.w600))), + getRatingLine(bookAppointmentsVM.doctorDetailsList[2].ratio, Color(0xffEBA727)), + ], + ), + Container( + margin: EdgeInsets.only(top: 10.0, left: 10.0, right: 10.0), + child: Text("${bookAppointmentsVM.doctorDetailsList[2].ratio.round()}%", style: TextStyle(fontSize: 14.0, color: Colors.black, fontWeight: FontWeight.w600)), + ), + ], + ), + ), + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + width: 100.0, + margin: EdgeInsets.only(top: 10.0, left: 15.0, right: 15.0), + child: Text(LocaleKeys.average.tr(context: context), style: TextStyle(fontSize: 13.0, color: Colors.black, fontWeight: FontWeight.w600))), + getRatingLine(bookAppointmentsVM.doctorDetailsList[3].ratio, Color(0xffEB7227)), + ], + ), + Container( + margin: EdgeInsets.only(top: 10.0, left: 10.0, right: 10.0), + child: Text("${bookAppointmentsVM.doctorDetailsList[3].ratio.round()}%", style: TextStyle(fontSize: 14.0, color: Colors.black, fontWeight: FontWeight.w600)), + ), + ], + ), + ), + Container( + margin: EdgeInsets.only(bottom: 30.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + width: 100.0, + margin: EdgeInsets.only(top: 10.0, left: 15.0, right: 15.0), + child: Text(LocaleKeys.average.tr(context: context), style: TextStyle(fontSize: 13.0, color: Colors.black, fontWeight: FontWeight.w600))), + getRatingLine(bookAppointmentsVM.doctorDetailsList[4].ratio, Color(0xffE20C0C)), + ], + ), + Container( + margin: EdgeInsets.only(top: 10.0, left: 10.0, right: 10.0), + child: Text("${bookAppointmentsVM.doctorDetailsList[4].ratio.round()}%", style: TextStyle(fontSize: 14.0, color: Colors.black, fontWeight: FontWeight.w600)), + ), + ], + ), + ), + ], + ); + }); + } + + double getRatingWidth(num patientNumber) { + var width = patientNumber; + return width.roundToDouble(); + } + + Widget getRatingLine(double patientNumber, Color color) { + return Container( + margin: EdgeInsets.only(top: 10.0), + child: Stack(children: [ + SizedBox( + width: 150.0, + height: 7.h, + child: Container( + color: Colors.grey[300], + ), + ), + SizedBox( + width: patientNumber * 1.55, + height: 7.h, + child: Container( + color: color, + ), + ), + ]), + ); + } +} diff --git a/lib/presentation/child_vaccine/my_child_vaccine_page.dart b/lib/presentation/child_vaccine/my_child_vaccine_page.dart new file mode 100644 index 00000000..6a226ab4 --- /dev/null +++ b/lib/presentation/child_vaccine/my_child_vaccine_page.dart @@ -0,0 +1,376 @@ +// import 'package:easy_localization/easy_localization.dart'; +// import 'package:flutter/material.dart'; +// import 'package:hmg_patient_app_new/core/app_assets.dart'; +// import 'package:hmg_patient_app_new/core/app_state.dart'; +// import 'package:hmg_patient_app_new/core/dependencies.dart'; +// import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +// import 'package:hmg_patient_app_new/core/utils/utils.dart'; +// import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +// import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +// import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; +// import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_view_model.dart'; +// import 'package:hmg_patient_app_new/features/blood_donation/models/blood_group_hospitals_model.dart'; +// import 'package:hmg_patient_app_new/features/blood_donation/widgets/hospital_selection.dart'; +// import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +// import 'package:hmg_patient_app_new/presentation/blood_donation/widgets/select_blood_group_widget.dart'; +// import 'package:hmg_patient_app_new/presentation/blood_donation/widgets/select_city_widget.dart'; +// import 'package:hmg_patient_app_new/presentation/blood_donation/widgets/select_gender_widget.dart'; +// import 'package:hmg_patient_app_new/presentation/book_appointment/select_clinic_page.dart'; +// import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; +// import 'package:hmg_patient_app_new/services/dialog_service.dart'; +// import 'package:hmg_patient_app_new/theme/colors.dart'; +// import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +// import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +// import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +// import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +// import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +// import 'package:lottie/lottie.dart'; +// import 'package:provider/provider.dart'; +// import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart'; +// +// class MyChildVaccinePage extends StatefulWidget { +// MyChildVaccinePage({super.key}); +// +// @override +// State createState() => _MyChildVaccinePageState(); +// } +// +// class _MyChildVaccinePageState extends State { +// late AppState appState; +// +// @override +// void initState() { +// super.initState(); +// Future.microtask(() => getIt.get().fetchAllData()); +// } +// +// @override +// Widget build(BuildContext context) { +// appState = getIt(); +// return Scaffold( +// backgroundColor: AppColors.bgScaffoldColor, +// body: Consumer(builder: (context, bloodDonationVM, child) { +// return Column( +// children: [ +// Expanded( +// child: CollapsingListView( +// title: "Child Vaccines".needTranslation, +// trailing: CustomButton( +// icon: AppAssets.add_icon, +// text: "Add Child", +// onPressed: () {}, +// backgroundColor: AppColors.bgRedLightColor, +// borderColor: AppColors.bgRedLightColor, +// textColor: AppColors.primaryRedColor, +// iconColor: AppColors.primaryRedColor, +// padding: EdgeInsetsGeometry.symmetric(vertical: 0.h, horizontal: 10.h)), +// child: Padding( +// padding: EdgeInsets.all(24.w), +// child: SingleChildScrollView( +// child: Container( +// decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: false), +// child: Padding( +// padding: EdgeInsets.all(16.h), +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Row( +// mainAxisAlignment: MainAxisAlignment.spaceBetween, +// children: [ +// Row( +// children: [ +// Utils.buildSvgWithAssets(icon: AppAssets.select_city_icon, width: 40.h, height: 40.h), +// SizedBox(width: 12.w), +// Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// LocaleKeys.city.tr().toText16(color: AppColors.textColor, weight: FontWeight.w500), +// (appState.isArabic() +// ? (bloodDonationVM.selectedCity?.descriptionN ?? LocaleKeys.select.tr()) +// : bloodDonationVM.selectedCity?.description ?? LocaleKeys.select.tr(context: context)) +// .toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), +// ], +// ), +// ], +// ), +// Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, width: 25.h, height: 25.h), +// ], +// ).onPress(() async { +// showCommonBottomSheetWithoutHeight(context, +// title: LocaleKeys.selectCity.tr(context: context), isDismissible: true, child: SelectCityWidget(bloodDonationViewModel: bloodDonationVM), callBackFunc: () {}); +// }), +// SizedBox(height: 16.h), +// Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), +// SizedBox(height: 16.h), +// Row( +// mainAxisAlignment: MainAxisAlignment.spaceBetween, +// children: [ +// Row( +// children: [ +// Utils.buildSvgWithAssets(icon: AppAssets.genderInputIcon, width: 40.h, height: 40.h), +// SizedBox(width: 12.w), +// Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// LocaleKeys.gender.tr().toText16(color: AppColors.textColor, weight: FontWeight.w500), +// (appState.isArabic() +// ? (bloodDonationVM.selectedGender?.typeAr ?? LocaleKeys.select.tr()) +// : bloodDonationVM.selectedGender?.type ?? LocaleKeys.select.tr(context: context)) +// .toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), +// ], +// ), +// ], +// ), +// Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, width: 25.h, height: 25.h), +// ], +// ).onPress(() { +// showCommonBottomSheetWithoutHeight(context, +// title: LocaleKeys.selectGender.tr(context: context), +// isDismissible: true, +// child: SelectGenderWidget(isArabic: appState.isArabic(), bloodDonationViewModel: bloodDonationVM), +// callBackFunc: () {}); +// }), +// SizedBox(height: 16.h), +// Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), +// SizedBox(height: 16.h), +// Row( +// mainAxisAlignment: MainAxisAlignment.spaceBetween, +// children: [ +// Row( +// children: [ +// Utils.buildSvgWithAssets(icon: AppAssets.bloodType, width: 40.h, height: 40.h), +// SizedBox(width: 12.w), +// Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// LocaleKeys.bloodType.tr().toText16(color: AppColors.textColor, weight: FontWeight.w500), +// // bloodDonationVM.selectedBloodType?.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), +// (appState.isArabic() +// ? (bloodDonationVM.selectedBloodType ?? LocaleKeys.select.tr()) +// : bloodDonationVM.selectedBloodType ?? LocaleKeys.select.tr(context: context)) +// .toText14(color: AppColors.greyTextColor, weight: FontWeight.w500) +// ], +// ), +// ], +// ), +// Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, width: 25.h, height: 25.h), +// ], +// ).onPress(() { +// showCommonBottomSheetWithoutHeight(context, +// title: LocaleKeys.select.tr(context: context), +// isDismissible: true, +// child: SelectBloodGroupWidget(isArabic: appState.isArabic(), bloodDonationViewModel: bloodDonationVM), +// callBackFunc: () {}); +// }), +// ], +// ), +// ), +// ), +// ), +// ), +// ), +// ), +// Container( +// decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), +// child: SizedBox( +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// ], +// ), +// ), +// ), +// ], +// ); +// }), +// ); +// } +// } + + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; + +class MyChildVaccinePage extends StatefulWidget { + const MyChildVaccinePage({super.key}); + + @override + State createState() => _MyChildVaccinePageState(); +} + +class _MyChildVaccinePageState extends State { + int selectedChildIndex = 0; + + final List> children = [ + {"name": "Faris", "gender": "Son", "image": AppAssets.babyBoyImg}, + {"name": "Zaina", "gender": "Daughter", "image": AppAssets.babyGirlImg}, + {"name": "Ahmed", "gender": "Son", "image": AppAssets.babyBoyImg}, + ]; + + final List> vaccinesData = [ + { + "age": "4 months", + "dueDate": "19 Aug, 2025", + "vaccines": ["IPV", "DTap", "Hepatitis B", "HIB", "Pneumococcal Conjugate (PCV)", "Rota"] + }, + { + "age": "2 months", + "dueDate": "19 June, 2025", + "vaccines": ["IPV", "DTap", "Hepatitis B", "HIB", "Pneumococcal Conjugate (PCV)", "Rota"] + } + ]; + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: Column( + children: [ + Expanded( + child: CollapsingListView( + title: "Child Vaccines".needTranslation, + trailing: CustomButton( + icon: AppAssets.add_icon, + text: "Add Child", + onPressed: () {}, + backgroundColor: AppColors.bgRedLightColor, + borderColor: AppColors.bgRedLightColor, + textColor: AppColors.primaryRedColor, + iconColor: AppColors.primaryRedColor, + padding: EdgeInsets.symmetric(vertical: 0.h, horizontal: 10.w), + ), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + _buildChildSelector(), + SizedBox(height: 24.h), + ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: EdgeInsets.symmetric(horizontal: 24.w), + itemCount: vaccinesData.length, + separatorBuilder: (context, index) => SizedBox(height: 16.h), + itemBuilder: (context, index) { + return _buildVaccineCard(vaccinesData[index]); + }, + ), + SizedBox(height: 24.h), + ], + ), + ), + ), + ), + ], + ), + ); + } + + Widget _buildChildSelector() { + return SizedBox( + height: 50.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + padding: EdgeInsets.symmetric(horizontal: 24.w), + itemCount: children.length, + separatorBuilder: (context, index) => SizedBox(width: 12.w), + itemBuilder: (context, index) { + bool isSelected = selectedChildIndex == index; + return GestureDetector( + onTap: () => setState(() => selectedChildIndex = index), + child: Container( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h), + decoration: BoxDecoration( + color: isSelected ? AppColors.bgRedLightColor : AppColors.whiteColor, + borderRadius: BorderRadius.circular(12.r), + border: Border.all( + color: isSelected ? AppColors.primaryRedColor : AppColors.borderOnlyColor.withValues(alpha: 0.1), + width: 1, + ), + ), + child: Row( + children: [ + Image.asset(children[index]['image'], width: 24.h, height: 24.h), + SizedBox(width: 8.w), + Text( + "${children[index]['name']} (${children[index]['gender']})", + style: TextStyle( + color: isSelected ? AppColors.primaryRedColor : AppColors.textColor, + fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500, + fontSize: 14.f, + ), + ), + ], + ), + ), + ); + }, + ), + ); + } + + Widget _buildVaccineCard(Map data) { + return Container( + padding: EdgeInsets.all(20.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + data['age'].toString().toText18(weight: FontWeight.bold, color: AppColors.textColor), + SizedBox(height: 12.h), + Container( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h), + decoration: BoxDecoration( + color: AppColors.bgScaffoldColor, + borderRadius: BorderRadius.circular(8.r), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.calendar, width: 16.h, height: 16.h, iconColor: AppColors.textColor), + SizedBox(width: 8.w), + "Due Date : ${data['dueDate']}".toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), + ], + ), + ), + SizedBox(height: 16.h), + ... (data['vaccines'] as List).map((vaccine) => Padding( + padding: EdgeInsets.only(bottom: 12.h), + child: Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.tickIcon, width: 20.h, height: 20.h, iconColor: Colors.green), + SizedBox(width: 12.w), + vaccine.toString().toText14(weight: FontWeight.w500, color: AppColors.textColor), + ], + ), + )), + SizedBox(height: 8.h), + CustomButton( + text: "Send via email", + onPressed: () {}, + backgroundColor: AppColors.bgRedLightColor, + borderColor: Colors.transparent, + textColor: AppColors.primaryRedColor, + icon: AppAssets.email, + iconColor: AppColors.primaryRedColor, + width: double.infinity, + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/presentation/comprehensive_checkup/cmc_selection_review_page.dart b/lib/presentation/comprehensive_checkup/cmc_selection_review_page.dart index 9b78ecad..840d38cf 100644 --- a/lib/presentation/comprehensive_checkup/cmc_selection_review_page.dart +++ b/lib/presentation/comprehensive_checkup/cmc_selection_review_page.dart @@ -1,4 +1,5 @@ import 'dart:developer'; +import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -22,7 +23,7 @@ import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/map/location_map_widget.dart'; -import 'package:maps_launcher/maps_launcher.dart'; +import 'package:map_launcher/map_launcher.dart'; import 'package:provider/provider.dart'; class CmcSelectionReviewPage extends StatefulWidget { @@ -319,16 +320,27 @@ class _CmcSelectionReviewPageState extends State { CmcUiSelectionHelper.showHospitalSelectionBottomSheet(context: context, onHospitalSelected: (hospital) => context.pop()); } - void _launchDirections(HospitalsModel selectedHospital) { + void _launchDirections(HospitalsModel selectedHospital) async { final double lat = double.parse(selectedHospital.latitude ?? "0.0"); final double lng = double.parse(selectedHospital.longitude ?? "0.0"); if (lat != 0.0 && lng != 0.0) { - MapsLauncher.launchCoordinates( - lat, - lng, - selectedHospital.name ?? "Hospital", - ); + await MapLauncher.showMarker( + mapType: MapType.google, + coords: Coords(lat, lng), + title: selectedHospital.name ?? "Hospital", + ).catchError((err) { + MapLauncher.showMarker( + mapType: Platform.isIOS ? MapType.apple : MapType.google, + coords: Coords(lat, lng), + title: selectedHospital.name ?? "Hospital", + ); + }); + // MapsLauncher.launchCoordinates( + // lat, + // lng, + // selectedHospital.name ?? "Hospital", + // ); } } diff --git a/lib/presentation/contact_us/feedback_page.dart b/lib/presentation/contact_us/feedback_page.dart index 62ae1d36..8f10bc54 100644 --- a/lib/presentation/contact_us/feedback_page.dart +++ b/lib/presentation/contact_us/feedback_page.dart @@ -148,20 +148,18 @@ class FeedbackPage extends StatelessWidget { ), child: Padding( padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.ask_doctor_icon, width: 24.w, height: 24.h, iconColor: AppColors.greyTextColor), - SizedBox(width: 12.w), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.feedbackType.tr().toText16(color: AppColors.textColor, weight: FontWeight.w500), + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.ask_doctor_icon, width: 24.w, height: 24.h, iconColor: AppColors.greyTextColor), + SizedBox(width: 12.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.feedbackType.tr().toText16(color: AppColors.textColor, weight: FontWeight.w500), (getIt.get().isArabic() ? contactUsViewModel.selectedFeedbackType.nameAR : contactUsViewModel.selectedFeedbackType.nameEN) .toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), ], diff --git a/lib/presentation/contact_us/live_chat_page.dart b/lib/presentation/contact_us/live_chat_page.dart index 2b86ccb4..a602eb14 100644 --- a/lib/presentation/contact_us/live_chat_page.dart +++ b/lib/presentation/contact_us/live_chat_page.dart @@ -1,6 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; @@ -8,12 +9,17 @@ 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/contact_us/contact_us_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'package:lottie/lottie.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -23,10 +29,12 @@ class LiveChatPage extends StatelessWidget { String chatURL = ""; late AppState appState; + late AuthenticationViewModel authVm; @override Widget build(BuildContext context) { appState = getIt.get(); + authVm = context.read(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: Consumer(builder: (context, contactUsVM, child) { @@ -158,13 +166,71 @@ class LiveChatPage extends StatelessWidget { child: CustomButton( text: LocaleKeys.liveChat.tr(context: context), onPressed: () async { - if (contactUsVM.chatRequestID != null) { - chatURL = "https://chat.hmg.com/Index.aspx?RequestedId=${contactUsVM.chatRequestID}"; - debugPrint("Chat URL: $chatURL"); - Uri uri = Uri.parse(chatURL); - launchUrl(uri, mode: LaunchMode.platformDefault, webOnlyWindowName: ""); + if(appState.isAuthenticated) { + if (contactUsVM.chatRequestID != null) { + chatURL = "${ApiConsts.CHAT_URL}${contactUsVM.chatRequestID}"; + debugPrint("Chat URL: $chatURL"); + Uri uri = Uri.parse(chatURL); + launchUrl(uri, mode: LaunchMode.platformDefault, webOnlyWindowName: ""); + } else { + debugPrint("Chat Request ID is null"); + } } else { - debugPrint("Chat Request ID is null"); + showCommonBottomSheetWithoutHeight( + context, + title: LocaleKeys.notice.tr(context: context), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Lottie.asset(AppAnimations.errorAnimation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill), + SizedBox(height: 8.h), + (LocaleKeys.loginToUseService.tr(context: context)).toText16(color: AppColors.blackColor), + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: CustomButton( + text: LocaleKeys.cancel.tr(context: context), + onPressed: () { + Navigator.of(context).pop(); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + icon: AppAssets.cancel, + iconColor: AppColors.primaryRedColor, + ), + ), + SizedBox(width: 8.h), + Expanded( + child: CustomButton( + text: LocaleKeys.confirm.tr(context: context), + onPressed: () async { + Navigator.of(context).pop(); + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); + await authVm.onLoginPressed(); + }, + backgroundColor: AppColors.bgGreenColor, + borderColor: AppColors.bgGreenColor, + textColor: Colors.white, + icon: AppAssets.confirm, + ), + ), + ], + ), + SizedBox(height: 16.h), + ], + ).center, + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); } }, backgroundColor: contactUsVM.selectedLiveChatProjectIndex == -1 ? AppColors.greyColor : AppColors.primaryRedColor, diff --git a/lib/presentation/contact_us/widgets/find_us_item_card.dart b/lib/presentation/contact_us/widgets/find_us_item_card.dart index 6e295e08..1774404d 100644 --- a/lib/presentation/contact_us/widgets/find_us_item_card.dart +++ b/lib/presentation/contact_us/widgets/find_us_item_card.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -10,7 +12,7 @@ import 'package:hmg_patient_app_new/features/contact_us/models/resp_models/get_h import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; -import 'package:maps_launcher/maps_launcher.dart'; +import 'package:map_launcher/map_launcher.dart'; import 'package:url_launcher/url_launcher.dart'; class FindUsItemCard extends StatelessWidget { @@ -82,8 +84,18 @@ class FindUsItemCard extends StatelessWidget { iconColor: AppColors.whiteColor, backgroundColor: AppColors.textColor.withValues(alpha: 0.8), textColor: AppColors.whiteColor, - onChipTap: () { - MapsLauncher.launchCoordinates(double.parse(getHMGLocationsModel.latitude ?? "0.0"), double.parse(getHMGLocationsModel.longitude ?? "0.0"), getHMGLocationsModel.locationName!); + onChipTap: () async { + await MapLauncher.showMarker( + mapType: MapType.google, + coords: Coords(double.parse(getHMGLocationsModel.latitude ?? "0.0"), double.parse(getHMGLocationsModel.longitude ?? "0.0")), + title: getHMGLocationsModel.locationName ?? "Hospital", + ).catchError((err) { + MapLauncher.showMarker( + mapType: Platform.isIOS ? MapType.apple : MapType.google, + coords: Coords(double.parse(getHMGLocationsModel.latitude ?? "0.0"), double.parse(getHMGLocationsModel.longitude ?? "0.0")), + title: getHMGLocationsModel.locationName ?? "Hospital", + ); + }); }, ), SizedBox(width: 4.w), diff --git a/lib/presentation/e_referral/widget/e_referral_patient_info.dart b/lib/presentation/e_referral/widget/e_referral_patient_info.dart index 0cc5c7ee..61b92c76 100644 --- a/lib/presentation/e_referral/widget/e_referral_patient_info.dart +++ b/lib/presentation/e_referral/widget/e_referral_patient_info.dart @@ -140,6 +140,7 @@ class PatientInformationStepState extends State { controller: _phoneController, padding: const EdgeInsets.all(8), keyboardType: TextInputType.number, + fontFamily: "Poppins", onChange: (value) { formManager.updatePatientPhone(value ?? ''); }, diff --git a/lib/presentation/e_referral/widget/search_e_referral_form.dart b/lib/presentation/e_referral/widget/search_e_referral_form.dart index fa3ae197..33697d5e 100644 --- a/lib/presentation/e_referral/widget/search_e_referral_form.dart +++ b/lib/presentation/e_referral/widget/search_e_referral_form.dart @@ -73,6 +73,7 @@ class SearchEReferralFormFormState extends State { hintText: formManager.searchCriteria == 0 ? "Enter Identification Number" : "Enter Referral Number", labelText: formManager.searchCriteria == 0 ? "Identification Number" : "Referral Number", keyboardType: TextInputType.number, + fontFamily: "Poppins", errorMessage: formManager.errors.searchValue, hasError: !ValidationUtils.isNullOrEmpty(formManager.errors.searchValue), onChange: (value) { @@ -94,6 +95,7 @@ class SearchEReferralFormFormState extends State { controller: _phoneController, padding: const EdgeInsets.all(8), keyboardType: TextInputType.number, + fontFamily: "Poppins", onChange: (value) { formManager.updateSearchPhone(value ?? ''); // _validateForm(formManager); diff --git a/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart b/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart index e86104b2..5a368a3b 100644 --- a/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart +++ b/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart @@ -526,8 +526,8 @@ class CallAmbulancePage extends StatelessWidget { textPlaceInput(context) { return Consumer(builder: (_, vm, __) { - print("the data is ${vm.geocodeResponse?.results.first.formattedAddress ?? vm.selectedPrediction?.description}"); - return SizedBox( + // print("the data is ${vm.geocodeResponse?.results.first.formattedAddress ?? vm.selectedPrediction?.description}"); + return (vm.geocodeResponse != null && vm.geocodeResponse!.results.isNotEmpty) ? SizedBox( width: MediaQuery.sizeOf(context).width, child: TextInputWidget( labelText: LocaleKeys.enterPickupLocationManually.tr(context: context), @@ -549,7 +549,7 @@ class CallAmbulancePage extends StatelessWidget { ).onPress(() { openLocationInputBottomSheet(context); }), - ); + ) : SizedBox.shrink(); }); } diff --git a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart index 3850c1f4..999a43ee 100644 --- a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart +++ b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart @@ -159,6 +159,15 @@ class ErOnlineCheckinHome extends StatelessWidget { await vm.getPatientERPaymentInformation(onSuccess: (response) { LoaderBottomSheet.hideLoader(); vm.navigateToEROnlineCheckInPaymentPage(); + }, onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err.toString()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); }); }, onHospitalSearch: (value) { diff --git a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_details_page.dart b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_details_page.dart index 05348544..463121d9 100644 --- a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_details_page.dart +++ b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_details_page.dart @@ -62,25 +62,22 @@ class ErOnlineCheckinPaymentDetailsPage extends StatelessWidget { SizedBox(height: 12.h), Wrap( direction: Axis.horizontal, - spacing: 6.w, + spacing: 4.w, runSpacing: 6.h, children: [ AppCustomChipWidget( - labelText: "File No.: ${appState.getAuthenticatedUser()!.patientId!.toString()}", - labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w), + labelText: "${LocaleKeys.fileno.tr(context: context)}.: ${appState.getAuthenticatedUser()!.patientId!.toString()}", ), AppCustomChipWidget( labelText: LocaleKeys.erClinic.tr(), - labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w), ), AppCustomChipWidget( labelText: emergencyServicesViewModel.selectedHospital!.name, - labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w), ), AppCustomChipWidget( icon: AppAssets.calendar, labelText: DateUtil.formatDateToDate(DateTime.now(), false), - labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w), + labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 8.w), ), ], ), diff --git a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart index 70a5b08b..35101978 100644 --- a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart +++ b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart @@ -337,8 +337,6 @@ class _ErOnlineCheckinPaymentPageState extends State } startApplePay() async { - // showCommonBottomSheet(context, - // child: Utils.getLoadingWidget(), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.3, isCloseButtonVisible: false, isDismissible: false, isFullScreen: false); LoaderBottomSheet.showLoader(); transID = Utils.getAdvancePaymentTransID( emergencyServicesViewModel.selectedHospital!.iD, @@ -405,8 +403,8 @@ class _ErOnlineCheckinPaymentPageState extends State applePayShaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel!.shaRequest, currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED", onFailed: (failureResult) async { + LoaderBottomSheet.hideLoader(); log("failureResult: ${failureResult.message.toString()}"); - Navigator.of(context).pop(); showCommonBottomSheetWithoutHeight( context, child: Utils.getErrorWidget(loadingText: failureResult.message.toString()), @@ -416,6 +414,7 @@ class _ErOnlineCheckinPaymentPageState extends State ); }, onSucceeded: (successResult) async { + LoaderBottomSheet.hideLoader(); log("successResult: ${successResult.responseMessage.toString()}"); selectedPaymentMethod = successResult.paymentOption ?? "VISA"; checkPaymentStatus(); diff --git a/lib/presentation/habib_wallet/habib_wallet_page.dart b/lib/presentation/habib_wallet/habib_wallet_page.dart index b096e946..c90003a8 100644 --- a/lib/presentation/habib_wallet/habib_wallet_page.dart +++ b/lib/presentation/habib_wallet/habib_wallet_page.dart @@ -82,28 +82,26 @@ class _HabibWalletState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ CustomButton( + height: 40.h, icon: AppAssets.recharge_icon, - iconSize: 21.h, + iconSize: 24.w, + backgroundColor: AppColors.infoColor, + textColor: AppColors.whiteColor, text: LocaleKeys.recharge.tr(context: context), + borderWidth: 0.w, + fontWeight: FontWeight.w500, + borderColor: Colors.transparent, + padding: EdgeInsets.fromLTRB(4, 0, 12, 0), + fontSize: 14.f, onPressed: () { Navigator.of(context) - .push( - CustomPageRoute( - page: RechargeWalletPage(), - ), - ) + .push(CustomPageRoute( + page: RechargeWalletPage(), + )) .then((val) { habibWalletVM.getPatientBalanceAmount(); }); }, - backgroundColor: AppColors.infoColor, - borderColor: AppColors.infoColor, - textColor: AppColors.whiteColor, - fontSize: 14, - fontWeight: FontWeight.bold, - borderRadius: 12, - // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, ), ], ), diff --git a/lib/presentation/habib_wallet/recharge_wallet_page.dart b/lib/presentation/habib_wallet/recharge_wallet_page.dart index 33de5e6a..78608e8b 100644 --- a/lib/presentation/habib_wallet/recharge_wallet_page.dart +++ b/lib/presentation/habib_wallet/recharge_wallet_page.dart @@ -36,6 +36,7 @@ class _RechargeWalletPageState extends State { late HabibWalletViewModel habibWalletVM; late AppState appState; final TextEditingController amountTextController = TextEditingController(); + final TextEditingController notesTextController = TextEditingController(); @override void initState() { @@ -85,30 +86,27 @@ class _RechargeWalletPageState extends State { Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ - Utils.getPaymentAmountWithSymbol( - SizedBox( - width: 150.h, - child: TextInputWidget( - controller: amountTextController, - labelText: "", - hintText: "", - isEnable: true, - prefix: null, - isAllowRadius: true, - isBorderAllowed: false, - isAllowLeadingIcon: true, - autoFocus: true, - fontSize: 40, - padding: EdgeInsets.symmetric(horizontal: 8.h, vertical: 0.h), - focusNode: textFocusNode, - isWalletAmountInput: true, - keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true), - // leadingIcon: AppAssets.student_card, - ), - ), - AppColors.textColor, - 13.h, - isExpanded: false), + SizedBox( + width: 150.h, + child: TextInputWidget( + controller: amountTextController, + labelText: "", + hintText: "", + isEnable: true, + prefix: null, + isAllowRadius: true, + isBorderAllowed: false, + isAllowLeadingIcon: true, + autoFocus: true, + fontSize: 40.f, + padding: EdgeInsets.symmetric(horizontal: 8.h, vertical: 0.h), + focusNode: textFocusNode, + isWalletAmountInput: true, + keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true), + fontFamily: "Poppins", + // leadingIcon: AppAssets.student_card, + ), + ), const Spacer(), LocaleKeys.sar.tr(context: context).toText20(color: AppColors.greyTextColor, weight: FontWeight.w500), ], @@ -140,9 +138,9 @@ class _RechargeWalletPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - (habibWalletVM.getSelectedRechargeTypeValue()).toText16(color: AppColors.textColor, weight: FontWeight.w500), + (habibWalletVM.getSelectedRechargeTypeValue()).toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), "${LocaleKeys.medicalFile.tr(context: context)}: ${habibWalletVM.fileNumber}" - .toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + .toText14(color: AppColors.textColor, weight: FontWeight.w500, letterSpacing: -0.2), ], ), ], @@ -178,11 +176,11 @@ class _RechargeWalletPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.hospital.tr(context: context).toText16(color: AppColors.textColor, weight: FontWeight.w500), + LocaleKeys.hospital.tr(context: context).toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), SizedBox( width: MediaQuery.of(context).size.width * 0.55, child: (habibWalletVM.selectedHospital != null ? habibWalletVM.selectedHospital!.name : LocaleKeys.selectHospital.tr(context: context))! - .toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + .toText14(color: AppColors.textColor, weight: FontWeight.w500), ), ], ), @@ -198,19 +196,14 @@ class _RechargeWalletPageState extends State { Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), SizedBox(height: 16.h), Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( + Utils.buildSvgWithAssets(icon: AppAssets.email_icon, width: 40.h, height: 40.h), + SizedBox(width: 8.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Utils.buildSvgWithAssets(icon: AppAssets.email_icon, width: 40.h, height: 40.h), - SizedBox(width: 8.h), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.email.tr(context: context).toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), - "${appState.getAuthenticatedUser()!.emailAddress}".toText16(color: AppColors.textColor, weight: FontWeight.w500), - ], - ), + LocaleKeys.email.tr(context: context).toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + "${appState.getAuthenticatedUser()!.emailAddress}".toText14(color: AppColors.textColor, weight: FontWeight.w500, letterSpacing: -0.2), ], ), ], @@ -218,23 +211,19 @@ class _RechargeWalletPageState extends State { SizedBox(height: 16.h), Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), SizedBox(height: 16.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.notes_icon, width: 40.h, height: 40.h), - SizedBox(width: 8.h), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.notes.tr(context: context).toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), - "Lorem Ipsum".toText16(color: AppColors.textColor, weight: FontWeight.w500), - ], - ), - ], - ), - ], + TextInputWidget( + labelText: LocaleKeys.notes.tr(context: context), + hintText: "", + controller: notesTextController, + keyboardType: TextInputType.text, + isEnable: true, + prefix: null, + autoFocus: false, + isAllowRadius: true, + isBorderAllowed: false, + isAllowLeadingIcon: true, + leadingIcon: AppAssets.notes_icon, + hasError: false, ), SizedBox(height: 8.h), ], @@ -280,6 +269,7 @@ class _RechargeWalletPageState extends State { ); } else { habibWalletVM.setWalletRechargeAmount(num.parse(amountTextController.text)); + habibWalletVM.setNotesText(notesTextController.text); // habibWalletVM.setDepositorDetails(appState.getAuthenticatedUser()!.patientId.toString(), "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}", // appState.getAuthenticatedUser()!.mobileNumber!); Navigator.of(context).push( diff --git a/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart b/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart index 3dec5aa8..4585b7d0 100644 --- a/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart +++ b/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart @@ -316,6 +316,8 @@ class _WalletPaymentConfirmPageState extends State { context, child: Utils.getSuccessWidget(loadingText: "Payment Successful!"), callBackFunc: () { + habibWalletVM.initHabibWalletProvider(); + habibWalletVM.getPatientBalanceAmount(); Navigator.of(context).pop(); Navigator.of(context).pop(); }, diff --git a/lib/presentation/habib_wallet/widgets/select-medical_file.dart b/lib/presentation/habib_wallet/widgets/select-medical_file.dart index aaa00367..6fed6522 100644 --- a/lib/presentation/habib_wallet/widgets/select-medical_file.dart +++ b/lib/presentation/habib_wallet/widgets/select-medical_file.dart @@ -28,6 +28,8 @@ 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/habib_wallet/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; +import 'package:hmg_patient_app_new/features/medical_file/models/family_file_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -145,6 +147,10 @@ class _MultiPageBottomSheetState extends State { ], ).paddingAll(16.h), ).onPress(() { + habibWalletVM.setDepositorDetails(appState.getAuthenticatedUser()!.patientId!.toString(), + "${appState.getAuthenticatedUser()!.firstName.toString()} ${appState.getAuthenticatedUser()!.lastName.toString()}", appState.getAuthenticatedUser()!.mobileNumber!.toString()); + habibWalletVM.setCurrentIndex(0); + habibWalletVM.setSelectedRechargeType(1); Navigator.of(context).pop(); }), SizedBox(height: 16.h), @@ -167,7 +173,24 @@ class _MultiPageBottomSheetState extends State { Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h), ], ).paddingAll(16.h), - ), + ).onPress(() { + DialogService dialogService = getIt.get(); + dialogService.showFamilyBottomSheetWithoutH( + label: LocaleKeys.familyTitle.tr(context: context), + message: "", + isShowManageButton: false, + isForWalletRecharge: true, + onSwitchPress: (FamilyFileResponseModelLists profile) { + habibWalletVM.setDepositorDetails(profile.responseId.toString(), profile.patientName.toString(), profile.mobileNumber.toString()); + habibWalletVM.setCurrentIndex(0); + habibWalletVM.setSelectedRechargeType(2); + Navigator.of(context).pop(); + Navigator.of(context).pop(); + + // medicalFileViewModel.switchFamilyFiles(responseID: profile.responseId, patientID: profile.patientId, phoneNumber: profile.mobileNumber); + }, + profiles: getIt.get().patientFamilyFiles); + }), SizedBox(height: 16.h), Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( diff --git a/lib/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart b/lib/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart index 4a83f51b..8cccf031 100644 --- a/lib/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart +++ b/lib/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart @@ -52,8 +52,8 @@ class _HealthCalculatorDetailedPageState extends State(); Navigator.of(context).push(CustomPageRoute( page: SelectClinicPage(calculatorType: widget.calculatorType, calculatedResult: calculatedResult), )); @@ -174,69 +172,58 @@ class _HealthCalculatorDetailedPageState extends State { crossAxisCount: 3, // 4 icons per row crossAxisSpacing: 16.w, mainAxisSpacing: 16.w, - childAspectRatio: 0.80), + childAspectRatio: 0.85), physics: NeverScrollableScrollPhysics(), shrinkWrap: true, itemCount: type == HealthCalculatorEnum.general ? generalHealthServices.length : womenHealthServices.length, @@ -214,6 +214,7 @@ class _HealthCalculatorsPageState extends State { icon: type == HealthCalculatorEnum.general ? generalHealthServices[index].icon : womenHealthServices[index].icon, labelText: type == HealthCalculatorEnum.general ? generalHealthServices[index].title : womenHealthServices[index].title, onTap: () { + Navigator.pop(context); Navigator.of(context).push( CustomPageRoute( page: HealthCalculatorDetailedPage( diff --git a/lib/presentation/health_calculators_and_converts/widgets/bf.dart b/lib/presentation/health_calculators_and_converts/widgets/bf.dart index b00846b1..c8c949c4 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/bf.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/bf.dart @@ -589,7 +589,7 @@ class _BodyFatWidgetState extends State { children: [ Radio( value: unit, - groupValue: selectedHeightUnit, + groupValue: selectedHipUnit, activeColor: AppColors.errorColor, onChanged: (String? value) { if (value == null) return; diff --git a/lib/presentation/health_trackers/add_health_tracker_entry_page.dart b/lib/presentation/health_trackers/add_health_tracker_entry_page.dart index cf92c9ba..702875d9 100644 --- a/lib/presentation/health_trackers/add_health_tracker_entry_page.dart +++ b/lib/presentation/health_trackers/add_health_tracker_entry_page.dart @@ -10,7 +10,7 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; -import 'package:hmg_patient_app_new/presentation/health_trackers/health_trackers_view_model.dart'; +import 'package:hmg_patient_app_new/features/health_trackers/health_trackers_view_model.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; diff --git a/lib/presentation/health_trackers/health_tracker_detail_page.dart b/lib/presentation/health_trackers/health_tracker_detail_page.dart index 83b37f65..b5f41dae 100644 --- a/lib/presentation/health_trackers/health_tracker_detail_page.dart +++ b/lib/presentation/health_trackers/health_tracker_detail_page.dart @@ -16,7 +16,7 @@ import 'package:hmg_patient_app_new/features/health_trackers/models/blood_sugar/ import 'package:hmg_patient_app_new/features/health_trackers/models/weight/week_weight_measurement_result_average.dart'; import 'package:hmg_patient_app_new/features/health_trackers/models/weight/year_weight_measurement_result_average.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; -import 'package:hmg_patient_app_new/presentation/health_trackers/health_trackers_view_model.dart'; +import 'package:hmg_patient_app_new/features/health_trackers/health_trackers_view_model.dart'; import 'package:hmg_patient_app_new/presentation/health_trackers/widgets/tracker_last_value_card.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; diff --git a/lib/presentation/health_trackers/widgets/tracker_last_value_card.dart b/lib/presentation/health_trackers/widgets/tracker_last_value_card.dart index 570005bf..7e4ac2c9 100644 --- a/lib/presentation/health_trackers/widgets/tracker_last_value_card.dart +++ b/lib/presentation/health_trackers/widgets/tracker_last_value_card.dart @@ -6,7 +6,7 @@ import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; -import 'package:hmg_patient_app_new/presentation/health_trackers/health_trackers_view_model.dart'; +import 'package:hmg_patient_app_new/features/health_trackers/health_trackers_view_model.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:provider/provider.dart'; diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart index 0b37a4db..9aaf1591 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -29,6 +29,7 @@ import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dar import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; import 'package:hmg_patient_app_new/presentation/parking/paking_page.dart'; +import 'package:hmg_patient_app_new/presentation/servicesPriceList/services_price_list_page.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -44,13 +45,22 @@ import '../../features/qr_parking/qr_parking_view_model.dart'; import '../emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart'; class ServicesPage extends StatelessWidget { - ServicesPage({super.key}); + bool showBackIcon; + + ServicesPage({super.key, this.showBackIcon = true}); late BloodDonationViewModel bloodDonationViewModel; late MedicalFileViewModel medicalFileViewModel; late final List hmgServices = [ - HmgServicesComponentModel(11, LocaleKeys.emergencyServices.tr(), "", AppAssets.emergency_services_icon, bgColor: AppColors.primaryRedColor, true, route: null, onTap: () async { + HmgServicesComponentModel( + 11, + LocaleKeys.emergencyServices.tr(), + "", + AppAssets.emergency_services_icon, + bgColor: AppColors.primaryRedColor, + true, + route: null, onTap: () async { if (getIt.get().isAuthenticated) { getIt.get().flushData(); getIt.get().getTransportationOrders( @@ -59,7 +69,7 @@ class ServicesPage extends StatelessWidget { getIt.get().getRRTOrders( showLoader: false, ); - Navigator.of(GetIt.instance().navigatorKey.currentContext!).push( + Navigator.of(getIt.get().navigatorKey.currentContext!).push( CustomPageRoute( page: EmergencyServicesPage(), settings: const RouteSettings(name: '/EmergencyServicesPage'), @@ -78,7 +88,8 @@ class ServicesPage extends StatelessWidget { true, route: AppRoutes.bookAppointmentPage, ), - HmgServicesComponentModel(5, LocaleKeys.completeCheckup.tr(), "", AppAssets.comprehensiveCheckup, bgColor: AppColors.bgGreenColor, true, route: null, onTap: () async { + HmgServicesComponentModel( + 5, LocaleKeys.completeCheckup.tr(), "", AppAssets.comprehensiveCheckup, bgColor: AppColors.bgGreenColor, true, route: null, onTap: () async { if (getIt.get().isAuthenticated) { getIt.get().pushPageRoute(AppRoutes.comprehensiveCheckupPage); } else { @@ -131,7 +142,7 @@ class ServicesPage extends StatelessWidget { }, ), HmgServicesComponentModel( - 11, LocaleKeys.eReferralServices.tr(), "", AppAssets.eReferral, bgColor: AppColors.eReferralCardColor, true, route: null, onTap: () async { + 11, LocaleKeys.eReferralServices.tr(), "", AppAssets.eReferral, bgColor: AppColors.eReferralCardColor, true, route: null, onTap: () async { if (getIt.get().isAuthenticated) { getIt.get().pushPageRoute(AppRoutes.eReferralPage); } else { @@ -139,38 +150,46 @@ class ServicesPage extends StatelessWidget { } }), HmgServicesComponentModel( - 3, - LocaleKeys.bloodDonation.tr(), - "", - AppAssets.blood_donation_icon, - bgColor: AppColors.bloodDonationCardColor, - true, - route: null, onTap: () async { - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.pleaseWait.tr()); - await bloodDonationViewModel.getRegionSelectedClinics(onSuccess: (val) async { - // await bloodDonationViewModel.getPatientBloodGroupDetails(onSuccess: (val) { - LoaderBottomSheet.hideLoader(); - Navigator.of(GetIt.instance().navigatorKey.currentContext!).push( - CustomPageRoute( - page: BloodDonationPage(), - ), + 3, + LocaleKeys.bloodDonation.tr(context: getIt.get().navigatorKey.currentContext!), + "".needTranslation, + AppAssets.blood_donation_icon, + bgColor: AppColors.bloodDonationCardColor, + true, + route: AppRoutes.bloodDonationPage, + ), + HmgServicesComponentModel( + 113, + LocaleKeys.servicePriceList.tr(context: getIt.get().navigatorKey.currentContext!), + "".needTranslation, + AppAssets.saudi_riyal_icon, + bgColor: AppColors.textColor, + false, + route: null, + onTap: () { + Navigator.of(getIt.get().navigatorKey.currentContext!).push( + CustomPageRoute(page: ServicesPriceListPage()), ); - // }, onError: (err) { - // LoaderBottomSheet.hideLoader(); - // }); - }, onError: (err) { - LoaderBottomSheet.hideLoader(); - }); - }), + }, + ) // HmgServicesComponentModel( - // 11, - // "Covid 19 Test".needTranslation, + // 3, + // "My Child Vaccine".needTranslation, // "".needTranslation, - // AppAssets.covid19icon, - // bgColor: AppColors.covid29Color, + // AppAssets.my_child_vaccine_icon, + // bgColor: AppColors.myChildVaccineCardColor, // true, - // route: AppRoutes.covid19Test, + // route: AppRoutes.myChildVaccine, // ), +// HmgServicesComponentModel( +// 11, +// "Covid 19 Test".needTranslation, +// "".needTranslation, +// AppAssets.covid19icon, +// bgColor: AppColors.covid29Color, +// true, +// route: AppRoutes.covid19Test, +// ), // HmgServicesComponentModel( // 11, @@ -298,24 +317,26 @@ class ServicesPage extends StatelessWidget { Widget build(BuildContext context) { bloodDonationViewModel = Provider.of(context); medicalFileViewModel = Provider.of(context); + + hmgServices.removeWhere((element) => Utils.havePrivilege(element.action) == false); + return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( title: LocaleKeys.exploreServices.tr(), - isLeading: false, + isLeading: showBackIcon, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 16.h), - LocaleKeys.medicalAndCareServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), + LocaleKeys.medicalAndCareServices.tr().toText18(weight: FontWeight.w600).paddingSymmetrical(24.w, 0), SizedBox(height: 16.h), GridView.builder( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: (isFoldable || isTablet) ? 6 : 4, // 4 icons per row - crossAxisSpacing: 12.w, - mainAxisSpacing: 18.h, - childAspectRatio: 0.8, - ), + crossAxisCount: (isFoldable || isTablet) ? 6 : 4, // 4 icons per row + crossAxisSpacing: 21.w, + mainAxisSpacing: 18.h, + childAspectRatio: 80 / 94), physics: NeverScrollableScrollPhysics(), shrinkWrap: true, itemCount: hmgServices.length, @@ -325,7 +346,7 @@ class ServicesPage extends StatelessWidget { }, ).paddingSymmetrical(24.w, 0), SizedBox(height: 24.h), - LocaleKeys.hmgServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), + LocaleKeys.hmgServices.tr().toText18(weight: FontWeight.w600).paddingSymmetrical(24.w, 0), SizedBox(height: 16.h), SizedBox( height: 350.h, @@ -356,13 +377,14 @@ class ServicesPage extends StatelessWidget { ), ), SizedBox(height: 24.h), - LocaleKeys.personalServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), + LocaleKeys.personalServices.tr().toText18(weight: FontWeight.w600).paddingSymmetrical(24.w, 0), SizedBox(height: 16.h), Row( children: [ Expanded( child: Container( - height: 170.h, + height: 183.h, + width: 183.h, padding: EdgeInsets.all(16.w), decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, @@ -376,9 +398,9 @@ class ServicesPage extends StatelessWidget { spacing: 8.w, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Utils.buildSvgWithAssets(icon: AppAssets.wallet, width: 30.w, height: 30.h), + Utils.buildSvgWithAssets(icon: AppAssets.wallet, width: 40.w, height: 40.h), LocaleKeys.habibWallet.tr().toText14(weight: FontWeight.w600, maxlines: 2).expanded, - Utils.buildSvgWithAssets(icon: AppAssets.arrow_forward), + Utils.buildSvgWithAssets(icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward), ], ), Spacer(), @@ -393,7 +415,7 @@ class ServicesPage extends StatelessWidget { ? CustomButton( height: 40.h, icon: AppAssets.recharge_icon, - iconSize: 16.w, + iconSize: 24.w, iconColor: AppColors.infoColor, textColor: AppColors.infoColor, text: LocaleKeys.recharge.tr(), @@ -402,7 +424,7 @@ class ServicesPage extends StatelessWidget { borderColor: Colors.transparent, backgroundColor: Color(0xff45A2F8).withValues(alpha: 0.08), padding: EdgeInsets.all(8.w), - fontSize: 12.f, + fontSize: 14.f, onPressed: () { Navigator.of(context).push(CustomPageRoute(page: RechargeWalletPage())); }, @@ -421,7 +443,8 @@ class ServicesPage extends StatelessWidget { SizedBox(width: 16.w), Expanded( child: Container( - height: 170.h, + height: 183.h, + width: 183.h, padding: EdgeInsets.all(16.w), decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, @@ -435,37 +458,37 @@ class ServicesPage extends StatelessWidget { spacing: 8.w, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Utils.buildSvgWithAssets(icon: AppAssets.services_medical_file_icon, width: 30.w, height: 30.h), - LocaleKeys.medicalFile.tr().toText14(weight: FontWeight.w600, maxlines: 2).expanded, - Utils.buildSvgWithAssets(icon: AppAssets.arrow_forward), + Utils.buildSvgWithAssets(icon: AppAssets.services_medical_file_icon, width: 40.w, height: 40.h), + LocaleKeys.medicalFile.tr().toText16(weight: FontWeight.w600, maxlines: 2).expanded, + Utils.buildSvgWithAssets(icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward), ], ), Spacer(), getIt.get().isAuthenticated ? Wrap( - spacing: -8.h, + spacing: -12.h, // runSpacing: 0.h, children: [ Utils.buildImgWithAssets( icon: AppAssets.babyGirlImg, - height: 28.h, - width: 28.w, + height: 32.h, + width: 32.w, border: 1, fit: BoxFit.contain, borderRadius: 50.r, ), Utils.buildImgWithAssets( icon: AppAssets.femaleImg, - height: 28.h, - width: 28.w, + height: 32.h, + width: 32.w, border: 1, borderRadius: 50.r, fit: BoxFit.contain, ), Utils.buildImgWithAssets( icon: AppAssets.maleImg, - height: 28.h, - width: 28.w, + height: 32.h, + width: 32.w, border: 1, borderRadius: 50.r, fit: BoxFit.contain, @@ -478,7 +501,7 @@ class ServicesPage extends StatelessWidget { ? CustomButton( height: 40.h, icon: AppAssets.add_icon, - iconSize: 16.w, + iconSize: 24.h, iconColor: AppColors.primaryRedColor, textColor: AppColors.primaryRedColor, text: LocaleKeys.addMember.tr(), @@ -487,7 +510,7 @@ class ServicesPage extends StatelessWidget { borderColor: Colors.transparent, backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.08), padding: EdgeInsets.all(8.w), - fontSize: 12.f, + fontSize: 14.f, onPressed: () { DialogService dialogService = getIt.get(); medicalFileViewModel.clearAuthValues(); @@ -517,14 +540,14 @@ class ServicesPage extends StatelessWidget { ], ).paddingSymmetrical(24.w, 0), SizedBox(height: 24.h), - LocaleKeys.healthTools.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), + LocaleKeys.healthTools.tr().toText18(weight: FontWeight.w600).paddingSymmetrical(24.w, 0), SizedBox(height: 16.h), GridView.builder( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: (isFoldable || isTablet) ? 6 : 4, // 4 icons per row - crossAxisSpacing: 12.w, + crossAxisSpacing: 21.w, mainAxisSpacing: 18.h, - childAspectRatio: 0.8, + childAspectRatio: 80.w / 94.h, ), physics: NeverScrollableScrollPhysics(), shrinkWrap: true, @@ -540,7 +563,7 @@ class ServicesPage extends StatelessWidget { }, ).paddingSymmetrical(24.w, 0), SizedBox(height: 24.h), - LocaleKeys.supportServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), + LocaleKeys.supportServices.tr().toText18(weight: FontWeight.w600).paddingSymmetrical(24.w, 0), SizedBox(height: 16.h), Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -565,7 +588,7 @@ class ServicesPage extends StatelessWidget { fit: BoxFit.contain, ), SizedBox(width: 8.w), - LocaleKeys.virtualTour.tr().toText12(fontWeight: FontWeight.w500) + LocaleKeys.virtualTour.tr().toText14(weight: FontWeight.w500) ], ), ), @@ -594,7 +617,7 @@ class ServicesPage extends StatelessWidget { fit: BoxFit.contain, ), SizedBox(width: 8.w), - LocaleKeys.carParking.tr().toText12(fontWeight: FontWeight.w500) + LocaleKeys.carParking.tr().toText14(weight: FontWeight.w500) ], ).onPress(() { Navigator.push( @@ -633,7 +656,7 @@ class ServicesPage extends StatelessWidget { fit: BoxFit.contain, ), SizedBox(width: 8.w), - LocaleKeys.latestNews.tr().toText12(fontWeight: FontWeight.w500) + LocaleKeys.latestNews.tr().toText14(weight: FontWeight.w500) ], ), ), @@ -662,7 +685,7 @@ class ServicesPage extends StatelessWidget { fit: BoxFit.contain, ), SizedBox(width: 8.w), - LocaleKeys.hmgContact.tr().toText12(fontWeight: FontWeight.w500) + Expanded(child: LocaleKeys.hmgContact.tr().toText14(weight: FontWeight.w500)) ], ), ), diff --git a/lib/presentation/hmg_services/services_view.dart b/lib/presentation/hmg_services/services_view.dart index 59efb73f..3dd5c462 100644 --- a/lib/presentation/hmg_services/services_view.dart +++ b/lib/presentation/hmg_services/services_view.dart @@ -24,18 +24,19 @@ class ServiceGridViewItem extends StatelessWidget { @override Widget build(BuildContext context) { return InkWell( - onTap: () => hmgServiceComponentModel.isExternalLink - ? _openLink(hmgServiceComponentModel.route!) - : getIt - .get() - .pushPageRoute(hmgServiceComponentModel.route!), + onTap: () => hmgServiceComponentModel.route != null ? getIt.get().pushPageRoute(hmgServiceComponentModel.route!) : hmgServiceComponentModel.onTap!(), + // onTap: () => hmgServiceComponentModel.isExternalLink + // ? _openLink(hmgServiceComponentModel.route!) + // : getIt + // .get() + // .pushPageRoute(hmgServiceComponentModel.route!), child: Column( mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( height: 48.h, - width: 48.w, + width: 48.h, decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: hmgServiceComponentModel.bgColor, borderRadius: 12.r, @@ -50,7 +51,7 @@ class ServiceGridViewItem extends StatelessWidget { ), ), ), - SizedBox(height: 5.h), + SizedBox(height: 4.h), hmgServiceComponentModel.title.toText12( fontWeight: FontWeight.w500, color: AppColors.textColor, diff --git a/lib/presentation/home/data/landing_page_data.dart b/lib/presentation/home/data/landing_page_data.dart index ef2832b1..c52d5e19 100644 --- a/lib/presentation/home/data/landing_page_data.dart +++ b/lib/presentation/home/data/landing_page_data.dart @@ -1,4 +1,6 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/home/data/service_card_data.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -7,8 +9,8 @@ class LandingPageData { ServiceCardData( serviceName: "emergency", icon: AppAssets.emergency_services_icon, - title: "Emergency", - subtitle: "Services", + title: LocaleKeys.emergency, + subtitle: LocaleKeys.services2, backgroundColor: AppColors.primaryRedColor, iconColor: AppColors.whiteColor, textColor: AppColors.whiteColor, @@ -17,8 +19,8 @@ class LandingPageData { ServiceCardData( serviceName: "indoor_navigation", icon: AppAssets.indoor_nav_icon, - title: "Indoor", - subtitle: "Navigation", + title: LocaleKeys.indoor, + subtitle: LocaleKeys.navigation, backgroundColor: AppColors.whiteColor, iconColor: AppColors.blackColor, textColor: AppColors.blackColor, @@ -27,8 +29,8 @@ class LandingPageData { ServiceCardData( serviceName: "search_doctor", icon: AppAssets.search_doctor_icon, - title: "Search", - subtitle: "Doctor", + title: LocaleKeys.search, + subtitle: LocaleKeys.doctor, backgroundColor: AppColors.whiteColor, iconColor: AppColors.blackColor, textColor: AppColors.blackColor, @@ -37,8 +39,8 @@ class LandingPageData { ServiceCardData( serviceName: "health_calculators_and_converts", icon: AppAssets.health_calculators_icon, - title: "Health", - subtitle: "Calculators", + title: LocaleKeys.health, + subtitle: LocaleKeys.calculators, backgroundColor: AppColors.whiteColor, iconColor: AppColors.blackColor, textColor: AppColors.blackColor, @@ -47,8 +49,8 @@ class LandingPageData { ServiceCardData( serviceName: "health_converters", icon: AppAssets.health_converters_icon, - title: "Health", - subtitle: "Converters", + title: LocaleKeys.health, + subtitle: LocaleKeys.converters, backgroundColor: AppColors.whiteColor, iconColor: AppColors.blackColor, textColor: AppColors.blackColor, @@ -57,8 +59,8 @@ class LandingPageData { ServiceCardData( serviceName: "parking_guide", icon: AppAssets.car_parking_icon, - title: "Parking", - subtitle: "Guide", + title: LocaleKeys.parking, + subtitle: LocaleKeys.guide, backgroundColor: AppColors.whiteColor, iconColor: AppColors.blackColor, textColor: AppColors.blackColor, @@ -70,8 +72,8 @@ class LandingPageData { ServiceCardData( serviceName: "emergency", icon: AppAssets.emergency_services_icon, - title: "Emergency", - subtitle: "Services", + title: LocaleKeys.emergency, + subtitle: LocaleKeys.services2, backgroundColor: AppColors.primaryRedColor, iconColor: AppColors.whiteColor, textColor: AppColors.whiteColor, @@ -80,8 +82,8 @@ class LandingPageData { ServiceCardData( serviceName: "lab_results", icon: AppAssets.home_lab_result_icon, - title: "Lab", - subtitle: "Results", + title: LocaleKeys.lab, + subtitle: LocaleKeys.labSubtitle, backgroundColor: AppColors.whiteColor, iconColor: AppColors.blackColor, textColor: AppColors.blackColor, @@ -90,8 +92,8 @@ class LandingPageData { ServiceCardData( serviceName: "radiology_results", icon: AppAssets.my_radiology_icon, - title: "Radiology", - subtitle: "Results", + title: LocaleKeys.radiology, + subtitle: LocaleKeys.radiologySubtitle, backgroundColor: AppColors.whiteColor, iconColor: AppColors.blackColor, textColor: AppColors.blackColor, @@ -100,8 +102,8 @@ class LandingPageData { ServiceCardData( serviceName: "prescriptions", icon: AppAssets.my_prescription_icon, - title: "Prescriptions", - subtitle: "Details", + title: LocaleKeys.medicinesSubtitle, + subtitle: LocaleKeys.details, backgroundColor: AppColors.whiteColor, iconColor: AppColors.blackColor, textColor: AppColors.blackColor, @@ -110,8 +112,8 @@ class LandingPageData { ServiceCardData( serviceName: "insurance_update", icon: AppAssets.insurance_update_icon, - title: "Insurance", - subtitle: "Details", + title: LocaleKeys.insurance, + subtitle: LocaleKeys.details, backgroundColor: AppColors.whiteColor, iconColor: AppColors.blackColor, textColor: AppColors.blackColor, @@ -130,8 +132,8 @@ class LandingPageData { ServiceCardData( serviceName: "sick_leaves", icon: AppAssets.my_sick_leave_icon, - title: "Sick", - subtitle: "Leaves", + title: LocaleKeys.sick, + subtitle: LocaleKeys.sickSubtitle, backgroundColor: AppColors.whiteColor, iconColor: AppColors.blackColor, textColor: AppColors.blackColor, @@ -143,8 +145,8 @@ class LandingPageData { ServiceCardData( serviceName: "livecare", icon: AppAssets.small_livecare_icon, - title: "LiveCare", - subtitle: "Explore our app, View our services and offers", + title: LocaleKeys.liveCare, + subtitle: LocaleKeys.liveCareServiceDesc, largeCardIcon: AppAssets.liveCareService, backgroundColor: AppColors.successColor, iconColor: AppColors.whiteColor, @@ -161,8 +163,8 @@ class LandingPageData { ServiceCardData( serviceName: "home_health_care", icon: AppAssets.homeBottom, - title: "Home Health Care", - subtitle: "Explore our app, View our services and offers", + title: LocaleKeys.homeHealthCare, + subtitle: LocaleKeys.liveCareServiceDesc, largeCardIcon: AppAssets.homeHealthCareService, backgroundColor: AppColors.primaryRedColor, iconColor: AppColors.whiteColor, @@ -171,8 +173,8 @@ class LandingPageData { ServiceCardData( serviceName: "pharmacy", icon: AppAssets.pharmacy_icon, //359846 - title: "Pharmacy", - subtitle: "Explore our app, View our services and offers", + title: LocaleKeys.hmgPharmacy, + subtitle: LocaleKeys.liveCareServiceDesc, largeCardIcon: AppAssets.pharmacyService, backgroundColor: AppColors.pharmacyBGColor, iconColor: null, diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index f336c5bf..a8a64fa6 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -28,7 +28,6 @@ import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/notifications/notifications_view_model.dart'; -import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/appointment_queue_page.dart'; @@ -53,6 +52,7 @@ import 'package:hmg_patient_app_new/presentation/todo_section/ancillary_procedur import 'package:hmg_patient_app_new/presentation/todo_section/widgets/ancillary_orders_list.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; +import 'package:hmg_patient_app_new/services/zoom_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; @@ -62,8 +62,6 @@ import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/routes/spring_page_route_builder.dart'; import 'package:provider/provider.dart'; -import '../active_medication/active_medication_page.dart'; - import '../emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart'; class LandingPage extends StatefulWidget { @@ -91,6 +89,14 @@ class _LandingPageState extends State { final SwiperController _controller = SwiperController(); + final ScrollController _horizontalScrollController = ScrollController(); + + @override + void dispose() { + _horizontalScrollController.dispose(); + super.dispose(); + } + @override void initState() { authVM = context.read(); @@ -190,29 +196,30 @@ class _LandingPageState extends State { fontSize: 14.f, fontWeight: FontWeight.w500, borderRadius: 12.r, - padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), + padding: EdgeInsets.fromLTRB(12.h, 0, 12.h, 0), height: 40.h, ), Consumer(builder: (context, todoSectionVM, child) { return Row( mainAxisSize: MainAxisSize.min, - spacing: 12.h, + spacing: 18.h, children: [ Stack(children: [ - Utils.buildSvgWithAssets(icon: AppAssets.bell, height: 18.h, width: 18.h).onPress(() async { - if (appState.isAuthenticated) { - notificationsViewModel.setNotificationStatusID(2); - notificationsViewModel.getAllNotifications(); - Navigator.of(context).push( - CustomPageRoute( - page: NotificationsListPage(), - // page: LoginScreen(), - ), - ); - } else { - await authVM.onLoginPressed(); - } - }), + if (appState.isAuthenticated) + Utils.buildSvgWithAssets(icon: AppAssets.bell, height: 24.h, width: 24.h).onPress(() async { + if (appState.isAuthenticated) { + notificationsViewModel.setNotificationStatusID(2); + notificationsViewModel.getAllNotifications(); + Navigator.of(context).push( + CustomPageRoute( + page: NotificationsListPage(), + // page: LoginScreen(), + ), + ); + } else { + await authVM.onLoginPressed(); + } + }), (appState.isAuthenticated && (int.parse(todoSectionVM.notificationsCount ?? "0") > 0)) ? Positioned( right: 0, @@ -237,10 +244,10 @@ class _LandingPageState extends State { ) : SizedBox.shrink(), ]), - Utils.buildSvgWithAssets(icon: AppAssets.indoor_nav_icon, height: 18.h, width: 18.h).onPress(() { + Utils.buildSvgWithAssets(icon: AppAssets.indoor_nav_icon, height: 24.h, width: 24.w).onPress(() { openIndoorNavigationBottomSheet(context); }), - Utils.buildSvgWithAssets(icon: AppAssets.contact_icon, height: 18.h, width: 18.h).onPress(() { + Utils.buildSvgWithAssets(icon: AppAssets.contact_icon, height: 24.h, width: 24.h).onPress(() { showCommonBottomSheetWithoutHeight( context, title: LocaleKeys.contactUs.tr(), @@ -249,6 +256,11 @@ class _LandingPageState extends State { isFullScreen: false, ); }), + !appState.isAuthenticated + ? Utils.buildSvgWithAssets(icon: AppAssets.changeLanguageHomePageIcon, height: 24.h, width: 24.h).onPress(() { + context.setLocale(appState.isArabic() ? Locale('en', 'US') : Locale('ar', 'SA')); + }) + : SizedBox.shrink() ], ); }), @@ -275,24 +287,25 @@ class _LandingPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "How are you feeling today?".needTranslation.toText14(isBold: true), - "Check your symptoms with this scale".needTranslation.toText12(fontWeight: FontWeight.w500), + LocaleKeys.howAreYouFeelingToday.tr(context: context).toText14(weight: FontWeight.w600), + LocaleKeys.checkYourSymptomsWithScale.tr(context: context).toText12(fontWeight: FontWeight.w500), SizedBox(height: 14.h), CustomButton( - text: "Check your symptoms".needTranslation, + text: LocaleKeys.checkYourSymptoms.tr(context: context), onPressed: () async { context.navigateWithName(AppRoutes.userInfoSelection); }, + padding: EdgeInsetsGeometry.zero, backgroundColor: Color(0xFF2B353E), borderColor: Color(0xFF2B353E), textColor: AppColors.whiteColor, - fontSize: 14, + fontSize: 14.f, fontWeight: FontWeight.w600, - borderRadius: 12, + borderRadius: 12.r, height: 40.h, ), ], - ) + ).expanded ], ), ), @@ -305,12 +318,12 @@ class _LandingPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Appointments & Visits".toText16(isBold: true), + LocaleKeys.appointmentsAndVisits.tr(context: context).toText16(weight: FontWeight.w600), Row( children: [ - LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor), + LocaleKeys.viewAll.tr(context: context).toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500), SizedBox(width: 2.h), - Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h), ], ), ], @@ -394,19 +407,19 @@ class _LandingPageState extends State { layout: SwiperLayout.STACK, loop: true, itemWidth: MediaQuery.of(context).size.width - 48.h, - indicatorLayout: PageIndicatorLayout.COLOR, - axisDirection: AxisDirection.right, - controller: _controller, - itemHeight: 255.h, - pagination: SwiperPagination( - alignment: Alignment.bottomCenter, - margin: EdgeInsets.only(top: 240.h + 8 + 24), - builder: DotSwiperPaginationBuilder(color: Color(0xffD9D9D9), activeColor: AppColors.blackBgColor), - ), - itemBuilder: (BuildContext context, int index) { - return getIndexSwiperCard(index); - }, - ) + indicatorLayout: PageIndicatorLayout.COLOR, + axisDirection: AxisDirection.right, + controller: _controller, + itemHeight: 255.h, + pagination: SwiperPagination( + alignment: Alignment.bottomCenter, + margin: EdgeInsets.only(top: 240.h + 8 + 24), + builder: DotSwiperPaginationBuilder(color: Color(0xffD9D9D9), activeColor: AppColors.blackBgColor), + ), + itemBuilder: (BuildContext context, int index) { + return getIndexSwiperCard(index); + }, + ) : Container( width: double.infinity, decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), @@ -416,7 +429,7 @@ class _LandingPageState extends State { children: [ Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h), SizedBox(height: 12.h), - "You do not have any upcoming appointment. Please book an appointment".needTranslation.toText12(isCenter: true), + LocaleKeys.noUpcomingAppointmentPleaseBook.tr(context: context).toText12(isCenter: true), SizedBox(height: 12.h), CustomButton( text: LocaleKeys.bookAppo.tr(context: context), @@ -465,7 +478,7 @@ class _LandingPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ AppCustomChipWidget( - labelText: "ER Online Check-In Request", + labelText: LocaleKeys.erOnlineCheckInRequest.tr(context: context), backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.10), textColor: AppColors.primaryRedColor, ), @@ -476,7 +489,7 @@ class _LandingPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "You have ER Online Check-In Request".needTranslation.toText12(isBold: true), + LocaleKeys.youHaveEROnlineCheckInRequest.tr(context: context).toText12(isBold: true), Utils.buildSvgWithAssets( icon: AppAssets.forward_arrow_icon_small, iconColor: AppColors.blackColor, @@ -499,16 +512,16 @@ class _LandingPageState extends State { : SizedBox(height: 12.h); }, ), - + SizedBox(height: 24.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Quick Links".needTranslation.toText16(isBold: true), + LocaleKeys.quickLinks.tr(context: context).toText16(weight: FontWeight.w600), Row( children: [ - "View medical file".needTranslation.toText12(color: AppColors.primaryRedColor), + LocaleKeys.viewMedicalFile.tr(context: context).toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), SizedBox(width: 2.h), - Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h), ], ), ], @@ -517,92 +530,121 @@ class _LandingPageState extends State { }), SizedBox(height: 16.h), Container( - height: 121.h, + // height: 121.h, decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), child: Column( children: [ - Expanded( - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: LandingPageData.getLoggedInServiceCardsList.length, - shrinkWrap: true, - padding: EdgeInsets.only(left: 16.h, right: 16.h), - itemBuilder: (context, index) { - return AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 1000), - child: SlideAnimation( - horizontalOffset: 100.0, - child: FadeInAnimation( - child: SmallServiceCard( - icon: LandingPageData.getLoggedInServiceCardsList[index].icon, - title: LandingPageData.getLoggedInServiceCardsList[index].title, - subtitle: LandingPageData.getLoggedInServiceCardsList[index].subtitle, - iconColor: LandingPageData.getLoggedInServiceCardsList[index].iconColor!, - textColor: LandingPageData.getLoggedInServiceCardsList[index].textColor, - backgroundColor: LandingPageData.getLoggedInServiceCardsList[index].backgroundColor, - isBold: LandingPageData.getLoggedInServiceCardsList[index].isBold, - serviceName: LandingPageData.getLoggedInServiceCardsList[index].serviceName, + SizedBox( + height: 92.h + 32.h - 4.h, + child: RawScrollbar( + controller: _horizontalScrollController, + thumbVisibility: true, + radius: Radius.circular(10.0), + thumbColor: AppColors.primaryRedColor, + trackVisibility: true, + trackColor: Color(0xffD9D9D9), + trackBorderColor: Colors.transparent, + trackRadius: Radius.circular(10.0), + padding: EdgeInsets.only(top: 92.h + 32.h, left: MediaQuery.sizeOf(context).width / 2 - 35, right: MediaQuery.sizeOf(context).width / 2 - 35), + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: LandingPageData.getLoggedInServiceCardsList.length, + shrinkWrap: true, + controller: _horizontalScrollController, + padding: EdgeInsets.only(left: 16.h, right: 16.h, top: 16.h, bottom: 12.h), + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 1000), + child: SlideAnimation( + horizontalOffset: 100.0, + child: FadeInAnimation( + child: SmallServiceCard( + icon: LandingPageData.getLoggedInServiceCardsList[index].icon, + title: LandingPageData.getLoggedInServiceCardsList[index].title, + subtitle: LandingPageData.getLoggedInServiceCardsList[index].subtitle, + iconColor: LandingPageData.getLoggedInServiceCardsList[index].iconColor!, + textColor: LandingPageData.getLoggedInServiceCardsList[index].textColor, + backgroundColor: LandingPageData.getLoggedInServiceCardsList[index].backgroundColor, + isBold: LandingPageData.getLoggedInServiceCardsList[index].isBold, + serviceName: LandingPageData.getLoggedInServiceCardsList[index].serviceName, + ), ), ), - ), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => 10.width, + ); + }, + separatorBuilder: (BuildContext cxt, int index) => 10.width, + ), ), ), + SizedBox(height: 16.h), ], ), ).paddingSymmetrical(24.h, 0.h), ], ) : Container( - height: 127.h, + // height: 141.h, decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), child: Column( children: [ - Expanded( - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: LandingPageData.getNotLoggedInServiceCardsList.length, - shrinkWrap: true, - padding: EdgeInsets.only(left: 16.h, right: 16.h), - itemBuilder: (context, index) { - return AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 1000), - child: SlideAnimation( - horizontalOffset: 100.0, - child: FadeInAnimation( - child: SmallServiceCard( - serviceName: LandingPageData.getNotLoggedInServiceCardsList[index].serviceName, - icon: LandingPageData.getNotLoggedInServiceCardsList[index].icon, - title: LandingPageData.getNotLoggedInServiceCardsList[index].title, - subtitle: LandingPageData.getNotLoggedInServiceCardsList[index].subtitle, - iconColor: LandingPageData.getNotLoggedInServiceCardsList[index].iconColor!, - textColor: LandingPageData.getNotLoggedInServiceCardsList[index].textColor, - backgroundColor: LandingPageData.getNotLoggedInServiceCardsList[index].backgroundColor, - isBold: LandingPageData.getNotLoggedInServiceCardsList[index].isBold, + SizedBox( + height: 92.h + 32.h - 4.h, + child: RawScrollbar( + controller: _horizontalScrollController, + thumbVisibility: true, + radius: Radius.circular(10.0), + thumbColor: AppColors.primaryRedColor, + trackVisibility: true, + trackColor: Color(0xffD9D9D9), + trackBorderColor: Colors.transparent, + trackRadius: Radius.circular(10.0), + padding: EdgeInsets.only(top: 92.h + 32.h, left: MediaQuery.sizeOf(context).width / 2 - 35, right: MediaQuery.sizeOf(context).width / 2 - 35), + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: LandingPageData.getNotLoggedInServiceCardsList.length, + shrinkWrap: true, + controller: _horizontalScrollController, + padding: EdgeInsets.only(left: 16.h, right: 16.h, top: 16.h, bottom: 12.h), + // padding: EdgeInsets.zero, + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 1000), + child: SlideAnimation( + horizontalOffset: 100.0, + child: FadeInAnimation( + child: SmallServiceCard( + serviceName: LandingPageData.getNotLoggedInServiceCardsList[index].serviceName, + icon: LandingPageData.getNotLoggedInServiceCardsList[index].icon, + title: LandingPageData.getNotLoggedInServiceCardsList[index].title, + subtitle: LandingPageData.getNotLoggedInServiceCardsList[index].subtitle, + iconColor: LandingPageData.getNotLoggedInServiceCardsList[index].iconColor!, + textColor: LandingPageData.getNotLoggedInServiceCardsList[index].textColor, + backgroundColor: LandingPageData.getNotLoggedInServiceCardsList[index].backgroundColor, + isBold: LandingPageData.getNotLoggedInServiceCardsList[index].isBold, + ), ), ), - ), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => 0.width, + ); + }, + separatorBuilder: (BuildContext cxt, int index) => 0.width, + ), ), ), + SizedBox(height: 16.h), ], ), ).paddingSymmetrical(24.h, 0.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Services".toText16(isBold: true), + LocaleKeys.services2.tr(context: context).toText18(weight: FontWeight.w600), Row( children: [ - "View all services".toText12(color: AppColors.primaryRedColor), + LocaleKeys.viewAllServices.tr(context: context).toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500), SizedBox(width: 2.h), - Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h), ], ).onPress(() { Navigator.of(context).push(CustomPageRoute(page: ServicesPage())); @@ -610,7 +652,7 @@ class _LandingPageState extends State { ], ).paddingSymmetrical(24.w, 0.h), SizedBox( - height: 340.h, + height: 431.h, child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: LandingPageData.getServiceCardsList.length, @@ -623,7 +665,7 @@ class _LandingPageState extends State { child: SlideAnimation( horizontalOffset: 100.0, child: FadeInAnimation( - child: LargeServiceCard( + child: FadedLargeServiceCard( serviceCardData: LandingPageData.getServiceCardsList[index], image: LandingPageData.getServiceCardsList[index].icon, title: LandingPageData.getServiceCardsList[index].title, @@ -645,286 +687,354 @@ class _LandingPageState extends State { ); } + // Main card selector based on index and priority Widget getIndexSwiperCard(int index) { + // Index 0: Priority cards (Queue > LiveCare > Upcoming Appointment > History) if (index == 0) { - if (myAppointmentsViewModel.isPatientHasQueueAppointment) { - return Container( + return _buildFirstCardSlot(); + } + // Index 1: Secondary cards based on first card type + if (index == 1) { + return _buildSecondCardSlot(); + } + // Index 2: Tertiary cards + if (index == 2) { + return _buildThirdCardSlot(); + } + return Container(); + } + + // First slot: Shows highest priority card + Widget _buildFirstCardSlot() { + if (myAppointmentsViewModel.isPatientHasQueueAppointment) { + return _buildQueueCard(); + } + + if (immediateLiveCareViewModel.patientHasPendingLiveCareRequest) { + return _buildLiveCareRequestCard(); + } + + if (myAppointmentsViewModel.patientUpcomingAppointmentsHistoryList.isNotEmpty) { + return _buildAppointmentCardWrapper( + myAppointmentsViewModel.patientUpcomingAppointmentsHistoryList.first, + ); + } + + return _buildAppointmentCardWrapper( + myAppointmentsViewModel.patientAppointmentsHistoryList[0], + ); + } + + // Second slot: Shows appointment or ancillary orders + Widget _buildSecondCardSlot() { + final hasQueueOrLiveCare = myAppointmentsViewModel.isPatientHasQueueAppointment || immediateLiveCareViewModel.patientHasPendingLiveCareRequest; + + if (hasQueueOrLiveCare) { + return _buildAppointmentCardWrapper( + myAppointmentsViewModel.patientAppointmentsHistoryList.first, + ); + } + + if (todoSectionViewModel.patientAncillaryOrdersList.isNotEmpty) { + return _buildAncillaryOrderCardWrapper( + todoSectionViewModel.patientAncillaryOrdersList.first, + ); + } + + return _buildAppointmentCardWrapper( + myAppointmentsViewModel.patientAppointmentsHistoryList[1], + ); + } + + // Third slot: Shows ancillary orders or appointment + Widget _buildThirdCardSlot() { + final hasQueueOrLiveCare = myAppointmentsViewModel.isPatientHasQueueAppointment || immediateLiveCareViewModel.patientHasPendingLiveCareRequest; + final hasAncillaryOrders = todoSectionViewModel.patientAncillaryOrdersList.isNotEmpty; + + if (hasQueueOrLiveCare && hasAncillaryOrders) { + return _buildAncillaryOrderCardWrapper( + todoSectionViewModel.patientAncillaryOrdersList.first, + ); + } + + return _buildAppointmentCardWrapper( + myAppointmentsViewModel.patientAppointmentsHistoryList[2], + ); + } + + // Queue Card Widget + Widget _buildQueueCard() { + final currentQueue = myAppointmentsViewModel.currentPatientQueueDetails; + final currentStatus = myAppointmentsViewModel.currentQueueStatus; + + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + side: BorderSide( + color: Utils.getCardBorderColor(currentStatus), + width: 2.w, + ), + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildQueueHeader(currentStatus), + SizedBox(height: 8.h), + _buildQueueGreeting(), + SizedBox(height: 2.h), + LocaleKeys.thankYouForPatience.tr().toText12( + fontWeight: FontWeight.w500, + color: AppColors.textColorLight, + ), + SizedBox(height: 8.h), + currentQueue.queueNo!.toText28(isBold: true), + SizedBox(height: 6.h), + _buildServingNowSection(), + SizedBox(height: 5.h), + _buildQueueActionButton(currentStatus, currentQueue.roomNo ?? ""), + ], + ), + ), + ).onPress(() { + Navigator.of(context).push( + CustomPageRoute(page: AppointmentQueuePage()), + ); + }); + } + + // Queue Card: Header with status chip and icon + Widget _buildQueueHeader(int currentStatus) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + AppCustomChipWidget( + labelText: currentStatus == 0 ? LocaleKeys.inQueue.tr() : LocaleKeys.yourTurn.tr(), + backgroundColor: Utils.getCardBorderColor(currentStatus).withValues(alpha: 0.20), + textColor: Utils.getCardBorderColor(currentStatus), + ), + Utils.buildSvgWithAssets( + icon: AppAssets.waiting_icon, + width: 24.h, + height: 24.h, + ), + ], + ); + } + + // Queue Card: Greeting text + Widget _buildQueueGreeting() { + return LocaleKeys.halaFirstName.tr(namedArgs: {'firstName': appState.getAuthenticatedUser()!.firstName!}).toText16(isBold: true); + } + + // Queue Card: Currently serving section + Widget _buildServingNowSection() { + if (myAppointmentsViewModel.patientQueueDetailsList.isEmpty) { + return SizedBox(height: 12.h); + } + + final servingQueue = myAppointmentsViewModel.patientQueueDetailsList.first; + final isVitalCall = servingQueue.callType == 1; + + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + "${LocaleKeys.servingNow.tr()}: ".toText14(isBold: true), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + servingQueue.queueNo!.toText12(isBold: true), + SizedBox(width: 8.w), + AppCustomChipWidget( + deleteIcon: isVitalCall ? AppAssets.call_for_vitals : AppAssets.call_for_doctor, + labelText: isVitalCall ? LocaleKeys.callForVitalSigns.tr() : LocaleKeys.callForDoctor.tr(), + iconColor: isVitalCall ? AppColors.primaryRedColor : AppColors.successColor, + textColor: isVitalCall ? AppColors.primaryRedColor : AppColors.successColor, + iconSize: 14.w, + backgroundColor: isVitalCall ? AppColors.primaryRedColor.withValues(alpha: 0.1) : AppColors.successColor.withValues(alpha: 0.1), + labelPadding: EdgeInsetsDirectional.only( + start: 8.h, + end: -2.h, + ), + ), + ], + ), + ], + ); + } + + // Queue Card: Action button + Widget _buildQueueActionButton(int currentStatus, String roomNo) { + return CustomButton( + text: Utils.getCardButtonText(currentStatus, roomNo), + onPressed: () {}, + backgroundColor: Utils.getCardButtonColor(currentStatus), + borderColor: Utils.getCardButtonColor(currentStatus).withValues(alpha: 0.01), + textColor: Utils.getCardButtonTextColor(currentStatus), + fontSize: 12.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: 40.h, + iconColor: AppColors.whiteColor, + iconSize: 18.h, + ); + } + + // LiveCare Request Card Widget + Widget _buildLiveCareRequestCard() { + final liveCareData = immediateLiveCareViewModel.patientLiveCareHistoryList[0]; + + return Column( + children: [ + SizedBox(height: 12.h), + Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, - borderRadius: 20.h, - hasShadow: false, - side: BorderSide(color: Utils.getCardBorderColor(myAppointmentsViewModel.currentQueueStatus), width: 2.w), + borderRadius: 20.r, + hasShadow: true, + side: BorderSide(color: AppColors.ratingColorYellow, width: 3.h), ), child: Padding( padding: EdgeInsets.all(16.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - AppCustomChipWidget( - labelText: myAppointmentsViewModel.currentQueueStatus == 0 ? "In Queue".needTranslation : "Your Turn".needTranslation, - backgroundColor: Utils.getCardBorderColor(myAppointmentsViewModel.currentQueueStatus).withValues(alpha: 0.20), - textColor: Utils.getCardBorderColor(myAppointmentsViewModel.currentQueueStatus), - ), - Utils.buildSvgWithAssets(icon: AppAssets.waiting_icon, width: 24.h, height: 24.h), - ], - ), + LocaleKeys.immediateLiveCareRequest.tr(context: context).toText16(isBold: true), + SizedBox(height: 10.h), + _buildLiveCareHeader(liveCareData), + SizedBox(height: 10.h), + _buildLiveCareGreeting(), SizedBox(height: 8.h), - "Hala ${appState.getAuthenticatedUser()!.firstName}!!!".needTranslation.toText16(isBold: true), - SizedBox(height: 2.h), - "Thank you for your patience, here is your queue number.".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), + _buildLiveCareQueueInfo(liveCareData.patCount), SizedBox(height: 8.h), - myAppointmentsViewModel.currentPatientQueueDetails.queueNo!.toText28(isBold: true), - SizedBox(height: 6.h), - myAppointmentsViewModel.patientQueueDetailsList.isNotEmpty - ? Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - "Serving Now: ".needTranslation.toText14(isBold: true), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - myAppointmentsViewModel.patientQueueDetailsList.first.queueNo!.toText12(isBold: true), - SizedBox(width: 8.w), - AppCustomChipWidget( - deleteIcon: myAppointmentsViewModel.patientQueueDetailsList.first.callType == 1 ? AppAssets.call_for_vitals : AppAssets.call_for_doctor, - labelText: myAppointmentsViewModel.patientQueueDetailsList.first.callType == 1 ? "Call for vital signs".needTranslation : "Call for Doctor".needTranslation, - iconColor: myAppointmentsViewModel.patientQueueDetailsList.first.callType == 1 ? AppColors.primaryRedColor : AppColors.successColor, - textColor: myAppointmentsViewModel.patientQueueDetailsList.first.callType == 1 ? AppColors.primaryRedColor : AppColors.successColor, - iconSize: 14.w, - backgroundColor: myAppointmentsViewModel.patientQueueDetailsList.first.callType == 1 - ? AppColors.primaryRedColor.withValues(alpha: 0.1) - : AppColors.successColor.withValues(alpha: 0.1), - labelPadding: EdgeInsetsDirectional.only(start: 8.h, end: -2.h), - ), - ], - ), - ], - ) - : SizedBox(height: 12.h), - SizedBox(height: 5.h), - CustomButton( - text: Utils.getCardButtonText(myAppointmentsViewModel.currentQueueStatus, myAppointmentsViewModel.currentPatientQueueDetails.roomNo ?? ""), - onPressed: () {}, - backgroundColor: Utils.getCardButtonColor(myAppointmentsViewModel.currentQueueStatus), - borderColor: Utils.getCardButtonColor(myAppointmentsViewModel.currentQueueStatus).withValues(alpha: 0.01), - textColor: Utils.getCardButtonTextColor(myAppointmentsViewModel.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, - ), + _buildLiveCareWaitingTime(), ], ), ), - ).onPress(() { + ).paddingSymmetrical(0.h, 0.h).onPress(() { Navigator.of(context).push( - CustomPageRoute( - page: AppointmentQueuePage(), - ), + CustomPageRoute(page: ImmediateLiveCarePendingRequestPage()), ); - }); - } else if (immediateLiveCareViewModel.patientHasPendingLiveCareRequest) { - return Column( + }), + SizedBox(height: 12.h), + ], + ); + } + + // LiveCare Card: Header with status and date + Widget _buildLiveCareHeader(liveCareData) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( children: [ - SizedBox(height: 12.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.r, - hasShadow: true, - side: BorderSide(color: AppColors.ratingColorYellow, width: 3.h), - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "Immediate LiveCare Request".needTranslation.toText16(isBold: true), - SizedBox(height: 10.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - AppCustomChipWidget( - labelText: immediateLiveCareViewModel.patientLiveCareHistoryList[0].stringCallStatus, - backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.20), - textColor: AppColors.alertColor, - ), - SizedBox(width: 8.w), - AppCustomChipWidget( - icon: AppAssets.appointment_calendar_icon, - labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(immediateLiveCareViewModel.patientLiveCareHistoryList[0].arrivalTime), false)), - ], - ), - Utils.buildSvgWithAssets(icon: AppAssets.waiting_icon, width: 24.h, height: 24.h), - // Lottie.asset(AppAnimations.pending_loading_animation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 80.h, height: 80.h, fit: BoxFit.cover), - ], - ), - SizedBox(height: 10.h), - "Hala ${appState.getAuthenticatedUser()!.firstName}!!!".needTranslation.toText16(isBold: true), - SizedBox(height: 8.h), - "Your turn is after ${immediateLiveCareViewModel.patientLiveCareHistoryList[0].patCount} patients.".needTranslation.toText14(isBold: true), - SizedBox(height: 8.h), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "Expected waiting time: ".needTranslation.toText12(isBold: true), - SizedBox(height: 7.h), - ValueListenableBuilder( - valueListenable: immediateLiveCareViewModel.durationNotifier, - builder: (context, duration, child) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - buildTime(duration), - ], - ); - }, - ), - ], - ), - ], - ), + AppCustomChipWidget( + labelText: liveCareData.stringCallStatus, + backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.20), + textColor: AppColors.alertColor, + ), + SizedBox(width: 8.w), + AppCustomChipWidget( + icon: AppAssets.appointment_calendar_icon, + labelText: DateUtil.formatDateToDate( + DateUtil.convertStringToDate(liveCareData.arrivalTime), + false, ), - ).paddingSymmetrical(0.h, 0.h).onPress(() { - Navigator.of(context).push(CustomPageRoute(page: ImmediateLiveCarePendingRequestPage())); - }), - SizedBox(height: 12.h), + ), ], - ); - } else if (myAppointmentsViewModel.patientUpcomingAppointmentsHistoryList.isNotEmpty) { - return Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - hasShadow: true, - ), - child: AppointmentCard( - patientAppointmentHistoryResponseModel: myAppointmentsViewModel.patientUpcomingAppointmentsHistoryList.first, - myAppointmentsViewModel: myAppointmentsViewModel, - bookAppointmentsViewModel: bookAppointmentsViewModel, - isLoading: false, - isFromHomePage: true, - ), - ); - } else { - return Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - hasShadow: true, - ), - child: AppointmentCard( - patientAppointmentHistoryResponseModel: myAppointmentsViewModel.patientAppointmentsHistoryList[index], - myAppointmentsViewModel: myAppointmentsViewModel, - bookAppointmentsViewModel: bookAppointmentsViewModel, - isLoading: false, - isFromHomePage: true, - ), - ); - } - } else if (index == 1) { - if (myAppointmentsViewModel.isPatientHasQueueAppointment || immediateLiveCareViewModel.patientHasPendingLiveCareRequest) { - return Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - hasShadow: true, - ), - child: AppointmentCard( - patientAppointmentHistoryResponseModel: myAppointmentsViewModel.patientUpcomingAppointmentsHistoryList.first, - myAppointmentsViewModel: myAppointmentsViewModel, - bookAppointmentsViewModel: bookAppointmentsViewModel, - isLoading: false, - isFromHomePage: true, - ), - ); - } else if (todoSectionViewModel.patientAncillaryOrdersList.isNotEmpty) { - return AncillaryOrderCard( - order: todoSectionViewModel.patientAncillaryOrdersList.first, - isLoading: false, - isOrdersList: false, - onCheckIn: () { - log("Check-in for order: ${todoSectionViewModel.patientAncillaryOrdersList.first.orderNo}"); - }, - onViewDetails: () { - Navigator.of(context).push( - CustomPageRoute( - page: AncillaryOrderDetailsList( - appointmentNoVida: todoSectionViewModel.patientAncillaryOrdersList.first.appointmentNo ?? 0, - orderNo: todoSectionViewModel.patientAncillaryOrdersList.first.orderNo ?? 0, - projectID: todoSectionViewModel.patientAncillaryOrdersList.first.projectID ?? 0, - projectName: todoSectionViewModel.patientAncillaryOrdersList.first.projectName ?? "", - ), - ), - ); - }, - ); - } else { - return Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - hasShadow: true, - ), - child: AppointmentCard( - patientAppointmentHistoryResponseModel: myAppointmentsViewModel.patientAppointmentsHistoryList[index], - myAppointmentsViewModel: myAppointmentsViewModel, - bookAppointmentsViewModel: bookAppointmentsViewModel, - isLoading: false, - isFromHomePage: true, - ), - ); - } - } else if (index == 2) { - if ((myAppointmentsViewModel.isPatientHasQueueAppointment || immediateLiveCareViewModel.patientHasPendingLiveCareRequest) && - myAppointmentsViewModel.patientUpcomingAppointmentsHistoryList.isNotEmpty) { - return AncillaryOrderCard( - order: todoSectionViewModel.patientAncillaryOrdersList.first, - isLoading: false, - isOrdersList: false, - onCheckIn: () { - log("Check-in for order: ${todoSectionViewModel.patientAncillaryOrdersList.first.orderNo}"); - }, - onViewDetails: () { - Navigator.of(context).push( - CustomPageRoute( - page: AncillaryOrderDetailsList( - appointmentNoVida: todoSectionViewModel.patientAncillaryOrdersList.first.appointmentNo ?? 0, - orderNo: todoSectionViewModel.patientAncillaryOrdersList.first.orderNo ?? 0, - projectID: todoSectionViewModel.patientAncillaryOrdersList.first.projectID ?? 0, - projectName: todoSectionViewModel.patientAncillaryOrdersList.first.projectName ?? "", - ), - ), + ), + Utils.buildSvgWithAssets( + icon: AppAssets.waiting_icon, + width: 24.h, + height: 24.h, + ), + ], + ); + } + + // LiveCare Card: Greeting + Widget _buildLiveCareGreeting() { + return LocaleKeys.halaFirstName.tr( + namedArgs: {'firstName': appState.getAuthenticatedUser()!.firstName!}, + context: context, + ).toText16(isBold: true); + } + + // LiveCare Card: Queue position info + Widget _buildLiveCareQueueInfo(int? patCount) { + return LocaleKeys.yourTurnIsAfterPatients.tr( + namedArgs: {'count': (patCount ?? 0).toString()}, + context: context, + ).toText14(isBold: true); + } + + // LiveCare Card: Waiting time counter + Widget _buildLiveCareWaitingTime() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "${LocaleKeys.waitingTime.tr()}: ".toText12(isBold: true), + SizedBox(height: 7.h), + ValueListenableBuilder( + valueListenable: immediateLiveCareViewModel.durationNotifier, + builder: (context, duration, child) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + buildTime(duration), + ], ); }, - ); - } else { - return Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - hasShadow: true, - ), - child: AppointmentCard( - patientAppointmentHistoryResponseModel: myAppointmentsViewModel.patientAppointmentsHistoryList[index], - myAppointmentsViewModel: myAppointmentsViewModel, - bookAppointmentsViewModel: bookAppointmentsViewModel, - isLoading: false, - isFromHomePage: true, + ), + ], + ); + } + + // Appointment Card Wrapper (reusable) + Widget _buildAppointmentCardWrapper(appointment) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: appointment, + myAppointmentsViewModel: myAppointmentsViewModel, + bookAppointmentsViewModel: bookAppointmentsViewModel, + isLoading: false, + isFromHomePage: true, + ), + ); + } + + // Ancillary Order Card Wrapper (reusable) + Widget _buildAncillaryOrderCardWrapper(order) { + return AncillaryOrderCard( + order: order, + isLoading: false, + isOrdersList: false, + onCheckIn: () { + log("Check-in for order: ${order.orderNo}"); + }, + onViewDetails: () { + Navigator.of(context).push( + CustomPageRoute( + page: AncillaryOrderDetailsList( + appointmentNoVida: order.appointmentNo ?? 0, + orderNo: order.orderNo ?? 0, + projectID: order.projectID ?? 0, + projectName: order.projectName ?? "", + ), ), ); - } - } - return Container(); + }, + ); } void showQuickLogin(BuildContext context) { @@ -978,7 +1088,6 @@ class _LandingPageState extends State { isCloseButtonVisible: true, child: StatefulBuilder( builder: (context, setState) { - return RateAppointmentDoctor(); }, ), diff --git a/lib/presentation/home/navigation_screen.dart b/lib/presentation/home/navigation_screen.dart index 43cc3b90..5cfc8ff7 100644 --- a/lib/presentation/home/navigation_screen.dart +++ b/lib/presentation/home/navigation_screen.dart @@ -8,11 +8,8 @@ import 'package:hmg_patient_app_new/presentation/contact_us/feedback_page.dart'; import 'package:hmg_patient_app_new/presentation/hmg_services/services_page.dart'; import 'package:hmg_patient_app_new/presentation/home/landing_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; -import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_selection.dart'; -import 'package:hmg_patient_app_new/presentation/todo_section/todo_page.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/widgets/bottom_navigation/bottom_navigation.dart'; -import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; class LandingNavigation extends StatefulWidget { const LandingNavigation({super.key}); @@ -34,12 +31,12 @@ class _LandingNavigationState extends State { physics: const NeverScrollableScrollPhysics(), children: [ const LandingPage(), - appState.isAuthenticated ? MedicalFilePage() : /* need add feedback page */ FeedbackPage(), + appState.isAuthenticated ? MedicalFilePage(showBackIcon: false) : /* need add feedback page */ FeedbackPage(), SizedBox(), // const ToDoPage(), // appState.isAuthenticated ? UserInfoSelectionScreen() : /* need add news page */ SizedBox(), SizedBox(), - ServicesPage(), + ServicesPage(showBackIcon: false), ], ), bottomNavigationBar: BottomNavigation( @@ -53,11 +50,7 @@ class _LandingNavigationState extends State { } if (_currentIndex == 3) { if (appState.isAuthenticated) { - Navigator.of(context).push( - CustomPageRoute( - page: UserInfoSelectionScreen(), - ), - ); + context.navigateWithName(AppRoutes.userInfoSelection); } else { Utils.openWebView( url: 'https://x.com/HMG', diff --git a/lib/presentation/home/widgets/habib_wallet_card.dart b/lib/presentation/home/widgets/habib_wallet_card.dart index b2649f9c..01359837 100644 --- a/lib/presentation/home/widgets/habib_wallet_card.dart +++ b/lib/presentation/home/widgets/habib_wallet_card.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; @@ -5,7 +6,9 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/habib_wallet_page.dart'; +import 'package:hmg_patient_app_new/presentation/habib_wallet/recharge_wallet_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; @@ -21,14 +24,14 @@ class HabibWalletCard extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "My Balance".toText16(isBold: true), - Row( - children: [ - "View all services".toText12(color: AppColors.primaryRedColor), - SizedBox(width: 2.h), - Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), - ], - ), + LocaleKeys.myBalance.tr(context: context).toText16(weight: FontWeight.w600), + // Row( + // children: [ + // LocaleKeys.viewAllServices.tr(context: context).toText12(color: AppColors.primaryRedColor, weight: FontWeight.w500), + // SizedBox(width: 2.h), + // Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h), + // ], + // ), ], ).paddingSymmetrical(24.h, 0.h), SizedBox(height: 16.h), @@ -45,14 +48,14 @@ class HabibWalletCard extends StatelessWidget { child: ClipRRect(borderRadius: BorderRadius.circular(24.0), child: Utils.buildSvgWithAssets(icon: AppAssets.habib_background_icon, width: 150.h, height: 150.h)), ), Padding( - padding: EdgeInsets.all(14.h), + padding: EdgeInsets.all(16.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Habib Wallet".toText15(isBold: true), + LocaleKeys.habibWallet.tr(context: context).toText16(weight: FontWeight.w500, letterSpacing: -0.2), Container( height: 40.h, width: 40.h, @@ -84,7 +87,10 @@ class HabibWalletCard extends StatelessWidget { fit: BoxFit.contain, ), SizedBox(width: 8.h), - habibWalletVM.habibWalletAmount.toString().toText32(isBold: true).toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.h, width: 80.h, height: 40.h), + habibWalletVM.habibWalletAmount + .toString() + .toText32(isBold: true, isEnglishOnly: true) + .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.h, width: 80.h, height: 40.h), ], ); }), @@ -92,7 +98,7 @@ class HabibWalletCard extends StatelessWidget { padding: EdgeInsets.symmetric(horizontal: 50.h), child: Row( children: [ - "View details".toText12(color: AppColors.primaryRedColor), + LocaleKeys.viewDetails.tr(context: context).toText12(color: AppColors.primaryRedColor), SizedBox(width: 2.h), Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), ], @@ -113,18 +119,20 @@ class HabibWalletCard extends StatelessWidget { child: Utils.getPaymentMethods(), ), CustomButton( + height: 40.h, icon: AppAssets.recharge_icon, - iconSize: 18.h, - text: "Recharge".needTranslation, - onPressed: () {}, + iconSize: 24.h, backgroundColor: AppColors.infoColor, - borderColor: AppColors.infoColor, textColor: AppColors.whiteColor, - fontSize: 12, - fontWeight: FontWeight.bold, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 35.h, + text: LocaleKeys.recharge.tr(context: context), + borderWidth: 0.w, + fontWeight: FontWeight.w500, + borderColor: Colors.transparent, + padding: EdgeInsets.fromLTRB(4, 0, 12, 0), + fontSize: 14.f, + onPressed: () { + Navigator.of(context).push(CustomPageRoute(page: RechargeWalletPage())); + }, ), ], ), @@ -132,7 +140,13 @@ class HabibWalletCard extends StatelessWidget { ), ), ]), - ).paddingSymmetrical(24.h, 0.h), + ).paddingSymmetrical(24.h, 0.h).onPress(() { + Navigator.of(context).push( + CustomPageRoute( + page: HabibWalletPage(), + ), + ); + }), ], ); } diff --git a/lib/presentation/home/widgets/large_service_card.dart b/lib/presentation/home/widgets/large_service_card.dart index 5274d5b6..e25ed0b3 100644 --- a/lib/presentation/home/widgets/large_service_card.dart +++ b/lib/presentation/home/widgets/large_service_card.dart @@ -55,6 +55,7 @@ class LargeServiceCard extends StatelessWidget { right: 0.0, child: Container( height: 180.h, + padding: EdgeInsets.only(bottom: 16.h, top: 16.h), decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, customBorder: BorderRadius.only( @@ -69,7 +70,7 @@ class LargeServiceCard extends StatelessWidget { children: [ Container( height: 48.h, - width: 48.w, + width: 48.h, decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: serviceCardData.backgroundColor, borderRadius: 12.r, @@ -89,19 +90,19 @@ class LargeServiceCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - serviceCardData.title.toText14(isBold: true, color: AppColors.textColor), - SizedBox(width: 6.h), - serviceCardData.subtitle.toText14(weight: FontWeight.w500, color: AppColors.textColorLight), + serviceCardData.title.tr(context: context).toText14(isBold: true, color: AppColors.textColor), + serviceCardData.subtitle.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), ], ), ), ], - ).paddingSymmetrical(16.w, 20.h), + ).paddingSymmetrical(16.w, 0.h).expanded, CustomButton( - text: serviceCardData.isBold ? "Visit Pharmacy Online".needTranslation : LocaleKeys.bookNow.tr(context: context), + text: serviceCardData.isBold ? LocaleKeys.visitPharmacyOnline.tr(context: context) : LocaleKeys.bookNow.tr(context: context), onPressed: () { handleOnTap(); }, + padding: EdgeInsets.zero, backgroundColor: serviceCardData.isBold ? AppColors.successLightColor.withValues(alpha: 0.2) : AppColors.bgRedLightColor, borderColor: serviceCardData.isBold ? AppColors.successLightColor.withValues(alpha: 0.01) : AppColors.bgRedLightColor, textColor: serviceCardData.isBold ? AppColors.successColor : AppColors.primaryRedColor, @@ -146,3 +147,131 @@ class LargeServiceCard extends StatelessWidget { } } } + +class FadedLargeServiceCard extends StatelessWidget { + final ServiceCardData serviceCardData; + final String image; + final String icon; + final String title; + final String subtitle; + + const FadedLargeServiceCard({ + super.key, + required this.serviceCardData, + this.image = "", + this.icon = "", + this.title = "", + this.subtitle = "", + }); + + @override + Widget build(BuildContext context) { + return Container( + height: 411.h, + width: 252.w, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.transparent, borderRadius: 24.r), + child: Column( + children: [ + Stack( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(24.r), + child: Image.asset(serviceCardData.largeCardIcon, fit: BoxFit.cover, width: 520.w, height: 250.h), + ), + Positioned( + top: 0, + left: 0, + right: 0, + height: 250.h, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(24.r), + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + stops: [0.536, 1.0], // 53.6% and 100% + colors: [ + Color(0xFFF8F8F8).withValues(alpha: 0), // Transparent + Color(0xFFF8F8F8), // Solid #F8F8F8 + ], + ), + ), + ), + ), + ], + ), + SizedBox(height: 16.h), + Spacer(), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + height: 32.h, + width: 32.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: serviceCardData.backgroundColor, + borderRadius: 30.r, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(8.h), + child: Utils.buildSvgWithAssets( + icon: serviceCardData.icon, + iconColor: serviceCardData.iconColor, + fit: BoxFit.contain, + ), + ), + ), + SizedBox(width: 12.w), + serviceCardData.title.tr(context: context).toText18(weight: FontWeight.w500, color: AppColors.textColor).expanded, + ], + ), + SizedBox(height: 10.h), + serviceCardData.subtitle.tr(context: context).toText14(weight: FontWeight.w500, color: AppColors.blackBgColor, letterSpacing: 0), + SizedBox(height: 12.h), + CustomButton( + text: serviceCardData.isBold ? "Visit Pharmacy Online".needTranslation : LocaleKeys.bookNow.tr(context: context), + onPressed: () { + handleOnTap(); + }, + padding: EdgeInsets.zero, + backgroundColor: serviceCardData.isBold ? AppColors.successLightColor.withValues(alpha: 0.2) : AppColors.blackBgColor, + borderColor: serviceCardData.isBold ? AppColors.successLightColor.withValues(alpha: 0.01) : AppColors.blackBgColor, + textColor: serviceCardData.isBold ? AppColors.successColor : AppColors.whiteColor, + fontSize: 14.f, + fontWeight: FontWeight.w500, + borderRadius: 10.r, + height: 40.h, + ), + ], + ), + ); + } + + void handleOnTap() { + switch (serviceCardData.serviceName) { + case "livecare": + { + getIt.get().onTabChanged(1); + Navigator.of(getIt.get().navigatorKey.currentContext!).push( + CustomPageRoute( + page: BookAppointmentPage(), + ), + ); + } + case "home_health_care": + { + Navigator.of(getIt().navigatorKey.currentContext!).push( + CustomPageRoute( + page: HhcProceduresPage(), + ), + ); + } + case "pharmacy": + { + Uri uri = Uri.parse(PHARMACY_REDIRECT_URL); + launchUrl(uri, mode: LaunchMode.externalApplication); + } + } + } +} diff --git a/lib/presentation/home/widgets/small_service_card.dart b/lib/presentation/home/widgets/small_service_card.dart index 49db8bf1..84ef6fa2 100644 --- a/lib/presentation/home/widgets/small_service_card.dart +++ b/lib/presentation/home/widgets/small_service_card.dart @@ -51,25 +51,27 @@ class SmallServiceCard extends StatelessWidget { @override Widget build(BuildContext context) { - return Padding( - padding: EdgeInsets.symmetric(horizontal: 0.h, vertical: 12.h), - child: Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: backgroundColor, - borderRadius: 16, - ), - child: Padding( - padding: EdgeInsets.all(12.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Utils.buildSvgWithAssets(icon: icon, iconColor: iconColor, width: 32.h, height: 32.h), - SizedBox(height: 6.h), - title.toText11(color: textColor, isBold: isBold, isCenter: true), - subtitle.toText11(color: textColor, isBold: isBold, isCenter: true), - ], - ), - ), + return Container( + height: 92.h, + width: 92.h, + padding: EdgeInsets.all(4.0), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: backgroundColor, + borderRadius: 16, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Utils.buildSvgWithAssets(icon: icon, iconColor: iconColor, width: 32.h, height: 32.h), + SizedBox(height: 6.h), + if (isFoldable || isTablet) ...[ + title.tr(context: context).toText10(color: textColor, weight: FontWeight.w500, isCenter: true, letterSpacing: -0.2, maxlines: 1), + subtitle.tr(context: context).toText10(color: textColor, weight: FontWeight.w500, isCenter: true, letterSpacing: -0.2, maxlines: 1), + ] else ...[ + title.tr(context: context).toText12(color: textColor, fontWeight: FontWeight.w500, isCenter: true, letterSpacing: -0.2, maxLine: 1), + subtitle.tr(context: context).toText12(color: textColor, fontWeight: FontWeight.w500, isCenter: true, letterSpacing: -0.2, maxLine: 1), + ] + ], ), ).onPress(() { switch (serviceName) { @@ -119,8 +121,12 @@ class SmallServiceCard extends StatelessWidget { break; case "emergency": context.read().flushData(); - context.read().getTransportationOrders(showLoader: false,); - context.read().getRRTOrders(showLoader: false,); + context.read().getTransportationOrders( + showLoader: false, + ); + context.read().getRRTOrders( + showLoader: false, + ); Navigator.of(context).push( CustomPageRoute( page: EmergencyServicesPage(), @@ -173,7 +179,7 @@ class SmallServiceCard extends StatelessWidget { void openIndoorNavigationBottomSheet(BuildContext context) { showCommonBottomSheetWithoutHeight( - title: LocaleKeys.selectHospital.tr(), + title: LocaleKeys.selectHospital.tr(context: context), context, child: ChangeNotifierProvider( create: (context) => HospitalSelectionBottomSheetViewModel(getIt()), diff --git a/lib/presentation/home/widgets/welcome_widget.dart b/lib/presentation/home/widgets/welcome_widget.dart index 8ef0697d..1bb17b5b 100644 --- a/lib/presentation/home/widgets/welcome_widget.dart +++ b/lib/presentation/home/widgets/welcome_widget.dart @@ -1,7 +1,9 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; class WelcomeWidget extends StatelessWidget { @@ -31,7 +33,7 @@ class WelcomeWidget extends StatelessWidget { spacing: 4.h, mainAxisSize: MainAxisSize.min, children: [ - "Welcome".needTranslation.toText14(color: AppColors.greyTextColor, height: 1, weight: FontWeight.w500), + LocaleKeys.welcome.tr(context: context).toText14(color: AppColors.greyTextColor, height: 1, weight: FontWeight.w500), Row( spacing: 4.h, crossAxisAlignment: CrossAxisAlignment.center, diff --git a/lib/presentation/home_health_care/hhc_order_detail_page.dart b/lib/presentation/home_health_care/hhc_order_detail_page.dart index 92c93d13..7017441c 100644 --- a/lib/presentation/home_health_care/hhc_order_detail_page.dart +++ b/lib/presentation/home_health_care/hhc_order_detail_page.dart @@ -10,6 +10,7 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; @@ -121,7 +122,7 @@ class _HhcOrderDetailPageState extends State { Row( children: [ if (!isLoading) ...[ - "Request ID:".needTranslation.toText14( + LocaleKeys.requestID.tr(context: context).toText14( color: AppColors.textColorLight, weight: FontWeight.w500, ), @@ -186,7 +187,7 @@ class _HhcOrderDetailPageState extends State { ), child: Utils.getNoDataWidget( context, - noDataText: "You don't have any Home Health Care orders yet.".needTranslation, + noDataText: LocaleKeys.dontHaveHHCOrders.tr(context: context), isSmallWidget: true, width: 62.w, height: 62.h, @@ -199,7 +200,7 @@ class _HhcOrderDetailPageState extends State { @override Widget build(BuildContext context) { return CollapsingListView( - title: "HHC Orders".needTranslation, + title: LocaleKeys.hhcOrders.tr(context: context), isLeading: true, child: SingleChildScrollView( child: Column( diff --git a/lib/presentation/home_health_care/hhc_procedures_page.dart b/lib/presentation/home_health_care/hhc_procedures_page.dart index be97a886..ef5dce5c 100644 --- a/lib/presentation/home_health_care/hhc_procedures_page.dart +++ b/lib/presentation/home_health_care/hhc_procedures_page.dart @@ -11,6 +11,7 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_services_resp_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/home_health_care/hhc_order_detail_page.dart'; import 'package:hmg_patient_app_new/presentation/home_health_care/hhc_selection_review_page.dart'; import 'package:hmg_patient_app_new/presentation/home_health_care/widgets/hhc_ui_selection_helper.dart'; @@ -93,7 +94,7 @@ class _HhcProceduresPageState extends State { children: [ Row( children: [ - "Request ID:".needTranslation.toText14(color: AppColors.textColorLight, weight: FontWeight.w500), + LocaleKeys.requestID.tr(context: context).toText14(color: AppColors.textColorLight, weight: FontWeight.w500), SizedBox(width: 4.w), "${order.iD ?? '-'}".toText16(isBold: true), ], @@ -132,7 +133,7 @@ class _HhcProceduresPageState extends State { color: AppColors.primaryRedColor, ), SizedBox(width: 6.w), - "Requested Services".needTranslation.toText14( + LocaleKeys.requestedServices.tr().toText14( weight: FontWeight.w600, color: AppColors.blackColor, ), @@ -209,7 +210,7 @@ class _HhcProceduresPageState extends State { ), SizedBox(width: 8.w), Expanded( - child: "You have a pending order. Please wait for it to be processed.".needTranslation.toText12( + child: LocaleKeys.pendingOrderWait.tr(context: context).toText12( color: AppColors.infoBannerTextColor, fontWeight: FontWeight.w500, ), @@ -223,7 +224,7 @@ class _HhcProceduresPageState extends State { children: [ Expanded( child: CustomButton( - text: "Cancel Order".needTranslation, + text: LocaleKeys.cancelOrder.tr(context: context), onPressed: () => HhcUiSelectionHelper.showCancelConfirmationDialog(context: context, order: order), backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, @@ -248,7 +249,7 @@ class _HhcProceduresPageState extends State { hasBottomPadding: false, padding: EdgeInsets.only(top: 24.h), context, - title: 'Select Services'.needTranslation, + title: LocaleKeys.selectServices.tr(context: context), isCloseButtonVisible: true, isDismissible: true, callBackFunc: () {}, @@ -257,9 +258,9 @@ class _HhcProceduresPageState extends State { child: Padding( padding: EdgeInsets.all(24.h), child: Text( - 'No services available'.needTranslation, + LocaleKeys.noServicesAvailable.tr(context: context), style: TextStyle( - fontSize: 16.h, + fontSize: 16.f, color: AppColors.greyTextColor, ), ), @@ -300,7 +301,7 @@ class _HhcProceduresPageState extends State { duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, width: 24.w, - height: 24.w, + height: 24.h, decoration: BoxDecoration( color: isSelected ? AppColors.primaryRedColor : Colors.transparent, borderRadius: BorderRadius.circular(5.r), @@ -353,7 +354,7 @@ class _HhcProceduresPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Selected Services".needTranslation.toText12( + LocaleKeys.selectedServices.tr(context: context).toText12( color: AppColors.textColorLight, fontWeight: FontWeight.w600, ), @@ -368,7 +369,7 @@ class _HhcProceduresPageState extends State { SizedBox(height: 16.h), CustomButton( borderWidth: 0, - text: "Next".needTranslation, + text: LocaleKeys.next.tr(context: context), onPressed: () { Navigator.pop(context); _proceedWithSelectedService(); @@ -398,9 +399,9 @@ class _HhcProceduresPageState extends State { bool result = await navigationServices.push( CustomPageRoute( page: MapUtilityScreen( - confirmButtonString: "Submit Request ".needTranslation, - titleString: "Select Location", - subTitleString: "Please select the location".needTranslation, + confirmButtonString: LocaleKeys.submitRequest.tr(context: context), + titleString: LocaleKeys.selectLocation.tr(context: context), + subTitleString: LocaleKeys.pleaseSelectTheLocation.tr(context: context), isGmsAvailable: appState.isGMSAvailable, ), direction: AxisDirection.down), @@ -447,7 +448,7 @@ class _HhcProceduresPageState extends State { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( - title: "Home Health Care".needTranslation, + title: LocaleKeys.homeHealthCare.tr(context: context), history: () => Navigator.of(context).push(CustomPageRoute(page: HhcOrderDetailPage(), direction: AxisDirection.up)), bottomChild: Consumer( builder: (BuildContext context, HmgServicesViewModel hmgServicesViewModel, Widget? child) { @@ -466,7 +467,7 @@ class _HhcProceduresPageState extends State { padding: EdgeInsets.all(24.w), child: CustomButton( borderWidth: 0, - text: "Create new request".needTranslation, + text: LocaleKeys.createNewRequest.tr(context: context), onPressed: () => _buildServicesListBottomsSheet(hmgServicesViewModel.hhcServicesList), textColor: AppColors.whiteColor, borderRadius: 12.r, @@ -494,7 +495,7 @@ class _HhcProceduresPageState extends State { Center( child: Utils.getNoDataWidget( context, - noDataText: "You have no pending requests.".needTranslation, + noDataText: LocaleKeys.youHaveNoPendingRequests.tr(context: context), ), ), ], diff --git a/lib/presentation/home_health_care/hhc_selection_review_page.dart b/lib/presentation/home_health_care/hhc_selection_review_page.dart index 37410e21..9a9342d7 100644 --- a/lib/presentation/home_health_care/hhc_selection_review_page.dart +++ b/lib/presentation/home_health_care/hhc_selection_review_page.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; @@ -19,7 +21,7 @@ import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/map/location_map_widget.dart'; -import 'package:maps_launcher/maps_launcher.dart'; +import 'package:map_launcher/map_launcher.dart'; import 'package:provider/provider.dart'; class HhcSelectionReviewPage extends StatefulWidget { @@ -50,7 +52,7 @@ class _HhcSelectionReviewPageState extends State { final isArabic = appState.isArabic(); return CollapsingListView( - title: "Summary".needTranslation, + title: LocaleKeys.summary.tr(context: context), bottomChild: _buildBottomButton(), child: SingleChildScrollView( padding: EdgeInsets.all(16.w), @@ -75,7 +77,7 @@ class _HhcSelectionReviewPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Selected Services".needTranslation.toText14( + LocaleKeys.selectedServices.tr(context: context).toText14( weight: FontWeight.w600, color: AppColors.textColor, letterSpacing: -0.4, @@ -86,7 +88,7 @@ class _HhcSelectionReviewPageState extends State { runSpacing: 12.w, children: widget.selectedServices.map((service) { final serviceName = isArabic ? (service.textN ?? service.text ?? '') : (service.text ?? ''); - return AppCustomChipWidget(labelText: serviceName.needTranslation); + return AppCustomChipWidget(labelText: serviceName); }).toList(), ), ], @@ -110,7 +112,7 @@ class _HhcSelectionReviewPageState extends State { if (lat == 0.0 || lng == 0.0) return SizedBox.shrink(); // Get address from geocode response - String address = "Selected Location".needTranslation; + String address = LocaleKeys.selectLocation.tr(context: context); if (geocodeResponse != null && geocodeResponse.results.isNotEmpty) { address = geocodeResponse.results.first.formattedAddress; } @@ -133,7 +135,7 @@ class _HhcSelectionReviewPageState extends State { ), child: CustomButton( borderWidth: 0, - text: "Confirm".needTranslation, + text: LocaleKeys.confirm.tr(context: context), onPressed: () => _handleConfirm(), textColor: AppColors.whiteColor, borderRadius: 12.r, @@ -142,9 +144,19 @@ class _HhcSelectionReviewPageState extends State { ); } - void _launchDirectionsToLocation(double lat, double lng, String locationName) { + void _launchDirectionsToLocation(double lat, double lng, String locationName) async { if (lat != 0.0 && lng != 0.0) { - MapsLauncher.launchCoordinates(lat, lng, locationName); + await MapLauncher.showMarker( + mapType: MapType.google, + coords: Coords(lat, lng), + title: locationName, + ).catchError((err) { + MapLauncher.showMarker( + mapType: Platform.isIOS ? MapType.apple : MapType.google, + coords: Coords(lat, lng), + title: locationName, + ); + }); } } @@ -155,10 +167,10 @@ class _HhcSelectionReviewPageState extends State { padding: EdgeInsets.all(16.w), child: Column( children: [ - Utils.getSuccessWidget(loadingText: "Your request has been successfully submitted.".needTranslation), + Utils.getSuccessWidget(loadingText: LocaleKeys.requestSubmittedSuccessfully.tr(context: context)), Row( children: [ - "Here is your request #: ".needTranslation.toText14( + LocaleKeys.hereIsYourRequestNumber.tr(context: context).toText14( color: AppColors.textColorLight, weight: FontWeight.w500, ), @@ -200,7 +212,7 @@ class _HhcSelectionReviewPageState extends State { title: LocaleKeys.notice.tr(context: context), context, child: Utils.getWarningWidget( - loadingText: "Are you sure you want to submit this request?".needTranslation, + loadingText: LocaleKeys.confirmSubmitRequest.tr(context: context), isShowActionButtons: true, onCancelTap: () { Navigator.pop(context); diff --git a/lib/presentation/home_health_care/widgets/hhc_ui_selection_helper.dart b/lib/presentation/home_health_care/widgets/hhc_ui_selection_helper.dart index 688612c8..d5310b39 100644 --- a/lib/presentation/home_health_care/widgets/hhc_ui_selection_helper.dart +++ b/lib/presentation/home_health_care/widgets/hhc_ui_selection_helper.dart @@ -25,7 +25,7 @@ class HhcUiSelectionHelper { title: LocaleKeys.notice.tr(context: context), context, child: Utils.getWarningWidget( - loadingText: "Are you sure you want to cancel this order?".needTranslation, + loadingText: LocaleKeys.cancelOrderConfirmation.tr(context: context), isShowActionButtons: true, onCancelTap: () { Navigator.pop(context); @@ -51,7 +51,7 @@ class HhcUiSelectionHelper { padding: EdgeInsets.all(16.w), child: Column( children: [ - Utils.getSuccessWidget(loadingText: "Order has been cancelled successfully".needTranslation), + Utils.getSuccessWidget(loadingText: LocaleKeys.orderCancelledSuccessfully.tr(context: context)), SizedBox(height: 24.h), Row( children: [ diff --git a/lib/presentation/insurance/insurance_approval_details_page.dart b/lib/presentation/insurance/insurance_approval_details_page.dart index 415d66fe..a6d81575 100644 --- a/lib/presentation/insurance/insurance_approval_details_page.dart +++ b/lib/presentation/insurance/insurance_approval_details_page.dart @@ -56,7 +56,7 @@ class InsuranceApprovalDetailsPage extends StatelessWidget { AppCustomChipWidget( icon: (!insuranceApprovalResponseModel.isLiveCareAppointment! ? AppAssets.walkin_appointment_icon : AppAssets.small_livecare_icon), iconColor: !insuranceApprovalResponseModel.isLiveCareAppointment! ? AppColors.textColor : AppColors.whiteColor, - labelText: insuranceApprovalResponseModel.isLiveCareAppointment! ? LocaleKeys.livecare.tr(context: context) : "Walk In".needTranslation, + labelText: insuranceApprovalResponseModel.isLiveCareAppointment! ? LocaleKeys.livecare.tr(context: context) : LocaleKeys.walkin.tr(context: context), backgroundColor: (!insuranceApprovalResponseModel.isLiveCareAppointment! ? AppColors.greyColor : AppColors.successColor), textColor: (!insuranceApprovalResponseModel.isLiveCareAppointment! ? AppColors.textColor : AppColors.whiteColor), ), @@ -137,7 +137,7 @@ class InsuranceApprovalDetailsPage extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Status:".needTranslation.toText14(isBold: true), + "${LocaleKeys.status.tr(context: context)}: ".toText14(isBold: true), insuranceApprovalResponseModel.apporvalDetails!.status!.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), ], ), diff --git a/lib/presentation/insurance/insurance_approvals_page.dart b/lib/presentation/insurance/insurance_approvals_page.dart index b70c1162..52f8b1f6 100644 --- a/lib/presentation/insurance/insurance_approvals_page.dart +++ b/lib/presentation/insurance/insurance_approvals_page.dart @@ -95,7 +95,7 @@ class _InsuranceApprovalsPageState extends State { ), ), ) - : Utils.getNoDataWidget(context, noDataText: "You don't have any insurance approvals yet.".needTranslation); + : Utils.getNoDataWidget(context, noDataText: LocaleKeys.noInsuranceApprovals.tr(context: context)); }, separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), ), diff --git a/lib/presentation/insurance/insurance_home_page.dart b/lib/presentation/insurance/insurance_home_page.dart index b005e42d..519d8306 100644 --- a/lib/presentation/insurance/insurance_home_page.dart +++ b/lib/presentation/insurance/insurance_home_page.dart @@ -46,66 +46,63 @@ class _InsuranceHomePageState extends State { Widget build(BuildContext context) { appState = getIt.get(); insuranceViewModel = Provider.of(context, listen: false); - return Scaffold( - backgroundColor: AppColors.bgScaffoldColor, - body: CollapsingListView( - title: "${LocaleKeys.insurance.tr(context: context)} ${LocaleKeys.updateInsurance.tr(context: context)}", - history: () { - insuranceViewModel.setIsInsuranceHistoryLoading(true); - insuranceViewModel.getPatientInsuranceCardHistory(); - showCommonBottomSheetWithoutHeight(context, child: InsuranceHistory(), callBackFunc: () {}, title: "", isCloseButtonVisible: false, isFullScreen: false); - }, - child: SingleChildScrollView( - child: Consumer(builder: (context, insuranceVM, child) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - insuranceVM.isInsuranceLoading - ? LabResultItemView( - onTap: () {}, - labOrder: null, - index: 0, - isLoading: true, - ).paddingSymmetrical(24.h, 24.h) - : insuranceVM.patientInsuranceList.isNotEmpty - ? Padding( - padding: EdgeInsets.only(top: 24.h), - child: PatientInsuranceCard( - insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first, - isInsuranceExpired: DateTime.now().isAfter(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo))).paddingSymmetrical(24.w, 0.h), - ) - : Padding( - padding: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.12), - child: Utils.getNoDataWidget( - context, - noDataText: "You don't have insurance registered with HMG.".needTranslation, - callToActionButton: CustomButton( - icon: AppAssets.update_insurance_card_icon, - iconColor: AppColors.successColor, - iconSize: 15.h, - text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}", - onPressed: () { - insuranceViewModel.setIsInsuranceUpdateDetailsLoading(true); - insuranceViewModel.getPatientInsuranceDetailsForUpdate( - appState.getAuthenticatedUser()!.patientId.toString(), appState.getAuthenticatedUser()!.patientIdentificationNo.toString()); - showCommonBottomSheetWithoutHeight(context, - child: PatientInsuranceCardUpdateCard(), callBackFunc: () {}, title: "", isCloseButtonVisible: false, isFullScreen: false); - }, - backgroundColor: AppColors.bgGreenColor.withOpacity(0.20), - borderColor: AppColors.bgGreenColor.withOpacity(0.0), - textColor: AppColors.bgGreenColor, - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ).paddingSymmetrical(64.h, 0.h), - ), + return CollapsingListView( + title: "${LocaleKeys.insurance.tr(context: context)} ${LocaleKeys.updateInsurance.tr(context: context)}", + history: () { + insuranceViewModel.setIsInsuranceHistoryLoading(true); + insuranceViewModel.getPatientInsuranceCardHistory(); + showCommonBottomSheetWithoutHeight(context, child: InsuranceHistory(), callBackFunc: () {}, title: "", isCloseButtonVisible: false, isFullScreen: false); + }, + child: SingleChildScrollView( + child: Consumer(builder: (context, insuranceVM, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + insuranceVM.isInsuranceLoading + ? LabResultItemView( + onTap: () {}, + labOrder: null, + index: 0, + isLoading: true, + ).paddingSymmetrical(24.h, 24.h) + : insuranceVM.patientInsuranceList.isNotEmpty + ? Padding( + padding: EdgeInsets.only(top: 24.h), + child: PatientInsuranceCard( + insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first, + isInsuranceExpired: DateTime.now().isAfter(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo))) + .paddingSymmetrical(24.w, 0.h), + ) + : Padding( + padding: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.12), + child: Utils.getNoDataWidget( + context, + noDataText: LocaleKeys.noInsuranceWithHMG.tr(context: context), + callToActionButton: CustomButton( + icon: AppAssets.update_insurance_card_icon, + iconColor: AppColors.successColor, + iconSize: 15.h, + text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}", + onPressed: () { + insuranceViewModel.setIsInsuranceUpdateDetailsLoading(true); + insuranceViewModel.getPatientInsuranceDetailsForUpdate( + appState.getAuthenticatedUser()!.patientId.toString(), appState.getAuthenticatedUser()!.patientIdentificationNo.toString()); + showCommonBottomSheetWithoutHeight(context, child: PatientInsuranceCardUpdateCard(), callBackFunc: () {}, title: "", isCloseButtonVisible: false, isFullScreen: false); + }, + backgroundColor: AppColors.bgGreenColor.withOpacity(0.20), + borderColor: AppColors.bgGreenColor.withOpacity(0.0), + textColor: AppColors.bgGreenColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ).paddingSymmetrical(64.h, 0.h), ), - ], - ); - }), - ), + ), + ], + ); + }), ), ); } diff --git a/lib/presentation/insurance/widgets/insurance_approval_card.dart b/lib/presentation/insurance/widgets/insurance_approval_card.dart index ee31538f..588f9887 100644 --- a/lib/presentation/insurance/widgets/insurance_approval_card.dart +++ b/lib/presentation/insurance/widgets/insurance_approval_card.dart @@ -54,7 +54,7 @@ class InsuranceApprovalCard extends StatelessWidget { ? "Walk In" : insuranceApprovalResponseModel.isLiveCareAppointment! ? LocaleKeys.livecare.tr(context: context) - : "Walk In".needTranslation, + : LocaleKeys.walkin.tr(context: context), backgroundColor: isLoading ? AppColors.greyColor : (!insuranceApprovalResponseModel.isLiveCareAppointment! ? AppColors.greyColor : AppColors.successColor), textColor: isLoading ? AppColors.textColor : (!insuranceApprovalResponseModel.isLiveCareAppointment! ? AppColors.textColor : AppColors.whiteColor), ).toShimmer2(isShow: isLoading), diff --git a/lib/presentation/insurance/widgets/insurance_history.dart b/lib/presentation/insurance/widgets/insurance_history.dart index 341e2340..7219a34e 100644 --- a/lib/presentation/insurance/widgets/insurance_history.dart +++ b/lib/presentation/insurance/widgets/insurance_history.dart @@ -111,7 +111,7 @@ class InsuranceHistory extends StatelessWidget { ) : Utils.getNoDataWidget( context, - noDataText: "No insurance update requests found.".needTranslation, + noDataText: LocaleKeys.noInsuranceUpdateRequest.tr(context: context), // isSmallWidget: true, // width: 62, // height: 62, diff --git a/lib/presentation/insurance/widgets/insurance_update_details_card.dart b/lib/presentation/insurance/widgets/insurance_update_details_card.dart index c3bbcd7d..35be7f9e 100644 --- a/lib/presentation/insurance/widgets/insurance_update_details_card.dart +++ b/lib/presentation/insurance/widgets/insurance_update_details_card.dart @@ -1,6 +1,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; @@ -11,16 +13,20 @@ import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:provider/provider.dart'; class PatientInsuranceCardUpdateCard extends StatelessWidget { PatientInsuranceCardUpdateCard({super.key}); late InsuranceViewModel insuranceViewModel; + late AppState appState; @override Widget build(BuildContext context) { insuranceViewModel = Provider.of(context); + appState = getIt.get(); return Column( mainAxisSize: MainAxisSize.min, children: [ @@ -49,7 +55,7 @@ class PatientInsuranceCardUpdateCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Haroon Amjad".toText16(weight: FontWeight.w600), + insuranceViewModel.patientInsuranceUpdateResponseModel!.memberName!.toText16(weight: FontWeight.w600), "Policy: ${insuranceViewModel.patientInsuranceUpdateResponseModel!.policyNumber}".toText12(isBold: true, color: AppColors.lightGrayColor), SizedBox(height: 8.h), Row( @@ -90,7 +96,7 @@ class PatientInsuranceCardUpdateCard extends StatelessWidget { ], ).paddingSymmetrical(16.h, 16.h), ).paddingSymmetrical(24.h, 0.h) - : Utils.getNoDataWidget(context, noDataText: "No insurance data found...".needTranslation), + : Utils.getNoDataWidget(context, noDataText: LocaleKeys.noInsuranceDataFound.tr(context: context)), SizedBox( height: 24.h, ), @@ -99,7 +105,41 @@ class PatientInsuranceCardUpdateCard extends StatelessWidget { iconColor: AppColors.whiteColor, iconSize: 20.w, text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}", - onPressed: () {}, + onPressed: () { + LoaderBottomSheet.showLoader(); + insuranceViewModel.updatePatientInsuranceCard( + patientID: appState.getAuthenticatedUser()!.patientId!, + patientType: appState.getAuthenticatedUser()!.patientType!, + patientIdentificationID: appState.getAuthenticatedUser()!.patientIdentificationNo!, + mobileNo: appState.getAuthenticatedUser()!.mobileNumber!, + insuranceCardImage: "", + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.success.tr(context: context), + context, + child: Utils.getSuccessWidget(loadingText: LocaleKeys.success.tr(context: context)), + callBackFunc: () { + Navigator.pop(context); + }, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getErrorWidget(loadingText: err.toString()), + callBackFunc: () { + Navigator.pop(context); + }, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }, backgroundColor: insuranceViewModel.patientInsuranceUpdateResponseModel != null ? AppColors.successColor : AppColors.lightGrayBGColor, borderColor: AppColors.successColor.withOpacity(0.01), textColor: AppColors.whiteColor, diff --git a/lib/presentation/insurance/widgets/patient_insurance_card.dart b/lib/presentation/insurance/widgets/patient_insurance_card.dart index fde58119..c9ec7909 100644 --- a/lib/presentation/insurance/widgets/patient_insurance_card.dart +++ b/lib/presentation/insurance/widgets/patient_insurance_card.dart @@ -50,12 +50,12 @@ class PatientInsuranceCard extends StatelessWidget { children: [ SizedBox( width: MediaQuery.of(context).size.width * 0.45, child: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}".toText18(isBold: true)), - "Policy: ${insuranceCardDetailsModel.insurancePolicyNo}".needTranslation.toText12(isBold: true, color: AppColors.lightGrayColor), + LocaleKeys.policyNumber.tr(namedArgs: {'number': insuranceCardDetailsModel.insurancePolicyNo ?? ''}, context: context).toText12(isBold: true, color: AppColors.lightGrayColor), ], ), AppCustomChipWidget( icon: isInsuranceExpired ? AppAssets.cancel_circle_icon : AppAssets.insurance_active_icon, - labelText: isInsuranceExpired ? "Insurance Expired".needTranslation : "Insurance Active".needTranslation, + labelText: isInsuranceExpired ? LocaleKeys.insuranceExpired.tr(context: context) : LocaleKeys.insuranceActive.tr(context: context), iconColor: isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, textColor: isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, iconSize: 12, @@ -75,10 +75,11 @@ class PatientInsuranceCard extends StatelessWidget { children: [ AppCustomChipWidget( icon: AppAssets.doctor_calendar_icon, - labelText: "${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}", + // labelText: "${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}", + richText: "${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}".toText10(isEnglishOnly: true), labelPadding: EdgeInsetsDirectional.only(start: -4.h, end: 8.h), ), - AppCustomChipWidget(labelText: "Patient Card ID: ${insuranceCardDetailsModel.patientCardID}".needTranslation), + AppCustomChipWidget(richText: LocaleKeys.patientCardID.tr(namedArgs: {'id': insuranceCardDetailsModel.patientCardID ?? ''}, context: context).toText10(isEnglishOnly: true)), ], ), SizedBox(height: 10.h), diff --git a/lib/presentation/lab/alphabeticScroll.dart b/lib/presentation/lab/alphabeticScroll.dart index 29ded695..95d9da45 100644 --- a/lib/presentation/lab/alphabeticScroll.dart +++ b/lib/presentation/lab/alphabeticScroll.dart @@ -203,7 +203,6 @@ class _AlphabetScrollPageState extends State { verticalOffset: 100.0, child: FadeInAnimation( child: LabOrderByTest( - appState: getIt(), onTap: () { if (items[itemIndex].model != null) { diff --git a/lib/presentation/lab/lab_order_by_test.dart b/lib/presentation/lab/lab_order_by_test.dart index 837f4822..2e4a96b6 100644 --- a/lib/presentation/lab/lab_order_by_test.dart +++ b/lib/presentation/lab/lab_order_by_test.dart @@ -44,7 +44,7 @@ class LabOrderByTest extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ AppCustomChipWidget( - richText: '${"Last Tested:".needTranslation} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(tests!.createdOn), false)}'.toText12(fontWeight: FontWeight.w500), + richText: '${"${LocaleKeys.lastTested.tr(context: context)}:"} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(tests!.createdOn), false)}'.toText12(fontWeight: FontWeight.w500), backgroundColor: AppColors.greyLightColor, textColor: AppColors.textColor, ), diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index 90651f13..a6ee1544 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -1 +1 @@ -import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/app_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/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.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/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_toolbar.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; import 'alphabeticScroll.dart'; class LabOrdersPage extends StatefulWidget { const LabOrdersPage({super.key}); @override State createState() => _LabOrdersPageState(); } class _LabOrdersPageState extends State { late LabViewModel labProvider; late DateRangeSelectorRangeViewModel rangeViewModel; late AppState _appState; List?> labSuggestions = []; int? expandedIndex; String? selectedFilterText = ''; int activeIndex = 0; @override void initState() { scheduleMicrotask(() { labProvider.initLabProvider(); }); super.initState(); } @override Widget build(BuildContext context) { labProvider = Provider.of(context, listen: false); rangeViewModel = Provider.of(context); _appState = getIt(); return CollapsingToolbar( title: LocaleKeys.labResults.tr(), search: () async { final lavVM = Provider.of(context, listen: false); if (lavVM.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: lavVM.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; lavVM.filterLabReports(value); } } }, child: Consumer( builder: (context, model, child) { return SingleChildScrollView( physics: AlwaysScrollableScrollPhysics(), padding: EdgeInsets.all(24.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: CustomTabBar( activeTextColor: Color(0xffED1C2B), activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), tabs: [ CustomTabBarModel(null, "By Visit".needTranslation), CustomTabBarModel(null, "By Test".needTranslation), // CustomTabBarModel(null, "Completed".needTranslation), ], onTabChange: (index) { activeIndex = index; setState(() {}); }, ), ), ], ), if (activeIndex == 0) Padding( padding: EdgeInsets.symmetric(vertical: 10.h), child: Row( children: [ CustomButton( text: LocaleKeys.byClinic.tr(context: context), onPressed: () { model.setIsSortByClinic(true); }, backgroundColor: model.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, borderColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), textColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, fontSize: 12, fontWeight: FontWeight.w500, borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, ), SizedBox(width: 8.h), CustomButton( text: LocaleKeys.byHospital.tr(context: context), onPressed: () { model.setIsSortByClinic(false); }, backgroundColor: model.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, borderColor: model.isSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor, textColor: model.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, fontSize: 12, fontWeight: FontWeight.w500, borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, ), ], ), ), SizedBox(height: 8.h), selectedFilterText!.isNotEmpty ? CustomChipWidget( chipText: selectedFilterText!, chipType: ChipTypeEnum.alert, isSelected: true, ) : SizedBox(), activeIndex == 0 ? // By Visit - show grouped view when available model.isLabOrdersLoading ? ListView.builder( shrinkWrap: true, physics: AlwaysScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: 5, itemBuilder: (context, index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ), ) : (model.patientLabOrdersViewList.isNotEmpty ? ListView.builder( shrinkWrap: true, physics: AlwaysScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.patientLabOrdersViewList.length, itemBuilder: (context, index) { final group = model.patientLabOrdersViewList[index]; final isExpanded = expandedIndex == index; return AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: AnimatedContainer( duration: Duration(milliseconds: 300), curve: Curves.easeInOut, margin: EdgeInsets.symmetric(vertical: 8.h), decoration: RoundedRectangleBorder() .toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), child: InkWell( onTap: () { setState(() { expandedIndex = isExpanded ? null : index; }); }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.all(16.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ AppCustomChipWidget(labelText: "${group.length} ${'results'.needTranslation}"), Icon(isExpanded ? Icons.expand_less : Icons.expand_more), ], ), SizedBox(height: 8.h), Text( model.isSortByClinic ? (group.first.clinicDescription ?? 'Unknown') : (group.first.projectName ?? 'Unknown'), style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600), overflow: TextOverflow.ellipsis, ), ], ), ), AnimatedSwitcher( duration: Duration(milliseconds: 500), switchInCurve: Curves.easeIn, switchOutCurve: Curves.easeOut, transitionBuilder: (Widget child, Animation animation) { return FadeTransition( opacity: animation, child: SizeTransition( sizeFactor: animation, axisAlignment: 0.0, child: child, ), ); }, child: isExpanded ? Container( key: ValueKey(index), padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 0.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ...group.map((order) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisSize: MainAxisSize.min, children: [ Image.network( order.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", width: 24.w, height: 24.h, fit: BoxFit.cover, ).circle(100), SizedBox(width: 8.h), Expanded(child: (order.doctorName ?? order.doctorNameEnglish ?? "").toString().toText14(weight: FontWeight.w500)), ], ), SizedBox(height: 8.h), Wrap( direction: Axis.horizontal, spacing: 4.h, runSpacing: 4.h, children: [ AppCustomChipWidget( labelText: ("Order No: ".needTranslation + order.orderNo!), ), AppCustomChipWidget( labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(order.orderDate ?? ""), false), ), AppCustomChipWidget( labelText: model.isSortByClinic ? (order.clinicDescription ?? "") : (order.projectName ?? ""), ), ], ), // Row( // children: [ // CustomButton( // text: ("Order No: ".needTranslation + order.orderNo!), // onPressed: () {}, // backgroundColor: AppColors.greyColor, // borderColor: AppColors.greyColor, // textColor: AppColors.blackColor, // fontSize: 10, // fontWeight: FontWeight.w500, // borderRadius: 8, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 24.h, // ), // SizedBox(width: 8.h), // CustomButton( // text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(order.orderDate ?? ""), false), // onPressed: () {}, // backgroundColor: AppColors.greyColor, // borderColor: AppColors.greyColor, // textColor: AppColors.blackColor, // fontSize: 10, // fontWeight: FontWeight.w500, // borderRadius: 8, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 24.h, // ), // ], // ), // SizedBox(height: 8.h), // Row( // children: [ // CustomButton( // text: model.isSortByClinic ? (order.clinicDescription ?? "") : (order.projectName ?? ""), // onPressed: () {}, // backgroundColor: AppColors.greyColor, // borderColor: AppColors.greyColor, // textColor: AppColors.blackColor, // fontSize: 10, // fontWeight: FontWeight.w500, // borderRadius: 8, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 24.h, // ), // ], // ), SizedBox(height: 12.h), Row( children: [ Expanded(flex: 2, child: SizedBox()), // Expanded( // flex: 1, // child: Container( // height: 40.h, // width: 40.w, // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( // color: AppColors.textColor, // borderRadius: 12, // ), // child: Padding( // padding: EdgeInsets.all(12.h), // child: Transform.flip( // flipX: _appState.isArabic(), // child: Utils.buildSvgWithAssets( // icon: AppAssets.forward_arrow_icon_small, // iconColor: AppColors.whiteColor, // fit: BoxFit.contain, // ), // ), // ), // ).onPress(() { // model.currentlySelectedPatientOrder = order; // labProvider.getPatientLabResultByHospital(order); // labProvider.getPatientSpecialResult(order); // Navigator.of(context).push( // CustomPageRoute(page: LabResultByClinic(labOrder: order)), // ); // }), // ) Expanded( flex:2, child: CustomButton( icon: AppAssets.view_report_icon, iconColor: AppColors.primaryRedColor, iconSize: 16.h, text: "View Results".needTranslation, onPressed: () { model.currentlySelectedPatientOrder = order; labProvider.getPatientLabResultByHospital(order); labProvider.getPatientSpecialResult(order); Navigator.of(context).push( CustomPageRoute(page: LabResultByClinic(labOrder: order)), ); }, backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, fontSize: 14, fontWeight: FontWeight.w500, borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, ), ) ], ), SizedBox(height: 12.h), Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), SizedBox(height: 12.h), ], ); }).toList(), ], ), ) : SizedBox.shrink(), ), ], ), ), ), ), )); }, ) : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation)) : // By Test or other tabs keep existing behavior (model.isLabOrdersLoading) ? Column( children: List.generate( 5, (index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, )), ) : AlphabeticScroll( alpahbetsAvailable: model.indexedCharacterForUniqueTest, details: model.uniqueTestsList, labViewModel: model, rangeViewModel: rangeViewModel, appState: _appState, ) ], ) ); }, ), ); } Color getLabOrderStatusColor(num status) { switch (status) { case 44: return AppColors.warningColorYellow; case 45: return AppColors.warningColorYellow; case 16: return AppColors.successColor; case 17: return AppColors.successColor; default: return AppColors.greyColor; } } String getLabOrderStatusText(num status) { switch (status) { case 44: return LocaleKeys.resultsPending.tr(context: context); case 45: return LocaleKeys.resultsPending.tr(context: context); case 16: return LocaleKeys.resultsAvailable.tr(context: context); case 17: return LocaleKeys.resultsAvailable.tr(context: context); default: return ""; } } getLabSuggestions(LabViewModel model) { if (model.patientLabOrders.isEmpty) { return []; } return model.patientLabOrders.map((m) => m.testDetails).toList(); } } \ No newline at end of file +import 'dart:async'; import 'dart:convert'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/app_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/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.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/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_toolbar.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; import 'alphabeticScroll.dart'; class LabOrdersPage extends StatefulWidget { const LabOrdersPage({super.key}); @override State createState() => _LabOrdersPageState(); } class _LabOrdersPageState extends State { late LabViewModel labProvider; late DateRangeSelectorRangeViewModel rangeViewModel; late AppState _appState; List?> labSuggestions = []; int? expandedIndex; String? selectedFilterText = ''; int activeIndex = 0; @override void initState() { scheduleMicrotask(() { labProvider.initLabProvider(); }); super.initState(); } @override Widget build(BuildContext context) { labProvider = Provider.of(context, listen: false); rangeViewModel = Provider.of(context); _appState = getIt(); return CollapsingListView( title: LocaleKeys.labResults.tr(context: context), search: () async { final lavVM = Provider.of(context, listen: false); if (lavVM.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: lavVM.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; lavVM.filterLabReports(value); } } }, child: Consumer( builder: (context, model, child) { return SingleChildScrollView( physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.all(24.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: CustomTabBar( activeTextColor: Color(0xffED1C2B), activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), tabs: [ CustomTabBarModel(null, LocaleKeys.byVisit.tr()), CustomTabBarModel(null, LocaleKeys.byTest.tr()), // CustomTabBarModel(null, "Completed".needTranslation), ], onTabChange: (index) { activeIndex = index; setState(() {}); }, ), ), ], ), if (activeIndex == 0) Padding( padding: EdgeInsets.symmetric(vertical: 10.h), child: Row( children: [ CustomButton( text: LocaleKeys.byClinic.tr(context: context), onPressed: () { model.setIsSortByClinic(true); }, backgroundColor: model.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, borderColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), textColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, fontSize: 12, fontWeight: FontWeight.w500, borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, ), SizedBox(width: 8.h), CustomButton( text: LocaleKeys.byHospital.tr(context: context), onPressed: () { model.setIsSortByClinic(false); }, backgroundColor: model.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, borderColor: model.isSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor, textColor: model.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, fontSize: 12, fontWeight: FontWeight.w500, borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, ), ], ), ), SizedBox(height: 8.h), selectedFilterText!.isNotEmpty ? CustomChipWidget( chipText: selectedFilterText!, chipType: ChipTypeEnum.alert, isSelected: true, ) : SizedBox(), activeIndex == 0 ? // By Visit - show grouped view when available model.isLabOrdersLoading ? ListView.builder( shrinkWrap: true, physics: AlwaysScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: 5, itemBuilder: (context, index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ), ) : (model.patientLabOrdersViewList.isNotEmpty ? ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.patientLabOrdersViewList.length, itemBuilder: (context, index) { final group = model.patientLabOrdersViewList[index]; final isExpanded = expandedIndex == index; return AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: AnimatedContainer( duration: Duration(milliseconds: 300), curve: Curves.easeInOut, margin: EdgeInsets.symmetric(vertical: 8.h), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), child: InkWell( onTap: () { setState(() { expandedIndex = isExpanded ? null : index; }); }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.all(16.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ AppCustomChipWidget(labelText: "${group.length} ${LocaleKeys.results.tr(context: context)}"), Icon(isExpanded ? Icons.expand_less : Icons.expand_more), ], ), SizedBox(height: 8.h), Text( model.isSortByClinic ? (group.first.clinicDescription ?? 'Unknown') : (group.first.projectName ?? 'Unknown'), style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600), overflow: TextOverflow.ellipsis, ), ], ), ), AnimatedSwitcher( duration: Duration(milliseconds: 500), switchInCurve: Curves.easeIn, switchOutCurve: Curves.easeOut, transitionBuilder: (Widget child, Animation animation) { return FadeTransition( opacity: animation, child: SizeTransition( sizeFactor: animation, axisAlignment: 0.0, child: child, ), ); }, child: isExpanded ? Container( key: ValueKey(index), padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 0.h), child: ListView.separated( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemBuilder: (cxt, index) { PatientLabOrdersResponseModel order = group[index]; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisSize: MainAxisSize.min, children: [ Image.network( order.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", width: 24.w, height: 24.h, fit: BoxFit.cover, ).circle(100), SizedBox(width: 8.h), Expanded(child: (order.doctorName ?? order.doctorNameEnglish ?? "").toString().toText14(weight: FontWeight.w500)), ], ), SizedBox(height: 8.h), Wrap( direction: Axis.horizontal, spacing: 4.h, runSpacing: 4.h, children: [ AppCustomChipWidget( labelText: ("${LocaleKeys.orderNo.tr(context: context)}: ${order.orderNo!}"), ), AppCustomChipWidget( labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(order.orderDate ?? ""), false), ), AppCustomChipWidget( labelText: model.isSortByClinic ? (order.clinicDescription ?? "") : (order.projectName ?? ""), ), ], ), // Row( // children: [ // CustomButton( // text: ("Order No: ".needTranslation + order.orderNo!), // onPressed: () {}, // backgroundColor: AppColors.greyColor, // borderColor: AppColors.greyColor, // textColor: AppColors.blackColor, // fontSize: 10, // fontWeight: FontWeight.w500, // borderRadius: 8, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 24.h, // ), // SizedBox(width: 8.h), // CustomButton( // text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(order.orderDate ?? ""), false), // onPressed: () {}, // backgroundColor: AppColors.greyColor, // borderColor: AppColors.greyColor, // textColor: AppColors.blackColor, // fontSize: 10, // fontWeight: FontWeight.w500, // borderRadius: 8, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 24.h, // ), // ], // ), // SizedBox(height: 8.h), // Row( // children: [ // CustomButton( // text: model.isSortByClinic ? (order.clinicDescription ?? "") : (order.projectName ?? ""), // onPressed: () {}, // backgroundColor: AppColors.greyColor, // borderColor: AppColors.greyColor, // textColor: AppColors.blackColor, // fontSize: 10, // fontWeight: FontWeight.w500, // borderRadius: 8, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 24.h, // ), // ], // ), SizedBox(height: 12.h), Row( children: [ Expanded(flex: 2, child: SizedBox()), // Expanded( // flex: 1, // child: Container( // height: 40.h, // width: 40.w, // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( // color: AppColors.textColor, // borderRadius: 12, // ), // child: Padding( // padding: EdgeInsets.all(12.h), // child: Transform.flip( // flipX: _appState.isArabic(), // child: Utils.buildSvgWithAssets( // icon: AppAssets.forward_arrow_icon_small, // iconColor: AppColors.whiteColor, // fit: BoxFit.contain, // ), // ), // ), // ).onPress(() { // model.currentlySelectedPatientOrder = order; // labProvider.getPatientLabResultByHospital(order); // labProvider.getPatientSpecialResult(order); // Navigator.of(context).push( // CustomPageRoute(page: LabResultByClinic(labOrder: order)), // ); // }), // ) Expanded( flex: 2, child: CustomButton( icon: AppAssets.view_report_icon, iconColor: AppColors.primaryRedColor, iconSize: 16.h, text: LocaleKeys.viewResults.tr(context: context), onPressed: () { model.currentlySelectedPatientOrder = order; labProvider.getPatientLabResultByHospital(order); labProvider.getPatientSpecialResult(order); Navigator.of(context).push( CustomPageRoute(page: LabResultByClinic(labOrder: order)), ); }, backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, fontSize: 14, fontWeight: FontWeight.w500, borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, ), ) ], ), // SizedBox(height: 12.h), // Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), // SizedBox(height: 12.h), ], ).paddingOnly(top: 16, bottom: 16); }, separatorBuilder: (cxt, index) => Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), itemCount: group.length)) : SizedBox.shrink(), ), ], ), ), ), ), )); }, ) : Utils.getNoDataWidget(context, noDataText: LocaleKeys.noLabResults.tr(context: context))) : // By Test or other tabs keep existing behavior (model.isLabOrdersLoading) ? Column( children: List.generate( 5, (index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, )), ) : AlphabeticScroll( alpahbetsAvailable: model.indexedCharacterForUniqueTest, details: model.uniqueTestsList, labViewModel: model, rangeViewModel: rangeViewModel, appState: _appState, ) ], )); }, ), ); } Color getLabOrderStatusColor(num status) { switch (status) { case 44: return AppColors.warningColorYellow; case 45: return AppColors.warningColorYellow; case 16: return AppColors.successColor; case 17: return AppColors.successColor; default: return AppColors.greyColor; } } String getLabOrderStatusText(num status) { switch (status) { case 44: return LocaleKeys.resultsPending.tr(context: context); case 45: return LocaleKeys.resultsPending.tr(context: context); case 16: return LocaleKeys.resultsAvailable.tr(context: context); case 17: return LocaleKeys.resultsAvailable.tr(context: context); default: return ""; } } getLabSuggestions(LabViewModel model) { if (model.patientLabOrders.isEmpty) { return []; } return model.patientLabOrders.map((m) => m.testDetails).toList(); } } \ No newline at end of file diff --git a/lib/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart b/lib/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart index ad4a0320..8fb30900 100644 --- a/lib/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart +++ b/lib/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart @@ -1,17 +1,22 @@ -import 'package:easy_localization/easy_localization.dart' - show tr, StringTranslateExtension; +import 'package:easy_localization/easy_localization.dart' show tr, StringTranslateExtension; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; +import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_order_response_by_ai_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/LabResultList.dart'; +import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/ai_analysis_widget.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/lab_order_specialResult.dart'; +import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -25,6 +30,7 @@ class LabResultByClinic extends StatelessWidget { late LabViewModel labViewModel; PatientLabOrdersResponseModel labOrder; + @override Widget build(BuildContext context) { labViewModel = Provider.of(context, listen: false); @@ -35,6 +41,21 @@ class LabResultByClinic extends StatelessWidget { Expanded( child: CollapsingListView( title: LocaleKeys.labResults.tr(), + aiOverview: () async { + final _dialogService = getIt.get(); + await _dialogService.showCommonBottomSheetWithoutH( + message: LocaleKeys.aiDisclaimer.tr(), + label: LocaleKeys.consent.tr(), + okLabel: LocaleKeys.acceptLbl.tr(), + cancelLabel: LocaleKeys.rejectView.tr(), + onOkPressed: () { + context.pop(); + labViewModel.getAiOverviewLabOrders(labOrder: labOrder, loadingText: LocaleKeys.loadingAIAnalysis.tr(context: context)); + }, + onCancelPressed: () { + context.pop(); + }); + }, child: SingleChildScrollView( child: Column( spacing: 8.h, @@ -77,7 +98,7 @@ class LabResultByClinic extends StatelessWidget { } }, ), - LabOrderSpecialResult() + LabOrderSpecialResult(), ], ).paddingAll(24.h), )), @@ -89,9 +110,9 @@ class LabResultByClinic extends StatelessWidget { hasShadow: true, ), child: CustomButton( - text: "Download report".needTranslation, + text: LocaleKeys.downloadReport.tr(context: context), onPressed: () async { - LoaderBottomSheet.showLoader(loadingText: "Generating report, Please wait...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.generatingReport.tr(context: context)); await labViewModel .getLabResultReportPDF( labOrder: labOrder, @@ -114,7 +135,7 @@ class LabResultByClinic extends StatelessWidget { } catch (ex) { showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "Cannot open file".needTranslation), + child: Utils.getErrorWidget(loadingText: "Cannot open file"), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, diff --git a/lib/presentation/lab/lab_result_via_clinic/LabResultList.dart b/lib/presentation/lab/lab_result_via_clinic/LabResultList.dart index 3f054436..6caa4a59 100644 --- a/lib/presentation/lab/lab_result_via_clinic/LabResultList.dart +++ b/lib/presentation/lab/lab_result_via_clinic/LabResultList.dart @@ -1,8 +1,10 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/lab_result.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/lab_order_result_item.dart'; import 'package:provider/provider.dart' show Selector, Provider, ReadContext; @@ -17,8 +19,7 @@ class LabResultList extends StatelessWidget { builder: (__, list, ___) { if (list.isEmpty && context.read().labSpecialResult.isEmpty) { return Utils.getNoDataWidget(context, - noDataText: "You don't have any lab results yet." - .needTranslation); + noDataText: LocaleKeys.noLabResults.tr(context: context)); } else { return ListView.builder( physics: NeverScrollableScrollPhysics(), diff --git a/lib/presentation/lab/lab_result_via_clinic/ai_analysis_widget.dart b/lib/presentation/lab/lab_result_via_clinic/ai_analysis_widget.dart new file mode 100644 index 00000000..f0c9b26b --- /dev/null +++ b/lib/presentation/lab/lab_result_via_clinic/ai_analysis_widget.dart @@ -0,0 +1,101 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_order_response_by_ai_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +class AiAnalysisWidget extends StatelessWidget { + final LabOrderResponseByAi data; + + const AiAnalysisWidget({super.key, required this.data}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration(borderRadius: BorderRadius.circular(24.r), gradient: AppColors.aiLinearGradient), + padding: EdgeInsets.all(1.5.r), // Border thickness + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(22.r), + ), + padding: EdgeInsets.all(20.r), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.aiAnalysis.tr().toText18(weight: FontWeight.w700, color: AppColors.blackColor), + Container( + padding: EdgeInsets.all(8.r), + decoration: BoxDecoration(gradient: AppColors.aiLinearGradient, borderRadius: BorderRadius.circular(8.r)), + child: SvgPicture.asset(AppAssets.aiOverView, colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn), height: 18.r, width: 18.r), + ), + ], + ), + SizedBox(height: 16.h), + if (data.summary != null && data.summary!.isNotEmpty) ...[ + LocaleKeys.suggestion.tr().toText16(weight: FontWeight.w700, color: AppColors.blackColor), + SizedBox(height: 8.h), + data.summary!.toText14(color: AppColors.textColorLight, height: 1.5, weight: FontWeight.w400), + SizedBox(height: 16.h), + const Divider(color: AppColors.dividerColor, thickness: 1), + SizedBox(height: 16.h), + ], + if (data.aiPredictions != null && data.aiPredictions!.isNotEmpty) ...[ + LocaleKeys.prediction.tr().toText16(weight: FontWeight.w700, color: AppColors.blackColor), + SizedBox(height: 12.h), + ...data.aiPredictions!.map((item) => _buildBulletItem(item)), + SizedBox(height: 16.h), + const Divider(color: AppColors.dividerColor, thickness: 1), + SizedBox(height: 16.h), + ], + if (data.prevention != null && data.prevention!.isNotEmpty) ...[ + LocaleKeys.prevention.tr().toText16(weight: FontWeight.w700, color: AppColors.blackColor), + SizedBox(height: 12.h), + ...data.prevention!.map((item) => _buildBulletItem(item)), + SizedBox(height: 20.h), + ], + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon(Icons.info_outline, color: AppColors.greyTextColor, size: 20.r), + SizedBox(width: 8.w), + Expanded( + child: ("${LocaleKeys.disclaimer.tr()}:${LocaleKeys.thisAboveInfo.tr()}").toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w400), + ), + ], + ), + ], + ), + ), + ); + } + + Widget _buildBulletItem(String text) { + return Padding( + padding: EdgeInsets.only(bottom: 12.h), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only(top: 8.h, right: 12.w, left: 4.w), + child: Container( + width: 5.r, + height: 5.r, + decoration: const BoxDecoration(color: AppColors.blackColor, shape: BoxShape.circle), + ), + ), + Expanded( + child: text.toText14(color: AppColors.textColorLight, height: 1.5, weight: FontWeight.w400), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/lab/lab_result_via_clinic/lab_order_result_item.dart b/lib/presentation/lab/lab_result_via_clinic/lab_order_result_item.dart index c6841b34..ba97731f 100644 --- a/lib/presentation/lab/lab_result_via_clinic/lab_order_result_item.dart +++ b/lib/presentation/lab/lab_result_via_clinic/lab_order_result_item.dart @@ -11,6 +11,7 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:provider/provider.dart'; +import 'dart:ui' as ui; class LabOrderResultItem extends StatelessWidget { final VoidCallback onTap; @@ -43,49 +44,52 @@ class LabOrderResultItem extends StatelessWidget { ), (tests!.packageShortDescription ?? "").toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), SizedBox(height: 12.h), - Row( - mainAxisSize: MainAxisSize.max, - children: [ - Flexible( - child: Text( - "${tests?.resultValue} ${tests?.uOM}", - style: TextStyle( - fontSize: 24.f, - fontWeight: FontWeight.w600, - fontFamily: 'Poppins', - color: tests!.checkIfGraphShouldBeDisplayed() - ? context.read().getColor( - tests?.calculatedResultFlag ?? "", - ) - : Colors.grey.shade700, - letterSpacing: -2, - ), - ), - ), - SizedBox(width: 4.h,), - Visibility( - // visible: tests?.checkIfGraphShouldBeDisplayed() == true, - visible: true, - child: Expanded( - flex: 2, - child: Visibility( - visible: tests?.referanceRange != null, + Directionality( + textDirection: ui.TextDirection.ltr, + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Flexible( child: Text( - "(Reference range: ${tests?.referanceRange})".needTranslation, + "${tests?.resultValue} ${tests?.uOM}", style: TextStyle( - fontSize: 12.f, - fontWeight: FontWeight.w500, + fontSize: 24.f, + fontWeight: FontWeight.w600, fontFamily: 'Poppins', - color: AppColors.greyTextColor, + color: tests!.checkIfGraphShouldBeDisplayed() + ? context.read().getColor( + tests?.calculatedResultFlag ?? "", + ) + : Colors.grey.shade700, + letterSpacing: -2, ), - // overflow: TextOverflow.ellipsis, - // maxLines: 2, - softWrap: true, ), ), - ), - ) - ], + SizedBox(width: 4.h,), + Visibility( + // visible: tests?.checkIfGraphShouldBeDisplayed() == true, + visible: true, + child: Expanded( + flex: 2, + child: Visibility( + visible: tests?.referanceRange != null, + child: Text( + "(${LocaleKeys.referenceRange.tr(context: context)}: ${tests?.referanceRange})", + style: TextStyle( + fontSize: 12.f, + fontWeight: FontWeight.w500, + fontFamily: 'Poppins', + color: AppColors.greyTextColor, + ), + // overflow: TextOverflow.ellipsis, + // maxLines: 2, + softWrap: true, + ), + ), + ), + ) + ], + ), ), SizedBox(height: 12.h), diff --git a/lib/presentation/lab/lab_results/lab_result_details.dart b/lib/presentation/lab/lab_results/lab_result_details.dart index 1d54e067..cab20aed 100644 --- a/lib/presentation/lab/lab_results/lab_result_details.dart +++ b/lib/presentation/lab/lab_results/lab_result_details.dart @@ -1,45 +1,72 @@ - import 'package:easy_localization/easy_localization.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/lab_result.dart'; +import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_order_response_by_ai_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/ai_analysis_widget.dart'; +import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/date_range_calender.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_results/lab_result_list_item.dart'; import 'package:hmg_patient_app_new/theme/colors.dart' show AppColors; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/graph/custom_graph.dart'; -import 'package:provider/provider.dart' show Consumer, Provider, ReadContext; +import 'package:provider/provider.dart' show Consumer, Provider, ReadContext, Selector; -import '../../../widgets/common_bottom_sheet.dart' - show showCommonBottomSheetWithoutHeight; +import '../../../widgets/common_bottom_sheet.dart' show showCommonBottomSheetWithoutHeight; class LabResultDetails extends StatelessWidget { final LabResult recentLabResult; final String? testDescription; - const LabResultDetails( - {super.key, - required this.recentLabResult, - required this.testDescription}); + const LabResultDetails({super.key, required this.recentLabResult, required this.testDescription}); @override Widget build(BuildContext context) { + LabViewModel labViewModel = Provider.of(context, listen: false); + final appState = getIt.get(); return CollapsingListView( - title: 'Lab Result Details'.needTranslation, + title: LocaleKeys.labResultDetails.tr(context: context), + aiOverview: () async { + final _dialogService = getIt.get(); + await _dialogService.showCommonBottomSheetWithoutH( + message: LocaleKeys.aiDisclaimer.tr(), + label: LocaleKeys.consent.tr(), + okLabel: LocaleKeys.acceptLbl.tr(), + cancelLabel: LocaleKeys.rejectView.tr(), + onOkPressed: () { + context.pop(); + labViewModel.getAiOverviewSingleLabResult(langId: appState.getLanguageID().toString(), recentLabResult: recentLabResult, loadingText: LocaleKeys.loadingAIAnalysis.tr(context: context)); + }, + onCancelPressed: () { + context.pop(); + }); + }, child: SingleChildScrollView( child: Column( spacing: 16.h, children: [ LabNameAndStatus(context), getLabDescription(context), - LabGraph(context) + LabGraph(context), + Selector( + selector: (_, model) => model.labOrderResponseByAi, + builder: (_, aiData, __) { + if (aiData != null) { + return AiAnalysisWidget(data: aiData).paddingOnly(bottom: 16.h); + } + return const SizedBox.shrink(); + }, + ), ], ).paddingAll(24.h), ), @@ -47,11 +74,7 @@ class LabResultDetails extends StatelessWidget { } Widget LabNameAndStatus(BuildContext context) => Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.h, - hasShadow: true, - ), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), padding: EdgeInsets.all(16.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -74,7 +97,7 @@ class LabResultDetails extends StatelessWidget { .getSeverityText( recentLabResult.calculatedResultFlag ?? "", ) - .tr() + .tr(context: context) .toText10(weight: FontWeight.w500, color: AppColors.greyTextColor), Utils.buildSvgWithAssets( icon: AppAssets.lab_result_indicator, @@ -89,7 +112,7 @@ class LabResultDetails extends StatelessWidget { ], ), SizedBox(height: 4.h), - ("Result of ${recentLabResult.verifiedOn ?? ""}".needTranslation).toText11(weight: FontWeight.w500, color: AppColors.greyTextColor), + ("${LocaleKeys.resultOf.tr(context: context)} ${recentLabResult.verifiedOn ?? ""}").toText11(weight: FontWeight.w500, color: AppColors.greyTextColor), ], ), Row( @@ -107,7 +130,9 @@ class LabResultDetails extends StatelessWidget { ), letterSpacing: -2), ), - SizedBox(width: 4.h,), + SizedBox( + width: 4.h, + ), ], ), ), @@ -116,7 +141,7 @@ class LabResultDetails extends StatelessWidget { Visibility( visible: recentLabResult.referanceRange != null, child: Text( - "Reference range: \n${recentLabResult.referanceRange!.trim()}".needTranslation, + "${LocaleKeys.referenceRange.tr(context: context)}: \n${recentLabResult.referanceRange!.trim()}", style: TextStyle( fontSize: 12.f, fontWeight: FontWeight.w500, @@ -150,9 +175,7 @@ class LabResultDetails extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - labmodel.isGraphVisible - ? LocaleKeys.historyFlowchart.tr() - : LocaleKeys.history.tr(), + labmodel.isGraphVisible ? LocaleKeys.historyFlowchart.tr(context: context) : LocaleKeys.history.tr(context: context), style: TextStyle( fontSize: 16, fontFamily: 'Poppins', @@ -164,24 +187,14 @@ class LabResultDetails extends StatelessWidget { spacing: 16.h, children: [ //todo handle when the graph icon is being displayed - Utils.buildSvgWithAssets( - icon: labmodel.isGraphVisible - ? AppAssets.ic_list - : AppAssets.ic_graph, - width: 24.h, - height: 24.h) - .onPress(() { + Utils.buildSvgWithAssets(icon: labmodel.isGraphVisible ? AppAssets.ic_list : AppAssets.ic_graph, width: 24.h, height: 24.h).onPress(() { if (labmodel.shouldShowGraph) { labmodel.alterGraphVisibility(); } }), - Utils.buildSvgWithAssets( - icon: AppAssets.ic_date_filter, - width: 24, - height: 24) - .onPress(() { + Utils.buildSvgWithAssets(icon: AppAssets.ic_date_filter, width: 24, height: 24).onPress(() { showCommonBottomSheetWithoutHeight( - title: LocaleKeys.setTheDateRange.tr(), + title: LocaleKeys.setTheDateRange.tr(context: context), context, child: DateRangeSelector( onRangeSelected: (start, end) { @@ -218,21 +231,17 @@ class LabResultDetails extends StatelessWidget { Widget buildBottomLabel(String label) { return Padding( - padding: const EdgeInsets.only(top:8.0), + padding: const EdgeInsets.only(top: 8.0), child: Text( label, - style: TextStyle( - fontSize: 8.f, - fontFamily: 'Poppins', - fontWeight: FontWeight.w600, - color: AppColors.labelTextColor), + style: TextStyle(fontSize: 8.f, fontFamily: 'Poppins', fontWeight: FontWeight.w600, color: AppColors.labelTextColor), ), ); } Widget historyBody(LabViewModel labmodel) { if (labmodel.isGraphVisible && labmodel.shouldShowGraph) { - var graphColor = labmodel.getColor(recentLabResult.calculatedResultFlag??"N"); + var graphColor = labmodel.getColor(recentLabResult.calculatedResultFlag ?? "N"); return CustomGraph( dataPoints: labmodel.filteredGraphValues, makeGraphBasedOnActualValue: true, @@ -242,11 +251,11 @@ class LabResultDetails extends StatelessWidget { // maxY: (labmodel.maxY)+(getInterval(labmodel)??0)/2, maxY: (labmodel.maxY), minY: labmodel.minY, - maxX: labmodel.filteredGraphValues.length.toDouble()-.75, + maxX: labmodel.filteredGraphValues.length.toDouble() - .75, horizontalInterval: .1, - getDrawingHorizontalLine: (value){ + getDrawingHorizontalLine: (value) { value = double.parse(value.toStringAsFixed(1)); - if(value == labmodel.highRefrenceValue ||value== labmodel.lowRefenceValue) { + if (value == labmodel.highRefrenceValue || value == labmodel.lowRefenceValue) { return FlLine( color: AppColors.bgGreenColor.withOpacity(0.6), strokeWidth: 1, @@ -256,25 +265,28 @@ class LabResultDetails extends StatelessWidget { return FlLine( color: Colors.transparent, strokeWidth: 1, - );; + ); + ; }, leftLabelFormatter: (value) { value = double.parse(value.toStringAsFixed(1)); // return leftLabels(value.toStringAsFixed(2)); - if(value == labmodel.highRefrenceValue) - return leftLabels("High".needTranslation); + if (value == labmodel.highRefrenceValue) { + return leftLabels(LocaleKeys.high.tr()); + } - if(value== labmodel.lowRefenceValue) - return leftLabels("Low".needTranslation); + if (value == labmodel.lowRefenceValue) { + return leftLabels(LocaleKeys.low.tr()); + } - return SizedBox.shrink(); + return SizedBox.shrink(); // } }, - graphColor:AppColors.blackColor, + graphColor: AppColors.blackColor, graphShadowColor: Colors.transparent, graphGridColor: graphColor.withOpacity(.4), bottomLabelFormatter: (value, data) { - if(data.isEmpty) return SizedBox.shrink(); + if (data.isEmpty) return SizedBox.shrink(); if (value == 0) { return buildBottomLabel(data[value.toInt()].label); } @@ -286,34 +298,34 @@ class LabResultDetails extends StatelessWidget { } return SizedBox.shrink(); }, - rangeAnnotations: RangeAnnotations( - horizontalRangeAnnotations: _buildRangeShades(labmodel) - ), - minX:(labmodel.filteredGraphValues.length == 1)?null : -.2, + rangeAnnotations: RangeAnnotations(horizontalRangeAnnotations: _buildRangeShades(labmodel)), + minX: (labmodel.filteredGraphValues.length == 1) ? null : -.2, scrollDirection: Axis.horizontal, height: 180.h); - }else { + } else { return labHistoryList(labmodel); } } - List _buildRangeShades( LabViewModel model,) { + List _buildRangeShades( + LabViewModel model, + ) { List ranges = []; - ranges.add(HorizontalRangeAnnotation( - y1:model.minY, - y2: model.lowRefenceValue, - color: AppColors.highAndLow.withOpacity(0.05), - )); + ranges.add(HorizontalRangeAnnotation( + y1: model.minY, + y2: model.lowRefenceValue, + color: AppColors.highAndLow.withOpacity(0.05), + )); ranges.add(HorizontalRangeAnnotation( - y1:model.lowRefenceValue, + y1: model.lowRefenceValue, y2: model.highRefrenceValue, color: AppColors.bgGreenColor.withOpacity(0.05), )); ranges.add(HorizontalRangeAnnotation( - y1:model.highRefrenceValue, + y1: model.highRefrenceValue, y2: model.maxY, color: AppColors.criticalLowAndHigh.withOpacity(0.05), )); @@ -322,11 +334,12 @@ class LabResultDetails extends StatelessWidget { Widget labHistoryList(LabViewModel labmodel) { return SizedBox( - height: labmodel.filteredGraphValues.length<3?labmodel.filteredGraphValues.length*64:180.h, + height: labmodel.filteredGraphValues.length < 3 ? labmodel.filteredGraphValues.length * 64 : 180.h, child: ListView.separated( padding: EdgeInsets.zero, - itemCount: labmodel.filteredGraphValues.length,itemBuilder: (context, index){ - var data = labmodel.filteredGraphValues.reversed.toList()[index]; + itemCount: labmodel.filteredGraphValues.length, + itemBuilder: (context, index) { + var data = labmodel.filteredGraphValues.reversed.toList()[index]; return LabHistoryItem( dayNameAndDate: labmodel.getFormattedDate(data.time), result: data.actualValue, @@ -366,12 +379,8 @@ class LabResultDetails extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, spacing: 8.h, children: [ - "What is this result?" - .needTranslation - .toText16(weight: FontWeight.w600, color: AppColors.textColor), - testDescription?.toText12( - fontWeight: FontWeight.w500, color: AppColors.textColorLight) ?? - SizedBox.shrink() + LocaleKeys.whatIsThisResult.tr(context: context).toText16(weight: FontWeight.w600, color: AppColors.textColor), + testDescription?.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight) ?? SizedBox.shrink() ], )); } diff --git a/lib/presentation/labAiAnalysis/lab_ai_analysis_detailed_page.dart b/lib/presentation/labAiAnalysis/lab_ai_analysis_detailed_page.dart new file mode 100644 index 00000000..5d4fe4b0 --- /dev/null +++ b/lib/presentation/labAiAnalysis/lab_ai_analysis_detailed_page.dart @@ -0,0 +1,538 @@ +import 'dart:convert'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/expandable_list_widget.dart'; +import 'package:provider/provider.dart'; + +import '../../features/lab/models/resp_models/ai_overview_labresults_by_ai_model.dart'; + +class LabAiAnalysisDetailedPage extends StatefulWidget { + const LabAiAnalysisDetailedPage({super.key}); + + @override + State createState() => _LabAiAnalysisDetailedPageState(); +} + +class _LabAiAnalysisDetailedPageState extends State { + late LabViewModel labProvider; + + @override + Widget build(BuildContext context) { + labProvider = Provider.of(context, listen: false); + return CollapsingListView( + title: LocaleKeys.aiAnalysis.tr(context: context), + child: Consumer( + builder: (context, model, child) { + if (model.labOrdersResponseByAi == null || model.labOrdersResponseByAi?.analysis == null) { + return Utils.getNoDataWidget(context); + } + + final analysis = model.labOrdersResponseByAi!.analysis!; + List expandableItems = []; + + // Summary Section + if (analysis.summary != null && analysis.summary!.isNotEmpty) { + expandableItems.add( + ExpandableListItem( + title: LocaleKeys.summary.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), + initiallyExpanded: true, + children: [ + SizedBox(height: 10.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, side: BorderSide(width: 1, color: Color(0xFF0B85F7))), + padding: EdgeInsets.all(16.r), + child: (analysis.summary ?? "").toText14(color: AppColors.textColorLight, height: 1.5, weight: FontWeight.w400), + ), + SizedBox(height: 8.h), + ], + ), + ); + } + + // AI Generated Attributes Section (Lab Test Results) + if (analysis.aiGeneratedAttributes != null && analysis.aiGeneratedAttributes!.isNotEmpty) { + expandableItems.add( + ExpandableListItem( + title: LocaleKeys.labResults.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), + children: [ + SizedBox(height: 10.h), + ...analysis.aiGeneratedAttributes!.map((attr) { + return Column( + children: [ + _buildCombinedLabCard(attr), + SizedBox(height: 16.h), + ], + ); + }).toList(), + ], + ), + ); + } + + // AI Predictions Section + if (analysis.aiPredictions != null && analysis.aiPredictions!.isNotEmpty) { + expandableItems.add( + ExpandableListItem( + title: LocaleKeys.prediction.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), + children: [ + SizedBox(height: 10.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, side: BorderSide(width: 1, color: Color(0xFFFFAF15))), + padding: EdgeInsets.all(16.r), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: analysis.aiPredictions!.map((prediction) => _buildBulletItem(prediction)).toList(), + ), + ), + SizedBox(height: 8.h), + ], + ), + ); + } + + // AI Recommendations Section + if (analysis.aiRecommendations != null && analysis.aiRecommendations!.isNotEmpty) { + expandableItems.add( + ExpandableListItem( + title: LocaleKeys.suggestions.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), + children: [ + SizedBox(height: 10.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r), + padding: EdgeInsets.all(16.r), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: analysis.aiRecommendations!.asMap().entries.map((entry) { + final rec = entry.value; + final isLast = entry.key == analysis.aiRecommendations!.length - 1; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (rec.priority != null) ...[ + Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), + decoration: BoxDecoration(color: _getPriorityColor(rec.priority), borderRadius: BorderRadius.circular(4.r)), + child: rec.priority!.toText12(fontWeight: FontWeight.w600, color: _getPriorityTextColor(rec.priority)), + ), + SizedBox(height: 8.h), + ], + (rec.recommendation ?? "").toText14( + color: AppColors.textColorLight, + height: 1.5, + weight: FontWeight.w400, + ), + if (!isLast) ...[ + SizedBox(height: 12.h), + const Divider(color: AppColors.dividerColor, thickness: 1), + SizedBox(height: 12.h), + ], + ], + ); + }).toList(), + ), + ), + SizedBox(height: 8.h), + ], + ), + ); + } + + // Risk Factors Section + if (analysis.riskFactors != null && analysis.riskFactors!.isNotEmpty) { + expandableItems.add( + ExpandableListItem( + title: LocaleKeys.riskFactors.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), + children: [ + SizedBox(height: 10.h), + ...analysis.riskFactors!.asMap().entries.map((entry) { + final risk = entry.value; + return Column( + children: [ + Container( + decoration: RoundedRectangleBorder() + .toSmoothCornerDecoration(color: Color(0xFFFBCB6E).withOpacity(0.10), borderRadius: 12.r, side: BorderSide(width: 1, color: Color(0xFFFBCB6E).withOpacity(0.10))), + padding: EdgeInsets.all(16.r), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Container( + decoration: BoxDecoration(color: Color(0xFFFBCB6E).withAlpha(38), borderRadius: BorderRadius.circular(8)), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: (risk.risk ?? "").toText10(weight: FontWeight.w500, color: AppColors.blackColor), + ), + ), + ), + ], + ), + if (risk.risk != null && risk.risk!.isNotEmpty) ...[ + SizedBox(height: 4.h), + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.guradIcon), + Padding( + padding: const EdgeInsets.all(8.0), + child: (risk.factor ?? "").toText16(weight: FontWeight.w700, color: AppColors.blackColor), + ), + ], + ), + SizedBox(height: 4.h), + (risk.description ?? "").toText14( + color: AppColors.textColorLight, + height: 1.5, + weight: FontWeight.w400, + ), + ], + if (risk.description != null && risk.description!.isNotEmpty) ...[ + SizedBox(height: 8.h), + (risk.description ?? "").toText14( + color: AppColors.textColorLight, + height: 1.5, + weight: FontWeight.w400, + ), + ], + ], + ), + ), + SizedBox(height: 16.h), + ], + ); + }).toList(), + ], + ), + ); + } + + // Prevention Section + if (analysis.prevention != null && analysis.prevention!.isNotEmpty) { + expandableItems.add( + ExpandableListItem( + title: LocaleKeys.prevention.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), + children: [ + SizedBox(height: 10.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, side: BorderSide(width: 1, color: Color(0xFF18C273))), + padding: EdgeInsets.all(16.r), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: analysis.prevention!.map((prevention) => _buildBulletItem(prevention)).toList(), + ), + ), + SizedBox(height: 8.h), + ], + ), + ); + } + + // // Suggested Doctors Section + // if (analysis.suggestedDoctors != null && analysis.suggestedDoctors!.isNotEmpty) { + // expandableItems.add( + // ExpandableListItem( + // title: "Suggested Doctors".toText18(weight: FontWeight.w700, color: AppColors.blackColor), + // children: [ + // SizedBox(height: 10.h), + // Container( + // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + // color: AppColors.whiteColor, + // borderRadius: 12.r, + // side: BorderSide(width: 1, color: Color(0xFF3498DB)), + // ), + // padding: EdgeInsets.all(16.r), + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: analysis.suggestedDoctors!.asMap().entries.map((entry) { + // final doctor = entry.value; + // final isLast = entry.key == analysis.suggestedDoctors!.length - 1; + // return Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Row( + // children: [ + // Container( + // width: 40.r, + // height: 40.r, + // decoration: BoxDecoration( + // gradient: AppColors.aiLinearGradient, + // shape: BoxShape.circle, + // ), + // child: Icon( + // Icons.person, + // color: Colors.white, + // size: 24.r, + // ), + // ), + // SizedBox(width: 12.w), + // Expanded( + // child: doctor.toString().toText14( + // color: AppColors.textColorLight, + // height: 1.5, + // weight: FontWeight.w600, + // ), + // ), + // ], + // ), + // if (!isLast) ...[ + // SizedBox(height: 12.h), + // const Divider(color: AppColors.dividerColor, thickness: 1), + // SizedBox(height: 12.h), + // ], + // ], + // ); + // }).toList(), + // ), + // ), + // SizedBox(height: 8.h), + // ], + // ), + // ); + // } + + return Column( + children: [ + if (expandableItems.isEmpty) + Utils.getNoDataWidget(context) + else + CustomExpandableList( + expansionMode: ExpansionMode.exactlyOne, + dividerColor: Color(0xff2b353e1a), + itemPadding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 14.h), + items: expandableItems, + theme: ExpandableListTheme.custom( + defaultTrailingIcon: Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, height: 22.h, width: 22.w, iconColor: AppColors.textColor), + ), + ).paddingSymmetrical(16.w, 0.0), + ], + ); + }, + ), + ); + } + + Color _getPriorityColor(String? priority) { + if (priority == null) return AppColors.primaryRedColor; + switch (priority.toLowerCase()) { + case 'high': + return Color(0xFFED1C2B).withOpacity(.10); + case 'medium' || 'moderate': + return AppColors.lightGreenButtonColor; + case 'low': + return AppColors.warningColorYellow.withOpacity(.3); + default: + return AppColors.primaryRedColor; + } + } + + Color _getPriorityTextColor(String? priority) { + if (priority == null) return AppColors.whiteColor; + switch (priority.toLowerCase()) { + case 'high': + return AppColors.primaryRedColor; + case 'medium' || 'moderate': + return AppColors.textGreenColor; + case 'low': + return AppColors.alertColor; + default: + return AppColors.primaryRedColor; + } + } + + + + Color _getResultFlagColor(String? flag) { + if (flag == null || flag.isEmpty) return Colors.grey; + switch (flag.toUpperCase()) { + case 'H': // High + case 'HH': // Very High + case 'CRITICAL HIGH': + return AppColors.lightRedButtonColor; + case 'L': // Low + case 'LL': // Very Low + case 'CRITICAL LOW': + return AppColors.chipColorSeekMedicalAdvice.withAlpha(75); + case 'N': // Normal + return AppColors.lightGreenButtonColor; + case 'A': // Abnormal + return Colors.deepOrange; + default: + return AppColors.greyLightColor; + } + } + + Color _getResultTextColor(String? priority) { + if (priority == null) return AppColors.whiteColor; + switch (priority.toUpperCase()) { + case 'H': // High + case 'HH': // Very High + case 'CRITICAL HIGH': + return AppColors.primaryRedColor; + case 'N': + return AppColors.textGreenColor; + case 'L': // Low + case 'LL': // Very Low + case 'CRITICAL LOW': + return AppColors.chipTextColorSeekMedicalAdvice; + default: + return AppColors.textColor; + } + } + + Widget _buildBulletItem(String text) { + return Padding( + padding: EdgeInsets.only(bottom: 12.h), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: text.toText14(color: AppColors.textColorLight, height: 1.5, weight: FontWeight.w400), + ), + ], + ), + ); + } + + Widget _buildCombinedLabCard(AiGeneratedAttribute attr) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 2.h, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (attr.calculatedResultFlag != null && attr.calculatedResultFlag!.isNotEmpty && attr.calculatedResultFlag == "-") ...[ + Row( + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), + decoration: BoxDecoration(color: AppColors.greyLightColor, borderRadius: BorderRadius.circular(4.r)), + child: Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.informationIcon, width: 14.w, height: 14.h), + SizedBox(width: 4.w), + LocaleKeys.contextDependent.tr().toText12(fontWeight: FontWeight.w600, color: AppColors.textColor), + ], + ), + ), + ], + ), + ] else ...[ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), + decoration: BoxDecoration(color: _getResultFlagColor(attr.calculatedResultFlag), borderRadius: BorderRadius.circular(4.r)), + child: Row( + children: [ + (attr.calculatedResultFlag ?? "").toText12(fontWeight: FontWeight.w600, color: _getResultTextColor(attr.calculatedResultFlag)), + ], + ), + ), + if (attr.calculatedResultFlag != null && attr.calculatedResultFlag!.isNotEmpty && attr.calculatedResultFlag == "H") + Utils.buildSvgWithAssets(icon: AppAssets.highCautionIcon, iconColor: AppColors.primaryRedColor, width: 24.w, height: 24.h), + ], + ), + ], + SizedBox(height: 4.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded(child: (attr.description ?? "").toText16(isBold: true, color: _getResultTextColor(attr.calculatedResultFlag))), + + // if (attr.calculatedResultFlag != null && attr.calculatedResultFlag!.isNotEmpty) + // Column( + // spacing: 2.h, + // children: [ + // Consumer( + // builder: (context, labViewModel, child) { + // return labViewModel.getSeverityText(attr.calculatedResultFlag ?? "").tr(context: context).toText10(weight: FontWeight.w500, color: AppColors.greyTextColor); + // }, + // ), + // Consumer( + // builder: (context, labViewModel, child) { + // return Utils.buildSvgWithAssets( + // icon: AppAssets.lab_result_indicator, + // width: 21, + // height: 23, + // iconColor: labViewModel.getColor(attr.calculatedResultFlag ?? ""), + // ); + // }, + // ), + // ], + // ), + ], + ), + SizedBox(height: 4.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded(child: (attr.testShortDescription ?? "").toText12(color: AppColors.textColor)), + ], + ), + SizedBox(height: 4.h) + ], + ), + Row( + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), + decoration: BoxDecoration(color: _getResultFlagColor(attr.calculatedResultFlag), borderRadius: BorderRadius.circular(4.r)), + child: ("${LocaleKeys.calculatedValue.tr()}: ${attr.resultValue!}").toText12(fontWeight: FontWeight.w600, color: _getResultTextColor(attr.calculatedResultFlag)), + ), + SizedBox(width: 8.h), + Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), + decoration: BoxDecoration(color: AppColors.greyLightColor, borderRadius: BorderRadius.circular(4.r)), + child: ("${LocaleKeys.unit.tr()}: ${attr.uom!}").toText12(fontWeight: FontWeight.w600, color: AppColors.textColor), + ), + ], + ), + SizedBox( + height: 4.h, + ), + if (attr.referanceRange != null && attr.referanceRange!.isNotEmpty) + Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), + decoration: BoxDecoration(color: AppColors.greyLightColor, borderRadius: BorderRadius.circular(4.r)), + child: ("${LocaleKeys.referenceRange.tr(context: context)}: ${attr.referanceRange!.trim()}").toText12(fontWeight: FontWeight.w600, color: AppColors.textColor), + ), + if (attr.calculationFormula != null && attr.calculationFormula!.isNotEmpty) ...[ + SizedBox(height: 8.h), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.calendar, iconColor: Colors.grey), + SizedBox(width: 8.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + LocaleKeys.calculationFormula.tr().toText16(weight: FontWeight.w600), + (attr.calculationFormula ?? "").toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), + ], + ), + ), + ], + ), + ], + ], + ), + ); + } +} diff --git a/lib/presentation/medical_file/eye_measurement_details_page.dart b/lib/presentation/medical_file/eye_measurement_details_page.dart index 0662cb15..08226095 100644 --- a/lib/presentation/medical_file/eye_measurement_details_page.dart +++ b/lib/presentation/medical_file/eye_measurement_details_page.dart @@ -96,7 +96,7 @@ class EyeMeasurementDetailsPage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.leftEye.tr().needTranslation.toText14(isBold: true), + LocaleKeys.leftEye.tr().toText14(isBold: true), SizedBox(height: 16.h), getRow(LocaleKeys.sphere.tr(), '${patientAppointmentHistoryResponseModel.listHISGetGlassPrescription![0].leftEyeSpherical}', '-'), getRow(LocaleKeys.cylinder.tr(), '${patientAppointmentHistoryResponseModel.listHISGetGlassPrescription![0].leftEyeCylinder}', '-'), @@ -139,7 +139,7 @@ class EyeMeasurementDetailsPage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.leftEye.tr().needTranslation.toText14(isBold: true), + LocaleKeys.leftEye.tr().toText14(isBold: true), SizedBox(height: 16.h), getRow(LocaleKeys.brand.tr(), '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![1].brand}', ''), getRow('B.C', '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![1].baseCurve}', ''), diff --git a/lib/presentation/medical_file/eye_measurements_appointments_page.dart b/lib/presentation/medical_file/eye_measurements_appointments_page.dart index 3b82ad41..d8438ba4 100644 --- a/lib/presentation/medical_file/eye_measurements_appointments_page.dart +++ b/lib/presentation/medical_file/eye_measurements_appointments_page.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; @@ -7,6 +8,7 @@ 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/appointment_card.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; @@ -75,7 +77,7 @@ class EyeMeasurementsAppointmentsPage extends StatelessWidget { ), ), ) - : Utils.getNoDataWidget(context, noDataText: "No Ophthalmology appointments found...".needTranslation); + : Utils.getNoDataWidget(context, noDataText: LocaleKeys.noOphthalmologyAppointments.tr(context: context)); }, separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), ), diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index c6ff85f3..ddac1bb8 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -17,6 +17,7 @@ import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/active_prescriptions/models/active_prescriptions_response_model.dart'; +import 'package:hmg_patient_app_new/features/ask_doctor/ask_doctor_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; @@ -38,6 +39,7 @@ import 'package:hmg_patient_app_new/presentation/allergies/allergies_list_page.d import 'package:hmg_patient_app_new/presentation/appointments/my_appointments_page.dart'; import 'package:hmg_patient_app_new/presentation/appointments/my_doctors_page.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/book_appointment/book_appointment_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_profile_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart'; @@ -82,7 +84,9 @@ import '../prescriptions/prescription_detail_page.dart'; import 'widgets/medical_file_appointment_card.dart'; class MedicalFilePage extends StatefulWidget { - const MedicalFilePage({super.key}); + bool showBackIcon; + + MedicalFilePage({super.key, this.showBackIcon = true}); @override State createState() => _MedicalFilePageState(); @@ -161,21 +165,21 @@ class _MedicalFilePageState extends State { trailing: Row( children: [ Wrap( - spacing: -8.h, + spacing: -12.h, // runSpacing: 0.h, children: [ Utils.buildImgWithAssets( icon: AppAssets.babyGirlImg, - height: 28.h, - width: 28.w, + height: 32.h, + width: 32.w, border: 1, fit: BoxFit.contain, borderRadius: 50.r, ), Utils.buildImgWithAssets( icon: AppAssets.femaleImg, - height: 28.h, - width: 28.w, + height: 32.h, + width: 32.w, border: 1, borderRadius: 50.r, fit: BoxFit.contain, @@ -183,6 +187,7 @@ class _MedicalFilePageState extends State { Utils.buildImgWithAssets( icon: AppAssets.maleImg, height: 32.h, + width: 32.w, border: 1.5, borderRadius: 50.r, fit: BoxFit.contain, @@ -192,25 +197,26 @@ class _MedicalFilePageState extends State { SizedBox(width: 4.h), Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, height: 22.h, width: 22.w) ], - ).withHorizontalPadding(24.w).onPress(() { + ).onPress(() { DialogService dialogService = getIt.get(); dialogService.showFamilyBottomSheetWithoutH( - label: "Family Files".needTranslation, - message: "This clinic or doctor is only available for the below eligible profiles.".needTranslation, + label: LocaleKeys.familyTitle.tr(context: context), + message: "", + isShowManageButton: true, onSwitchPress: (FamilyFileResponseModelLists profile) { medicalFileViewModel.switchFamilyFiles(responseID: profile.responseId, patientID: profile.patientId, phoneNumber: profile.mobileNumber); }, profiles: medicalFileViewModel.patientFamilyFiles); }), - isLeading: true, - leadingCallback: () { - Navigator.pushAndRemoveUntil( - navigationService.navigatorKey.currentContext!, - CustomPageRoute( - page: LandingNavigation(), - ), - (r) => false); - }, + isLeading: widget.showBackIcon, + // leadingCallback: () { + // Navigator.pushAndRemoveUntil( + // navigationService.navigatorKey.currentContext!, + // CustomPageRoute( + // page: LandingNavigation(), + // ), + // (r) => false); + // }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -244,16 +250,8 @@ class _MedicalFilePageState extends State { children: [ AppCustomChipWidget( icon: AppAssets.file_icon, - labelText: "${LocaleKeys.fileno.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}", + richText: "${LocaleKeys.fileno.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}".toText10(isEnglishOnly: true), labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), - onChipTap: () { - navigationService.pushPage( - page: FamilyMedicalScreen( - profiles: medicalFileViewModel.patientFamilyFiles, - onSelect: (FamilyFileResponseModelLists p1) {}, - ), - ); - }, ), AppCustomChipWidget( icon: AppAssets.checkmark_icon, @@ -276,12 +274,12 @@ class _MedicalFilePageState extends State { runSpacing: 4.h, children: [ AppCustomChipWidget( - labelText: "${appState.getAuthenticatedUser()!.age} Years Old", + labelText: LocaleKeys.ageYearsOld.tr(namedArgs: {'age': '${appState.getAuthenticatedUser()!.age}', 'yearsOld': LocaleKeys.yearsOld.tr(context: context)}, context: context), labelPadding: EdgeInsetsDirectional.only(start: 8.w, end: 8.w), ), AppCustomChipWidget( icon: AppAssets.blood_icon, - labelText: "Blood: ${appState.getUserBloodGroup.isEmpty ? "N/A" : appState.getUserBloodGroup}", + labelText: LocaleKeys.bloodGroup.tr(namedArgs: {'bloodType': appState.getUserBloodGroup.isEmpty ? "N/A" : appState.getUserBloodGroup}, context: context), iconColor: AppColors.primaryRedColor, labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), padding: EdgeInsets.zero, @@ -289,12 +287,11 @@ class _MedicalFilePageState extends State { Consumer(builder: (context, insuranceVM, child) { return AppCustomChipWidget( icon: insuranceVM.isInsuranceExpired ? AppAssets.cancel_circle_icon : AppAssets.insurance_active_icon, - labelText: insuranceVM.isInsuranceExpired ? "Insurance Expired".needTranslation : "Insurance Active".needTranslation, + labelText: insuranceVM.isInsuranceExpired ? LocaleKeys.insuranceExpired.tr(context: context) : LocaleKeys.insuranceActive.tr(context: context), iconColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, textColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, iconSize: 12.w, - backgroundColor: - insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.successColor.withOpacity(0.1), + backgroundColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.successColor.withOpacity(0.1), labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), ); }), @@ -316,10 +313,10 @@ class _MedicalFilePageState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Vital Signs".needTranslation.toText16(weight: FontWeight.w500, letterSpacing: -0.2), + LocaleKeys.vitalSigns.tr(context: context).toText16(weight: FontWeight.w500, letterSpacing: -0.2), Row( children: [ - LocaleKeys.viewAll.tr().toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), SizedBox(width: 2.h), Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), ], @@ -359,7 +356,7 @@ class _MedicalFilePageState extends State { children: [ Utils.buildSvgWithAssets(icon: AppAssets.call_for_vitals, width: 32.h, height: 32.h), SizedBox(height: 12.h), - "No vital signs recorded yet".needTranslation.toText12(isCenter: true), + LocaleKeys.noVitalSignsRecordedYet.tr(context: context).toText12(isCenter: true), ], ), ), @@ -417,20 +414,20 @@ class _MedicalFilePageState extends State { }), SizedBox(height: 16.h), - TextInputWidget( - labelText: LocaleKeys.search.tr(context: context), - hintText: "Type any record".needTranslation, - controller: TextEditingController(), - keyboardType: TextInputType.number, - isEnable: true, - prefix: null, - autoFocus: false, - isBorderAllowed: false, - isAllowLeadingIcon: true, - padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h), - leadingIcon: AppAssets.search_icon, - hintColor: AppColors.textColor, - ).paddingSymmetrical(24.w, 0.0), + // TextInputWidget( + // labelText: LocaleKeys.search.tr(context: context), + // hintText: "Type any record".needTranslation, + // controller: TextEditingController(), + // keyboardType: TextInputType.number, + // isEnable: true, + // prefix: null, + // autoFocus: false, + // isBorderAllowed: false, + // isAllowLeadingIcon: true, + // padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h), + // leadingIcon: AppAssets.search_icon, + // hintColor: AppColors.textColor, + // ).paddingSymmetrical(24.w, 0.0), SizedBox(height: 16.h), // Using CustomExpandableList CustomExpandableList( @@ -439,23 +436,23 @@ class _MedicalFilePageState extends State { itemPadding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 14.h), items: [ ExpandableListItem( - title: "Medical Services".toText18(weight: FontWeight.w600), + title: LocaleKeys.medicalServices.tr(context: context).toText18(weight: FontWeight.w600), children: [ SizedBox(height: 10.h), getSelectedTabData(0), ], ), ExpandableListItem( - title: "Medical Reports".toText18( - weight: FontWeight.w600, - ), + title: LocaleKeys.medicalReports.tr(context: context).toText18( + weight: FontWeight.w600, + ), expandedBackgroundColor: Colors.transparent, children: [ SizedBox(height: 10.h), getSelectedTabData(2), ]), ExpandableListItem( - title: "Insurance & Payments".toText18(weight: FontWeight.w600), + title: LocaleKeys.insuranceAndPayments.tr(context: context).toText18(weight: FontWeight.w600), expandedBackgroundColor: Colors.transparent, children: [ SizedBox(height: 10.h), @@ -463,7 +460,7 @@ class _MedicalFilePageState extends State { ], ), ExpandableListItem( - title: "Tracker & Others".toText18(weight: FontWeight.w600), + title: LocaleKeys.trackerAndOthers.tr(context: context).toText18(weight: FontWeight.w600), expandedBackgroundColor: Colors.transparent, children: [ SizedBox(height: 10.h), @@ -547,7 +544,7 @@ class _MedicalFilePageState extends State { onSuccess: (dynamic respData) async { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( - title: "Pick a Date".needTranslation, + title: LocaleKeys.pickADate.tr(context: context), context, child: AppointmentCalendar(), isFullScreen: false, @@ -577,10 +574,10 @@ class _MedicalFilePageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Appointments & visits".needTranslation.toText16(weight: FontWeight.w500, letterSpacing: -0.2), + LocaleKeys.appointmentsAndVisits.tr(context: context).toText16(weight: FontWeight.w500, letterSpacing: -0.2), Row( children: [ - LocaleKeys.viewAll.tr().toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), SizedBox(width: 2.h), Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), ], @@ -609,13 +606,12 @@ class _MedicalFilePageState extends State { ? Container( padding: EdgeInsets.all(12.w), width: MediaQuery.of(context).size.width, - decoration: - RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, hasShadow: false), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, hasShadow: false), child: Column( children: [ Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h), SizedBox(height: 12.h), - "You do not have any appointments. Please book an appointment".needTranslation.toText12(isCenter: true), + LocaleKeys.noUpcomingAppointmentPleaseBook.tr(context: context).toText12(isCenter: true), SizedBox(height: 12.h), CustomButton( text: LocaleKeys.bookAppo.tr(context: context), @@ -645,6 +641,7 @@ class _MedicalFilePageState extends State { scrollDirection: Axis.horizontal, shrinkWrap: true, itemCount: myAppointmentsVM.patientAppointmentsHistoryList.length, + padding: EdgeInsets.zero, itemBuilder: (context, index) { return AnimationConfiguration.staggeredList( position: index, @@ -662,7 +659,7 @@ class _MedicalFilePageState extends State { openDoctorScheduleCalendar(myAppointmentsVM.patientAppointmentsHistoryList[index]); }, onAskDoctorTap: () async { - LoaderBottomSheet.showLoader(loadingText: "Checking doctor availability...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingDoctorAvailability.tr(context: context)); await myAppointmentsViewModel.isDoctorAvailable( projectID: myAppointmentsVM.patientAppointmentsHistoryList[index].projectID, doctorId: myAppointmentsVM.patientAppointmentsHistoryList[index].doctorID, @@ -677,8 +674,7 @@ class _MedicalFilePageState extends State { child: AskDoctorRequestTypeSelect( askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList, myAppointmentsViewModel: myAppointmentsViewModel, - patientAppointmentHistoryResponseModel: - myAppointmentsVM.patientAppointmentsHistoryList[index], + patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index], ), callBackFunc: () {}, isFullScreen: false, @@ -687,7 +683,6 @@ class _MedicalFilePageState extends State { }); } else { LoaderBottomSheet.hideLoader(); - print("Doctor is not available"); } }, onError: (_) { @@ -700,12 +695,12 @@ class _MedicalFilePageState extends State { ), )); }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 12.h), + separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 12.w), ), - ).paddingSymmetrical(0.w, 0.h); + ); }), SizedBox(height: 10.h), - "Lab & Radiology".needTranslation.toText16(weight: FontWeight.w500, letterSpacing: -0.2), + LocaleKeys.labAndRadiology.tr(context: context).toText16(weight: FontWeight.w500, letterSpacing: -0.2), SizedBox(height: 16.h), Row( children: [ @@ -728,7 +723,7 @@ class _MedicalFilePageState extends State { Expanded( child: LabRadCard( icon: AppAssets.radiology_icon, - labelText: "${LocaleKeys.radiology.tr(context: context)} Results".needTranslation, + labelText: "${LocaleKeys.radiology.tr(context: context)} ${LocaleKeys.results.tr(context: context)}", // labOrderTests: ["Complete blood count", "Creatinine", "Blood Sugar", // labOrderTests: ["Chest X-ray", "Abdominal Ultrasound", "Dental X-ray"], labOrderTests: [], @@ -744,7 +739,7 @@ class _MedicalFilePageState extends State { ], ).paddingSymmetrical(0.w, 0.h), SizedBox(height: 24.h), - "Active Medications & Prescriptions".needTranslation.toText16(weight: FontWeight.w500, letterSpacing: -0.2), + LocaleKeys.activeMedicationsAndPrescriptions.tr(context: context).toText16(weight: FontWeight.w500, letterSpacing: -0.2), SizedBox(height: 16.h), Consumer(builder: (context, prescriptionVM, child) { return prescriptionVM.isPrescriptionsOrdersLoading @@ -757,8 +752,7 @@ class _MedicalFilePageState extends State { child: Column( children: [ ListView.separated( - itemCount: - prescriptionVM.patientPrescriptionOrders.length <= 2 ? prescriptionVM.patientPrescriptionOrders.length : 2, + itemCount: prescriptionVM.patientPrescriptionOrders.length <= 2 ? prescriptionVM.patientPrescriptionOrders.length : 2, shrinkWrap: true, padding: EdgeInsets.only(left: 0, right: 8.w), physics: NeverScrollableScrollPhysics(), @@ -789,13 +783,11 @@ class _MedicalFilePageState extends State { spacing: 3.w, runSpacing: 4.w, children: [ - AppCustomChipWidget( - labelText: prescriptionVM.patientPrescriptionOrders[index].clinicDescription!), + AppCustomChipWidget(labelText: prescriptionVM.patientPrescriptionOrders[index].clinicDescription!), AppCustomChipWidget( icon: AppAssets.doctor_calendar_icon, labelText: DateUtil.formatDateToDate( - DateUtil.convertStringToDate( - prescriptionVM.patientPrescriptionOrders[index].appointmentDate), + DateUtil.convertStringToDate(prescriptionVM.patientPrescriptionOrders[index].appointmentDate), false, ), ), @@ -808,19 +800,13 @@ class _MedicalFilePageState extends State { Transform.flip( flipX: appState.isArabic(), child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon_small, - width: 15.w, - height: 15.h, - fit: BoxFit.contain, - iconColor: AppColors.textColor)), + icon: AppAssets.forward_arrow_icon_small, width: 15.w, height: 15.h, fit: BoxFit.contain, iconColor: AppColors.textColor)), ], ).onPress(() { prescriptionVM.setPrescriptionsDetailsLoading(); Navigator.of(context).push( CustomPageRoute( - page: PrescriptionDetailPage( - isFromAppointments: false, - prescriptionsResponseModel: prescriptionVM.patientPrescriptionOrders[index]), + page: PrescriptionDetailPage(isFromAppointments: false, prescriptionsResponseModel: prescriptionVM.patientPrescriptionOrders[index]), ), ); }), @@ -836,7 +822,7 @@ class _MedicalFilePageState extends State { children: [ Expanded( child: CustomButton( - text: "All Prescriptions".needTranslation, + text: LocaleKeys.allPrescriptions.tr(context: context), onPressed: () { Navigator.of(context).push( CustomPageRoute( @@ -859,7 +845,7 @@ class _MedicalFilePageState extends State { SizedBox(width: 6.w), Expanded( child: CustomButton( - text: "All Medications".needTranslation, + text: LocaleKeys.allMedications.tr(context: context), onPressed: () {}, backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, @@ -887,7 +873,7 @@ class _MedicalFilePageState extends State { ), child: Utils.getNoDataWidget( context, - noDataText: "You don't have any prescriptions yet.".needTranslation, + noDataText: LocaleKeys.youDontHaveAnyPrescriptionsYet.tr(context: context), isSmallWidget: true, width: 62.w, height: 62.h, @@ -902,7 +888,7 @@ class _MedicalFilePageState extends State { LocaleKeys.myDoctor.tr(context: context).toText16(weight: FontWeight.w500, letterSpacing: -0.2), Row( children: [ - LocaleKeys.viewAll.tr().toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), SizedBox(width: 2.w), Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), ], @@ -929,10 +915,7 @@ class _MedicalFilePageState extends State { fit: BoxFit.cover, ).circle(100).toShimmer2(isShow: true, radius: 50.r), SizedBox(height: 8.h), - ("Dr. John Smith Smith Smith") - .toString() - .toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2) - .toShimmer2(isShow: true), + ("Dr. John Smith Smith Smith").toString().toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2).toShimmer2(isShow: true), ], ) : myAppointmentsVM.patientMyDoctorsList.isEmpty @@ -945,7 +928,7 @@ class _MedicalFilePageState extends State { ), child: Utils.getNoDataWidget( context, - noDataText: "You don't have any completed visits yet".needTranslation, + noDataText: LocaleKeys.youDontHaveAnyCompletedVisitsYet.tr(context: context), isSmallWidget: true, width: 62.w, height: 62.h, @@ -1017,7 +1000,7 @@ class _MedicalFilePageState extends State { ).paddingSymmetrical(0.w, 0); }), SizedBox(height: 24.h), - "Others".needTranslation.toText16(weight: FontWeight.w500, letterSpacing: -0.2), + LocaleKeys.others.tr(context: context).toText16(weight: FontWeight.w500, letterSpacing: -0.2), SizedBox(height: 16.h), GridView( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( @@ -1031,7 +1014,7 @@ class _MedicalFilePageState extends State { shrinkWrap: true, children: [ MedicalFileCard( - label: "Eye Test Results".needTranslation, + label: LocaleKeys.eyeMeasurements.tr(context: context), textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon, @@ -1048,7 +1031,7 @@ class _MedicalFilePageState extends State { ); }), MedicalFileCard( - label: "Allergy Info".needTranslation, + label: "Allergy Information".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.allergy_info_icon, @@ -1063,7 +1046,7 @@ class _MedicalFilePageState extends State { ); }), MedicalFileCard( - label: "Vaccine Info".needTranslation, + label: "Vaccine Information".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.vaccine_info_icon, @@ -1108,7 +1091,7 @@ class _MedicalFilePageState extends State { ), child: Utils.getNoDataWidget( context, - noDataText: "You don't have insurance registered with HMG.".needTranslation, + noDataText: LocaleKeys.noInsuranceWithHMG.tr(context: context), isSmallWidget: true, width: 62.w, height: 62.h, @@ -1119,14 +1102,9 @@ class _MedicalFilePageState extends State { text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}", onPressed: () { insuranceViewModel.setIsInsuranceUpdateDetailsLoading(true); - insuranceViewModel.getPatientInsuranceDetailsForUpdate(appState.getAuthenticatedUser()!.patientId.toString(), - appState.getAuthenticatedUser()!.patientIdentificationNo.toString()); - showCommonBottomSheetWithoutHeight(context, - child: PatientInsuranceCardUpdateCard(), - callBackFunc: () {}, - title: "", - isCloseButtonVisible: false, - isFullScreen: false); + insuranceViewModel.getPatientInsuranceDetailsForUpdate( + appState.getAuthenticatedUser()!.patientId.toString(), appState.getAuthenticatedUser()!.patientIdentificationNo.toString()); + showCommonBottomSheetWithoutHeight(context, child: PatientInsuranceCardUpdateCard(), callBackFunc: () {}, title: "", isCloseButtonVisible: false, isFullScreen: false); }, backgroundColor: AppColors.bgGreenColor.withOpacity(0.20), borderColor: AppColors.bgGreenColor.withOpacity(0.0), @@ -1153,7 +1131,7 @@ class _MedicalFilePageState extends State { shrinkWrap: true, children: [ MedicalFileCard( - label: "Update Insurance".needTranslation, + label: LocaleKeys.updateInsuranceInfo.tr(context: context), textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.update_insurance_icon, @@ -1177,7 +1155,7 @@ class _MedicalFilePageState extends State { ); }), MedicalFileCard( - label: "My Invoices List".needTranslation, + label: LocaleKeys.myInvoicesList.tr(context: context), textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.invoices_list_icon, @@ -1191,7 +1169,7 @@ class _MedicalFilePageState extends State { ); }), MedicalFileCard( - label: "Ancillary Orders List".needTranslation, + label: LocaleKeys.ancillaryOrdersList.tr(context: context), textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.ancillary_orders_list_icon, @@ -1232,7 +1210,7 @@ class _MedicalFilePageState extends State { ), child: Utils.getNoDataWidget( context, - noDataText: "You don't have any sick leaves yet.".needTranslation, + noDataText: LocaleKeys.youDontHaveAnySickLeavesYet.tr(context: context), isSmallWidget: true, width: 62.w, height: 62.h, @@ -1267,7 +1245,7 @@ class _MedicalFilePageState extends State { ); }), MedicalFileCard( - label: "Medical Reports".needTranslation, + label: LocaleKeys.medicalReports.tr(context: context), textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.medical_reports_icon, @@ -1283,7 +1261,7 @@ class _MedicalFilePageState extends State { ); }), MedicalFileCard( - label: "Sick Leave Report".needTranslation, + label: LocaleKeys.sickLeaveReport.tr(context: context), textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.sick_leave_report_icon, @@ -1308,7 +1286,7 @@ class _MedicalFilePageState extends State { children: [ Row( children: [ - "Health Trackers".needTranslation.toText16(weight: FontWeight.w500, color: AppColors.textColor), + LocaleKeys.healthTrackers.tr(context: context).toText16(weight: FontWeight.w500, color: AppColors.textColor), ], ), SizedBox(height: 16.h), @@ -1324,7 +1302,7 @@ class _MedicalFilePageState extends State { shrinkWrap: true, children: [ MedicalFileCard( - label: "Blood Sugar".needTranslation, + label: LocaleKeys.bloodSugar.tr(context: context), textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.blood_sugar_icon, @@ -1332,7 +1310,7 @@ class _MedicalFilePageState extends State { iconSize: 36.w, ).onPress(() => context.navigateWithName(AppRoutes.healthTrackerDetailPage, arguments: HealthTrackerTypeEnum.bloodSugar)), MedicalFileCard( - label: "Blood Pressure".needTranslation, + label: LocaleKeys.bloodPressure.tr(context: context), textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.lab_result_icon, @@ -1340,7 +1318,7 @@ class _MedicalFilePageState extends State { iconSize: 36.w, ).onPress(() => context.navigateWithName(AppRoutes.healthTrackerDetailPage, arguments: HealthTrackerTypeEnum.bloodPressure)), MedicalFileCard( - label: "Weight Tracker".needTranslation, + label: LocaleKeys.weightTracker.tr(context: context), textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.weight_tracker_icon, @@ -1352,7 +1330,7 @@ class _MedicalFilePageState extends State { SizedBox(height: 16.h), Row( children: [ - "Others".needTranslation.toText16(weight: FontWeight.w500, color: AppColors.textColor), + LocaleKeys.others.tr(context: context).toText16(weight: FontWeight.w500, color: AppColors.textColor), ], ), SizedBox(height: 16.h), @@ -1368,21 +1346,28 @@ class _MedicalFilePageState extends State { shrinkWrap: true, children: [ MedicalFileCard( - label: "Ask Your Doctor".needTranslation, + label: LocaleKeys.askYourDoctor.tr(context: context), textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.ask_doctor_medical_file_icon, isLargeText: true, iconSize: 36.w, - ).onPress(() {}), - MedicalFileCard( - label: "Internet Pairing".needTranslation, - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.internet_pairing_icon, - isLargeText: true, - iconSize: 36.w, - ).onPress(() {}), + ).onPress(() { + getIt.get().initAskDoctorViewModel(); + Navigator.of(context).push( + CustomPageRoute( + page: AskDoctorPage(), + ), + ); + }), + // MedicalFileCard( + // label: LocaleKeys.internetPairing.tr(context: context), + // textColor: AppColors.blackColor, + // backgroundColor: AppColors.whiteColor, + // svgIcon: AppAssets.internet_pairing_icon, + // isLargeText: true, + // iconSize: 36.w, + // ).onPress(() {}), ], ).paddingSymmetrical(0.w, 0.0), SizedBox(height: 24.h), @@ -1479,13 +1464,13 @@ class _MedicalFilePageState extends State { return [ // Page 1: BMI + Height Padding( - padding: EdgeInsets.only(left: 24.w), + padding: EdgeInsets.only(left: 24.w), child: Row( children: [ Expanded( child: _buildVitalSignCard( icon: AppAssets.bmiVital, - label: "BMI", + label: LocaleKeys.bmi.tr(context: context), value: vitalSign.bodyMassIndex?.toString() ?? '--', unit: '', status: vitalSign.bodyMassIndex != null ? _getBMIStatus(vitalSign.bodyMassIndex) : null, @@ -1496,7 +1481,7 @@ class _MedicalFilePageState extends State { Expanded( child: _buildVitalSignCard( icon: AppAssets.heightVital, - label: "Height", + label: LocaleKeys.height.tr(context: context), value: vitalSign.heightCm?.toString() ?? '--', unit: 'cm', status: null, @@ -1507,37 +1492,36 @@ class _MedicalFilePageState extends State { ), ), // Page 2: Weight + Blood Pressure - Padding(padding: EdgeInsets.symmetric(horizontal: 12.w),child: Row( - children: [ - Expanded( - child: _buildVitalSignCard( - icon: AppAssets.weightVital, - label: "Weight", - value: vitalSign.weightKg?.toString() ?? '--', - unit: 'kg', - status: vitalSign.weightKg != null ? "Normal" : null, - onTap: onTap, + Padding( + padding: EdgeInsets.symmetric(horizontal: 12.w), + child: Row( + children: [ + Expanded( + child: _buildVitalSignCard( + icon: AppAssets.weightVital, + label: LocaleKeys.weight.tr(context: context), + value: vitalSign.weightKg?.toString() ?? '--', + unit: 'kg', + status: vitalSign.weightKg != null ? "Normal" : null, + onTap: onTap, + ), ), - ), - SizedBox(width: 12.w), - Expanded( - child: _buildVitalSignCard( - icon: AppAssets.bloodPressure, - label: "Blood Pressure", - value: vitalSign.bloodPressureLower != null && vitalSign.bloodPressureHigher != null - ? "${vitalSign.bloodPressureHigher}/${vitalSign.bloodPressureLower}" - : '--', - unit: '', - status: _getBloodPressureStatus( - systolic: vitalSign.bloodPressureHigher, - diastolic: vitalSign.bloodPressureLower, + SizedBox(width: 12.w), + Expanded( + child: _buildVitalSignCard( + icon: AppAssets.bloodPressure, + label: LocaleKeys.bloodPressure.tr(context: context), + value: vitalSign.bloodPressureLower != null && vitalSign.bloodPressureHigher != null ? "${vitalSign.bloodPressureHigher}/${vitalSign.bloodPressureLower}" : '--', + unit: '', + status: _getBloodPressureStatus( + systolic: vitalSign.bloodPressureHigher, + diastolic: vitalSign.bloodPressureLower, + ), + onTap: onTap, ), - onTap: onTap, ), - ), - ], - )), - + ], + )), ]; } @@ -1595,6 +1579,7 @@ class _MedicalFilePageState extends State { weight: FontWeight.w600, ), ), + Utils.buildSvgWithAssets(icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward, width: 18.w, height: 18.h), ], ), SizedBox(height: 14.h), @@ -1617,6 +1602,7 @@ class _MedicalFilePageState extends State { child: value.toText17( isBold: true, color: AppColors.textColor, + isEnglishOnly: true ), ), if (unit.isNotEmpty) ...[ @@ -1636,14 +1622,12 @@ class _MedicalFilePageState extends State { backgroundColor: scheme.chipBg, textColor: scheme.chipFg, ), - ] - else + ] else AppCustomChipWidget( labelText: "", backgroundColor: AppColors.bgScaffoldColor, - textColor:null, + textColor: null, ) - ], ), ), diff --git a/lib/presentation/medical_file/patient_sickleaves_list_page.dart b/lib/presentation/medical_file/patient_sickleaves_list_page.dart index ef5aaeb4..93cf17ff 100644 --- a/lib/presentation/medical_file/patient_sickleaves_list_page.dart +++ b/lib/presentation/medical_file/patient_sickleaves_list_page.dart @@ -17,6 +17,7 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:open_filex/open_filex.dart'; import 'package:provider/provider.dart'; @@ -197,55 +198,29 @@ class _PatientSickleavesListPageState extends State { ], ), SizedBox(height: 8.h), - Row( + Wrap( + direction: Axis.horizontal, + spacing: 6.h, + runSpacing: 6.h, children: [ - CustomButton( - text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(sickLeave.appointmentDate), false), - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 24.h, + AppCustomChipWidget( + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(sickLeave.appointmentDate), false), ), - SizedBox(width: 8.h), - CustomButton( - text: model.isSickLeavesSortByClinic ? sickLeave.projectName! : sickLeave.clinicName!, - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 24.h, + AppCustomChipWidget( + labelText: model.isSickLeavesSortByClinic ? sickLeave.projectName! : sickLeave.clinicName!, ), - SizedBox(width: 8.h), - CustomButton( - text: "${sickLeave.sickLeaveDays} Days", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 24.h, + AppCustomChipWidget( + labelText: "${sickLeave.sickLeaveDays} Days", ), ], ), - SizedBox(height: 8.h), + SizedBox(height: 12.h), Row( children: [ Expanded( flex: 6, child: CustomButton( - text: "Download Report".needTranslation, + text: LocaleKeys.downloadReport.tr(context: context), onPressed: () async { LoaderBottomSheet.showLoader(); await medicalFileViewModel.getPatientSickLeavePDF(sickLeave, appState.getAuthenticatedUser()!).then((val) async { @@ -293,7 +268,7 @@ class _PatientSickleavesListPageState extends State { ), ), ) - : Utils.getNoDataWidget(context, noDataText: "You don't have any sick leaves yet.".needTranslation); + : Utils.getNoDataWidget(context, noDataText: LocaleKeys.youDontHaveAnySickLeavesYet.tr(context: context)); }, ).paddingSymmetrical(24.h, 0.h), ], diff --git a/lib/presentation/medical_file/vaccine_list_page.dart b/lib/presentation/medical_file/vaccine_list_page.dart index 777426f6..bf02cb74 100644 --- a/lib/presentation/medical_file/vaccine_list_page.dart +++ b/lib/presentation/medical_file/vaccine_list_page.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -9,6 +10,7 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:provider/provider.dart'; @@ -40,7 +42,7 @@ class _VaccineListPageState extends State { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( - title: "Vaccine Info".needTranslation, + title: LocaleKeys.vaccineInfo.tr(context: context), child: SingleChildScrollView( child: Consumer(builder: (context, medicalFileVM, child) { return Column( @@ -170,7 +172,7 @@ class _VaccineListPageState extends State { ), ), ) - : Utils.getNoDataWidget(context, noDataText: "No vaccines data found...".needTranslation); + : Utils.getNoDataWidget(context, noDataText: LocaleKeys.noDataAvailable.tr(context: context)); }, separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), ), diff --git a/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart b/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart index fbe79bb7..73dd1652 100644 --- a/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart +++ b/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart @@ -18,6 +18,8 @@ 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 'dart:ui' as ui; + class MedicalFileAppointmentCard extends StatelessWidget { final PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel; final MyAppointmentsViewModel myAppointmentsViewModel; @@ -39,11 +41,12 @@ class MedicalFileAppointmentCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ AppCustomChipWidget( - richText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false) - .toText12( - color: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor, - fontWeight: FontWeight.w500) - .paddingOnly(left: 8.w), + richText: Directionality( + textDirection: ui.TextDirection.ltr, + child: DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false) + .toText12(color: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor, fontWeight: FontWeight.w500, isEnglishOnly: true) + .paddingSymmetrical(8.w, 0), + ), icon: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppAssets.appointment_calendar_icon : AppAssets.alarm_clock_icon, iconColor: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor, iconSize: 16.w, @@ -71,9 +74,7 @@ class MedicalFileAppointmentCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - (patientAppointmentHistoryResponseModel.doctorNameObj ?? "") - .toText14(isBold: true, maxlines: 1) - .toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading), + (patientAppointmentHistoryResponseModel.doctorNameObj ?? "").toText14(isBold: true, maxlines: 1).toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading), (patientAppointmentHistoryResponseModel.clinicName ?? "") .toText12(maxLine: 1, fontWeight: FontWeight.w500, color: AppColors.greyTextColor) .toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading), @@ -103,10 +104,8 @@ class MedicalFileAppointmentCard extends StatelessWidget { // widget.myAppointmentsViewModel.getPatientAppointments(true, false); }); }, - backgroundColor: - AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.15), - borderColor: - AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), + backgroundColor: AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.15), + borderColor: AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), textColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction), fontSize: 14.f, fontWeight: FontWeight.w500, @@ -183,7 +182,7 @@ class MedicalFileAppointmentCard extends StatelessWidget { iconSize: 16.h, ) : CustomButton( - text: "Rebook".needTranslation, + text: LocaleKeys.rebook.tr(context: context), onPressed: () { onRescheduleTap(); }, diff --git a/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart b/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart index 6f9b8b51..16c05d8b 100644 --- a/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart +++ b/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart @@ -94,7 +94,7 @@ class PatientSickLeaveCard extends StatelessWidget { : Expanded( flex: 6, child: CustomButton( - text: "Download Report".needTranslation, + text: LocaleKeys.downloadReport.tr(context: context), onPressed: () async { LoaderBottomSheet.showLoader(); await medicalFileViewModel.getPatientSickLeavePDF(patientSickLeavesResponseModel, _appState.getAuthenticatedUser()!).then((val) async { @@ -106,7 +106,7 @@ class PatientSickLeaveCard extends StatelessWidget { } catch (ex) { showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "Cannot open file".needTranslation), + child: Utils.getErrorWidget(loadingText: "Cannot open file"), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, diff --git a/lib/presentation/medical_report/medical_report_request_page.dart b/lib/presentation/medical_report/medical_report_request_page.dart index 8eabcdd1..47a2df32 100644 --- a/lib/presentation/medical_report/medical_report_request_page.dart +++ b/lib/presentation/medical_report/medical_report_request_page.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; @@ -6,6 +7,7 @@ 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/medical_file/medical_file_view_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/appointment_card.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; @@ -20,7 +22,7 @@ class MedicalReportRequestPage extends StatelessWidget { Widget build(BuildContext context) { medicalFileViewModel = Provider.of(context, listen: false); return CollapsingListView( - title: "Medical Reports".needTranslation, + title: LocaleKeys.medicalReports.tr(context: context), isClose: true, child: Column( children: [ diff --git a/lib/presentation/medical_report/medical_reports_page.dart b/lib/presentation/medical_report/medical_reports_page.dart index f6d7576f..87420fd5 100644 --- a/lib/presentation/medical_report/medical_reports_page.dart +++ b/lib/presentation/medical_report/medical_reports_page.dart @@ -44,7 +44,7 @@ class _MedicalReportsPageState extends State { children: [ Expanded( child: CollapsingListView( - title: "Medical Reports".needTranslation, + title: LocaleKeys.medicalReports.tr(context: context), child: SingleChildScrollView( child: Consumer(builder: (context, medicalFileVM, child) { return Column( @@ -88,7 +88,7 @@ class _MedicalReportsPageState extends State { Row( children: [ CustomButton( - text: "Requested".needTranslation, + text: LocaleKeys.requested.tr(context: context), onPressed: () { setState(() { expandedIndex = null; @@ -300,7 +300,7 @@ class _MedicalReportsPageState extends State { Expanded( flex: 6, child: CustomButton( - text: "Download Report".needTranslation, + text: LocaleKeys.downloadReport.tr(context: context), onPressed: () async { LoaderBottomSheet.showLoader(); await medicalFileViewModel.getPatientMedicalReportPDF(report, appState.getAuthenticatedUser()!).then((val) async { @@ -348,7 +348,7 @@ class _MedicalReportsPageState extends State { ), ), ) - : Utils.getNoDataWidget(context, noDataText: "You don't have any medical reports yet.".needTranslation) + : Utils.getNoDataWidget(context, noDataText: LocaleKeys.youDontHaveAnyMedicalReportsYet.tr(context: context)) .paddingSymmetrical(24.h, 24.h); }, ).paddingSymmetrical(24.h, 0.h), @@ -366,7 +366,7 @@ class _MedicalReportsPageState extends State { hasShadow: true, ), child: CustomButton( - text: "Request medical report".needTranslation, + text: LocaleKeys.requestMedicalReport.tr(context: context), onPressed: () async { LoaderBottomSheet.showLoader(); await medicalFileViewModel.getPatientMedicalReportAppointmentsList(onSuccess: (val) async { @@ -385,7 +385,7 @@ class _MedicalReportsPageState extends State { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "You do not have any appointments to request a medical report.".needTranslation), + child: Utils.getErrorWidget(loadingText: LocaleKeys.youDoNotHaveAnyAppointmentsToRequestMedicalReport.tr(context: context)), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, @@ -414,7 +414,7 @@ class _MedicalReportsPageState extends State { title: LocaleKeys.notice.tr(context: context), context, child: Utils.getWarningWidget( - loadingText: "Are you sure you want to request a medical report for this appointment?".needTranslation, + loadingText: LocaleKeys.areYouSureYouWantToRequestMedicalReport.tr(context: context), isShowActionButtons: true, onCancelTap: () { Navigator.pop(context); @@ -425,7 +425,7 @@ class _MedicalReportsPageState extends State { await medicalFileViewModel.insertRequestForMedicalReport(onSuccess: (val) { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight(context, - child: Utils.getSuccessWidget(loadingText: "Your medical report request has been successfully submitted.".needTranslation), + child: Utils.getSuccessWidget(loadingText: LocaleKeys.yourMedicalReportRequestSubmittedSuccessfully.tr(context: context)), callBackFunc: () { medicalFileViewModel.setIsPatientMedicalReportsLoading(true); medicalFileViewModel.onMedicalReportTabChange(0); diff --git a/lib/presentation/medical_report/widgets/patient_medical_report_card.dart b/lib/presentation/medical_report/widgets/patient_medical_report_card.dart index 413858d7..282a2d60 100644 --- a/lib/presentation/medical_report/widgets/patient_medical_report_card.dart +++ b/lib/presentation/medical_report/widgets/patient_medical_report_card.dart @@ -144,7 +144,7 @@ class PatientMedicalReportCard extends StatelessWidget { } catch (ex) { showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "Cannot open file".needTranslation), + child: Utils.getErrorWidget(loadingText: "Cannot open file"), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, diff --git a/lib/presentation/monthly_report/monthly_report.dart b/lib/presentation/monthly_report/monthly_report.dart index 17765104..43c7fc1d 100644 --- a/lib/presentation/monthly_report/monthly_report.dart +++ b/lib/presentation/monthly_report/monthly_report.dart @@ -80,7 +80,10 @@ class MonthlyReport extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.email.tr(context: context).toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), - "${appState.getAuthenticatedUser()!.emailAddress}".toText16(color: AppColors.textColor, weight: FontWeight.w500), + SizedBox( + width: MediaQuery.of(context).size.width * 0.7, + child: "${appState.getAuthenticatedUser()!.emailAddress}".toText16(color: AppColors.textColor, weight: FontWeight.w500, maxlines: 2), + ), ], ), ], @@ -95,10 +98,7 @@ class MonthlyReport extends StatelessWidget { Utils.buildSvgWithAssets(icon: AppAssets.prescription_remarks_icon, width: 18.w, height: 18.h), SizedBox(width: 9.h), Expanded( - child: - "This monthly health summary report reflects the health indicators and analysis results of the latest visits. Please note that this will be sent automatically from the system and it’s not considered as a official report so no medical decision should be taken based on it" - .needTranslation - .toText10(weight: FontWeight.w500, color: AppColors.greyTextColorLight), + child: LocaleKeys.monthlyHealthSummaryReportDisclaimer.tr(context: context).toText10(weight: FontWeight.w500, color: AppColors.greyTextColorLight), ), ], ), @@ -146,7 +146,7 @@ class MonthlyReport extends StatelessWidget { CustomButton( text: LocaleKeys.save.tr(), onPressed: () async { - LoaderBottomSheet.showLoader(loadingText: "Updating Monthly Report Status...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.updatingMonthlyReportStatus.tr(context: context)); await monthlyReportVM.updatePatientHealthSummaryReport( rSummaryReport: monthlyReportVM.isHealthSummaryEnabled, onSuccess: (response) async { @@ -157,7 +157,7 @@ class MonthlyReport extends StatelessWidget { ); showCommonBottomSheetWithoutHeight( context, - child: Utils.getSuccessWidget(loadingText: "Monthly Report Status Updated Successfully".needTranslation), + child: Utils.getSuccessWidget(loadingText: LocaleKeys.monthlyReportStatusUpdatedSuccessfully.tr(context: context)), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, diff --git a/lib/presentation/monthly_reports/monthly_reports_page.dart b/lib/presentation/monthly_reports/monthly_reports_page.dart deleted file mode 100644 index d1a4d0ce..00000000 --- a/lib/presentation/monthly_reports/monthly_reports_page.dart +++ /dev/null @@ -1,310 +0,0 @@ -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/monthly_reports/monthly_reports_view_model.dart'; -import 'package:hmg_patient_app_new/presentation/monthly_reports/user_agreement_page.dart'; -import 'package:provider/provider.dart'; - -import '../../generated/locale_keys.g.dart'; -import '../../theme/colors.dart'; -import '../../widgets/appbar/app_bar_widget.dart'; -import '../../widgets/input_widget.dart'; -import '../../widgets/loader/bottomsheet_loader.dart'; - -class MonthlyReportsPage extends StatefulWidget { - const MonthlyReportsPage({super.key}); - - @override - State createState() => _MonthlyReportsPageState(); -} - -class _MonthlyReportsPageState extends State { - bool isHealthSummaryEnabled = false; - bool isTermsAccepted = false; - - final TextEditingController emailController = TextEditingController(); - - @override - void dispose() { - emailController.dispose(); - super.dispose(); - } - - void _showError(String message) { - ScaffoldMessenger.of(context).hideCurrentSnackBar(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(message), - behavior: SnackBarBehavior.floating, - ), - ); - } - - void _showSuccessSnackBar() { - ScaffoldMessenger.of(context).hideCurrentSnackBar(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - "Successfully updated".needTranslation, - style: const TextStyle( - color: AppColors.whiteColor, - fontWeight: FontWeight.w600, - ), - ), - behavior: SnackBarBehavior.floating, - backgroundColor: AppColors.textGreenColor, - duration: const Duration(seconds: 2), - ), - ); - } - - Future _onSavePressed() async { - if (!isTermsAccepted) { - _showError("Please accept the terms and conditions".needTranslation); - return; - } - - final email = emailController.text.trim(); - if (email.isEmpty) { - _showError("Please enter your email".needTranslation); - return; - } - - final vm = context.read(); - - // LoaderBottomSheet.showLoader(); - final ok = await vm.saveMonthlyReport(email: email); - // LoaderBottomSheet.hideLoader(); - - if (ok) { - setState(() => isHealthSummaryEnabled = true); - _showSuccessSnackBar(); - } else { - // _showError("Failed to update".needTranslation); - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: AppColors.scaffoldBgColor, - appBar: CustomAppBar( - onBackPressed: () => Navigator.of(context).pop(), - onLanguageChanged: (_) {}, - hideLogoAndLang: true, - ), - body: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Monthly Reports".needTranslation, - style: TextStyle( - color: AppColors.textColor, - fontSize: 27.f, - fontWeight: FontWeight.w600, - ), - ), - SizedBox(height: 16.h), - - Container( - padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h), - height: 54.h, - alignment: Alignment.center, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: (12.r), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Patient Health Summary Report".needTranslation, - style: TextStyle( - color: AppColors.textColor, - fontSize: 14.f, - fontWeight: FontWeight.w600, - ), - ), - _buildToggle(), - ], - ), - ), - - SizedBox(height: 16.h), - - TextInputWidget( - controller: emailController, - labelText: "Eamil*".needTranslation, - hintText: "email@email.com", - isEnable: true, - prefix: null, - isAllowRadius: true, - isBorderAllowed: false, - isAllowLeadingIcon: true, - autoFocus: true, - keyboardType: TextInputType.emailAddress, - padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h), - onChange: (value) { - setState(() {}); - }, - ).paddingOnly(top: 8.h, bottom: 8.h), - - Row( - children: [ - Text( - "To View The Terms and Conditions".needTranslation, - style: TextStyle( - color: AppColors.textColor, - fontSize: 14.f, - fontWeight: FontWeight.w600, - ), - ), - InkWell( - child: Text( - "Click here".needTranslation, - style: TextStyle( - color: AppColors.primaryRedColor, - fontSize: 14.f, - fontWeight: FontWeight.w600, - ), - ), - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => const UserAgreementPage(), - ), - ); - }, - ), - ], - ), - - SizedBox(height: 12.h), - - GestureDetector( - onTap: () => setState(() => isTermsAccepted = !isTermsAccepted), - child: Row( - children: [ - AnimatedContainer( - duration: const Duration(milliseconds: 200), - height: 24.h, - width: 24.h, - decoration: BoxDecoration( - color: isTermsAccepted - ? AppColors.textGreenColor - : Colors.transparent, - borderRadius: BorderRadius.circular(6), - border: Border.all( - color: isTermsAccepted - ? AppColors.lightGreenColor - : AppColors.greyColor, - width: 2.h, - ), - ), - child: isTermsAccepted - ? Icon(Icons.check, size: 16.f, color: AppColors.whiteColor,) - : null, - ), - SizedBox(width: 12.h), - Text( - "I agree to the terms and conditions".needTranslation, - style: context.dynamicTextStyle( - fontSize: 12.f, - fontWeight: FontWeight.w500, - color: AppColors.textColor, - ), - ), - ], - ), - ), - - SizedBox(height: 12.h), - - Text( - "This monthly Health Summary Report reflects the health indicators and analysis results of the latest visits. Please note that this will be sent automatically from the system and it's not considered as an official report so no medical decisions should be taken based on it" - .needTranslation, - style: TextStyle( - color: AppColors.textColor, - fontSize: 10.f, - fontWeight: FontWeight.w600, - ), - ), - - SizedBox(height: 12.h), - - Image.asset('assets/images/jpg/report.jpg'), - - SizedBox(height: 16.h), - - Row( - children: [ - Expanded( - child: ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: AppColors.successColor, - foregroundColor: AppColors.whiteColor, - elevation: 0, - padding: const EdgeInsets.symmetric(vertical: 14), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - onPressed: _onSavePressed, - child: Text( - LocaleKeys.save.tr(), - style: TextStyle( - fontWeight: FontWeight.w600, - fontSize: 16.f, - ), - ), - ), - ), - ], - ), - ], - ), - ).paddingAll(16), - ); - } - - Widget _buildToggle() { - final value = isHealthSummaryEnabled; - - return AbsorbPointer( - absorbing: true, - child: AnimatedContainer( - duration: const Duration(milliseconds: 200), - width: 50.h, - height: 28.h, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: value - ? AppColors.lightGreenColor - : AppColors.greyColor.withOpacity(0.3), - ), - child: AnimatedAlign( - duration: const Duration(milliseconds: 200), - alignment: value ? Alignment.centerRight : Alignment.centerLeft, - child: Padding( - padding: const EdgeInsets.all(3), - child: Container( - width: 22.h, - height: 22.h, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: value - ? AppColors.textGreenColor - : AppColors.greyTextColor, - ), - ), - ), - ), - ), - ); - } -} diff --git a/lib/presentation/monthly_reports/user_agreement_page.dart b/lib/presentation/monthly_reports/user_agreement_page.dart deleted file mode 100644 index 73ea5643..00000000 --- a/lib/presentation/monthly_reports/user_agreement_page.dart +++ /dev/null @@ -1,117 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; -import 'package:hmg_patient_app_new/features/monthly_reports/terms_conditions_view_model.dart'; -import 'package:provider/provider.dart'; -import 'package:webview_flutter/webview_flutter.dart'; - -import '../../theme/colors.dart'; -import '../../widgets/appbar/app_bar_widget.dart'; - -class UserAgreementPage extends StatefulWidget { - const UserAgreementPage({super.key}); - - @override - State createState() => _UserAgreementPageState(); -} - -class _UserAgreementPageState extends State { - late final WebViewController _webViewController; - bool _isLoading = true; - String? _errorMessage; - - @override - void initState() { - super.initState(); - - _webViewController = WebViewController() - ..setJavaScriptMode(JavaScriptMode.unrestricted) - ..setBackgroundColor(const Color(0x00000000)) - ..setNavigationDelegate( - NavigationDelegate( - onPageStarted: (_) { - setState(() { - _isLoading = true; - }); - }, - onPageFinished: (_) { - setState(() { - _isLoading = false; - }); - }, - onWebResourceError: (error) { - }, - ), - ); - - WidgetsBinding.instance.addPostFrameCallback((_) { - final vm = - Provider.of(context, listen: false); - - vm.getTermsConditions( - onSuccess: () { - final htmlString = vm.termsConditionsHtml ?? ''; - - if (htmlString.isNotEmpty) { - setState(() { - _errorMessage = null; - _isLoading = true; - }); - _webViewController.loadHtmlString(htmlString); - } else { - setState(() { - _isLoading = false; - _errorMessage = 'لا توجد شروط متاحة حالياً'.needTranslation; - }); - } - }, - onError: (msg) { - setState(() { - _isLoading = false; - _errorMessage = msg; - }); - }, - ); - }); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: AppColors.scaffoldBgColor, - appBar: CustomAppBar( - onBackPressed: () => Navigator.of(context).pop(), - onLanguageChanged: (_) {}, - hideLogoAndLang: true, - ), - body: Stack( - children: [ - WebViewWidget(controller: _webViewController), - - if (_errorMessage != null) - Center( - child: Container( - margin: const EdgeInsets.all(16), - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: AppColors.whiteColor, - borderRadius: BorderRadius.circular(8), - ), - child: Text( - _errorMessage!, - textAlign: TextAlign.center, - style: TextStyle( - color: AppColors.primaryRedColor, - fontWeight: FontWeight.w600, - ), - ), - ), - ), - if (_isLoading) - const Center( - child: CircularProgressIndicator(), - ), - ], - ), - ); - } -} diff --git a/lib/presentation/my_family/my_family.dart b/lib/presentation/my_family/my_family.dart index 07f1a4f3..dca75747 100644 --- a/lib/presentation/my_family/my_family.dart +++ b/lib/presentation/my_family/my_family.dart @@ -1,4 +1,3 @@ - import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -20,14 +19,7 @@ import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import 'package:provider/provider.dart'; class FamilyMedicalScreen extends StatefulWidget { - final List profiles; - final Function(FamilyFileResponseModelLists) onSelect; - - const FamilyMedicalScreen({ - super.key, - required this.profiles, - required this.onSelect, - }); + const FamilyMedicalScreen({super.key}); @override State createState() => _FamilyMedicalScreenState(); @@ -42,6 +34,7 @@ class _FamilyMedicalScreenState extends State { medicalVM = context.read(); WidgetsBinding.instance.addPostFrameCallback((_) { medicalVM?.onFamilyFileTabChange(0); + medicalVM?.getAllPendingRecordsByResponseId(); }); } @@ -50,30 +43,31 @@ class _FamilyMedicalScreenState extends State { AppState appState = getIt.get(); return CollapsingListView( - title: "Medical Files".needTranslation, + title: LocaleKeys.familyTitle.tr(context: context), bottomChild: appState.getAuthenticatedUser()!.isParentUser! ? Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - customBorder: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)), - ), - padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 20.h), - child: CustomButton( - text: "Add a new family member".needTranslation, - onPressed: () { - DialogService dialogService = getIt.get(); - medicalVM!.clearAuthValues(); - dialogService.showAddFamilyFileSheet( - label: "Add Family Member".needTranslation, - message: "Please fill the below field to add a new family member to your profile".needTranslation, - onVerificationPress: () { - medicalVM!.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms); - }); - }, - icon: AppAssets.add_icon, - fontSize: 16.f, - borderRadius: 12.r, - fontWeight: FontWeight.w500)) + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + customBorder: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)), + ), + padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 20.h), + child: CustomButton( + text: LocaleKeys.addANewFamilyMember.tr(context: context), + onPressed: () { + DialogService dialogService = getIt.get(); + medicalVM!.clearAuthValues(); + dialogService.showAddFamilyFileSheet( + label: LocaleKeys.addFamilyMember.tr(context: context), + message: LocaleKeys.pleaseFillBelowFieldToAddNewFamilyMember.tr(context: context), + onVerificationPress: () { + medicalVM!.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms); + }); + }, + icon: AppAssets.add_icon, + fontSize: 16.f, + borderRadius: 12.r, + fontWeight: FontWeight.w500)) + .paddingOnly(bottom: 20.h) : SizedBox(), child: Column( mainAxisSize: MainAxisSize.min, @@ -94,18 +88,28 @@ class _FamilyMedicalScreenState extends State { ), ), appState.isChildLoggedIn ? SizedBox() : SizedBox(height: 25.h), - Selector(selector: (_, model) => model.getSelectedFamilyFileTabIndex, builder: (context, selectedIndex, child) => getFamilyTabs(index: selectedIndex)), + Selector patientFiles, List pendingFiles})>( + selector: (_, model) => (selectedIndex: model.getSelectedFamilyFileTabIndex, patientFiles: model.patientFamilyFiles, pendingFiles: model.pendingFamilyFiles), + shouldRebuild: (previous, next) { + return previous.selectedIndex != next.selectedIndex || previous.patientFiles.length != next.patientFiles.length || previous.pendingFiles.length != next.pendingFiles.length; + }, + builder: (context, data, child) => getFamilyTabs(index: data.selectedIndex, patientFiles: data.patientFiles, pendingFiles: data.pendingFiles), + ), SizedBox(height: 20.h), ], ).paddingSymmetrical(20, 0), ); } - Widget getFamilyTabs({required int index}) { + Widget getFamilyTabs({ + required int index, + required List patientFiles, + required List pendingFiles, + }) { switch (index) { case 0: return FamilyCards( - profiles: medicalVM!.patientFamilyFiles, + profiles: patientFiles, onSelect: (FamilyFileResponseModelLists profile) { medicalVM!.switchFamilyFiles(responseID: profile.responseId, patientID: profile.patientId, phoneNumber: profile.mobileNumber); }, @@ -118,7 +122,7 @@ class _FamilyMedicalScreenState extends State { ); case 1: return FamilyCards( - profiles: medicalVM!.pendingFamilyFiles, + profiles: pendingFiles, isRequestDesign: medicalVM!.getSelectedFamilyFileTabIndex == 1, onSelect: (FamilyFileResponseModelLists profile) { medicalVM!.acceptRejectFileFromFamilyMembers(id: profile.id, status: 3); diff --git a/lib/presentation/my_family/widget/family_cards.dart b/lib/presentation/my_family/widget/family_cards.dart index 3621cc30..826b1741 100644 --- a/lib/presentation/my_family/widget/family_cards.dart +++ b/lib/presentation/my_family/widget/family_cards.dart @@ -8,6 +8,7 @@ import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/family_file_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; @@ -25,6 +26,7 @@ class FamilyCards extends StatefulWidget { final bool isRequestDesign; final bool isLeftAligned; final bool isShowRemoveButton; + final bool isForWalletRecharge; const FamilyCards( {super.key, @@ -35,6 +37,7 @@ class FamilyCards extends StatefulWidget { this.isBottomSheet = false, this.isRequestDesign = false, this.isLeftAligned = false, + this.isForWalletRecharge = false, this.isShowRemoveButton = false}); @override @@ -54,12 +57,10 @@ class _FamilyCardsState extends State { children: [ Utils.buildSvgWithAssets(icon: AppAssets.alertSquare), SizedBox(width: 8.h), - "Who can view my medical file ?" - .needTranslation - .toText14(color: AppColors.textColor, isUnderLine: true, weight: FontWeight.w500) + LocaleKeys.whoCanViewMyMedicalFile.tr(context: context).toText14(color: AppColors.textColor, isUnderLine: true, weight: FontWeight.w500) .onPress(() { dialogService.showFamilyBottomSheetWithoutHWithChild( - label: "Manage Family".needTranslation, + label: LocaleKeys.manageFiles.tr(context: context), message: "", child: manageFamily(), onOkPressed: () {}, @@ -153,7 +154,7 @@ class _FamilyCardsState extends State { crossAxisCount: 2, crossAxisSpacing: 10.w, mainAxisSpacing: 10.h, - childAspectRatio: widget.isShowDetails ? 0.56.h : 0.65.h, + childAspectRatio: widget.isShowDetails ? 0.56.h : 0.64.h, ), padding: EdgeInsets.only(bottom: 20.h), itemBuilder: (context, index) { @@ -208,12 +209,26 @@ class _FamilyCardsState extends State { height: 4.h, ), Spacer(), - CustomButton( + widget.isForWalletRecharge ? CustomButton( + height: 40.h, + onPressed: () { + widget.onSelect(profile); + // if (canSwitch) widget.onSelect(profile); + }, + text: LocaleKeys.select.tr(context: context), + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 13.h, + icon: AppAssets.activeCheck, + iconColor: isActive || !canSwitch ? (isActive ? null : AppColors.greyTextColor) : AppColors.primaryRedColor, + padding: EdgeInsets.symmetric(vertical: 0, horizontal: 0), + ).paddingOnly(top: 0, bottom: 0) : CustomButton( height: 40.h, onPressed: () { if (canSwitch) widget.onSelect(profile); }, - text: isActive ? "Active".needTranslation : "Switch".needTranslation, + text: isActive ? LocaleKeys.active.tr(context: context) : LocaleKeys.switchLogin.tr(context: context), backgroundColor: isActive || !canSwitch ? Colors.grey.shade200 : AppColors.secondaryLightRedColor, borderColor: isActive || !canSwitch ? Colors.grey.shade200 : AppColors.secondaryLightRedColor, textColor: isActive || !canSwitch ? AppColors.greyTextColor : AppColors.primaryRedColor, @@ -293,6 +308,7 @@ class _FamilyCardsState extends State { alignment: WrapAlignment.start, children: [ (profile.patientName ?? "").toText14(isBold: false, isCenter: true, maxlines: 1, weight: FontWeight.w600), + SizedBox(width: 2.w,), (getStatusTextByRequest( FamilyFileEnum.values.firstWhere((e) => e.toInt == profile.status), profile.isRequestFromMySide ?? false)) .toText14( @@ -309,7 +325,7 @@ class _FamilyCardsState extends State { height: 30.h, chipType: ChipTypeEnum.alert, backgroundColor: AppColors.lightGrayBGColor, - chipText: "Medical File: ${profile.patientId ?? "N/A".needTranslation}", + chipText: "${LocaleKeys.medicalFile.tr(context: context)}: ${profile.patientId ?? "N/A"}", iconAsset: null, isShowBorder: false, borderRadius: 8.h, @@ -323,7 +339,7 @@ class _FamilyCardsState extends State { : Expanded( child: CustomButton( height: 40.h, - text: LocaleKeys.confirm.tr(), + text: LocaleKeys.acceptLbl.tr(), onPressed: () { navigationService.pop(); widget.onSelect(profile); @@ -331,6 +347,7 @@ class _FamilyCardsState extends State { backgroundColor: AppColors.lightGreenButtonColor, borderColor: AppColors.lightGreenButtonColor, textColor: AppColors.textGreenColor, + borderWidth: 0, icon: null, ), ), @@ -338,7 +355,7 @@ class _FamilyCardsState extends State { Expanded( child: CustomButton( height: 40.h, - text: profile.status == FamilyFileEnum.active.toInt ? LocaleKeys.removeMember.tr() : LocaleKeys.cancel.tr(), + text: profile.status == FamilyFileEnum.active.toInt ? LocaleKeys.removeMember.tr() : LocaleKeys.rejectView.tr(), onPressed: () { navigationService.pop(); widget.onRemove(profile); @@ -364,26 +381,26 @@ class _FamilyCardsState extends State { switch (status) { case FamilyFileEnum.active: if (isRequestFromMySide) { - return "${status.displayName} your request to be your family member".needTranslation; + return LocaleKeys.acceptedYourRequestToBeYourFamilyMember.tr(namedArgs: {'status': status.displayName}, context: context); } else { - return "can view your file".needTranslation; + return LocaleKeys.canViewYourFile.tr(context: context); } case FamilyFileEnum.pending: if (isRequestFromMySide) { - return "has a request ${status.displayName} to be your family member".needTranslation; + return LocaleKeys.hasARequestPendingToBeYourFamilyMember.tr(namedArgs: {'status': status.displayName}, context: context); } else { - return "wants to add you as their family member".needTranslation; + return LocaleKeys.wantsToAddYouAsTheirFamilyMember.tr(context: context); } case FamilyFileEnum.rejected: if (isRequestFromMySide) { - return "${status.displayName} your request to be your family member".needTranslation; + return LocaleKeys.rejectedYourRequestToBeYourFamilyMember.tr(namedArgs: {'status': status.displayName}, context: context); } else { - return "${status.displayName} your family member request".needTranslation; + return LocaleKeys.rejectedYourFamilyMemberRequest.tr(namedArgs: {'status': status.displayName}, context: context); } case FamilyFileEnum.inactive: - return "Inactive".needTranslation; + return LocaleKeys.inactive.tr(context: context); default: - return "N/A".needTranslation; + return LocaleKeys.notAvailable.tr(context: context); } } } diff --git a/lib/presentation/my_family/widget/my_family_sheet.dart b/lib/presentation/my_family/widget/my_family_sheet.dart index d469ab23..50f1b2ac 100644 --- a/lib/presentation/my_family/widget/my_family_sheet.dart +++ b/lib/presentation/my_family/widget/my_family_sheet.dart @@ -1,7 +1,9 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.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/medical_file/models/family_file_response_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/my_family/widget/family_cards.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -15,8 +17,8 @@ class MyFamilySheet { titleWidget: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - 'Please select a profile'.needTranslation.toText21(isBold: true), - 'switch from the below list of medical file'.needTranslation.toText16(weight: FontWeight.w100, color: AppColors.greyTextColor), + LocaleKeys.selectAProfile.tr(context: context).toText21(isBold: true), + LocaleKeys.switchFamilyFile.tr(context: context).toText16(weight: FontWeight.w100, color: AppColors.greyTextColor), ], ), child: FamilyCards( diff --git a/lib/presentation/my_invoices/my_invoices_details_page.dart b/lib/presentation/my_invoices/my_invoices_details_page.dart index cccd671b..a38194fe 100644 --- a/lib/presentation/my_invoices/my_invoices_details_page.dart +++ b/lib/presentation/my_invoices/my_invoices_details_page.dart @@ -39,9 +39,9 @@ class _MyInvoicesDetailsPageState extends State { children: [ Expanded( child: CollapsingListView( - title: "Invoice Details".needTranslation, + title: LocaleKeys.invoiceDetails.tr(context: context), sendEmail: () async { - LoaderBottomSheet.showLoader(loadingText: "Sending email, Please wait...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.sendingEmailPleaseWait.tr(context: context)); await myInvoicesViewModel.sendInvoiceEmail( appointmentNo: widget.getInvoiceDetailsResponseModel.appointmentNo!, projectID: widget.getInvoiceDetailsResponseModel.projectID!, @@ -49,7 +49,7 @@ class _MyInvoicesDetailsPageState extends State { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( context, - child: Utils.getSuccessWidget(loadingText: "Email sent successfully.".needTranslation), + child: Utils.getSuccessWidget(loadingText: LocaleKeys.emailSentSuccessfullyMessage.tr(context: context)), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, @@ -223,12 +223,12 @@ class _MyInvoicesDetailsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 24.h), - "Total Balance".needTranslation.toText18(isBold: true).paddingSymmetrical(24.h, 0.h), + LocaleKeys.totalBalance.tr(context: context).toText18(isBold: true).paddingSymmetrical(24.h, 0.h), SizedBox(height: 17.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Amount before tax".needTranslation.toText14(isBold: true), + LocaleKeys.amountBeforeTax.tr(context: context).toText14(isBold: true), Utils.getPaymentAmountWithSymbol(widget.getInvoiceDetailsResponseModel.listConsultation!.first.totalShare.toString().toText16(isBold: true), AppColors.blackColor, 13, isSaudiCurrency: true), ], @@ -236,7 +236,7 @@ class _MyInvoicesDetailsPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "VAT 15%".needTranslation.toText14(isBold: true, color: AppColors.greyTextColor), + LocaleKeys.vat15.tr(context: context).toText14(isBold: true, color: AppColors.greyTextColor), Utils.getPaymentAmountWithSymbol( widget.getInvoiceDetailsResponseModel.listConsultation!.first.totalVATAmount!.toString().toText14(isBold: true, color: AppColors.greyTextColor), AppColors.greyTextColor, 13, isSaudiCurrency: true), @@ -246,7 +246,7 @@ class _MyInvoicesDetailsPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Discount".needTranslation.toText14(isBold: true), + LocaleKeys.discount.tr(context: context).toText14(isBold: true), Utils.getPaymentAmountWithSymbol(widget.getInvoiceDetailsResponseModel.listConsultation!.first.discountAmount!.toString().toText14(isBold: true, color: AppColors.primaryRedColor), AppColors.primaryRedColor, 13, isSaudiCurrency: true), @@ -255,7 +255,7 @@ class _MyInvoicesDetailsPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Paid".needTranslation.toText14(isBold: true), + LocaleKeys.paid.tr(context: context).toText14(isBold: true), Utils.getPaymentAmountWithSymbol( widget.getInvoiceDetailsResponseModel.listConsultation!.first.grandTotal!.toString().toText14(isBold: true, color: AppColors.textColor), AppColors.textColor, 13, isSaudiCurrency: true), diff --git a/lib/presentation/my_invoices/my_invoices_list.dart b/lib/presentation/my_invoices/my_invoices_list.dart index ef1a9c26..177dde84 100644 --- a/lib/presentation/my_invoices/my_invoices_list.dart +++ b/lib/presentation/my_invoices/my_invoices_list.dart @@ -52,7 +52,7 @@ class _MyInvoicesListState extends State { children: [ SizedBox(height: 24.h), ListView.builder( - itemCount: myInvoicesVM.isInvoicesListLoading ? 4 : myInvoicesVM.allInvoicesList.length, + itemCount: myInvoicesVM.isInvoicesListLoading ? 4 : myInvoicesVM.allInvoicesList.isEmpty ? 1 : myInvoicesVM.allInvoicesList.length, physics: NeverScrollableScrollPhysics(), shrinkWrap: true, padding: EdgeInsetsGeometry.zero, @@ -64,7 +64,7 @@ class _MyInvoicesListState extends State { index: index, isLoading: true, ) - : AnimationConfiguration.staggeredList( + : myInvoicesVM.allInvoicesList.isNotEmpty ? AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( @@ -77,7 +77,7 @@ class _MyInvoicesListState extends State { getInvoicesListResponseModel: myInvoicesVM.allInvoicesList[index], onTap: () async { myInvoicesVM.setInvoiceDetailLoading(); - LoaderBottomSheet.showLoader(loadingText: "Fetching invoice details, Please wait...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingInvoiceDetails.tr(context: context)); await myInvoicesVM.getInvoiceDetails( appointmentNo: myInvoicesVM.allInvoicesList[index].appointmentNo!, invoiceNo: myInvoicesVM.allInvoicesList[index].invoiceNo!, @@ -105,7 +105,7 @@ class _MyInvoicesListState extends State { ), ), ), - ); + ) : Utils.getNoDataWidget(context); }).paddingSymmetrical(24.w, 0.h), ], ); diff --git a/lib/presentation/my_invoices/widgets/invoice_list_card.dart b/lib/presentation/my_invoices/widgets/invoice_list_card.dart index 27ca79a2..4a328c5e 100644 --- a/lib/presentation/my_invoices/widgets/invoice_list_card.dart +++ b/lib/presentation/my_invoices/widgets/invoice_list_card.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; @@ -41,11 +42,11 @@ class InvoiceListCard extends StatelessWidget { AppCustomChipWidget( icon: AppAssets.walkin_appointment_icon, iconColor: AppColors.textColor, - labelText: 'Walk In'.needTranslation, + labelText: LocaleKeys.walkin.tr(context: context), textColor: AppColors.textColor, ), AppCustomChipWidget( - labelText: 'OutPatient'.needTranslation, + labelText: LocaleKeys.outPatient.tr(context: context), backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.1), textColor: AppColors.primaryRedColor, ), @@ -127,7 +128,7 @@ class InvoiceListCard extends StatelessWidget { ), SizedBox(height: 16.h), CustomButton( - text: "View invoice details".needTranslation, + text: LocaleKeys.viewInvoiceDetails.tr(context: context), onPressed: () { if (onTap != null) { onTap!(); diff --git a/lib/presentation/notifications/notification_details_page.dart b/lib/presentation/notifications/notification_details_page.dart index a4aef029..1607e8f5 100644 --- a/lib/presentation/notifications/notification_details_page.dart +++ b/lib/presentation/notifications/notification_details_page.dart @@ -1,12 +1,13 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.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/notifications/models/resp_models/notification_response_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; -import 'package:intl/intl.dart'; import 'package:share_plus/share_plus.dart'; class NotificationDetailsPage extends StatelessWidget { @@ -19,26 +20,18 @@ class NotificationDetailsPage extends StatelessWidget { @override Widget build(BuildContext context) { - // Debug logging - print('=== Notification Details ==='); - print('Message: ${notification.message}'); - print('MessageType: ${notification.messageType}'); - print('MessageTypeData: ${notification.messageTypeData}'); - print('VideoURL: ${notification.videoURL}'); - print('========================'); - return CollapsingListView( - title: "Notification Details".needTranslation, - trailing: IconButton( - icon: Icon( - Icons.share_outlined, - size: 24.h, - color: AppColors.textColor, - ), - onPressed: () { - _shareNotification(); - }, - ), + title: LocaleKeys.notificationDetails.tr(), + // trailing: IconButton( + // icon: Icon( + // Icons.share_outlined, + // size: 24.h, + // color: AppColors.textColor, + // ), + // onPressed: () { + // _shareNotification(); + // }, + // ), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -115,7 +108,7 @@ class NotificationDetailsPage extends StatelessWidget { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - 'Message'.needTranslation.toText14( + LocaleKeys.messageNotification.tr().toText14( weight: FontWeight.w600, color: AppColors.greyTextColor, ), @@ -153,7 +146,7 @@ class NotificationDetailsPage extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - 'Attached Image'.needTranslation.toText14( + LocaleKeys.attachedImage.tr().toText14( weight: FontWeight.w600, color: AppColors.greyTextColor, ), @@ -183,7 +176,7 @@ class NotificationDetailsPage extends StatelessWidget { color: AppColors.greyTextColor, ), SizedBox(height: 8.h), - 'Failed to load image'.needTranslation.toText12( + LocaleKeys.failedToLoadImage.tr().toText12( color: AppColors.greyTextColor, ), SizedBox(height: 4.h), @@ -229,21 +222,21 @@ class NotificationDetailsPage extends StatelessWidget { ), // Additional notification info - if (notification.notificationType != null && notification.notificationType!.isNotEmpty) - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - 'Type'.needTranslation.toText14( - weight: FontWeight.w600, - color: AppColors.greyTextColor, - ), - SizedBox(height: 8.h), - notification.notificationType!.toText16( - weight: FontWeight.w400, - color: AppColors.textColor, - ), - ], - ), + // if (notification.notificationType != null && notification.notificationType!.isNotEmpty) + // Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // LocaleKeys.typeNotification.tr().toText14( + // weight: FontWeight.w600, + // color: AppColors.greyTextColor, + // ), + // SizedBox(height: 8.h), + // notification.notificationType!.toText16( + // weight: FontWeight.w400, + // color: AppColors.textColor, + // ), + // ], + // ), ], ), ); diff --git a/lib/presentation/notifications/notifications_list_page.dart b/lib/presentation/notifications/notifications_list_page.dart index 650753c3..6c344b2e 100644 --- a/lib/presentation/notifications/notifications_list_page.dart +++ b/lib/presentation/notifications/notifications_list_page.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -8,6 +9,7 @@ import 'package:hmg_patient_app_new/extensions/int_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/notifications/notifications_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/notifications/notification_details_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -21,7 +23,7 @@ class NotificationsListPage extends StatelessWidget { @override Widget build(BuildContext context) { return CollapsingListView( - title: "Notifications".needTranslation, + title: LocaleKeys.notification.tr(context: context), child: SingleChildScrollView( child: Consumer(builder: (context, notificationsVM, child) { return Container( diff --git a/lib/presentation/onboarding/onboarding_screen.dart b/lib/presentation/onboarding/onboarding_screen.dart index a40a27b4..4855c0cf 100644 --- a/lib/presentation/onboarding/onboarding_screen.dart +++ b/lib/presentation/onboarding/onboarding_screen.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; @@ -6,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/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -36,7 +38,7 @@ class _OnboardingScreenState extends State { CustomPageRoute( page: LandingNavigation(), ), - (r) => false); + (r) => false); // Navigator.of(context).pushReplacement(FadePage(page: LandingNavigation())); } @@ -67,13 +69,13 @@ class _OnboardingScreenState extends State { children: [ onboardingView( AppAnimations.onboarding_1, - "Booking appointment has never been easy".needTranslation, - "In few clicks find yourself having consultation with the doctor of your choice.".needTranslation, + LocaleKeys.onboardingHeading1.tr(context: context), + LocaleKeys.onboardingBody1.tr(context: context), ), onboardingView( AppAnimations.onboarding_2, - "Access the medical history on finger tips".needTranslation, - "Keep track on your medical history including labs, prescription, insurance, etc".needTranslation, + LocaleKeys.onboardingHeading2.tr(context: context), + LocaleKeys.onboardingBody2.tr(context: context), ), ], onPageChanged: (int index) { @@ -88,15 +90,13 @@ class _OnboardingScreenState extends State { duration: const Duration(milliseconds: 250), height: 6.h, width: selectedIndex == 0 ? 18.w : 6.w, - decoration: BoxDecoration( - color: selectedIndex == 0 ? AppColors.textColor : AppColors.inputLabelTextColor, borderRadius: BorderRadius.circular(30.r)), + decoration: BoxDecoration(color: selectedIndex == 0 ? AppColors.textColor : AppColors.inputLabelTextColor, borderRadius: BorderRadius.circular(30.r)), ), AnimatedContainer( duration: const Duration(milliseconds: 250), height: 6.h, width: selectedIndex == 1 ? 18.w : 6.w, - decoration: BoxDecoration( - color: selectedIndex == 1 ? AppColors.textColor : AppColors.inputLabelTextColor, borderRadius: BorderRadius.circular(30.r)), + decoration: BoxDecoration(color: selectedIndex == 1 ? AppColors.textColor : AppColors.inputLabelTextColor, borderRadius: BorderRadius.circular(30.r)), ), ], ).paddingOnly(left: 24.w, right: 24.w), @@ -107,7 +107,7 @@ class _OnboardingScreenState extends State { transitionBuilder: (child, anim) => FadeTransition(opacity: anim, child: child), child: selectedIndex == 0 ? CustomButton( - text: "Skip".needTranslation, + text: LocaleKeys.skip.tr(context: context), onPressed: () => goToHomePage(), width: 86.w, height: 56.h, @@ -121,7 +121,7 @@ class _OnboardingScreenState extends State { AnimatedContainer( duration: const Duration(milliseconds: 400), curve: Curves.easeInOut, - width: selectedIndex == 0 ? 86.w : MediaQuery.of(context).size.width - 48.w, + width: selectedIndex == 0 ? 56.w : MediaQuery.of(context).size.width - 48.w, margin: EdgeInsets.only(left: 24.w, right: 24.w), decoration: BoxDecoration( color: AppColors.primaryRedColor, @@ -134,15 +134,15 @@ class _OnboardingScreenState extends State { ? CustomButton( icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward, iconSize: 32.w, - width: 86.w, + width: 56.w, height: 56.h, - text: "".needTranslation, + text: "", backgroundColor: Colors.transparent, onPressed: () { pageController.animateToPage(1, duration: Duration(milliseconds: 400), curve: Curves.easeInOut); }) : CustomButton( - text: "Get Started".needTranslation, + text: LocaleKeys.getStarted.tr(context: context), fontWeight: FontWeight.w500, fontSize: 16.f, height: 56.h, @@ -153,7 +153,7 @@ class _OnboardingScreenState extends State { ), ), ], - ).paddingOnly(bottom: (isFoldable || isTablet) ? 20.h : 10.h), + ).paddingOnly(bottom: (isFoldable || isTablet) ? 20.h : 32.h), ], ), ), @@ -170,16 +170,11 @@ class _OnboardingScreenState extends State { alignment: Alignment.bottomCenter, child: Transform.flip( flipX: getIt.get().isArabic() ? true : false, - child: Lottie.asset(icon, - repeat: true, - reverse: false, - frameRate: FrameRate(60), - width: MediaQuery.sizeOf(context).width - 50.w, - height: MediaQuery.sizeOf(context).width - 50.w))) + child: Lottie.asset(icon, repeat: true, reverse: false, frameRate: FrameRate(60), width: MediaQuery.sizeOf(context).width - 50.w, height: MediaQuery.sizeOf(context).width - 50.w))) .expanded, Text( heading, - style: TextStyle(fontSize: 36.f, fontWeight: FontWeight.w600, color: AppColors.textColor, letterSpacing: -0.4, height: 1), + style: TextStyle(fontSize: 36.f, fontWeight: FontWeight.w600, color: AppColors.textColor, letterSpacing: -1, height: 1.2), ), Text( body, diff --git a/lib/presentation/parking/paking_page.dart b/lib/presentation/parking/paking_page.dart index d9cc1d87..db451b27 100644 --- a/lib/presentation/parking/paking_page.dart +++ b/lib/presentation/parking/paking_page.dart @@ -1,9 +1,10 @@ - +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.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/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/parking/parking_slot.dart'; import 'package:provider/provider.dart'; @@ -85,7 +86,7 @@ class _ParkingPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Parking".needTranslation, + LocaleKeys.parking.tr(context: context), style: TextStyle( color: AppColors.textColor, fontSize: 27.f, @@ -100,22 +101,7 @@ class _ParkingPageState extends State { hasShadow: true, ), child: Padding( - padding: EdgeInsets.all(16.h), - child: Text( - "Dr. Sulaiman Al Habib hospital are conduction a test for the emerging corona" - " virus and issuing travel certificates 24/7 in a short time and with high accuracy." - " Those wishing to benefit from this service can visit one of Dr. Sulaiman Al Habib branches " - "to conduct a corona test within few minutes. Dr. Sulaiman Al Habib hospital are conduction" - " a test for the emerging corona virus and issuing travel certificates 24/7 in a short time and with high accuracy. " - "Those wishing to benefit from this service can visit one of Dr. Sulaiman Al Habib branches to conduct a corona test within few minutes.", - style: TextStyle( - color: AppColors.textColor, - fontSize: 12, - height: 1.4, - fontWeight: FontWeight.w500, - ), - ), - ), + padding: EdgeInsets.all(16.h), child: LocaleKeys.parkingDescription.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.textColor)), ).paddingOnly(top: 16, bottom: 16), ], ), @@ -131,18 +117,16 @@ class _ParkingPageState extends State { ), child: Padding( padding: EdgeInsets.all(24.h), - child: SizedBox( - width: double.infinity, - height: 56, - child: CustomButton( - text: "Read Barcodes".needTranslation, - onPressed: () => _readQR(context), // always non-null - isDisabled: vm.isLoading, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, - fontSize: 18, - fontWeight: FontWeight.bold, - ), + child: CustomButton( + text: LocaleKeys.scanQRCode.tr(context: context), + onPressed: () => _readQR(context), + // always non-null + isDisabled: vm.isLoading, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + fontSize: 18.f, + height: 56.h, + fontWeight: FontWeight.bold, ), ), ), diff --git a/lib/presentation/parking/parking_slot.dart b/lib/presentation/parking/parking_slot.dart index 0eb3718b..e8c4f097 100644 --- a/lib/presentation/parking/parking_slot.dart +++ b/lib/presentation/parking/parking_slot.dart @@ -1,16 +1,18 @@ +import 'dart:io'; - +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/qr_parking/models/qr_parking_response_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:map_launcher/map_launcher.dart'; import '../../features/qr_parking/qr_parking_view_model.dart'; import '../../theme/colors.dart'; import '../../widgets/appbar/app_bar_widget.dart'; import '../../widgets/buttons/custom_button.dart'; import '../../widgets/chip/app_custom_chip_widget.dart'; -import 'package:maps_launcher/maps_launcher.dart'; import 'package:provider/provider.dart'; import '../../widgets/routes/custom_page_route.dart'; @@ -27,7 +29,7 @@ class ParkingSlot extends StatefulWidget { } class _ParkingSlotState extends State { - void _openDirection() { + void _openDirection() async { final lat = widget.model.latitude; final lng = widget.model.longitude; @@ -46,7 +48,17 @@ class _ParkingSlotState extends State { return; } - MapsLauncher.launchCoordinates(lat, lng); + await MapLauncher.showMarker( + mapType: MapType.google, + coords: Coords(lat, lng), + title: "Parking Location", + ).catchError((err) { + MapLauncher.showMarker( + mapType: Platform.isIOS ? MapType.apple : MapType.google, + coords: Coords(lat, lng), + title: "Parking Location", + ); + }); } @@ -157,7 +169,7 @@ class _ParkingSlotState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Parking Slot Details".needTranslation, + LocaleKeys.parkingSlotDetails.tr(context: context), style: TextStyle( fontSize: 16.f, fontWeight: FontWeight.w600, @@ -170,16 +182,16 @@ class _ParkingSlotState extends State { runSpacing: 4, children: [ AppCustomChipWidget( - labelText: "Slot: ${widget.model.qRParkingCode ?? '-'}".needTranslation, + labelText: LocaleKeys.slotNumber.tr(namedArgs: {'code': widget.model.qRParkingCode ?? '-'}, context: context), ), AppCustomChipWidget( - labelText: "Basement: ${widget.model.floorDescription ?? '-'}".needTranslation, + labelText: LocaleKeys.basement.tr(namedArgs: {'description': widget.model.floorDescription ?? '-'}, context: context), ), AppCustomChipWidget( - labelText: "Date: ${_formatPrettyDate(widget.model.createdOn)}".needTranslation, + labelText: LocaleKeys.parkingDate.tr(namedArgs: {'date': _formatPrettyDate(widget.model.createdOn)}, context: context), ), AppCustomChipWidget( - labelText: "Parked Since: ${_formatPrettyTime(widget.model.createdOn)}".needTranslation, + labelText: LocaleKeys.parkedSince.tr(namedArgs: {'time': _formatPrettyTime(widget.model.createdOn)}, context: context), ), ], ), @@ -193,7 +205,7 @@ class _ParkingSlotState extends State { width: double.infinity, height: 48.h, child: CustomButton( - text: "Get Direction".needTranslation, + text: LocaleKeys.getDirections.tr(context: context), onPressed: _openDirection, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, @@ -210,14 +222,14 @@ class _ParkingSlotState extends State { width: double.infinity, height: 48.h, child: CustomButton( - text: "Reset Direction".needTranslation, + text: LocaleKeys.resetDirection.tr(context: context), onPressed: _resetDirection, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, - fontSize: 18, + fontSize: 18.f, fontWeight: FontWeight.bold, - borderRadius: 10, + borderRadius: 10.r, ), ), ], diff --git a/lib/presentation/prescriptions/prescription_delivery_order_summary_page.dart b/lib/presentation/prescriptions/prescription_delivery_order_summary_page.dart index 0bedbe23..5a340346 100644 --- a/lib/presentation/prescriptions/prescription_delivery_order_summary_page.dart +++ b/lib/presentation/prescriptions/prescription_delivery_order_summary_page.dart @@ -118,7 +118,7 @@ class PrescriptionDeliveryOrderSummaryPage extends StatelessWidget { child: CustomButton( text: LocaleKeys.submit.tr(context: context), onPressed: () async { - LoaderBottomSheet.showLoader(loadingText: "Submitting your request..."); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr(context: context)); await prescriptionsViewModel.submitPrescriptionDeliveryRequest( latitude: prescriptionsViewModel.locationGeocodeResponse.results.first.geometry.location.lat.toString(), longitude: prescriptionsViewModel.locationGeocodeResponse.results.first.geometry.location.lng.toString(), @@ -129,7 +129,7 @@ class PrescriptionDeliveryOrderSummaryPage extends StatelessWidget { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( context, - child: Utils.getSuccessWidget(loadingText: "Request sent successfully.".needTranslation), + child: Utils.getSuccessWidget(loadingText: LocaleKeys.requestSubmittedSuccessfully.tr(context: context)), callBackFunc: () { Navigator.of(context).pop(); }, diff --git a/lib/presentation/prescriptions/prescription_delivery_orders_list_page.dart b/lib/presentation/prescriptions/prescription_delivery_orders_list_page.dart index e2ce865d..7760fd2d 100644 --- a/lib/presentation/prescriptions/prescription_delivery_orders_list_page.dart +++ b/lib/presentation/prescriptions/prescription_delivery_orders_list_page.dart @@ -88,7 +88,7 @@ class PrescriptionDeliveryOrdersListPage extends StatelessWidget { ), ), ) - : Utils.getNoDataWidget(context, noDataText: "You don't have any prescription orders yet.".needTranslation); + : Utils.getNoDataWidget(context, noDataText: LocaleKeys.noPrescriptionOrdersYet.tr(context: context)); }, ).paddingSymmetrical(24.h, 0.h), ], diff --git a/lib/presentation/prescriptions/prescription_detail_page.dart b/lib/presentation/prescriptions/prescription_detail_page.dart index 1216c619..5bb9cbd7 100644 --- a/lib/presentation/prescriptions/prescription_detail_page.dart +++ b/lib/presentation/prescriptions/prescription_detail_page.dart @@ -62,7 +62,7 @@ class _PrescriptionDetailPageState extends State { child: CollapsingListView( title: LocaleKeys.prescriptions.tr(context: context), instructions: () async { - LoaderBottomSheet.showLoader(loadingText: "Fetching prescription PDF, Please wait...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingPrescriptionPDFPleaseWait.tr(context: context)); await prescriptionsViewModel.getPrescriptionInstructionsPDF(widget.prescriptionsResponseModel, onSuccess: (val) { LoaderBottomSheet.hideLoader(); if (prescriptionsViewModel.prescriptionInstructionsPDFLink.isNotEmpty) { @@ -71,7 +71,7 @@ class _PrescriptionDetailPageState extends State { } else { showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "Unable to fetch PDF".needTranslation), + child: Utils.getErrorWidget(loadingText: "Unable to fetch PDF"), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, @@ -136,19 +136,19 @@ class _PrescriptionDetailPageState extends State { AppCustomChipWidget( icon: AppAssets.rating_icon, iconColor: AppColors.ratingColorYellow, - labelText: "Rating: ${widget.prescriptionsResponseModel.decimalDoctorRate}".needTranslation, + labelText: LocaleKeys.ratingValue.tr(namedArgs: {'rating': widget.prescriptionsResponseModel.decimalDoctorRate.toString()}, context: context), ), AppCustomChipWidget( - labelText: widget.prescriptionsResponseModel.name!, + labelText: widget.prescriptionsResponseModel.name ?? "", ), ], ), SizedBox(height: 16.h), CustomButton( - text: "Download Prescription".needTranslation, + text: LocaleKeys.downloadPrescription.tr(context: context), onPressed: () async { - LoaderBottomSheet.showLoader(loadingText: "Fetching prescription PDF, Please wait...".needTranslation); - await prescriptionVM.getPrescriptionPDFBase64(widget.prescriptionsResponseModel).then((val) async { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingPrescriptionPDFPleaseWait.tr(context: context)); + await prescriptionVM.getPrescriptionPDFBase64(widget.prescriptionsResponseModel, onSuccess: (value) async { LoaderBottomSheet.hideLoader(); if (prescriptionVM.prescriptionPDFBase64Data.isNotEmpty) { String path = await Utils.createFileFromString(prescriptionVM.prescriptionPDFBase64Data, "pdf"); @@ -157,13 +157,22 @@ class _PrescriptionDetailPageState extends State { } catch (ex) { showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "Cannot open file".needTranslation), + child: Utils.getErrorWidget(loadingText: "Cannot open file"), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, ); } } + }, onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); }); }, backgroundColor: AppColors.successColor.withValues(alpha: 0.15), @@ -221,7 +230,7 @@ class _PrescriptionDetailPageState extends State { : LocaleKeys.prescriptionDeliveryError.tr(context: context), onPressed: () async { if (widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported!) { - LoaderBottomSheet.showLoader(loadingText: "Fetching prescription details...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingPrescriptionDetails.tr(context: context)); await prescriptionsViewModel.getPrescriptionDetails(widget.prescriptionsResponseModel, onSuccess: (val) { LoaderBottomSheet.hideLoader(); prescriptionsViewModel.initiatePrescriptionDelivery(); diff --git a/lib/presentation/prescriptions/prescription_item_view.dart b/lib/presentation/prescriptions/prescription_item_view.dart index a2a3e7fb..03a343f7 100644 --- a/lib/presentation/prescriptions/prescription_item_view.dart +++ b/lib/presentation/prescriptions/prescription_item_view.dart @@ -10,6 +10,7 @@ import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_mo import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; @@ -56,74 +57,18 @@ class PrescriptionItemView extends StatelessWidget { spacing: 6.h, runSpacing: 6.h, children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: "${LocaleKeys.route.tr(context: context)}: ${isLoading ? "" : prescriptionVM.prescriptionDetailsList[index].route}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ).toShimmer2(isShow: isLoading), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: "${LocaleKeys.frequency.tr(context: context)}: ${isLoading ? "" : prescriptionVM.prescriptionDetailsList[index].frequency}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ).toShimmer2(isShow: isLoading), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: "${LocaleKeys.dailyDoses.tr(context: context)}: ${isLoading ? "" : prescriptionVM.prescriptionDetailsList[index].doseDailyQuantity}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ).toShimmer2(isShow: isLoading), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: "${LocaleKeys.days.tr(context: context)}: ${isLoading ? "" : prescriptionVM.prescriptionDetailsList[index].days}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ).toShimmer2(isShow: isLoading), - ], - ), + AppCustomChipWidget( + labelText: "${LocaleKeys.route.tr(context: context)}: ${isLoading ? "" : prescriptionVM.prescriptionDetailsList[index].route}", + ).toShimmer2(isShow: isLoading), + AppCustomChipWidget( + labelText: "${LocaleKeys.frequency.tr(context: context)}: ${isLoading ? "" : prescriptionVM.prescriptionDetailsList[index].frequency}", + ).toShimmer2(isShow: isLoading), + AppCustomChipWidget( + labelText: "${LocaleKeys.dailyDoses.tr(context: context)}: ${isLoading ? "" : prescriptionVM.prescriptionDetailsList[index].doseDailyQuantity}", + ).toShimmer2(isShow: isLoading), + AppCustomChipWidget( + labelText: "${LocaleKeys.days.tr(context: context)}: ${isLoading ? "" : prescriptionVM.prescriptionDetailsList[index].days}", + ).toShimmer2(isShow: isLoading), ], ).paddingSymmetrical(16.h, 0.h), SizedBox(height: 8.h), @@ -210,26 +155,26 @@ class PrescriptionItemView extends StatelessWidget { ], ).paddingSymmetrical(16.h, 0.h), SizedBox(height: 14.h), - Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), - Row( - children: [ - Expanded( - child: CustomButton( - text: LocaleKeys.checkAvailability.tr(context: context), - onPressed: () {}, - backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), - borderColor: AppColors.primaryRedColor.withOpacity(0.0), - textColor: AppColors.primaryRedColor, - fontSize: 14.f, - fontWeight: FontWeight.w500, - borderRadius: 12.r, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ).toShimmer2(isShow: isLoading), - ), - // SizedBox(width: 16.h), - ], - ).paddingSymmetrical(16.h, 16.h), + // Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), + // Row( + // children: [ + // Expanded( + // child: CustomButton( + // text: LocaleKeys.checkAvailability.tr(context: context), + // onPressed: () {}, + // backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), + // borderColor: AppColors.primaryRedColor.withOpacity(0.0), + // textColor: AppColors.primaryRedColor, + // fontSize: 14.f, + // fontWeight: FontWeight.w500, + // borderRadius: 12.r, + // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + // height: 40.h, + // ).toShimmer2(isShow: isLoading), + // ), + // // SizedBox(width: 16.h), + // ], + // ).paddingSymmetrical(16.h, 16.h), ], ), ); diff --git a/lib/presentation/prescriptions/prescription_reminder_view.dart b/lib/presentation/prescriptions/prescription_reminder_view.dart index 2f1154f1..aab587d4 100644 --- a/lib/presentation/prescriptions/prescription_reminder_view.dart +++ b/lib/presentation/prescriptions/prescription_reminder_view.dart @@ -54,7 +54,7 @@ class _PrescriptionReminderViewState extends State { ), child: RadioListTile( title: Text( - "${_options[index]} minutes before".needTranslation, + "${_options[index]} ${LocaleKeys.minute.tr(context: context)}", style: TextStyle( fontSize: 16.h, fontWeight: FontWeight.w500, diff --git a/lib/presentation/prescriptions/prescriptions_list_page.dart b/lib/presentation/prescriptions/prescriptions_list_page.dart index 8b60159c..cd507d41 100644 --- a/lib/presentation/prescriptions/prescriptions_list_page.dart +++ b/lib/presentation/prescriptions/prescriptions_list_page.dart @@ -19,6 +19,7 @@ import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_detail_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; @@ -164,7 +165,7 @@ class _PrescriptionsListPageState extends State { ], ), SizedBox(height: 8.h), - model.patientPrescriptionOrdersViewList[index].filterName!.toText16(isBold: true) + (model.patientPrescriptionOrdersViewList[index].filterName ?? "").toText16(isBold: true) ], ), ), @@ -207,32 +208,16 @@ class _PrescriptionsListPageState extends State { ], ), SizedBox(height: 8.h), - Row( + Wrap( + direction: Axis.horizontal, + spacing: 6.h, + runSpacing: 6.h, children: [ - CustomButton( - text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(prescription.appointmentDate), false), - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 24.h, + AppCustomChipWidget( + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(prescription.appointmentDate), false), ), - SizedBox(width: 8.h), - CustomButton( - text: model.isSortByClinic ? prescription.name! : prescription.clinicDescription!, - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 24.h, + AppCustomChipWidget( + labelText: model.isSortByClinic ? prescription.name ?? "" : prescription.clinicDescription!, ), ], ), @@ -247,7 +232,7 @@ class _PrescriptionsListPageState extends State { : LocaleKeys.prescriptionDeliveryError.tr(context: context), onPressed: () async { if (prescription.isHomeMedicineDeliverySupported!) { - LoaderBottomSheet.showLoader(loadingText: "Fetching prescription details...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingPrescriptionDetails.tr(context: context)); await prescriptionsViewModel.getPrescriptionDetails(prescriptionsViewModel.patientPrescriptionOrders[index], onSuccess: (val) { LoaderBottomSheet.hideLoader(); @@ -322,7 +307,7 @@ class _PrescriptionsListPageState extends State { ), ), ) - : Utils.getNoDataWidget(context, noDataText: "You don't have any prescriptions yet.".needTranslation); + : Utils.getNoDataWidget(context, noDataText: LocaleKeys.youDontHaveAnyPrescriptionsYet.tr(context: context)); }, ).paddingSymmetrical(24.h, 0.h), ], diff --git a/lib/presentation/profile_settings/profile_settings.dart b/lib/presentation/profile_settings/profile_settings.dart index 1c164390..a3c2667b 100644 --- a/lib/presentation/profile_settings/profile_settings.dart +++ b/lib/presentation/profile_settings/profile_settings.dart @@ -13,6 +13,7 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/int_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; @@ -70,7 +71,7 @@ class ProfileSettingsState extends State { } // Compute and clamp using sensible bounds (uses .h extension) - final double minH = 210.h; + final double minH = 215.h; final double maxH = 380.h; final double computed = (shorter * multiplier); @@ -83,8 +84,10 @@ class ProfileSettingsState extends State { @override Widget build(BuildContext context) { return CollapsingListView( - title: "Profile & Settings".needTranslation, - logout: () {}, + title: LocaleKeys.profileAndSettings.tr(context: context), + logout: () { + context.read().logout(); + }, isClose: true, child: SingleChildScrollView( padding: EdgeInsets.only(top: 24.h, bottom: 24.h), @@ -114,8 +117,8 @@ class ProfileSettingsState extends State { onAddFamilyMemberPress: () { DialogService dialogService = getIt.get(); dialogService.showAddFamilyFileSheet( - label: "Add Family Member".needTranslation, - message: "Please fill the below field to add a new family member to your profile".needTranslation, + label: LocaleKeys.addFamilyMember.tr(context: context), + message: LocaleKeys.pleaseFillBelowFieldToAddNewFamilyMember.tr(context: context), onVerificationPress: () { medicalVm.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms); }); @@ -149,7 +152,7 @@ class ProfileSettingsState extends State { crossAxisAlignment: CrossAxisAlignment.center, children: [ Utils.buildSvgWithAssets(icon: AppAssets.wallet, width: 40.w, height: 40.h), - "Habib Wallet".needTranslation.toText16(weight: FontWeight.w600, maxlines: 2).expanded, + LocaleKeys.habibWallet.tr(context: context).toText16(weight: FontWeight.w600, maxlines: 2).expanded, Utils.buildSvgWithAssets(icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward), ], ), @@ -165,7 +168,7 @@ class ProfileSettingsState extends State { iconSize: 22.w, iconColor: AppColors.infoColor, textColor: AppColors.infoColor, - text: "Recharge".needTranslation, + text: LocaleKeys.recharge.tr(context: context), borderWidth: 0.w, fontWeight: FontWeight.w500, borderColor: Colors.transparent, @@ -183,10 +186,7 @@ class ProfileSettingsState extends State { ), ], ), - "Quick Actions" - .needTranslation - .toText18(weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1) - .paddingOnly(left: 24.w, right: 24.w), + LocaleKeys.quickActions.tr(context: context).toText18(weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1).paddingOnly(left: 24.w, right: 24.w), Container( margin: EdgeInsets.only(left: 24.w, right: 24.w, top: 16.h, bottom: 24.h), padding: EdgeInsets.only(top: 4.h, bottom: 4.h), @@ -195,25 +195,22 @@ class ProfileSettingsState extends State { children: [ actionItem(AppAssets.language_change, LocaleKeys.language.tr(context: context), () { showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.language.tr(context: context), child: AppLanguageChange(), callBackFunc: () {}, isFullScreen: false); - }, trailingLabel: Utils.appState.isArabic() ? "العربية".needTranslation : "English".needTranslation), + }, trailingLabel: Utils.appState.isArabic() ? "العربية" : "English"), 1.divider, - actionItem(AppAssets.bell, "Notifications Settings".needTranslation, () {}), + actionItem(AppAssets.bell, LocaleKeys.notificationsSettings.tr(context: context), () {}), 1.divider, - actionItem(AppAssets.touch_face_id, "Touch ID / Face ID Services".needTranslation, () {}, switchValue: true), + actionItem(AppAssets.touch_face_id, LocaleKeys.touchIDFaceIDServices.tr(context: context), () {}, switchValue: true), ], ), ), - "Personal Information" - .needTranslation - .toText18(weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1) - .paddingOnly(left: 24.w, right: 24.w), + LocaleKeys.personalInformation.tr(context: context).toText18(weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1).paddingOnly(left: 24.w, right: 24.w), Container( margin: EdgeInsets.only(left: 24.w, right: 24.w, top: 16.h, bottom: 24.h), padding: EdgeInsets.only(top: 4.h, bottom: 4.h), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), child: Column( children: [ - actionItem(AppAssets.email_transparent, "Update Email Address".needTranslation, () {}), + actionItem(AppAssets.email_transparent, LocaleKeys.updateEmailAddress.tr(context: context), () {}), // 1.divider, // actionItem(AppAssets.smart_phone_fill, "Phone Number".needTranslation, () {}), // 1.divider, @@ -223,10 +220,7 @@ class ProfileSettingsState extends State { ], ), ), - "Help & Support" - .needTranslation - .toText18(weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1) - .paddingOnly(left: 24.w, right: 24.w), + LocaleKeys.helpAndSupport.tr(context: context).toText18(weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1).paddingOnly(left: 24.w, right: 24.w), Container( margin: EdgeInsets.only(left: 24.w, right: 24.w, top: 16.h), padding: EdgeInsets.only(top: 4.h, bottom: 4.h), @@ -237,9 +231,9 @@ class ProfileSettingsState extends State { launchUrl(Uri.parse("tel://" + "+966 11 525 9999")); }, trailingLabel: "011 525 9999"), 1.divider, - actionItem(AppAssets.permission, "Permissions".needTranslation, () {}, trailingLabel: "Location, Camera"), + actionItem(AppAssets.permission, LocaleKeys.permissions.tr(context: context), () {}, trailingLabel: "Location, Camera"), 1.divider, - actionItem(AppAssets.rate, "Rate Our App".needTranslation, () { + actionItem(AppAssets.rate, LocaleKeys.rateApp.tr(context: context), () { if (Platform.isAndroid) { Utils.openWebView( url: 'https://play.google.com/store/apps/details?id=com.ejada.hmg', @@ -251,13 +245,13 @@ class ProfileSettingsState extends State { } }, isExternalLink: true), 1.divider, - actionItem(AppAssets.privacy_terms, "Privacy Policy".needTranslation, () { + actionItem(AppAssets.privacy_terms, LocaleKeys.privacyPolicy.tr(context: context), () { Utils.openWebView( url: 'https://hmg.com/en/Pages/Privacy.aspx', ); }, isExternalLink: true), 1.divider, - actionItem(AppAssets.privacy_terms, "Terms & Conditions".needTranslation, () { + actionItem(AppAssets.privacy_terms, LocaleKeys.termsConditoins.tr(context: context), () { Utils.openWebView( url: 'https://hmg.com/en/Pages/Terms.aspx', ); @@ -268,7 +262,7 @@ class ProfileSettingsState extends State { CustomButton( height: 56.h, icon: AppAssets.minus, - text: "Deactivate account".needTranslation, + text: LocaleKeys.deactivateAccount.tr(context: context), onPressed: () {}, ).paddingAll(24.w), ], @@ -287,8 +281,7 @@ class ProfileSettingsState extends State { children: [ Utils.buildSvgWithAssets(icon: icon, iconColor: AppColors.greyTextColor), label.toText14(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, - if (trailingLabel.isNotEmpty) - trailingLabel.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1), + if (trailingLabel.isNotEmpty) trailingLabel.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1), switchValue != null ? Switch( value: switchValue, @@ -352,7 +345,8 @@ class FamilyCardWidget extends StatelessWidget { ), ], ).expanded, - Icon(Icons.qr_code, size: 56.h) + // Icon(Icons.qr_code, size: 56.h) + Image.network("https://api.qrserver.com/v1/create-qr-code/?size=250x250&data=${profile.responseId.toString()}", fit: BoxFit.contain, height: 56.h, width: 56.w) ], ), SizedBox(height: 4.h), @@ -363,7 +357,7 @@ class FamilyCardWidget extends StatelessWidget { runSpacing: 4.h, children: [ AppCustomChipWidget( - labelText: "${profile.age} Years Old".needTranslation, + labelText: LocaleKeys.ageYearsOld.tr(namedArgs: {'age': profile.age.toString(), 'yearsOld': LocaleKeys.yearsOld.tr(context: context)}), ), isActive && appState.getAuthenticatedUser()!.bloodGroup != null ? AppCustomChipWidget( @@ -396,17 +390,17 @@ class FamilyCardWidget extends StatelessWidget { if (isLoading) { icon = AppAssets.cancel_circle_icon; - labelText = "Insurance".needTranslation; + labelText = LocaleKeys.insurance.tr(context: context); iconColor = AppColors.primaryRedColor; backgroundColor = AppColors.primaryRedColor; } else if (isExpired) { icon = AppAssets.cancel_circle_icon; - labelText = "Insurance Expired".needTranslation; + labelText = LocaleKeys.insuranceExpired.tr(context: context); iconColor = AppColors.primaryRedColor; backgroundColor = AppColors.primaryRedColor.withValues(alpha: 0.15); } else { icon = AppAssets.insurance_active_icon; - labelText = "Insurance Active".needTranslation; + labelText = LocaleKeys.insuranceActive.tr(context: context); iconColor = AppColors.successColor; backgroundColor = AppColors.successColor.withValues(alpha: 0.15); } @@ -426,8 +420,8 @@ class FamilyCardWidget extends StatelessWidget { ), ), ], - ).paddingOnly(top: 16.h, right: 16.w, left: 16.w, bottom: 12.h), - 1.divider, + ).paddingOnly(top: 16.h, right: 16.w, left: 16.w, bottom: 0.h), + 1.divider.paddingSymmetrical(16.w, 0.h), _buildActionButton(appState), ], ), @@ -451,13 +445,13 @@ class FamilyCardWidget extends StatelessWidget { return CustomButton( icon: canSwitch ? AppAssets.switch_user : AppAssets.add_family, - text: canSwitch ? "Switch Family File".needTranslation : "Add a new family member".needTranslation, + text: canSwitch ? LocaleKeys.switchAccount.tr() : LocaleKeys.addANewFamilyMember.tr(), onPressed: canSwitch ? () => onFamilySwitchPress(profile) : onAddFamilyMemberPress, backgroundColor: canSwitch ? AppColors.secondaryLightRedColor : AppColors.primaryRedColor, borderColor: canSwitch ? AppColors.secondaryLightRedColor : AppColors.primaryRedColor, textColor: canSwitch ? AppColors.primaryRedColor : AppColors.whiteColor, iconColor: canSwitch ? AppColors.primaryRedColor : AppColors.whiteColor, - height: isFoldable ? 50.h : 40.h, + height: isFoldable ? 50.h : 45.h, fontSize: 14.f, ).paddingOnly(top: 12.h, right: 16.w, left: 16.w, bottom: 16.h); } @@ -467,7 +461,7 @@ class FamilyCardWidget extends StatelessWidget { return CustomButton( icon: AppAssets.switch_user, - text: canSwitchBack ? "Switch Back To Family File".needTranslation : "Switch".needTranslation, + text: canSwitchBack ? LocaleKeys.switchBackFamilyFile.tr() : LocaleKeys.switchAccount.tr(), backgroundColor: canSwitchBack ? AppColors.primaryRedColor : Colors.grey.shade200, borderColor: canSwitchBack ? AppColors.primaryRedColor : Colors.grey.shade200, textColor: canSwitchBack ? AppColors.whiteColor : AppColors.greyTextColor, diff --git a/lib/presentation/profile_settings/widgets/family_card_widget.dart b/lib/presentation/profile_settings/widgets/family_card_widget.dart index eaee4c02..b21c52d4 100644 --- a/lib/presentation/profile_settings/widgets/family_card_widget.dart +++ b/lib/presentation/profile_settings/widgets/family_card_widget.dart @@ -73,7 +73,7 @@ class FamilyCardWidget extends StatelessWidget { runSpacing: 4.h, children: [ AppCustomChipWidget( - labelText: "${profile.age} Years Old".needTranslation, + labelText: "${profile.age} ${LocaleKeys.yearsOld.tr(context: context)}", ), isActive && appState.getAuthenticatedUser()!.bloodGroup != null ? AppCustomChipWidget( @@ -106,17 +106,17 @@ class FamilyCardWidget extends StatelessWidget { if (isLoading) { icon = AppAssets.cancel_circle_icon; - labelText = "Insurance".needTranslation; + labelText = LocaleKeys.insurance.tr(context: context); iconColor = AppColors.primaryRedColor; backgroundColor = AppColors.primaryRedColor; } else if (isExpired) { icon = AppAssets.cancel_circle_icon; - labelText = "Insurance Expired".needTranslation; + labelText = LocaleKeys.insuranceExpired.tr(context: context); iconColor = AppColors.primaryRedColor; backgroundColor = AppColors.primaryRedColor.withValues(alpha: 0.15); } else { icon = AppAssets.insurance_active_icon; - labelText = "Insurance Active".needTranslation; + labelText = LocaleKeys.insuranceActive.tr(context: context); iconColor = AppColors.successColor; backgroundColor = AppColors.successColor.withValues(alpha: 0.15); } @@ -161,7 +161,7 @@ class FamilyCardWidget extends StatelessWidget { return CustomButton( icon: canSwitch ? AppAssets.switch_user : AppAssets.add_family, - text: canSwitch ? "Switch Family File".needTranslation : "Add a new family member".needTranslation, + text: canSwitch ? LocaleKeys.switchAccount.tr() : LocaleKeys.addANewFamilyMember.tr(), onPressed: canSwitch ? () => onFamilySwitchPress(profile) : onAddFamilyMemberPress, backgroundColor: canSwitch ? AppColors.secondaryLightRedColor : AppColors.primaryRedColor, borderColor: canSwitch ? AppColors.secondaryLightRedColor : AppColors.primaryRedColor, @@ -177,7 +177,7 @@ class FamilyCardWidget extends StatelessWidget { return CustomButton( icon: AppAssets.switch_user, - text: canSwitchBack ? "Switch Back To Family File".needTranslation : "Switch".needTranslation, + text: canSwitchBack ? LocaleKeys.switchBackFamilyFile.tr() : LocaleKeys.switchLogin.tr(), backgroundColor: canSwitchBack ? AppColors.primaryRedColor : Colors.grey.shade200, borderColor: canSwitchBack ? AppColors.primaryRedColor : Colors.grey.shade200, textColor: canSwitchBack ? AppColors.whiteColor : AppColors.greyTextColor, diff --git a/lib/presentation/radiology/radiology_orders_page.dart b/lib/presentation/radiology/radiology_orders_page.dart index fb153ead..f9b94e27 100644 --- a/lib/presentation/radiology/radiology_orders_page.dart +++ b/lib/presentation/radiology/radiology_orders_page.dart @@ -35,6 +35,7 @@ class _RadiologyOrdersPageState extends State { late RadiologyViewModel radiologyViewModel; String selectedFilterText = ''; int? expandedIndex; + // Scroll controller to ensure expanded group is visible late ScrollController _scrollController; final Map _groupKeys = {}; @@ -58,128 +59,128 @@ class _RadiologyOrdersPageState extends State { Widget build(BuildContext context) { radiologyViewModel = Provider.of(context); return Scaffold( - backgroundColor: AppColors.bgScaffoldColor, - body: CollapsingListView( - title: LocaleKeys.radiology.tr(context: context), - search: () async { - final lavVM = Provider.of(context, listen: false); - if (lavVM.isLabOrdersLoading) { - return; - } else { - String? value = await Navigator.of(context).push( - CustomPageRoute( - page: SearchRadiologyContent(radiologySuggestionsList: radiologyViewModel.radiologySuggestions), - fullScreenDialog: true, - direction: AxisDirection.down, - ), - ); - if (value != null) { - selectedFilterText = value; - radiologyViewModel.filterRadiologyReports(value); - } + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: LocaleKeys.radiology.tr(context: context), + search: () async { + final lavVM = Provider.of(context, listen: false); + if (lavVM.isLabOrdersLoading) { + return; + } else { + String? value = await Navigator.of(context).push( + CustomPageRoute( + page: SearchRadiologyContent(radiologySuggestionsList: radiologyViewModel.radiologySuggestions), + fullScreenDialog: true, + direction: AxisDirection.down, + ), + ); + if (value != null) { + selectedFilterText = value; + radiologyViewModel.filterRadiologyReports(value); } - }, - child: SingleChildScrollView( - controller: _scrollController, - physics: NeverScrollableScrollPhysics(), - child: Consumer( - builder: (context, model, child) { - // Build grouping lists if we have data and none constructed yet - if (!model.isRadiologyOrdersLoading && model.patientRadiologyOrders.isNotEmpty && model.patientRadiologyOrdersViewList.isEmpty) { - final clinicMap = >{}; - final hospitalMap = >{}; - for (var order in model.patientRadiologyOrders) { - final clinicKey = (order.clinicDescription ?? 'Unknown').trim(); - clinicMap.putIfAbsent(clinicKey, () => []).add(order); - final hospitalKey = (order.projectName ?? order.projectID ?? 'Unknown').toString().trim(); - hospitalMap.putIfAbsent(hospitalKey, () => []).add(order); - } - model.patientRadiologyOrdersByClinic = clinicMap.values.toList(); - model.patientRadiologyOrdersByHospital = hospitalMap.values.toList(); - model.patientRadiologyOrdersViewList = model.isSortByClinic ? model.patientRadiologyOrdersByClinic : model.patientRadiologyOrdersByHospital; + } + }, + child: SingleChildScrollView( + controller: _scrollController, + physics: NeverScrollableScrollPhysics(), + child: Consumer( + builder: (context, model, child) { + // Build grouping lists if we have data and none constructed yet + if (!model.isRadiologyOrdersLoading && model.patientRadiologyOrders.isNotEmpty && model.patientRadiologyOrdersViewList.isEmpty) { + final clinicMap = >{}; + final hospitalMap = >{}; + for (var order in model.patientRadiologyOrders) { + final clinicKey = (order.clinicDescription ?? 'Unknown').trim(); + clinicMap.putIfAbsent(clinicKey, () => []).add(order); + final hospitalKey = (order.projectName ?? order.projectID ?? 'Unknown').toString().trim(); + hospitalMap.putIfAbsent(hospitalKey, () => []).add(order); } + model.patientRadiologyOrdersByClinic = clinicMap.values.toList(); + model.patientRadiologyOrdersByHospital = hospitalMap.values.toList(); + model.patientRadiologyOrdersViewList = model.isSortByClinic ? model.patientRadiologyOrdersByClinic : model.patientRadiologyOrdersByHospital; + } - return Padding( - padding: EdgeInsets.symmetric(horizontal: 24.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Clinic / Hospital toggle - SizedBox(height: 16.h), - Row( - children: [ - CustomButton( - text: LocaleKeys.byClinic.tr(context: context), - onPressed: () { - model.setIsSortByClinic(true); - }, - backgroundColor: model.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, - borderColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), - textColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, - fontSize: 12, - fontWeight: FontWeight.w500, - borderRadius: 10, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ), - SizedBox(width: 8.h), - CustomButton( - text: LocaleKeys.byHospital.tr(context: context), - onPressed: () { - model.setIsSortByClinic(false); - }, - backgroundColor: model.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, - borderColor: model.isSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor, - textColor: model.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, - fontSize: 12, - fontWeight: FontWeight.w500, - borderRadius: 10, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ), - ], - ), + return Padding( + padding: EdgeInsets.symmetric(horizontal: 24.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Clinic / Hospital toggle + SizedBox(height: 16.h), + Row( + children: [ + CustomButton( + text: LocaleKeys.byClinic.tr(context: context), + onPressed: () { + model.setIsSortByClinic(true); + }, + backgroundColor: model.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, + borderColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), + textColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 10, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + SizedBox(width: 8.h), + CustomButton( + text: LocaleKeys.byHospital.tr(context: context), + onPressed: () { + model.setIsSortByClinic(false); + }, + backgroundColor: model.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, + borderColor: model.isSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor, + textColor: model.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 10, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + ], + ), - SizedBox(height: 8.h), - selectedFilterText.isNotEmpty - ? AppCustomChipWidget( - padding: EdgeInsets.symmetric(horizontal: 5.h), - labelText: selectedFilterText, - deleteIcon: 'assets/images/svg/cross_circle.svg', - backgroundColor: AppColors.alertColor, - textColor: AppColors.whiteColor, - deleteIconColor: AppColors.whiteColor, - deleteIconHasColor: true, - onDeleteTap: () { - setState(() { - selectedFilterText = ''; - model.filterRadiologyReports(''); - }); - }, - // chipType: ChipTypeEnum.alert, - // isSelected: true, - ) - : SizedBox(), + SizedBox(height: 8.h), + selectedFilterText.isNotEmpty + ? AppCustomChipWidget( + padding: EdgeInsets.symmetric(horizontal: 5.h), + labelText: selectedFilterText, + deleteIcon: 'assets/images/svg/cross_circle.svg', + backgroundColor: AppColors.alertColor, + textColor: AppColors.whiteColor, + deleteIconColor: AppColors.whiteColor, + deleteIconHasColor: true, + onDeleteTap: () { + setState(() { + selectedFilterText = ''; + model.filterRadiologyReports(''); + }); + }, + // chipType: ChipTypeEnum.alert, + // isSelected: true, + ) + : SizedBox(), - // Grouped view when available - Builder(builder: (ctx) { - if (model.isRadiologyOrdersLoading) { - return ListView.builder( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - padding: EdgeInsets.zero, - itemCount: 5, - itemBuilder: (context, index) => LabResultItemView( - onTap: () {}, - labOrder: null, - index: index, - isLoading: true, - ), - ); - } + // Grouped view when available + Builder(builder: (ctx) { + if (model.isRadiologyOrdersLoading) { + return ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.zero, + itemCount: 5, + itemBuilder: (context, index) => LabResultItemView( + onTap: () {}, + labOrder: null, + index: index, + isLoading: true, + ), + ); + } if (model.patientRadiologyOrdersViewList.isEmpty) { - return Utils.getNoDataWidget(ctx, noDataText: "You don't have any radiology results yet.".needTranslation); + return Utils.getNoDataWidget(ctx, noDataText: LocaleKeys.youDontHaveRadiologyOrders.tr(context: context)); } return ListView.builder( @@ -239,7 +240,7 @@ class _RadiologyOrdersPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - AppCustomChipWidget(labelText: "${group.length} ${'results'.needTranslation}"), + AppCustomChipWidget(labelText: "${group.length} ${LocaleKeys.results.tr(context: context)}"), Icon(isExpanded ? Icons.expand_less : Icons.expand_more), ], ), @@ -323,7 +324,7 @@ class _RadiologyOrdersPageState extends State { icon: AppAssets.view_report_icon, iconColor: AppColors.primaryRedColor, iconSize: 16.h, - text: "View Results".needTranslation, + text: LocaleKeys.viewResults.tr(context: context), onPressed: () { model.navigationService.push( CustomPageRoute( diff --git a/lib/presentation/radiology/radiology_result_page.dart b/lib/presentation/radiology/radiology_result_page.dart index 1fc2d9f1..2328d0ea 100644 --- a/lib/presentation/radiology/radiology_result_page.dart +++ b/lib/presentation/radiology/radiology_result_page.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; @@ -10,6 +11,7 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/radiology/models/resp_models/patient_radiology_response_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -49,7 +51,7 @@ class _RadiologyResultPageState extends State { children: [ Expanded( child: CollapsingListView( - title: "Radiology Result".needTranslation, + title: LocaleKeys.radiologyResult.tr(context: context), child: SingleChildScrollView( child: Padding( padding: EdgeInsets.symmetric(horizontal: 24.h), @@ -72,13 +74,13 @@ class _RadiologyResultPageState extends State { widget.patientRadiologyResponseModel.reportData!.trim().toText12(isBold: true, color: AppColors.textColorLight), SizedBox(height: 16.h), CustomButton( - text: "View Radiology Image".needTranslation, + text: LocaleKeys.viewRadiologyImage.tr(context: context), onPressed: () async { if (radiologyViewModel.radiologyImageURL.isNotEmpty) { Uri uri = Uri.parse(radiologyViewModel.radiologyImageURL); launchUrl(uri, mode: LaunchMode.platformDefault, webOnlyWindowName: ""); } else { - Utils.showToast("Radiology image not available".needTranslation); + Utils.showToast("Radiology image not available"); } }, backgroundColor: AppColors.primaryRedColor, @@ -111,7 +113,7 @@ class _RadiologyResultPageState extends State { hasShadow: true, ), child: CustomButton( - text: "Download report".needTranslation, + text: LocaleKeys.downloadReport.tr(context: context), onPressed: () async { LoaderBottomSheet.showLoader(); await radiologyViewModel.getRadiologyPDF(patientRadiologyResponseModel: widget.patientRadiologyResponseModel, authenticatedUser: _appState.getAuthenticatedUser()!, onError: (err) { @@ -132,7 +134,7 @@ class _RadiologyResultPageState extends State { } catch (ex) { showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "Cannot open file".needTranslation), + child: Utils.getErrorWidget(loadingText: "Cannot open file"), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, diff --git a/lib/presentation/rate_appointment/rate_appointment_clinic.dart b/lib/presentation/rate_appointment/rate_appointment_clinic.dart index 5fb1fa3c..e7e29f67 100644 --- a/lib/presentation/rate_appointment/rate_appointment_clinic.dart +++ b/lib/presentation/rate_appointment/rate_appointment_clinic.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; @@ -5,6 +6,7 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_view_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/rate_appointment/widget/doctor_row.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -77,9 +79,7 @@ class _RateAppointmentClinicState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - - "Rate Clinic".needTranslation.toText16(isBold: true), - + LocaleKeys.rateClinic.tr(context: context).toText16(isBold: true), SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.center, @@ -147,13 +147,13 @@ class _RateAppointmentClinicState extends State { children: [ Expanded( child: CustomButton( - text: "Back".needTranslation, + text: LocaleKeys.back.tr(context: context), backgroundColor: Color(0xffFEE9EA), borderColor: Color(0xffFEE9EA), textColor: Color(0xffED1C2B), onPressed: () { - appointmentRatingViewModel!.setTitle("Rate Doctor".needTranslation); - appointmentRatingViewModel!.setSubTitle("How was your last visit with doctor?".needTranslation); + appointmentRatingViewModel!.setTitle(LocaleKeys.rateDoctor.tr(context: context)); + appointmentRatingViewModel!.setSubTitle(LocaleKeys.howWasYourLastVisitWithDoctor.tr(context: context)); appointmentRatingViewModel!.setClinicOrDoctor(false); setState(() { @@ -164,7 +164,7 @@ class _RateAppointmentClinicState extends State { SizedBox(width: 10), Expanded( child: CustomButton( - text: "Submit".needTranslation, + text: LocaleKeys.submit.tr(context: context), onPressed: () { submitRating(); diff --git a/lib/presentation/rate_appointment/rate_appointment_doctor.dart b/lib/presentation/rate_appointment/rate_appointment_doctor.dart index ac79744e..b9a6c587 100644 --- a/lib/presentation/rate_appointment/rate_appointment_doctor.dart +++ b/lib/presentation/rate_appointment/rate_appointment_doctor.dart @@ -1,9 +1,11 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_view_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/rate_appointment/rate_appointment_clinic.dart'; import 'package:hmg_patient_app_new/presentation/rate_appointment/widget/doctor_row.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -88,9 +90,7 @@ class _RateAppointmentDoctorState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - - "Please rate the doctor".needTranslation.toText16(isBold: true), - + "Please rate the doctor".toText16(isBold: true), SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.center, @@ -142,7 +142,7 @@ class _RateAppointmentDoctorState extends State { maxLines: 4, decoration: InputDecoration.collapsed( - hintText: "Notes".needTranslation, + hintText: LocaleKeys.notes.tr(context: context), hintStyle: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, @@ -172,7 +172,7 @@ class _RateAppointmentDoctorState extends State { children: [ Expanded( child: CustomButton( - text: "Later".needTranslation, + text: "Later", backgroundColor: Color(0xffFEE9EA), borderColor: Color(0xffFEE9EA), textColor: Color(0xffED1C2B), @@ -184,11 +184,11 @@ class _RateAppointmentDoctorState extends State { SizedBox(width: 10), Expanded( child: CustomButton( - text: "Next".needTranslation, + text: LocaleKeys.next.tr(context: context), onPressed: () { // Set up clinic rating and show clinic rating view - appointmentRatingViewModel!.setTitle("Rate Clinic".needTranslation); - appointmentRatingViewModel!.setSubTitle("How was your appointment?".needTranslation); + appointmentRatingViewModel!.setTitle(LocaleKeys.rateDoctor.tr(context: context),); + appointmentRatingViewModel!.setSubTitle(LocaleKeys.howWasYourLastVisitWithDoctor.tr(context: context),); appointmentRatingViewModel!.setClinicOrDoctor(true); setState(() {}); diff --git a/lib/presentation/servicesPriceList/services_price_list_page.dart b/lib/presentation/servicesPriceList/services_price_list_page.dart new file mode 100644 index 00000000..0ee81ae3 --- /dev/null +++ b/lib/presentation/servicesPriceList/services_price_list_page.dart @@ -0,0 +1,199 @@ +import 'dart:async'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/services_price_list/services_price_list_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:provider/provider.dart'; +import 'package:shimmer/shimmer.dart'; + +class ServicesPriceListPage extends StatefulWidget { + const ServicesPriceListPage({super.key}); + + @override + State createState() => _ServicesPriceListPageState(); +} + +class _ServicesPriceListPageState extends State { + late ServicesPriceListViewModel servicesPriceListViewModel; + late AppState appState; + + @override + void initState() { + scheduleMicrotask(() { + servicesPriceListViewModel.initServicesPriceListProvider(); + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + servicesPriceListViewModel = Provider.of(context, listen: false); + appState = getIt.get(); + + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: LocaleKeys.servicePriceList.tr(context: context), + child: SingleChildScrollView( + padding: EdgeInsets.all(24.h), + child: Consumer(builder: (context, model, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h), + child: LocaleKeys.servicePriceListDesc.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), + ), + ), + SizedBox(height: 16.h), + model.isServicesPriceListLoading + ? ListView.separated( + shrinkWrap: true, + padding: EdgeInsets.zero, + physics: NeverScrollableScrollPhysics(), + itemCount: 5, + separatorBuilder: (context, index) => SizedBox(height: 12.h), + itemBuilder: (context, index) => _buildLoadingCard(), + ) + : model.filteredServicesPriceList.isEmpty + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + padding: EdgeInsets.all(40.h), + child: Center( + child: LocaleKeys.noDataAvailable.tr(context: context).toText16( + color: AppColors.textColorLight, + ), + ), + ) + : Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.serviceName.tr(context: context).toText18(weight: FontWeight.bold, color: AppColors.textColor), + SizedBox(height: 16.h), + ListView.separated( + shrinkWrap: true, + padding: EdgeInsets.zero, + physics: NeverScrollableScrollPhysics(), + itemCount: model.filteredServicesPriceList.length, + separatorBuilder: (context, index) => Divider(height: 1.h, color: AppColors.dividerColor).withVerticalPadding(), + itemBuilder: (context, index) { + final service = model.filteredServicesPriceList[index]; + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: (appState.isArabic() ? service.nameAR ?? service.nameEN ?? '' : service.nameEN ?? service.nameAR ?? '').toText16( + weight: FontWeight.w500, + color: AppColors.textColor, + ), + ), + SizedBox(width: 12.w), + Utils.getPaymentAmountWithSymbol('${service.price ?? 0}'.toText16(isBold: true), AppColors.blackColor, 13, isSaudiCurrency: true), + ], + ), + ], + ), + ), + ), + ); + }, + ), + ], + ), + ), + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h), + child: LocaleKeys.servicePriceListRights.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), + ), + ), + ], + ); + }), + ), + ), + ); + } + + Widget _buildLoadingCard() { + return Shimmer.fromColors( + baseColor: AppColors.greyColor.withValues(alpha: 0.3), + highlightColor: AppColors.greyColor.withValues(alpha: 0.1), + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Container( + height: 20.h, + decoration: BoxDecoration( + color: AppColors.greyColor, + borderRadius: BorderRadius.circular(4.r), + ), + ), + ), + SizedBox(width: 12.w), + Container( + width: 80.w, + height: 32.h, + decoration: BoxDecoration( + color: AppColors.greyColor, + borderRadius: BorderRadius.circular(12.r), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/presentation/smartwatches/health_dashboard/health_dashboard.dart b/lib/presentation/smartwatches/health_dashboard/health_dashboard.dart index 7c537ba7..f30d87a9 100644 --- a/lib/presentation/smartwatches/health_dashboard/health_dashboard.dart +++ b/lib/presentation/smartwatches/health_dashboard/health_dashboard.dart @@ -24,8 +24,8 @@ class HealthDashboard extends StatefulWidget { class _HealthDashboardState extends State with SingleTickerProviderStateMixin { late TabController _tabController; - final dateFormat = DateFormat('MMM dd, yyyy'); - final timeFormat = DateFormat('hh:mm a'); + final dateFormat = DateFormat('MMM dd, yyyy', "en-US"); + final timeFormat = DateFormat('hh:mm a', "en-US"); @override void initState() { diff --git a/lib/presentation/smartwatches/smartwatch_home_page.dart b/lib/presentation/smartwatches/smartwatch_home_page.dart new file mode 100644 index 00000000..8fd79098 --- /dev/null +++ b/lib/presentation/smartwatches/smartwatch_home_page.dart @@ -0,0 +1,180 @@ +import 'dart:io'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/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/smartwatch_health_data/health_provider.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/smartwatches/widgets/supported_watches_list.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:provider/provider.dart'; + +class SmartwatchHomePage extends StatelessWidget { + const SmartwatchHomePage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: LocaleKeys.smartWatches.tr(), + bottomChild: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + ), + child: CustomButton( + text: LocaleKeys.supportedSmartWatches.tr(context: context), + onPressed: () { + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.supportedSmartWatches.tr(context: context), + context, + child: SupportedWatchesList(), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }, + backgroundColor: AppColors.infoColor.withAlpha(40), + borderColor: AppColors.infoColor.withAlpha(0), + textColor: AppColors.infoColor, + fontSize: 16.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + height: 50.h, + icon: AppAssets.ask_doctor_icon, + iconColor: AppColors.infoColor, + iconSize: 18.h, + ).paddingSymmetrical(24.w, 30.h), + ), + child: SingleChildScrollView( + child: Column( + children: [ + Padding( + padding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 24.h), + child: GridView( + padding: EdgeInsets.zero, + shrinkWrap: true, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 16.h, + mainAxisSpacing: 16.w, + mainAxisExtent: 240.h, + ), + children: [ + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + ), + child: Column( + children: [ + Image.asset("assets/images/png/smartwatches/apple-watch-5.jpg", width: 136.w, height: 136.h).paddingSymmetrical(24.w, 8.h), + "Apple Watch".needTranslation.toText16(isBold: true), + CustomButton( + text: LocaleKeys.select.tr(context: context), + onPressed: () { + context.read().setSelectedWatchType("apple", "assets/images/png/smartwatches/apple-watch-5.jpg"); + }, + backgroundColor: AppColors.primaryRedColor.withAlpha(40), + borderColor: AppColors.primaryRedColor.withAlpha(0), + textColor: AppColors.primaryRedColor, + fontSize: 16.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + height: 40.h, + ).paddingSymmetrical(16.w, 12.h), + ], + ), + ), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + ), + child: Column( + children: [ + Image.asset("assets/images/png/smartwatches/galaxy_watch_8_classic.jpeg", fit: BoxFit.contain, width: 136.w, height: 136.h).paddingSymmetrical(24.w, 8.h), + "Samsung Watch".needTranslation.toText16(isBold: true), + CustomButton( + text: LocaleKeys.select.tr(context: context), + onPressed: () { + context.read().setSelectedWatchType("samsung", "assets/images/png/smartwatches/galaxy_watch_8_classic.jpeg"); + }, + backgroundColor: AppColors.primaryRedColor.withAlpha(40), + borderColor: AppColors.primaryRedColor.withAlpha(0), + textColor: AppColors.primaryRedColor, + fontSize: 16.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + height: 40.h, + ).paddingSymmetrical(16.w, 12.h), + ], + ), + ), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + ), + child: Column( + children: [ + Image.asset("assets/images/png/smartwatches/Huawei_Watch.png", width: 136.w, height: 136.h).paddingSymmetrical(24.w, 8.h), + "Huawei Watch".needTranslation.toText16(isBold: true), + CustomButton( + text: LocaleKeys.select.tr(context: context), + onPressed: () { + context.read().setSelectedWatchType("huawei", "assets/images/png/smartwatches/Huawei_Watch.png"); + }, + backgroundColor: AppColors.primaryRedColor.withAlpha(40), + borderColor: AppColors.primaryRedColor.withAlpha(0), + textColor: AppColors.primaryRedColor, + fontSize: 16.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + height: 40.h, + ).paddingSymmetrical(16.w, 12.h), + ], + ), + ), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + ), + child: Column( + children: [ + Image.asset("assets/images/png/smartwatches/Whoop_Watch.png", width: 136.w, height: 136.h).paddingSymmetrical(24.w, 8.h), + "Whoop Watch".needTranslation.toText16(isBold: true), + CustomButton( + text: LocaleKeys.select.tr(context: context), + onPressed: () { + context.read().setSelectedWatchType("whoop", "assets/images/png/smartwatches/Whoop_Watch.png"); + }, + backgroundColor: AppColors.primaryRedColor.withAlpha(40), + borderColor: AppColors.primaryRedColor.withAlpha(0), + textColor: AppColors.primaryRedColor, + fontSize: 16.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + height: 40.h, + ).paddingSymmetrical(16.w, 12.h), + ], + ), + ), + ], + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/presentation/smartwatches/smartwatch_instructions_page.dart b/lib/presentation/smartwatches/smartwatch_instructions_page.dart index 7f17f5a3..48683d50 100644 --- a/lib/presentation/smartwatches/smartwatch_instructions_page.dart +++ b/lib/presentation/smartwatches/smartwatch_instructions_page.dart @@ -1,19 +1,12 @@ -import 'dart:io'; - import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:health/health.dart'; -import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; -import 'package:hmg_patient_app_new/presentation/smartwatches/health_dashboard/health_dashboard.dart'; -import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; -import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; -import 'package:url_launcher/url_launcher.dart'; class SmartwatchInstructionsPage extends StatelessWidget { const SmartwatchInstructionsPage({super.key}); @@ -22,445 +15,28 @@ class SmartwatchInstructionsPage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, - body: CollapsingListView( - title: LocaleKeys.smartWatches.tr(), - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 24.h), - child: SingleChildScrollView( - child: Platform.isIOS ? getIOSInstructionsUI(context) : getAndroidInstructionsUI(context), - ), - ), - ), - ); - } - - Widget getAndroidInstructionsUI(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "Supported Smart Watches".needTranslation.toText20(isBold: true), - SizedBox(height: 16.h), - Row( - children: [ - Expanded( - child: Container( - margin: EdgeInsets.only(top: 10.0), - child: Row( - children: [ - Image.asset("assets/images/png/smartwatches/galaxy_watch_ultra.jpg", width: 70.0, height: 70.0), - Padding( - padding: const EdgeInsets.only(left: 8.0, right: 8.0), - child: SizedBox( - width: MediaQuery.of(context).size.width * 0.21, - child: Text("Galaxy Watch Ultra", overflow: TextOverflow.clip, style: TextStyle(fontSize: 12.0)), - ), - ) - ], - ), - ), - ), - Expanded( - child: Container( - margin: EdgeInsets.only(top: 10.0), - child: Row( - children: [ - Image.asset("assets/images/png/smartwatches/galaxy_watch_8_classic.jpeg", width: 70.0, height: 70.0), - Padding( - padding: const EdgeInsets.only(left: 8.0, right: 8.0), - child: SizedBox( - width: MediaQuery.of(context).size.width * 0.21, - child: Text("Galaxy Watch 8 Classic", overflow: TextOverflow.clip, style: TextStyle(fontSize: 12.0)), - ), - ) - ], - ), - ), - ), - ], - ), - Container( - margin: EdgeInsets.only(top: 10.0), - child: Row( - children: [ - Expanded( - child: Container( - margin: EdgeInsets.only(top: 15.0), - child: Row( - children: [ - Image.asset("assets/images/png/smartwatches/galaxy_watch_8.jpg", width: 70.0, height: 70.0), - Padding( - padding: const EdgeInsets.only(left: 8.0, right: 8.0), - child: Container( - width: MediaQuery.of(context).size.width * 0.21, - child: Text("Galaxy Watch 8", overflow: TextOverflow.clip, style: TextStyle(fontSize: 12.0)), - ), - ) - ], - ), - ), - ), - Expanded( - child: Container( - margin: EdgeInsets.only(top: 15.0), - child: Row( - children: [ - Image.asset("assets/images/png/smartwatches/galaxy_watch_7_classic.jpg", width: 70.0, height: 70.0), - Padding( - padding: const EdgeInsets.only(left: 8.0, right: 8.0), - child: SizedBox( - width: MediaQuery.of(context).size.width * 0.21, - child: Text("Galaxy Watch 7 Classic", overflow: TextOverflow.clip, style: TextStyle(fontSize: 12.0)), - ), - ) - ], - ), - ), - ), - ], - ), - ), - Container( - margin: EdgeInsets.only(top: 10.0), - child: Row( - children: [ - Expanded( - child: Container( - margin: EdgeInsets.only(top: 15.0), - child: Row( - children: [ - Image.asset("assets/images/png/smartwatches/galaxy_watch_7.webp", width: 70.0, height: 70.0), - Padding( - padding: const EdgeInsets.only(left: 8.0, right: 8.0), - child: SizedBox( - width: MediaQuery.of(context).size.width * 0.21, - child: Text("Galaxy Watch 7", overflow: TextOverflow.clip, style: TextStyle(fontSize: 12.0)), - ), - ) - ], - ), - ), - ), - Expanded( - child: Container( - margin: EdgeInsets.only(top: 15.0), - child: Row( - children: [ - Image.asset("assets/images/png/smartwatches/galaxy_fit_3.jpg", width: 70.0, height: 70.0), - Padding( - padding: const EdgeInsets.only(left: 8.0, right: 8.0), - child: SizedBox( - width: MediaQuery.of(context).size.width * 0.21, - child: Text("Galaxy Fit3", overflow: TextOverflow.clip, style: TextStyle(fontSize: 12.0)), - ), - ) - ], - ), - ), - ), - ], - ), - ), - SizedBox(height: 12), - "Please make sure that your Samsung Watch is connected to your Phone, is actively synced & updated.".needTranslation.toText14(isBold: true), - SizedBox(height: 12), - "Before syncing data, please make sure that you have followed the instructions properly.".needTranslation.toText14(isBold: true), - SizedBox(height: 12), - InkWell( - onTap: () { - showInstructionsDialog(context); - }, - child: "View watch instructions".needTranslation.toText12(isBold: true, color: AppColors.textColor, isUnderLine: true)), - SizedBox( - height: 130.h, - ), - CustomButton( - text: LocaleKeys.confirm.tr(context: context), - onPressed: () async { - await Health().getHealthConnectSdkStatus().then((val) { - print('Health Connect SDK Status: $val'); - if (val == HealthConnectSdkStatus.sdkAvailable) { - Navigator.of(context).push( - CustomPageRoute( - page: HealthDashboard(), - ), - ); - } else { - getIt.get().showErrorBottomSheet( - message: "Seems like you do not have Health Connect App installed. Please install it from the Play Store to sync your health data.".needTranslation, - onOkPressed: () { - Navigator.pop(context); - Uri uri = Uri.parse("https://play.google.com/store/apps/details?id=com.google.android.apps.healthdata"); - launchUrl(uri, mode: LaunchMode.externalApplication); - }); - return; - } - }); - }, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, - textColor: AppColors.whiteColor, - fontSize: 16.f, - fontWeight: FontWeight.w500, - borderRadius: 10.r, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 50.h, - iconColor: AppColors.whiteColor, - iconSize: 18.h, - ), - // DefaultButton( - // LocaleKeys.confirm.tr(context: context), - // () async { - // await Health().getHealthConnectSdkStatus().then((val) { - // print('Health Connect SDK Status: $val'); - // if (val == HealthConnectSdkStatus.sdkAvailable) { - // Navigator.of(context).push( - // FadePage( - // page: HealthDashboard(), // Replace with the actual vital signs page - // // page: HealthApp(), // Replace with the actual vital signs page - // ), - // ); - // } else { - // Utils.showAppDialog(context, LocaleKeys.error.tr(context: context), LocaleKeys.healthConnectNotInstalled.tr(), () { - // Navigator.pop(context); - // Uri uri = Uri.parse("https://play.google.com/store/apps/details?id=com.google.android.apps.healthdata"); - // launchUrl(uri, mode: LaunchMode.externalApplication); - // }); - // return; - // } - // }); - // }, - // ) - ], - ); - } - - Widget getIOSInstructionsUI(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "Supported Smart Watches".needTranslation.toText20(isBold: true), - SizedBox(height: 16.h), - Row( - children: [ - Expanded( - child: Container( - margin: EdgeInsets.only(top: 10.0), - child: Row( - children: [ - Image.asset("assets/images/png/smartwatches/apple-watch-1.jpeg", width: 70.0, height: 70.0), - Padding( - padding: const EdgeInsets.only(left: 8.0, right: 8.0), - child: Container( - width: MediaQuery.of(context).size.width * 0.21, - child: Text("Apple Watch Series 5", overflow: TextOverflow.clip, style: TextStyle(fontSize: 12.0)), - ), - ) - ], - ), - ), + body: CollapsingListView( + title: "How does it work".needTranslation, + bottomChild: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, ), - Expanded( - child: Container( - margin: EdgeInsets.only(top: 10.0), - child: Row( - children: [ - Image.asset("assets/images/png/smartwatches/apple-watch-2.jpg", width: 70.0, height: 70.0), - Padding( - padding: const EdgeInsets.only(left: 8.0, right: 8.0), - child: Container( - width: MediaQuery.of(context).size.width * 0.21, - child: Text("Apple Watch Series 6", overflow: TextOverflow.clip, style: TextStyle(fontSize: 12.0)), - ), - ) - ], - ), - ), - ), - ], - ), - Container( - margin: EdgeInsets.only(top: 10.0), - child: Row( - children: [ - Expanded( - child: Container( - margin: EdgeInsets.only(top: 15.0), - child: Row( - children: [ - Image.asset("assets/images/png/smartwatches/apple-watch-3.jpg", width: 70.0, height: 70.0), - Padding( - padding: const EdgeInsets.only(left: 8.0, right: 8.0), - child: Container( - width: MediaQuery.of(context).size.width * 0.21, - child: Text("Apple Watch Series 7", overflow: TextOverflow.clip, style: TextStyle(fontSize: 12.0)), - ), - ) - ], - ), - ), - ), - Expanded( - child: Container( - margin: EdgeInsets.only(top: 15.0), - child: Row( - children: [ - Image.asset("assets/images/png/smartwatches/apple-watch-4.jpg", width: 70.0, height: 70.0), - Padding( - padding: const EdgeInsets.only(left: 8.0, right: 8.0), - child: Container( - width: MediaQuery.of(context).size.width * 0.21, - child: Text("Apple Watch Series 8", overflow: TextOverflow.clip, style: TextStyle(fontSize: 12.0)), - ), - ) - ], - ), - ), - ), - ], - ), - ), - Container( - margin: EdgeInsets.only(top: 10.0), - child: Row( - children: [ - Expanded( - child: Container( - margin: EdgeInsets.only(top: 15.0), - child: Row( - children: [ - Image.asset("assets/images/png/smartwatches/apple-watch-5.jpg", width: 70.0, height: 70.0), - Padding( - padding: const EdgeInsets.only(left: 8.0, right: 8.0), - child: Container( - width: MediaQuery.of(context).size.width * 0.21, - child: Text("Apple Watch Series 9", overflow: TextOverflow.clip, style: TextStyle(fontSize: 12.0)), - ), - ) - ], - ), - ), - ), - Expanded( - child: Container( - margin: EdgeInsets.only(top: 15.0), - child: Row( - children: [ - Image.asset("assets/images/png/smartwatches/Apple-Watch-6.png", width: 70.0, height: 70.0), - Padding( - padding: const EdgeInsets.only(left: 8.0, right: 8.0), - child: Container( - width: MediaQuery.of(context).size.width * 0.21, - child: Text("Apple Watch Series 10", overflow: TextOverflow.clip, style: TextStyle(fontSize: 12.0)), - ), - ) - ], - ), - ), - ), - ], + child: CustomButton( + text: LocaleKeys.getStarted.tr(context: context), + onPressed: () { + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + height: 50.h, + ).paddingSymmetrical(24.w, 30.h), ), + child: SingleChildScrollView(), ), - SizedBox(height: 12), - "Please make sure that your Apple Watch is connected to your iPhone, is actively synced & updated.".toText14(isBold: true), - SizedBox(height: 12), - "Before syncing data, please make sure that you have followed the instructions properly.".toText14(isBold: true), - SizedBox(height: 12), - InkWell( - onTap: () { - showInstructionsDialog(context); - }, - child: "View watch instructions".toText12(isBold: true, color: AppColors.textColor, isUnderLine: true), - ), - SizedBox( - height: 130.h, - ), - CustomButton( - text: LocaleKeys.confirm.tr(context: context), - onPressed: () async { - Navigator.of(context).push( - CustomPageRoute( - page: HealthDashboard(), - ), - ); - }, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, - textColor: AppColors.whiteColor, - fontSize: 16.f, - fontWeight: FontWeight.w500, - borderRadius: 10.r, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 50.h, - iconColor: AppColors.whiteColor, - iconSize: 18.h, - ), - ], ); } - - showInstructionsDialog(BuildContext context) { - showGeneralDialog( - barrierColor: Colors.black.withOpacity(0.5), - transitionBuilder: (context, a1, a2, widget) { - final curvedValue = Curves.easeInOutBack.transform(a1.value) - 1.0; - return Transform( - transform: Matrix4.translationValues(0.0, curvedValue * 200, 0.0), - child: Opacity( - opacity: a1.value, - child: Dialog( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: 350.0, - padding: EdgeInsets.all(21), - color: Colors.white, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - "SmartWatch Tracker".toText18(), - IconButton( - icon: Icon( - Icons.close, - color: Color(0xff2E303A), - ), - onPressed: () { - Navigator.pop(context); - }, - ), - ], - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - (Platform.isIOS - ? "Make sure that you have installed 'Health' App & 'Watch' App from Apple Store." - : "Please make sure that your Samsung Watch is connected to your Phone, is actively synced & updated.") - .needTranslation - .toText14(), - SizedBox(height: 12), - ], - ) - ], - ), - ), - ], - ), - ), - ), - ); - }, - transitionDuration: Duration(milliseconds: 500), - barrierDismissible: true, - barrierLabel: '', - context: context, - pageBuilder: (context, animation1, animation2) { - return SizedBox(); - //Chanbged By Aamir - }); - } } diff --git a/lib/presentation/smartwatches/widgets/health_metric.dart b/lib/presentation/smartwatches/widgets/health_metric.dart index 53759666..43a57468 100644 --- a/lib/presentation/smartwatches/widgets/health_metric.dart +++ b/lib/presentation/smartwatches/widgets/health_metric.dart @@ -1,8 +1,10 @@ import 'dart:io'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:health/health.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; class HealthMetricInfo { @@ -39,7 +41,7 @@ class HealthMetrics { unit: 'BPM', color: AppColors.primaryRedColor, icon: Icons.favorite, - description: "Your heart rate indicates how many times your heart beats per minute".needTranslation, + description: LocaleKeys.heartRateDescription.tr(), minHealthyValue: 60, maxHealthyValue: 100, svgIcon: "assets/images/smartwatches/heartrate_icon.svg"), @@ -51,7 +53,7 @@ class HealthMetrics { // color: Colors.blue, color: Color(0xff3A3558), icon: Icons.air, - description: "Blood oxygen level indicates how much oxygen your red blood cells are carrying".needTranslation, + description: LocaleKeys.bloodOxygenDescription.tr(), minHealthyValue: 95, maxHealthyValue: 100, svgIcon: "assets/images/smartwatches/bloodoxygen_icon.svg"), diff --git a/lib/presentation/smartwatches/widgets/supported_watches_list.dart b/lib/presentation/smartwatches/widgets/supported_watches_list.dart new file mode 100644 index 00000000..839af802 --- /dev/null +++ b/lib/presentation/smartwatches/widgets/supported_watches_list.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +class SupportedWatchesList extends StatelessWidget { + const SupportedWatchesList({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.r, + ), + child: Padding( + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + "Apple Watches".needTranslation.toText16(isBold: true), + ], + ), + SizedBox(height: 4.h), + "• Apple Watch Ultra".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + "• Apple Watch 11".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + "• Apple Watch 10".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + "• Apple Watch 9".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + "• Apple Watch 8".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + SizedBox(height: 16.h), + Row( + children: [ + "Samsung Watches".needTranslation.toText16(isBold: true), + ], + ), + SizedBox(height: 4.h), + "• Samsung Watch Ultra".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + "• Galaxy Watch 8".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + "• Galaxy Watch 7".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + "• Galaxy Watch 6".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + "• Galaxy Watch FE".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + "• Galaxy Watch Fit".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + SizedBox(height: 16.h), + Row( + children: [ + "Huawei Watches".needTranslation.toText16(isBold: true), + ], + ), + SizedBox(height: 4.h), + "• Huawei Watch Ultimate Series".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + "• Huawei Watch Series".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + "• Huawei Watch Fit Series".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + "• Huawei Watch Bands".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + SizedBox(height: 16.h), + Row( + children: [ + "Whoop Watches".needTranslation.toText16(isBold: true), + ], + ), + SizedBox(height: 4.h), + "• Whoop 1".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + "• Whoop 2".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/symptoms_checker/organ_selector_screen.dart b/lib/presentation/symptoms_checker/organ_selector_screen.dart index 1786dece..253fccdc 100644 --- a/lib/presentation/symptoms_checker/organ_selector_screen.dart +++ b/lib/presentation/symptoms_checker/organ_selector_screen.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; @@ -6,9 +7,9 @@ import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; -import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/interactive_body_widget.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -38,11 +39,13 @@ class _OrganSelectorPageState extends State { void _onNextPressed(SymptomsCheckerViewModel viewModel) async { if (!viewModel.validateSelection()) { dialogService.showErrorBottomSheet( - message: 'Please select at least one organ'.needTranslation, + message: LocaleKeys.noOrgansSelected.tr(context: context), ); return; } - LoaderBottomSheet.showLoader(loadingText: "Please wait".needTranslation); + LoaderBottomSheet.showLoader( + loadingText: LocaleKeys.pleaseWait.tr(context: context), + ); final String userName = 'guest_user'; final String password = '123456'; @@ -96,11 +99,11 @@ class _OrganSelectorPageState extends State { flipX: _appState.isArabic(), child: IconButton( icon: Utils.buildSvgWithAssets( - icon: AppAssets.arrow_back, - width: 32.h, - height: 32.h, + icon: AppAssets.forward_top_nav_icon, + width: 24.w, + height: 24.h, ), - padding: const EdgeInsets.only(left: 12), + padding: EdgeInsetsDirectional.only(start: 12, end: 12), onPressed: () => Navigator.pop(context), highlightColor: Colors.transparent, ), @@ -112,7 +115,7 @@ class _OrganSelectorPageState extends State { return Padding( padding: EdgeInsets.symmetric(horizontal: 16.w), child: Text( - "Organ Selector".needTranslation, + LocaleKeys.organSelector.tr(context: context), style: TextStyle( color: AppColors.textColor, fontSize: 22.f, @@ -123,21 +126,44 @@ class _OrganSelectorPageState extends State { } Widget _buildBodyViewer(SymptomsCheckerViewModel viewModel) { - return Padding( - padding: EdgeInsets.fromLTRB(16.h, 16.h, 16.h, 60.h), - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 600), - transitionBuilder: (child, animation) => _build3DFlipTransition(child, animation), - switchInCurve: Curves.easeInOut, - switchOutCurve: Curves.easeInOut, - child: InteractiveBodyWidget( - key: ValueKey(viewModel.currentView), - bodyImageAsset: viewModel.currentView == BodyView.front ? AppAssets.fullBodyFront : AppAssets.fullBodyBack, - organs: viewModel.currentOrgans, - selectedOrganIds: viewModel.selectedOrganIds, - onOrganTap: viewModel.toggleOrganSelection, - isBodyHidden: viewModel.isBodyHidden, - tooltipOrganId: viewModel.tooltipOrganId, + return GestureDetector( + onHorizontalDragEnd: (details) { + // Swipe left or right to toggle view + if (details.primaryVelocity != null) { + if (details.primaryVelocity! < -200 || details.primaryVelocity! > 200) { + viewModel.toggleView(); + } + } + }, + child: Padding( + padding: EdgeInsets.fromLTRB(16.h, 16.h, 16.h, 60.h), + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 600), + transitionBuilder: (child, animation) => _build3DFlipTransition(child, animation), + switchInCurve: Curves.easeInOut, + switchOutCurve: Curves.easeInOut, + child: Builder( + key: ValueKey(viewModel.currentView), + builder: (context) { + // Detect female gender from viewModel; allow Arabic value as fallback + final bool isFemale = + (viewModel.selectedGender != null && (viewModel.selectedGender!.toLowerCase() == 'female' || viewModel.selectedGender == 'أنثى')); + + final String bodyAsset = viewModel.currentView == BodyView.front + ? (isFemale ? AppAssets.fullBodyFrontFemale : AppAssets.fullBodyFrontMale) + : (isFemale ? AppAssets.fullBodyBackFemale : AppAssets.fullBodyBackMale); + + return InteractiveBodyWidget( + bodyImageAsset: bodyAsset, + organs: viewModel.currentOrgans, + selectedOrganIds: viewModel.selectedOrganIds, + onOrganTap: viewModel.toggleOrganSelection, + isBodyHidden: viewModel.isBodyHidden, + tooltipOrganId: viewModel.tooltipOrganId, + isArabic: _appState.isArabic(), + ); + }, + ), ), ), ); @@ -167,8 +193,8 @@ class _OrganSelectorPageState extends State { } Widget _buildViewToggleButtons(SymptomsCheckerViewModel viewModel) { - return Positioned( - left: 16.w, + return PositionedDirectional( + start: 16.w, top: 36.h, child: Column( children: [ @@ -249,7 +275,7 @@ class _OrganSelectorPageState extends State { return Padding( padding: EdgeInsets.symmetric(horizontal: 16.w), child: Text( - 'Selected Organs'.needTranslation, + LocaleKeys.selectedOrgans.tr(context: context), style: TextStyle( fontSize: 16.f, fontWeight: FontWeight.w600, @@ -264,7 +290,7 @@ class _OrganSelectorPageState extends State { return Padding( padding: EdgeInsets.symmetric(horizontal: 16.w), child: Text( - 'No organs selected yet'.needTranslation, + LocaleKeys.noOrgansSelected.tr(context: context), style: TextStyle( color: AppColors.greyTextColor, fontSize: 14.f, @@ -273,6 +299,8 @@ class _OrganSelectorPageState extends State { ); } + final isArabic = _appState.isArabic(); + return ConstrainedBox( constraints: BoxConstraints(maxHeight: 150.h), child: SingleChildScrollView( @@ -282,7 +310,7 @@ class _OrganSelectorPageState extends State { runSpacing: 8.h, children: viewModel.selectedOrgans.map((organ) { return AppCustomChipWidget( - labelText: organ.description, + labelText: organ.getLocalizedDescription(isArabic), backgroundColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, deleteIcon: AppAssets.cancel, @@ -301,7 +329,7 @@ class _OrganSelectorPageState extends State { return Padding( padding: EdgeInsets.symmetric(horizontal: 16.w), child: CustomButton( - text: 'Next'.needTranslation, + text: LocaleKeys.next.tr(context: context), onPressed: () => _onNextPressed(viewModel), isDisabled: viewModel.selectedOrgans.isEmpty, backgroundColor: AppColors.primaryRedColor, @@ -311,8 +339,8 @@ class _OrganSelectorPageState extends State { } Widget _buildExpandCollapseButton(SymptomsCheckerViewModel viewModel) { - return Positioned( - right: 24.w, + return PositionedDirectional( + end: 24.w, top: -24.h, child: GestureDetector( onTap: viewModel.toggleBottomSheet, @@ -329,13 +357,16 @@ class _OrganSelectorPageState extends State { borderRadius: 11.r, ), child: Center( - child: AnimatedRotation( - duration: const Duration(milliseconds: 300), - turns: viewModel.isBottomSheetExpanded ? 0.25 : -0.25, - child: Utils.buildSvgWithAssets( - icon: AppAssets.arrowRight, - width: 25.w, - height: 25.h, + child: Transform.flip( + flipX: _appState.isArabic(), + child: AnimatedRotation( + duration: const Duration(milliseconds: 300), + turns: viewModel.isBottomSheetExpanded ? 0.25 : -0.25, + child: Utils.buildSvgWithAssets( + icon: AppAssets.arrowRight, + width: 25.w, + height: 25.h, + ), ), ), ), diff --git a/lib/presentation/symptoms_checker/possible_conditions_screen.dart b/lib/presentation/symptoms_checker/possible_conditions_screen.dart index a63d1e2d..3d288d78 100644 --- a/lib/presentation/symptoms_checker/possible_conditions_screen.dart +++ b/lib/presentation/symptoms_checker/possible_conditions_screen.dart @@ -3,13 +3,14 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; -import 'package:hmg_patient_app_new/features/symptoms_checker/models/conditions_model.dart'; -import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/triage_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/condition_card.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; @@ -45,13 +46,13 @@ class PossibleConditionsPage extends StatelessWidget { ); } - Widget _buildPredictionsList(BuildContext context, List conditions) { + Widget _buildPredictionsList(BuildContext context, List conditions, List symptoms) { if (conditions.isEmpty) { return Center( child: Padding( padding: EdgeInsets.all(24.h), child: Text( - 'No Predictions available'.needTranslation, + LocaleKeys.noPredictionsAvailable.tr(context: context), style: TextStyle( fontSize: 16.h, color: AppColors.greyTextColor, @@ -74,19 +75,13 @@ class PossibleConditionsPage extends StatelessWidget { physics: NeverScrollableScrollPhysics(), separatorBuilder: (context, index) => SizedBox(height: 16.h), itemBuilder: (context, index) { - final conditionModel = conditions[index]; + final condition = conditions[index]; return ConditionCard( - icon: conditionModel.icon, - title: conditionModel.title, - percentage: conditionModel.percentage, - tagText: conditionModel.tagText, - clinic: conditionModel.clinic, - symptoms: conditionModel.symptoms, - description: conditionModel.description, - possibleConditionsSeverityEnum: conditionModel.possibleConditionsSeverityEnum, + condition: condition, + symptoms: symptoms, onActionPressed: () { dialogService.showErrorBottomSheet( - message: 'We are not available for a week. May you Rest In Peace :(', + message: 'icd10 Code is ${condition.conditionDetails?.icd10Code}. We need to get the clinics mapped against this code.', ); }, ); @@ -102,7 +97,7 @@ class PossibleConditionsPage extends StatelessWidget { title: LocaleKeys.notice.tr(context: context), context, child: Utils.getWarningWidget( - loadingText: "Are you sure you want to restart the organ selection?".needTranslation, + loadingText: LocaleKeys.areYouSureYouWantToExitProgress.tr(context: context), isShowActionButtons: true, onCancelTap: () => Navigator.pop(context), onConfirmTap: () => onConfirm(), @@ -113,47 +108,88 @@ class PossibleConditionsPage extends StatelessWidget { ); } - _restartOrganSelection(BuildContext context) async { - final symptomsCheckerVm = context.read(); - symptomsCheckerVm.reset(); - context.pop(); - await Future.delayed(Duration(seconds: 1)).whenComplete(() => context.pop()); - } + Widget _buildInfoTile({required PossibleConditionsSeverityEnum severityEnum, required BuildContext context}) { + String title; + String description; + Color dotColor; - _navigateToLandingPage() { - NavigationService navigationService = getIt.get(); - navigationService.replaceAllRoutesAndNavigateToLanding(); - } + switch (severityEnum) { + case PossibleConditionsSeverityEnum.monitorOnly: + title = LocaleKeys.monitor.tr(context: context); + description = LocaleKeys.monitorDescription.tr(context: context); + dotColor = AppColors.chipColorMonitor; + break; + case PossibleConditionsSeverityEnum.seekMedicalAdvice: + title = LocaleKeys.seekMedicalAdvice.tr(context: context); + description = LocaleKeys.seekMedicalAdviceDescription.tr(context: context); + dotColor = AppColors.chipColorSeekMedicalAdvice; + break; + case PossibleConditionsSeverityEnum.emergency: + title = LocaleKeys.emergency.tr(context: context); + description = LocaleKeys.emergencyDescription.tr(context: context); + dotColor = AppColors.chipColorEmergency; + break; + } - _buildTrailingSection(BuildContext context) { return Row( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - height: 40.h, - padding: EdgeInsets.all(8.w), - child: Center( - child: Utils.buildSvgWithAssets( - icon: AppAssets.refreshIcon, - height: 20.h, - width: 20.w, - iconColor: AppColors.textColor, - ), + height: 14.h, + width: 14.w, + decoration: BoxDecoration(shape: BoxShape.circle, color: dotColor), + ).paddingOnly(top: 3.h), + SizedBox(width: 8.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title.toText14(weight: FontWeight.w600, color: AppColors.textColor), + SizedBox(height: 4.h), + description.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + ], ), - ).onPress(() => _buildConfirmationBottomSheet(context: context, onConfirm: () => _restartOrganSelection(context))), - Container( - height: 40.h, - padding: EdgeInsets.all(8.w), - child: Center( - child: Utils.buildSvgWithAssets( - icon: AppAssets.homeBorderedIcon, - height: 20.h, - width: 20.w, - iconColor: AppColors.textColor, - ), - ), - ).onPress(() => _buildConfirmationBottomSheet(context: context, onConfirm: () => _navigateToLandingPage())), + ), ], - ).paddingSymmetrical(20.w, 0); + ); + } + + _buildSeverityDetailsBottomsheet({required BuildContext context}) { + return showCommonBottomSheetWithoutHeight( + title: LocaleKeys.colorScience.tr(context: context), + context, + child: Container( + padding: EdgeInsets.all(16.w), + width: double.infinity, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), + child: Column( + children: [ + _buildInfoTile(severityEnum: PossibleConditionsSeverityEnum.monitorOnly, context: context), + Divider(color: AppColors.bottomNAVBorder, height: 24.h, thickness: 1), + _buildInfoTile(severityEnum: PossibleConditionsSeverityEnum.seekMedicalAdvice, context: context), + Divider(color: AppColors.bottomNAVBorder, height: 24.h, thickness: 1), + _buildInfoTile(severityEnum: PossibleConditionsSeverityEnum.emergency, context: context), + ], + ), + ), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + + _buildTrailingSection(BuildContext context) { + return Container( + height: 40.h, + padding: EdgeInsets.all(8.w), + child: Center( + child: Utils.buildSvgWithAssets( + icon: AppAssets.coloredDotsIcon, + height: 20.h, + width: 20.w, + ), + ), + ).onPress(() => _buildSeverityDetailsBottomsheet(context: context)); } @override @@ -161,14 +197,34 @@ class PossibleConditionsPage extends StatelessWidget { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( - title: "Possible Conditions".needTranslation, + title: LocaleKeys.possibleConditions.tr(context: context), + leadingCallback: () => _buildConfirmationBottomSheet( + context: context, + onConfirm: () { + context.pop(); + final SymptomsCheckerViewModel symptomsCheckerViewModel = context.read(); + symptomsCheckerViewModel.reset(); // Clear all symptoms checker data + final navigationService = getIt.get(); + // navigationService.replaceAllRoutesAndNavigateToLanding(); + navigationService.replaceAllRoutesAndNavigateToLandingAuth(); + }, + ), trailing: _buildTrailingSection(context), child: Consumer( builder: (context, symptomsCheckerViewModel, child) { - if (symptomsCheckerViewModel.isPossibleConditionsLoading || symptomsCheckerViewModel.isPossibleConditionsLoading) { + if (symptomsCheckerViewModel.isPossibleConditionsLoading || symptomsCheckerViewModel.isTriageDiagnosisLoading) { return _buildLoadingShimmer(); } - return _buildPredictionsList(context, dummyConditions); + // Get conditions directly from ViewModel + final conditions = symptomsCheckerViewModel.currentConditions ?? []; + // Get selected symptoms names for display + final symptoms = symptomsCheckerViewModel + .getAllSelectedSymptoms() + .map((s) => s.commonName ?? s.name ?? '') + .where((name) => name.isNotEmpty) + .take(3) + .toList(); + return _buildPredictionsList(context, conditions, symptoms); }, ), ), diff --git a/lib/presentation/symptoms_checker/risk_factors_screen.dart b/lib/presentation/symptoms_checker/risk_factors_screen.dart index 8669c3ca..484ad2d7 100644 --- a/lib/presentation/symptoms_checker/risk_factors_screen.dart +++ b/lib/presentation/symptoms_checker/risk_factors_screen.dart @@ -1,13 +1,16 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; @@ -23,11 +26,13 @@ class RiskFactorsScreen extends StatefulWidget { class _RiskFactorsScreenState extends State { late DialogService dialogService; + late AppState _appState; @override void initState() { super.initState(); dialogService = getIt(); + _appState = getIt(); // Fetch risk factors based on selected symptoms WidgetsBinding.instance.addPostFrameCallback((_) { final viewModel = context.read(); @@ -44,7 +49,7 @@ class _RiskFactorsScreenState extends State { context.navigateWithName(AppRoutes.suggestionsPage); } else { dialogService.showErrorBottomSheet( - message: 'Please select at least one risk before proceeding'.needTranslation, + message: LocaleKeys.pleaseSelectAtLeastOneRiskBeforeProceeding.tr(context: context), ); } } @@ -88,6 +93,8 @@ class _RiskFactorsScreenState extends State { } Widget _buildRiskFactorsList(SymptomsCheckerViewModel viewModel) { + final isArabic = _appState.isArabic(); + return Container( key: ValueKey(viewModel.riskFactorsList.length), width: double.infinity, @@ -98,7 +105,7 @@ class _RiskFactorsScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ ...viewModel.riskFactorsList.map((factor) { - return _buildRiskFactorItem(viewModel, factor.id ?? '', factor.getDisplayName()); + return _buildRiskFactorItem(viewModel, factor.id ?? '', factor.getDisplayName(isArabic: isArabic)); }), SizedBox(height: 12.w), Row( @@ -118,14 +125,14 @@ class _RiskFactorsScreenState extends State { fontSize: 13.f, fontWeight: FontWeight.w500, color: AppColors.greyInfoTextColor, + fontFamily: isArabic ? 'GESSTwo' : 'Poppins' ), children: [ TextSpan( - text: "Above you see the most common risk factors. Although /diagnosis may return questions about risk factors, " - .needTranslation, + text: LocaleKeys.aboveYouSeeCommonRiskFactors.tr(context: context), ), TextSpan( - text: "read more".needTranslation, + text: LocaleKeys.readMore.tr(context: context), style: TextStyle( color: AppColors.primaryRedColor, fontWeight: FontWeight.w500, @@ -195,7 +202,7 @@ class _RiskFactorsScreenState extends State { border: Border.all(color: AppColors.bottomNAVBorder, width: 1), ), child: Text( - 'Not Applicable Risk Factor', + 'Not Applicable Risk Factor'.needTranslation, style: TextStyle(fontSize: 14.f, color: AppColors.textColor), ), ).toShimmer2(isShow: true, radius: 24.r); @@ -216,7 +223,7 @@ class _RiskFactorsScreenState extends State { children: [ Expanded( child: CollapsingListView( - title: "Risk Factors".needTranslation, + title: LocaleKeys.riskFactors.tr(context: context), leadingCallback: () => context.pop(), child: viewModel.isRiskFactorsLoading ? _buildLoadingShimmer() @@ -249,7 +256,7 @@ class _RiskFactorsScreenState extends State { Icon(Icons.info_outline, size: 64.h, color: AppColors.greyTextColor), SizedBox(height: 16.h), Text( - 'No risk factors found'.needTranslation, + LocaleKeys.noRiskFactorsFound.tr(context: context), style: TextStyle( fontSize: 18.f, fontWeight: FontWeight.w600, @@ -258,7 +265,7 @@ class _RiskFactorsScreenState extends State { ), SizedBox(height: 8.h), Text( - 'Based on your selected symptoms, no additional risk factors were identified.'.needTranslation, + LocaleKeys.basedOnYourSelectedSymptomsNoRiskFactors.tr(context: context), textAlign: TextAlign.center, style: TextStyle( fontSize: 14.f, @@ -282,7 +289,7 @@ class _RiskFactorsScreenState extends State { children: [ Expanded( child: CustomButton( - text: "Previous".needTranslation, + text: LocaleKeys.previous.tr(context: context), onPressed: _onPreviousPressed, backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.11), borderColor: Colors.transparent, @@ -293,7 +300,7 @@ class _RiskFactorsScreenState extends State { SizedBox(width: 12.w), Expanded( child: CustomButton( - text: "Next".needTranslation, + text: LocaleKeys.next.tr(context: context), onPressed: () => _onNextPressed(viewModel), backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, diff --git a/lib/presentation/symptoms_checker/suggestions_screen.dart b/lib/presentation/symptoms_checker/suggestions_screen.dart index d0d5b2f4..8d32b7f4 100644 --- a/lib/presentation/symptoms_checker/suggestions_screen.dart +++ b/lib/presentation/symptoms_checker/suggestions_screen.dart @@ -1,12 +1,15 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; @@ -22,11 +25,13 @@ class SuggestionsScreen extends StatefulWidget { class _SuggestionsScreenState extends State { late DialogService dialogService; + late AppState _appState; @override void initState() { super.initState(); dialogService = getIt(); + _appState = getIt(); // Initialize symptom groups based on selected organs WidgetsBinding.instance.addPostFrameCallback((_) { final viewModel = context.read(); @@ -46,7 +51,7 @@ class _SuggestionsScreenState extends State { context.navigateWithName(AppRoutes.triagePage); } else { dialogService.showErrorBottomSheet( - message: 'Please select at least one option before proceeding'.needTranslation, + message: LocaleKeys.pleaseSelectAtLeastOneOptionBeforeProceeding.tr(), ); } } @@ -90,6 +95,8 @@ class _SuggestionsScreenState extends State { } Widget _buildSuggestionsList(SymptomsCheckerViewModel viewModel) { + final isArabic = _appState.isArabic(); + return Container( key: ValueKey(viewModel.suggestionsList.length), width: double.infinity, @@ -100,7 +107,7 @@ class _SuggestionsScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ ...viewModel.suggestionsList.map((factor) { - return _buildSuggestionItem(viewModel, factor.id ?? '', factor.getDisplayName()); + return _buildSuggestionItem(viewModel, factor.id ?? '', factor.getDisplayName(isArabic: isArabic)); }), SizedBox(height: 12.w), Row( @@ -113,7 +120,7 @@ class _SuggestionsScreenState extends State { ), SizedBox(width: 12.w), Expanded( - child: "This is a list of symptoms suggested by our AI, based on the information gathered so far during the interview".toText12( + child: LocaleKeys.suggestionAIListDescription.tr(context: context).toText12( color: AppColors.greyInfoTextColor, ), ) @@ -192,7 +199,7 @@ class _SuggestionsScreenState extends State { children: [ Expanded( child: CollapsingListView( - title: "Suggestions".needTranslation, + title: LocaleKeys.suggestions.tr(), leadingCallback: () => context.pop(), child: viewModel.isSuggestionsLoading ? _buildLoadingShimmer() @@ -225,7 +232,7 @@ class _SuggestionsScreenState extends State { Icon(Icons.info_outline, size: 64.h, color: AppColors.greyTextColor), SizedBox(height: 16.h), Text( - 'No organs selected'.needTranslation, + LocaleKeys.noOrgansSelected.tr(), style: TextStyle( fontSize: 18.f, fontWeight: FontWeight.w600, @@ -234,7 +241,7 @@ class _SuggestionsScreenState extends State { ), SizedBox(height: 8.h), Text( - 'Please go back and select organs first'.needTranslation, + LocaleKeys.pleaseGoBackAndSelectOrgansFirst.tr(), textAlign: TextAlign.center, style: TextStyle( fontSize: 14.f, @@ -258,7 +265,7 @@ class _SuggestionsScreenState extends State { children: [ Expanded( child: CustomButton( - text: "Previous".needTranslation, + text: LocaleKeys.previous.tr(), onPressed: _onPreviousPressed, backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.11), borderColor: Colors.transparent, @@ -269,7 +276,7 @@ class _SuggestionsScreenState extends State { SizedBox(width: 12.w), Expanded( child: CustomButton( - text: "Next".needTranslation, + text: LocaleKeys.next.tr(), onPressed: () => _onNextPressed(viewModel), backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, diff --git a/lib/presentation/symptoms_checker/symptoms_selector_screen.dart b/lib/presentation/symptoms_checker/symptoms_selector_screen.dart index d6036c62..cbbfc68e 100644 --- a/lib/presentation/symptoms_checker/symptoms_selector_screen.dart +++ b/lib/presentation/symptoms_checker/symptoms_selector_screen.dart @@ -1,15 +1,16 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; -import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/body_symptom_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -26,11 +27,13 @@ class SymptomsSelectorPage extends StatefulWidget { class _SymptomsSelectorPageState extends State { late DialogService dialogService; + late AppState _appState; @override void initState() { super.initState(); dialogService = getIt(); + _appState = getIt(); // Initialize symptom groups based on selected organs WidgetsBinding.instance.addPostFrameCallback((_) { final viewModel = context.read(); @@ -44,7 +47,7 @@ class _SymptomsSelectorPageState extends State { context.navigateWithName(AppRoutes.riskFactorsPage); } else { dialogService.showErrorBottomSheet( - message: 'Please select at least one symptom before proceeding'.needTranslation, + message: LocaleKeys.pleaseSelectAtLeastOneOptionBeforeProceeding.tr(), ); } } @@ -58,7 +61,7 @@ class _SymptomsSelectorPageState extends State { title: LocaleKeys.notice.tr(context: context), context, child: Utils.getWarningWidget( - loadingText: "Are you sure you want to restart the organ selection?".needTranslation, + loadingText: LocaleKeys.areYouSureYouWantToRestartOrganSelection.tr(), isShowActionButtons: true, onCancelTap: () => Navigator.pop(context), onConfirmTap: () => onConfirm(), @@ -79,13 +82,16 @@ class _SymptomsSelectorPageState extends State { children: [ Expanded( child: CollapsingListView( - title: "Symptoms Selector".needTranslation, + title: LocaleKeys.symptomsSelector.tr(), leadingCallback: () => _buildConfirmationBottomSheet( context: context, - onConfirm: () => { - context.pop(), - context.pop(), - }), + onConfirm: () { + context.pop(); + viewModel.reset(); // Clear all symptoms checker data + final navigationService = getIt.get(); + // navigationService.replaceAllRoutesAndNavigateToLanding(); + navigationService.replaceAllRoutesAndNavigateToLandingAuth(); + }), child: viewModel.isBodySymptomsLoading ? _buildLoadingShimmer() : viewModel.organSymptomsResults.isEmpty @@ -98,10 +104,11 @@ class _SymptomsSelectorPageState extends State { // Find matching organ ID from selected organs String? organId; String? organName; + final isArabic = _appState.isArabic(); for (var organ in viewModel.selectedOrgans) { if (organ.name == organResult.name) { organId = organ.id; - organName = organ.description; + organName = organ.getLocalizedDescription(isArabic); break; } } @@ -139,6 +146,8 @@ class _SymptomsSelectorPageState extends State { String organName, List symptoms, ) { + final isArabic = _appState.isArabic(); + return Container( width: double.infinity, margin: EdgeInsets.symmetric(horizontal: 24.w), @@ -152,7 +161,7 @@ class _SymptomsSelectorPageState extends State { children: [ Expanded( child: Text( - 'Possible symptoms related to "$organName"', + '${LocaleKeys.possibleSymptomsRelatedTo.tr(context: context)} "$organName"', style: TextStyle(fontSize: 18.f, fontWeight: FontWeight.w600, color: AppColors.textColor), ), ), @@ -164,7 +173,7 @@ class _SymptomsSelectorPageState extends State { spacing: 8.w, children: symptoms.map((symptom) { final symptomId = symptom.id ?? ''; - final symptomLabel = symptom.getDisplayName(false); // Pass isArabic from AppState if needed + final symptomLabel = symptom.getDisplayName(isArabic); bool isSelected = viewModel.isSymptomSelected(organId, symptomId); return GestureDetector( onTap: () => viewModel.toggleSymptomSelection(organId, symptomId), @@ -252,7 +261,7 @@ class _SymptomsSelectorPageState extends State { Icon(Icons.info_outline, size: 64.h, color: AppColors.greyTextColor), SizedBox(height: 16.h), Text( - 'No organs selected'.needTranslation, + LocaleKeys.noOrgansSelected.tr(context: context), style: TextStyle( fontSize: 18.f, fontWeight: FontWeight.w600, @@ -261,7 +270,7 @@ class _SymptomsSelectorPageState extends State { ), SizedBox(height: 8.h), Text( - 'Please go back and select organs first'.needTranslation, + LocaleKeys.pleaseGoBackAndSelectOrgansFirst.tr(), textAlign: TextAlign.center, style: TextStyle( fontSize: 14.f, @@ -285,7 +294,7 @@ class _SymptomsSelectorPageState extends State { children: [ Expanded( child: CustomButton( - text: "Previous".needTranslation, + text: LocaleKeys.previous.tr(context: context), onPressed: _onPreviousPressed, backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.11), borderColor: Colors.transparent, @@ -296,7 +305,7 @@ class _SymptomsSelectorPageState extends State { SizedBox(width: 12.w), Expanded( child: CustomButton( - text: "Next".needTranslation, + text: LocaleKeys.next.tr(context: context), onPressed: () => _onNextPressed(viewModel), backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, @@ -304,7 +313,7 @@ class _SymptomsSelectorPageState extends State { fontSize: 16.f, ), ), - ], + ], ), SizedBox(height: 24.h), ], diff --git a/lib/presentation/symptoms_checker/triage_screen.dart b/lib/presentation/symptoms_checker/triage_screen.dart index 9d5d884b..0231ef19 100644 --- a/lib/presentation/symptoms_checker/triage_screen.dart +++ b/lib/presentation/symptoms_checker/triage_screen.dart @@ -4,6 +4,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; @@ -14,6 +15,7 @@ import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_v import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/custom_progress_bar.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -31,12 +33,14 @@ class TriagePage extends StatefulWidget { class _TriagePageState extends State { late SymptomsCheckerViewModel viewModel; late DialogService dialogService; + late AppState _appState; @override void initState() { super.initState(); viewModel = context.read(); dialogService = getIt.get(); + _appState = getIt.get(); // Start triage process when screen loads WidgetsBinding.instance.addPostFrameCallback((_) { @@ -117,15 +121,16 @@ class _TriagePageState extends State { Lottie.asset(AppAnimations.ambulanceAlert, repeat: false, reverse: false, frameRate: FrameRate(60), width: 120.h, height: 120.h, fit: BoxFit.contain), SizedBox(height: 8.h), - "Emergency".needTranslation.toText28(color: AppColors.whiteColor, isBold: true), + LocaleKeys.emergencyTriage.tr(context: context).toText28(color: AppColors.whiteColor, isBold: true), SizedBox(height: 8.h), - "Emergency evidence detected. Please seek medical attention." - .needTranslation - .toText14(color: AppColors.whiteColor, weight: FontWeight.w500), + LocaleKeys.emergencyEvidenceDetected.tr(context: context).toText14(color: AppColors.whiteColor, weight: FontWeight.w500), SizedBox(height: 24.h), CustomButton( text: LocaleKeys.confirm.tr(context: context), - onPressed: () async => Navigator.of(context).pop(), + onPressed: () { + context.pop(); + context.navigateWithName(AppRoutes.possibleConditionsPage); + }, backgroundColor: AppColors.whiteColor, borderColor: AppColors.whiteColor, textColor: AppColors.primaryRedColor, @@ -159,14 +164,14 @@ class _TriagePageState extends State { final currentQuestion = viewModel.currentTriageQuestion; if (currentQuestion?.items == null || currentQuestion!.items!.isEmpty) { dialogService.showErrorBottomSheet( - message: 'No question items available'.needTranslation, + message: LocaleKeys.noQuestionItemsAvailable.tr(context: context), ); return; } // Check if all items have been answered if (!viewModel.areAllTriageItemsAnswered) { - dialogService.showErrorBottomSheet(message: 'Please answer all questions before proceeding'.needTranslation); + dialogService.showErrorBottomSheet(message: LocaleKeys.pleaseAnswerAllQuestionsBeforeProceeding.tr(context: context)); return; } @@ -198,7 +203,7 @@ class _TriagePageState extends State { // Call API with updated evidence viewModel.getDiagnosisForTriage( age: viewModel.selectedAge!, - sex: viewModel.selectedGender!.toLowerCase(), + gender: viewModel.selectedGender!.toLowerCase() == "male" ? 1 : 2, evidenceIds: initialEvidenceIds, triageEvidence: triageEvidence, language: viewModel.appState.isArabic() ? 'ar' : 'en', @@ -222,7 +227,7 @@ class _TriagePageState extends State { children: [ Expanded( child: CollapsingListView( - title: "Triage".needTranslation, + title: LocaleKeys.triage.tr(context: context), leadingCallback: () => _showConfirmationBeforeExit(context), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -304,12 +309,15 @@ class _TriagePageState extends State { title: LocaleKeys.notice.tr(context: context), context, child: Utils.getWarningWidget( - loadingText: "Are you sure you want to exit? Your progress will be lost.".needTranslation, + loadingText: LocaleKeys.areYouSureYouWantToExitProgress.tr(context: context), isShowActionButtons: true, onCancelTap: () => Navigator.pop(context), onConfirmTap: () { - Navigator.pop(context); context.pop(); + viewModel.reset(); // Clear all symptoms checker data + final navigationService = getIt.get(); + // navigationService.replaceAllRoutesAndNavigateToLanding(); + navigationService.replaceAllRoutesAndNavigateToLandingAuth(); }, ), callBackFunc: () {}, @@ -325,7 +333,7 @@ class _TriagePageState extends State { if (viewModel.currentTriageQuestion == null) { return Center( - child: "No question available".needTranslation.toText16(weight: FontWeight.w500), + child: LocaleKeys.noQuestionAvailable.tr(context: context).toText16(weight: FontWeight.w500), ); } @@ -432,7 +440,8 @@ class _TriagePageState extends State { Widget _buildStickyBottomCard(BuildContext context, SymptomsCheckerViewModel viewModel) { // Get the top condition with highest probability final conditions = viewModel.currentConditions ?? []; - String suggestedCondition = "Analyzing..."; + final isArabic = _appState.isArabic(); + String suggestedCondition = LocaleKeys.analyzing.tr(context: context); double probability = 0.0; if (conditions.isNotEmpty) { @@ -441,7 +450,7 @@ class _TriagePageState extends State { sortedConditions.sort((a, b) => (b.probability ?? 0.0).compareTo(a.probability ?? 0.0)); final topCondition = sortedConditions.first; - suggestedCondition = topCondition.commonName ?? topCondition.name ?? "Unknown"; + suggestedCondition = topCondition.getDisplayName(isArabic: isArabic); probability = (topCondition.probability ?? 0.0) * 100; // Convert to percentage } // final bool isHighConfidence = probability >= 70.0; @@ -457,20 +466,16 @@ class _TriagePageState extends State { children: [ RichText( text: TextSpan( - text: "Possible symptom: ".needTranslation, + text: LocaleKeys.possibleSymptom.tr(context: context), style: TextStyle( - color: AppColors.greyTextColor, - fontWeight: FontWeight.w600, - fontSize: 14.f, - ), + color: AppColors.greyTextColor, fontWeight: FontWeight.w600, fontSize: 14.f, fontFamily: isArabic ? 'GESSTwo' : 'Poppins'), children: [ TextSpan( text: suggestedCondition, style: TextStyle( color: AppColors.textColor, fontWeight: FontWeight.w600, - fontSize: 14.f, - ), + fontSize: 14.f, fontFamily: isArabic ? 'GESSTwo' : 'Poppins'), ), ], ), @@ -486,17 +491,19 @@ class _TriagePageState extends State { text: TextSpan( text: "${probability.toStringAsFixed(1)}% ", style: TextStyle( + fontFamily: isArabic ? 'GESSTwo' : 'Poppins', color: AppColors.primaryRedColor, fontWeight: FontWeight.w600, fontSize: 14.f, ), children: [ TextSpan( - text: "- Symptoms checker finding score".needTranslation, + text: LocaleKeys.symptomsCheckerFindingScore.tr(context: context), style: TextStyle( color: AppColors.textColor, fontWeight: FontWeight.w500, fontSize: 13.f, + fontFamily: isArabic ? 'GESSTwo' : 'Poppins', ), ), ], @@ -510,7 +517,7 @@ class _TriagePageState extends State { children: [ Expanded( child: CustomButton( - text: "Previous".needTranslation, + text: LocaleKeys.previous.tr(context: context), onPressed: isFirstQuestion ? () {} : _onPreviousPressed, isDisabled: isFirstQuestion || viewModel.isTriageDiagnosisLoading, backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.11), @@ -522,7 +529,7 @@ class _TriagePageState extends State { SizedBox(width: 12.w), Expanded( child: CustomButton( - text: "Next".needTranslation, + text: LocaleKeys.next.tr(context: context), isDisabled: viewModel.isTriageDiagnosisLoading, onPressed: _onNextPressed, backgroundColor: AppColors.primaryRedColor, diff --git a/lib/presentation/symptoms_checker/user_info_selection.dart b/lib/presentation/symptoms_checker/user_info_selection.dart index b4384203..f1e8413d 100644 --- a/lib/presentation/symptoms_checker/user_info_selection.dart +++ b/lib/presentation/symptoms_checker/user_info_selection.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; @@ -8,20 +9,22 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:provider/provider.dart'; -class UserInfoSelectionScreen extends StatefulWidget { - const UserInfoSelectionScreen({super.key}); +class UserInfoSelectionPage extends StatefulWidget { + const UserInfoSelectionPage({super.key}); @override - State createState() => _UserInfoSelectionScreenState(); + State createState() => _UserInfoSelectionPageState(); } -class _UserInfoSelectionScreenState extends State { +class _UserInfoSelectionPageState extends State { @override void initState() { super.initState(); @@ -37,17 +40,19 @@ class _UserInfoSelectionScreenState extends State { if (appState.isAuthenticated) { final user = appState.getAuthenticatedUser(); + getIt.get().getPatientVitalSign(); if (user == null) return; // Populate gender (gender is int: 1=Male, 2=Female) + // Use internal keys (male/female) for storage if (user.gender != null) { - String genderStr = user.gender == 1 - ? "Male" + String genderKey = user.gender == 1 + ? "male" : user.gender == 2 - ? "Female" - : "Other"; - viewModel.setGender(genderStr); + ? "female" + : "other"; + viewModel.setGender(genderKey); } if (user.dateofBirth != null && user.dateofBirth!.isNotEmpty) { @@ -65,13 +70,14 @@ class _UserInfoSelectionScreenState extends State { // If not authenticated or fields are empty, user will fill them manually } - _buildEditInfoTile({ + Widget _buildEditInfoTile({ required String leadingIcon, required String title, required String subTitle, required VoidCallback onTap, required String trailingIcon, required BuildContext context, + required HmgServicesViewModel hmgServicesVM, Color? iconColor, }) { return InkWell( @@ -82,17 +88,20 @@ class _UserInfoSelectionScreenState extends State { child: Row( children: [ Container( - height: 40.h, - width: 40.h, - margin: EdgeInsets.only(right: 10.h), - padding: EdgeInsets.all(8.h), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 12.r, color: AppColors.greyColor), - child: Utils.buildSvgWithAssets(icon: leadingIcon, iconColor: iconColor)), + height: 40.h, + width: 40.h, + margin: EdgeInsetsDirectional.only(end: 10.h), + padding: EdgeInsets.all(8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 12.r, color: AppColors.greyColor), + child: Utils.buildSvgWithAssets(icon: leadingIcon, iconColor: iconColor), + ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ title.toText14(weight: FontWeight.w500), - subTitle.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + subTitle + .toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500) + .toShimmer2(isShow: (leadingIcon == AppAssets.rulerIcon || leadingIcon == AppAssets.weightScale) && hmgServicesVM.isVitalSignLoading), ], ), ], @@ -110,6 +119,20 @@ class _UserInfoSelectionScreenState extends State { ).paddingSymmetrical(0, 8.h); } + /// Convert internal gender key to localized display string + String _getLocalizedGender(String? genderKey, BuildContext context) { + if (genderKey == null) return LocaleKeys.notSet.tr(context: context); + + final lowerKey = genderKey.toLowerCase(); + if (lowerKey == "male") { + return LocaleKeys.malE.tr(context: context); + } else if (lowerKey == "female") { + return LocaleKeys.femaleGender.tr(context: context); + } else { + return genderKey; // Return as-is if not recognized + } + } + @override Widget build(BuildContext context) { AppState appState = getIt.get(); @@ -130,6 +153,7 @@ class _UserInfoSelectionScreenState extends State { age--; } userAgeFromDOB = age; + getIt.get().setSelectedAge(userAgeFromDOB); } catch (e) { // If date parsing fails, ignore } @@ -140,29 +164,26 @@ class _UserInfoSelectionScreenState extends State { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, - body: Consumer( - builder: (context, viewModel, child) { + body: Consumer2( + builder: (context, viewModel, hmgServicesVM, child) { // Check if any field is empty - bool hasEmptyFields = viewModel.selectedGender == null || - viewModel.selectedAge == null || - viewModel.selectedHeight == null || - viewModel.selectedWeight == null; + bool hasEmptyFields = viewModel.selectedGender == null || viewModel.selectedAge == null || viewModel.selectedHeight == null || viewModel.selectedWeight == null; // Get display values - String genderText = viewModel.selectedGender ?? "Not set"; + String genderText = _getLocalizedGender(viewModel.selectedGender, context); // Show age calculated from DOB (prefer viewModel's age, fallback to calculated from user's DOB) int? displayAge = viewModel.selectedAge ?? userAgeFromDOB; - String ageText = displayAge != null ? "$displayAge Years" : "Not set"; - String heightText = - viewModel.selectedHeight != null ? "${viewModel.selectedHeight!.round()} ${viewModel.isHeightCm ? 'cm' : 'ft'}" : "Not set"; - String weightText = - viewModel.selectedWeight != null ? "${viewModel.selectedWeight!.round()} ${viewModel.isWeightKg ? 'kg' : 'lbs'}" : "Not set"; + String ageText = displayAge != null ? "$displayAge ${LocaleKeys.years.tr(context: context)}" : LocaleKeys.notSet.tr(context: context); + + String heightText = viewModel.selectedHeight != null ? "${viewModel.selectedHeight!.round()} ${viewModel.isHeightCm ? 'cm' : 'ft'}" : LocaleKeys.notSet.tr(context: context); + + String weightText = viewModel.selectedWeight != null ? "${viewModel.selectedWeight!.round()} ${viewModel.isWeightKg ? 'kg' : 'lbs'}" : LocaleKeys.notSet.tr(context: context); return Column( children: [ Expanded( child: CollapsingListView( - title: "Symptoms Checker".needTranslation, + title: LocaleKeys.symptomsChecker.tr(context: context), isLeading: true, child: SingleChildScrollView( child: Column( @@ -173,59 +194,59 @@ class _UserInfoSelectionScreenState extends State { padding: EdgeInsets.symmetric(vertical: 24.h, horizontal: 16.w), child: Column( children: [ - "Hello $name, Is your information up to date?".needTranslation.toText16( - weight: FontWeight.w600, - color: AppColors.textColor, - ), + LocaleKeys.helloIsYourInformationUpToDate.tr(namedArgs: {'name': name}).toText16( + weight: FontWeight.w600, + color: AppColors.textColor, + ), SizedBox(height: 32.h), _buildEditInfoTile( - context: context, - leadingIcon: AppAssets.genderIcon, - title: "Gender".needTranslation, - subTitle: genderText, - onTap: () { - viewModel.setUserInfoPage(0, isSinglePageEdit: true); - context.navigateWithName(AppRoutes.userInfoFlowManager); - }, - trailingIcon: AppAssets.edit_icon, - ), + context: context, + leadingIcon: AppAssets.genderIcon, + title: LocaleKeys.gender.tr(context: context), + subTitle: genderText, + onTap: () { + viewModel.setUserInfoPage(0, isSinglePageEdit: true); + context.navigateWithName(AppRoutes.userInfoFlowManager); + }, + trailingIcon: AppAssets.edit_icon, + hmgServicesVM: hmgServicesVM), _getDivider(), _buildEditInfoTile( - context: context, - leadingIcon: AppAssets.calendarGrey, - title: "Age".needTranslation, - subTitle: ageText, - iconColor: AppColors.greyTextColor, - onTap: () { - viewModel.setUserInfoPage(1, isSinglePageEdit: true); - context.navigateWithName(AppRoutes.userInfoFlowManager); - }, - trailingIcon: AppAssets.edit_icon, - ), + context: context, + leadingIcon: AppAssets.calendarGrey, + title: LocaleKeys.age.tr(context: context), + subTitle: ageText, + iconColor: AppColors.greyTextColor, + onTap: () { + viewModel.setUserInfoPage(1, isSinglePageEdit: true); + context.navigateWithName(AppRoutes.userInfoFlowManager); + }, + trailingIcon: AppAssets.edit_icon, + hmgServicesVM: hmgServicesVM), _getDivider(), _buildEditInfoTile( - context: context, - leadingIcon: AppAssets.rulerIcon, - title: "Height".needTranslation, - subTitle: heightText, - onTap: () { - viewModel.setUserInfoPage(2, isSinglePageEdit: true); - context.navigateWithName(AppRoutes.userInfoFlowManager); - }, - trailingIcon: AppAssets.edit_icon, - ), + context: context, + leadingIcon: AppAssets.rulerIcon, + title: LocaleKeys.height.tr(context: context), + subTitle: heightText, + onTap: () { + viewModel.setUserInfoPage(2, isSinglePageEdit: true); + context.navigateWithName(AppRoutes.userInfoFlowManager); + }, + trailingIcon: AppAssets.edit_icon, + hmgServicesVM: hmgServicesVM), _getDivider(), _buildEditInfoTile( - context: context, - leadingIcon: AppAssets.weightScale, - title: "Weight".needTranslation, - subTitle: weightText, - onTap: () { - viewModel.setUserInfoPage(3, isSinglePageEdit: true); - context.navigateWithName(AppRoutes.userInfoFlowManager); - }, - trailingIcon: AppAssets.edit_icon, - ), + context: context, + leadingIcon: AppAssets.weightScale, + title: LocaleKeys.weight.tr(context: context), + subTitle: weightText, + onTap: () { + viewModel.setUserInfoPage(3, isSinglePageEdit: true); + context.navigateWithName(AppRoutes.userInfoFlowManager); + }, + trailingIcon: AppAssets.edit_icon, + hmgServicesVM: hmgServicesVM), ], ), ), @@ -255,7 +276,7 @@ class _UserInfoSelectionScreenState extends State { children: [ Expanded( child: CustomButton( - text: "No, Edit all".needTranslation, + text: LocaleKeys.noEditAll.tr(context: context), icon: AppAssets.edit_icon, iconColor: AppColors.primaryRedColor, onPressed: () { @@ -266,14 +287,16 @@ class _UserInfoSelectionScreenState extends State { borderColor: Colors.transparent, textColor: AppColors.primaryRedColor, fontSize: 16.f, + iconSize: 20.w, ), ), SizedBox(width: 12.w), Expanded( child: CustomButton( - text: "Yes, It is".needTranslation, + text: LocaleKeys.yesItIs.tr(context: context), icon: AppAssets.tickIcon, iconColor: hasEmptyFields ? AppColors.greyTextColor : AppColors.whiteColor, + isDisabled: getIt.get().isVitalSignLoading || hasEmptyFields, onPressed: hasEmptyFields ? () {} // Empty function for disabled state : () => context.navigateWithName(AppRoutes.organSelectorPage), @@ -281,6 +304,7 @@ class _UserInfoSelectionScreenState extends State { borderColor: hasEmptyFields ? AppColors.greyLightColor : AppColors.primaryRedColor, textColor: hasEmptyFields ? AppColors.greyTextColor : AppColors.whiteColor, fontSize: 16.f, + iconSize: 20.w, ), ), ], diff --git a/lib/presentation/symptoms_checker/user_info_selection/pages/age_selection_page.dart b/lib/presentation/symptoms_checker/user_info_selection/pages/age_selection_page.dart index 8366545e..37802a27 100644 --- a/lib/presentation/symptoms_checker/user_info_selection/pages/age_selection_page.dart +++ b/lib/presentation/symptoms_checker/user_info_selection/pages/age_selection_page.dart @@ -1,8 +1,10 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_selection/widgets/custom_date_picker.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:provider/provider.dart'; @@ -25,7 +27,7 @@ class AgeSelectionPage extends StatelessWidget { builder: (BuildContext context, symptomsViewModel, Widget? child) { return Column( children: [ - "What is your Date of Birth?".needTranslation.toText18(weight: FontWeight.w600, color: AppColors.textColor).paddingAll(24.w), + LocaleKeys.dateOfBirthSymptoms.tr(context: context).toText18(weight: FontWeight.w600, color: AppColors.textColor).paddingAll(24.w), SizedBox(height: 30.h), ThreeColumnDatePicker( enableHaptic: true, diff --git a/lib/presentation/symptoms_checker/user_info_selection/pages/gender_selection_page.dart b/lib/presentation/symptoms_checker/user_info_selection/pages/gender_selection_page.dart index 85cb6e2c..07f44f16 100644 --- a/lib/presentation/symptoms_checker/user_info_selection/pages/gender_selection_page.dart +++ b/lib/presentation/symptoms_checker/user_info_selection/pages/gender_selection_page.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; @@ -5,6 +6,7 @@ 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/symptoms_checker/symptoms_checker_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:provider/provider.dart'; @@ -13,12 +15,16 @@ class GenderSelectionPage extends StatelessWidget { final String? selectedGender; final Function(String) onGenderSelected; - GenderSelectionPage({ + const GenderSelectionPage({ super.key, required this.selectedGender, required this.onGenderSelected, }); + // Internal gender keys (constant, not localized) + static const String maleKey = "male"; + static const String femaleKey = "female"; + _buildGenderOption(String iconPng, String label, bool isSelected) { return Container( height: 160.h, @@ -38,7 +44,27 @@ class GenderSelectionPage extends StatelessWidget { ); } - final genders = ["Male", "Female"]; + /// Check if the selected gender matches the given key + /// Handles both internal keys and localized strings + bool _isGenderSelected(String genderKey, String? currentSelection, BuildContext context) { + if (currentSelection == null) return false; + + final lowerSelection = currentSelection.toLowerCase(); + + if (genderKey == maleKey) { + // Check against internal key, English, and Arabic + return lowerSelection == maleKey || + lowerSelection == "male" || + currentSelection == LocaleKeys.malE.tr(context: context) || + currentSelection == "ذكر"; + } else { + // Check against internal key, English, and Arabic + return lowerSelection == femaleKey || + lowerSelection == "female" || + currentSelection == LocaleKeys.femaleGender.tr(context: context) || + currentSelection == "أنثى"; + } + } @override Widget build(BuildContext context) { @@ -48,22 +74,31 @@ class GenderSelectionPage extends StatelessWidget { builder: (BuildContext context, symptomsViewModel, Widget? child) { return Column( children: [ - "What is your gender?".needTranslation.toText18(weight: FontWeight.w600, color: AppColors.textColor), + LocaleKeys.genderSymptoms.tr(context: context).toText18(weight: FontWeight.w600, color: AppColors.textColor), SizedBox(height: 70.h), Row( children: [ Expanded( child: InkWell( - onTap: () => onGenderSelected(genders[0]), - child: _buildGenderOption(AppAssets.maleIcon, "Male".needTranslation, symptomsViewModel.selectedGender == genders[0]), + onTap: () => onGenderSelected(maleKey), + child: _buildGenderOption( + AppAssets.maleIcon, + LocaleKeys.malE.tr(context: context), + _isGenderSelected(maleKey, symptomsViewModel.selectedGender, context), + ), ), ), SizedBox(width: 16.w), Expanded( - child: InkWell( - onTap: () => onGenderSelected(genders[1]), - child: _buildGenderOption(AppAssets.femaleIcon, "Female".needTranslation, symptomsViewModel.selectedGender == genders[1]), - )) + child: InkWell( + onTap: () => onGenderSelected(femaleKey), + child: _buildGenderOption( + AppAssets.femaleIcon, + LocaleKeys.femaleGender.tr(context: context), + _isGenderSelected(femaleKey, symptomsViewModel.selectedGender, context), + ), + ), + ) ], ), ], diff --git a/lib/presentation/symptoms_checker/user_info_selection/pages/height_selection_page.dart b/lib/presentation/symptoms_checker/user_info_selection/pages/height_selection_page.dart index 0744e817..ce8087d4 100644 --- a/lib/presentation/symptoms_checker/user_info_selection/pages/height_selection_page.dart +++ b/lib/presentation/symptoms_checker/user_info_selection/pages/height_selection_page.dart @@ -1,10 +1,9 @@ -import 'dart:developer'; - +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/symptoms_checker/symptoms_checker_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_selection/widgets/height_scale.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:provider/provider.dart'; @@ -107,7 +106,7 @@ class HeightSelectionPage extends StatelessWidget { SizedBox(height: 24.h), Center( child: Text( - 'How tall are you?'.needTranslation, + LocaleKeys.heightSymptoms.tr(context: context), style: TextStyle(fontSize: 18.f, fontWeight: FontWeight.w600, color: AppColors.textColor), ), ), @@ -152,14 +151,14 @@ class HeightSelectionPage extends StatelessWidget { ), Expanded( child: HeightScale( + key: ValueKey(viewModel.isHeightCm), enableHaptic: true, enableSound: true, minValue: minValue, maxValue: maxValue, - initialHeight: viewModel.selectedHeight ?? 100, + initialHeight: (viewModel.selectedHeight ?? 100).toDouble(), isCm: viewModel.isHeightCm, onHeightChanged: (newHeight) { - log("height: $newHeight"); viewModel.setHeight(newHeight, viewModel.isHeightCm); }, ), diff --git a/lib/presentation/symptoms_checker/user_info_selection/pages/weight_selection_page.dart b/lib/presentation/symptoms_checker/user_info_selection/pages/weight_selection_page.dart index 1d38a91e..6e68eef9 100644 --- a/lib/presentation/symptoms_checker/user_info_selection/pages/weight_selection_page.dart +++ b/lib/presentation/symptoms_checker/user_info_selection/pages/weight_selection_page.dart @@ -1,10 +1,12 @@ import 'dart:developer'; +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/symptoms_checker/symptoms_checker_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_selection/widgets/weight_scale.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:provider/provider.dart'; @@ -113,7 +115,7 @@ class WeightSelectionPage extends StatelessWidget { SizedBox(height: 24.h), Center( child: Text( - 'What is your weight?'.needTranslation, + LocaleKeys.weightSymptoms.tr(context: context), style: TextStyle(fontSize: 18.f, fontWeight: FontWeight.w600, color: AppColors.textColor), ), ), @@ -148,7 +150,7 @@ class WeightSelectionPage extends StatelessWidget { enableSound: true, minValue: minValue, maxValue: maxValue, - initialWeight: viewModel.selectedWeight!, + initialWeight: (viewModel.selectedWeight ?? 70).toDouble(), isKg: isKg, onWeightChanged: (newWeight) { log("weight: $newWeight"); diff --git a/lib/presentation/symptoms_checker/user_info_selection/user_info_flow_manager.dart b/lib/presentation/symptoms_checker/user_info_selection/user_info_flow_manager.dart index 523470e8..fdfe2288 100644 --- a/lib/presentation/symptoms_checker/user_info_selection/user_info_flow_manager.dart +++ b/lib/presentation/symptoms_checker/user_info_selection/user_info_flow_manager.dart @@ -1,10 +1,11 @@ import 'dart:developer'; +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/route_extensions.dart'; -import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_selection/pages/age_selection_page.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_selection/pages/gender_selection_page.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_selection/pages/height_selection_page.dart'; @@ -27,13 +28,15 @@ class _UserInfoFlowManagerState extends State { late PageController _pageController; late SymptomsCheckerViewModel _viewModel; - // Page titles - final List _pageTitles = [ - "Your Gender", - "Your Birth Date", - "Your Height", - "Your Weight", - ]; + // Get localized page titles + List _getPageTitles(BuildContext context) { + return [ + LocaleKeys.genderSymptoms.tr(context: context), + LocaleKeys.dateOfBirthSymptoms.tr(context: context), + LocaleKeys.heightSymptoms.tr(context: context), + LocaleKeys.weightSymptoms.tr(context: context), + ]; + } @override void initState() { @@ -184,10 +187,12 @@ class _UserInfoFlowManagerState extends State { child: isSingleEdit ? // Single page edit mode - show only Save button CustomButton( - text: "Save".needTranslation, - onPressed: _onNext, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, + text: LocaleKeys.save.tr(context: context), + isDisabled: !(viewModel.isCurrentUserInfoPageValid), + onPressed: () => viewModel.isCurrentUserInfoPageValid ? _onNext() : () {}, + backgroundColor: + viewModel.isCurrentUserInfoPageValid ? AppColors.primaryRedColor : AppColors.primaryRedColor.withValues(alpha: 0.5), + borderColor: viewModel.isCurrentUserInfoPageValid ? AppColors.primaryRedColor : AppColors.primaryRedColor.withValues(alpha: 0.5), textColor: AppColors.whiteColor, fontSize: 16.f, ) @@ -197,7 +202,7 @@ class _UserInfoFlowManagerState extends State { if (!isFirstPage) ...[ Expanded( child: CustomButton( - text: "Previous".needTranslation, + text: LocaleKeys.previous.tr(context: context), onPressed: _onPrevious, backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.11), borderColor: Colors.transparent, @@ -209,7 +214,7 @@ class _UserInfoFlowManagerState extends State { ], Expanded( child: CustomButton( - text: isLastPage ? "Submit".needTranslation : "Next".needTranslation, + text: isLastPage ? LocaleKeys.submit.tr(context: context) : LocaleKeys.next.tr(context: context), onPressed: _onNext, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, @@ -226,6 +231,8 @@ class _UserInfoFlowManagerState extends State { @override Widget build(BuildContext context) { + final pageTitles = _getPageTitles(context); + return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: Column( @@ -233,7 +240,7 @@ class _UserInfoFlowManagerState extends State { Expanded( child: CollapsingListView( physics: NeverScrollableScrollPhysics(), - title: _pageTitles[_viewModel.userInfoCurrentPage].needTranslation, + title: pageTitles[_viewModel.userInfoCurrentPage], isLeading: true, child: Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/presentation/symptoms_checker/user_info_selection/widgets/height_scale.dart b/lib/presentation/symptoms_checker/user_info_selection/widgets/height_scale.dart index 29baf646..e09380eb 100644 --- a/lib/presentation/symptoms_checker/user_info_selection/widgets/height_scale.dart +++ b/lib/presentation/symptoms_checker/user_info_selection/widgets/height_scale.dart @@ -43,10 +43,17 @@ class _HeightScaleState extends State { // Get increment based on unit (CM = 1.0, FT = 0.1) double get _increment => widget.isCm ? 1.0 : 0.1; + late final int _childCount; + @override void initState() { super.initState(); + // prepare child count and initial index using normal order (min at index 0) + _childCount = ((widget.maxValue - widget.minValue) / _increment).round() + 1; + // initialIndex should point to position of initialHeight in normal list int initialIndex = ((widget.initialHeight - widget.minValue) / _increment).round(); + if (initialIndex < 0) initialIndex = 0; + if (initialIndex >= _childCount) initialIndex = _childCount - 1; _scrollController = FixedExtentScrollController(initialItem: initialIndex); } @@ -95,19 +102,21 @@ class _HeightScaleState extends State { perspective: 0.001, physics: const BouncingScrollPhysics(), onSelectedItemChanged: (index) { + // Map index (0..n-1) to value normal: value = min + index*increment final selectedValue = widget.minValue + (index * _increment); widget.onHeightChanged?.call(selectedValue); _scheduleFeedback(); }, childDelegate: ListWheelChildBuilderDelegate( - childCount: ((widget.maxValue - widget.minValue) / _increment).round() + 1, + childCount: _childCount, builder: (context, index) { + // Displayed height value for this index (normal order) final height = widget.minValue + (index * _increment); // For CM: main mark every 10, mid mark every 5 // For FT: main mark every 1.0 (10 ticks), mid mark every 0.5 (5 ticks) - final isMainMark = widget.isCm ? height % 10 == 0 : (height * 10).round() % 10 == 0; - final isMidMark = widget.isCm ? height % 5 == 0 : (height * 10).round() % 5 == 0; + final isMainMark = widget.isCm ? height.round() % 10 == 0 : ((height * 10).round() % 10 == 0); + final isMidMark = widget.isCm ? height.round() % 5 == 0 : ((height * 10).round() % 5 == 0); return SizedBox( width: 100.w, diff --git a/lib/presentation/symptoms_checker/widgets/condition_card.dart b/lib/presentation/symptoms_checker/widgets/condition_card.dart index 87a8f3d1..e48f41ea 100644 --- a/lib/presentation/symptoms_checker/widgets/condition_card.dart +++ b/lib/presentation/symptoms_checker/widgets/condition_card.dart @@ -1,42 +1,145 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/triage_response_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/custom_progress_bar.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; class ConditionCard extends StatelessWidget { - final IconData icon; - final String title; - final int percentage; - final String tagText; - final String clinic; + final TriageCondition condition; final List symptoms; - final String description; - final String? monitorNote; - final String? appointmentLabel; - final PossibleConditionsSeverityEnum possibleConditionsSeverityEnum; final VoidCallback? onActionPressed; const ConditionCard({ super.key, - required this.icon, - required this.title, - required this.percentage, - required this.tagText, - required this.clinic, + required this.condition, required this.symptoms, - required this.description, - this.monitorNote, - this.appointmentLabel, - required this.possibleConditionsSeverityEnum, this.onActionPressed, }); + /// Get title from condition (localized) + String get title { + final appState = getIt(); + return condition.getDisplayName(isArabic: appState.isArabic()); + } + + /// Get percentage from probability (0-1 to 0-100) + int get percentage => ((condition.probability ?? 0) * 100).toInt(); + + /// Get clinic name from category + String get clinic => condition.conditionDetails?.category?.name ?? ''; + + /// Get description from hint + String get description => condition.conditionDetails?.hint ?? ''; + + /// Get severity enum based on triage level + PossibleConditionsSeverityEnum get severityEnum { + final triageLevel = condition.conditionDetails?.triageLevel; + if (triageLevel != null) { + switch (triageLevel) { + case TriageLevelEnum.emergencyAmbulance: + case TriageLevelEnum.emergency: + return PossibleConditionsSeverityEnum.emergency; + case TriageLevelEnum.consultation24: + case TriageLevelEnum.consultation: + return PossibleConditionsSeverityEnum.seekMedicalAdvice; + case TriageLevelEnum.selfCare: + return PossibleConditionsSeverityEnum.monitorOnly; + } + } + // Default based on probability + if (percentage >= 70) { + return PossibleConditionsSeverityEnum.seekMedicalAdvice; + } else { + return PossibleConditionsSeverityEnum.monitorOnly; + } + } + + /// Get tag text based on triage level + String getTagText(BuildContext context) { + final triageLevel = condition.conditionDetails?.triageLevel; + if (triageLevel != null) { + switch (triageLevel) { + case TriageLevelEnum.emergencyAmbulance: + case TriageLevelEnum.emergency: + return LocaleKeys.emergency.tr(context: context); + case TriageLevelEnum.consultation24: + case TriageLevelEnum.consultation: + return LocaleKeys.seekMedicalAdvice.tr(context: context); + case TriageLevelEnum.selfCare: + return LocaleKeys.monitor.tr(context: context); + } + } + // Default based on probability + if (percentage >= 70) { + return LocaleKeys.seekMedicalAdvice.tr(context: context); + } else { + return LocaleKeys.monitor.tr(context: context); + } + } + + /// Get monitor note if applicable + String? getMonitorNote(BuildContext context) { + if (severityEnum == PossibleConditionsSeverityEnum.monitorOnly) { + return LocaleKeys.noNeedToVisitDoctor.tr(context: context); + } + return null; + } + + /// Get icon based on category name from API + IconData get icon { + final categoryName = condition.conditionDetails?.category?.name?.toLowerCase() ?? ''; + + if (categoryName.contains('cardio') || categoryName.contains('heart')) { + return Icons.favorite; + } else if (categoryName.contains('neuro') || categoryName.contains('brain')) { + return Icons.psychology_alt; + } else if (categoryName.contains('psychiatr') || categoryName.contains('mental')) { + return Icons.psychology; + } else if (categoryName.contains('eye') || categoryName.contains('ophthalmo')) { + return Icons.remove_red_eye; + } else if (categoryName.contains('ent') || categoryName.contains('ear')) { + return Icons.hearing; + } else if (categoryName.contains('derma') || categoryName.contains('skin')) { + return Icons.face; + } else if (categoryName.contains('ortho') || categoryName.contains('bone')) { + return Icons.accessibility_new; + } else if (categoryName.contains('gyneco') || categoryName.contains('obstet')) { + return Icons.pregnant_woman; + } else if (categoryName.contains('pediatr') || categoryName.contains('child')) { + return Icons.child_care; + } else if (categoryName.contains('surg')) { + return Icons.local_hospital; + } else if (categoryName.contains('pulmo') || categoryName.contains('lung') || categoryName.contains('respiratory')) { + return Icons.air; + } else if (categoryName.contains('gastro') || categoryName.contains('digest')) { + return Icons.restaurant; + } else if (categoryName.contains('nephro') || categoryName.contains('kidney')) { + return Icons.water_drop; + } else if (categoryName.contains('hemato') || categoryName.contains('blood')) { + return Icons.bloodtype; + } else if (categoryName.contains('infect') || categoryName.contains('virus')) { + return Icons.coronavirus; + } else if (categoryName.contains('emergency')) { + return Icons.emergency; + } else if (categoryName.contains('geriatr') || categoryName.contains('elder')) { + return Icons.elderly; + } else if (categoryName.contains('internal')) { + return Icons.medical_services; + } + + return Icons.medical_services; + } + Color getChipColorBySeverityEnum(PossibleConditionsSeverityEnum possibleConditionsSeverityEnum) { switch (possibleConditionsSeverityEnum) { case PossibleConditionsSeverityEnum.seekMedicalAdvice: @@ -65,7 +168,7 @@ class ConditionCard extends StatelessWidget { crossAxisAlignment: WrapCrossAlignment.center, children: [ for (int i = 0; i < symptoms.length; i++) ...[ - Text(symptoms[i], style: TextStyle(color: AppColors.greyTextColor, fontWeight: FontWeight.w500, fontSize: 12.f)), + "● ${symptoms[i]}".toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), if (i != symptoms.length - 1) Padding( padding: EdgeInsets.symmetric(horizontal: 2.w), @@ -78,9 +181,12 @@ class ConditionCard extends StatelessWidget { @override Widget build(BuildContext context) { + // final monitorNote = getMonitorNote(context); + final tagText = getTagText(context); + return Container( width: double.infinity, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -88,14 +194,14 @@ class ConditionCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - width: 48.w, - height: 48.w, + width: 32.w, + height: 32.w, decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: getChipColorBySeverityEnum(possibleConditionsSeverityEnum).withValues(alpha: 0.2), - borderRadius: 12.r, + color: getChipColorBySeverityEnum(severityEnum).withValues(alpha: 0.2), + borderRadius: 8.r, hasShadow: false, ), - child: Icon(icon, color: getChipTextColorBySeverityEnum(possibleConditionsSeverityEnum), size: 24.f), + child: Icon(icon, color: getChipTextColorBySeverityEnum(severityEnum), size: 16.f), ), SizedBox(width: 12.w), Expanded( @@ -106,34 +212,23 @@ class ConditionCard extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( - child: Text( - title, - style: TextStyle(fontSize: 18.f, fontWeight: FontWeight.w600, color: AppColors.textColor), - overflow: TextOverflow.ellipsis, - ), + child: title.toText18(weight: FontWeight.w600, color: AppColors.textColor), ), AppCustomChipWidget( labelText: tagText, - backgroundColor: getChipColorBySeverityEnum(possibleConditionsSeverityEnum).withValues(alpha: 0.2), - textColor: getChipTextColorBySeverityEnum(possibleConditionsSeverityEnum), + backgroundColor: getChipColorBySeverityEnum(severityEnum).withValues(alpha: 0.2), + textColor: getChipTextColorBySeverityEnum(severityEnum), ), ], ), CustomRoundedProgressBar( percentage: percentage, height: 6.h, - color: getChipColorBySeverityEnum(possibleConditionsSeverityEnum), + color: getChipColorBySeverityEnum(severityEnum), backgroundColor: AppColors.scaffoldBgColor, titleWidget: Row( children: [ - Text( - "$percentage%", - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 12.f, - color: getChipColorBySeverityEnum(possibleConditionsSeverityEnum), - ), - ), + "$percentage%".toText12(fontWeight: FontWeight.bold, color: getChipColorBySeverityEnum(severityEnum)), ], ).paddingSymmetrical(0, 4.h), ), @@ -146,50 +241,42 @@ class ConditionCard extends StatelessWidget { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - clinic, - style: TextStyle( - fontWeight: FontWeight.w600, - fontSize: 14.f, - color: AppColors.textColor, - ), - ), + LocaleKeys.commonSymptom.tr(context: context).toText14(weight: FontWeight.w600, color: AppColors.textColor), _buildSymptomsRow(), SizedBox(height: 16.h), - Text("Description".needTranslation, style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14.f, color: AppColors.textColor)), + LocaleKeys.description.tr(context: context).toText14(weight: FontWeight.bold, color: AppColors.textColor), SizedBox(height: 2.h), - Text(description, style: TextStyle(color: AppColors.greyTextColor, fontWeight: FontWeight.w500, fontSize: 12.f)), - if (possibleConditionsSeverityEnum == PossibleConditionsSeverityEnum.emergency) + description.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + if (severityEnum != PossibleConditionsSeverityEnum.monitorOnly) CustomButton( - text: appointmentLabel ?? "Book Appointment".needTranslation, + text: LocaleKeys.bookAppointment.tr(context: context), onPressed: () { if (onActionPressed != null) { onActionPressed!(); } }, - backgroundColor: AppColors.lightRedButtonColor, + backgroundColor: AppColors.primaryRedColor, borderColor: Colors.transparent, - textColor: AppColors.primaryRedColor, - fontSize: 16.f, + textColor: AppColors.whiteColor, + fontSize: 14.f, fontWeight: FontWeight.w500, - borderRadius: 12.r, padding: EdgeInsets.symmetric(horizontal: 10.w), - height: 48.h, icon: AppAssets.add_icon, - iconColor: AppColors.primaryRedColor, - iconSize: 18.h, + height: 48.h, + iconColor: AppColors.whiteColor, ).paddingOnly(top: 16.w), - if (monitorNote != null) - Container( - margin: EdgeInsets.only(top: 12.h), - child: AppCustomChipWidget( - labelText: monitorNote!, - backgroundColor: AppColors.whiteColor, - textColor: AppColors.textColor, - padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 8.h), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.r)), - ), - ), + + // if (monitorNote != null) + // Container( + // margin: EdgeInsets.only(top: 12.h), + // child: AppCustomChipWidget( + // labelText: monitorNote, + // backgroundColor: AppColors.whiteColor, + // textColor: AppColors.textColor, + // padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 8.h), + // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.r)), + // ), + // ), ], ).paddingAll(16.w), ], diff --git a/lib/presentation/symptoms_checker/widgets/interactive_body_widget.dart b/lib/presentation/symptoms_checker/widgets/interactive_body_widget.dart index 8957fb19..62c38f9a 100644 --- a/lib/presentation/symptoms_checker/widgets/interactive_body_widget.dart +++ b/lib/presentation/symptoms_checker/widgets/interactive_body_widget.dart @@ -13,6 +13,7 @@ class InteractiveBodyWidget extends StatefulWidget { final bool isBodyHidden; final Function(String organId) onOrganTap; final String? tooltipOrganId; + final bool isArabic; const InteractiveBodyWidget({ super.key, @@ -22,6 +23,7 @@ class InteractiveBodyWidget extends StatefulWidget { required this.onOrganTap, this.isBodyHidden = false, this.tooltipOrganId, + this.isArabic = false, }); @override @@ -118,7 +120,10 @@ class _InteractiveBodyWidgetState extends State { (organ) { final centerX = organ.position.x * imageConstraints.maxWidth; final centerY = organ.position.y * imageConstraints.maxHeight; - return OrganTooltipWidget(organName: organ.description, position: Offset(centerX, centerY)); + return OrganTooltipWidget( + organName: organ.getLocalizedDescription(widget.isArabic), + position: Offset(centerX, centerY), + ); }, ), ], diff --git a/lib/presentation/symptoms_checker/widgets/selected_organs_section.dart b/lib/presentation/symptoms_checker/widgets/selected_organs_section.dart index c0f1be36..b66ef037 100644 --- a/lib/presentation/symptoms_checker/widgets/selected_organs_section.dart +++ b/lib/presentation/symptoms_checker/widgets/selected_organs_section.dart @@ -1,8 +1,10 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/organ_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; @@ -52,7 +54,7 @@ class _SelectedOrgansSectionState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - 'Selected Organs'.needTranslation, + LocaleKeys.selectedOrgans.tr(context: context), style: TextStyle( fontSize: 16.f, fontWeight: FontWeight.w600, @@ -101,7 +103,7 @@ class _SelectedOrgansSectionState extends State { Padding( padding: EdgeInsets.symmetric(vertical: 8.h), child: Text( - 'No organs selected yet'.needTranslation, + LocaleKeys.noOrgansSelected.tr(context: context), style: TextStyle( color: AppColors.greyTextColor, fontSize: 14.f, diff --git a/lib/presentation/todo_section/ancillary_order_payment_page.dart b/lib/presentation/todo_section/ancillary_order_payment_page.dart index 054108df..fe071867 100644 --- a/lib/presentation/todo_section/ancillary_order_payment_page.dart +++ b/lib/presentation/todo_section/ancillary_order_payment_page.dart @@ -83,7 +83,7 @@ class _AncillaryOrderPaymentPageState extends State { children: [ Expanded( child: CollapsingListView( - title: "Select Payment Method".needTranslation, + title: LocaleKeys.selectPaymentMethod.tr(context: context), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -103,9 +103,9 @@ class _AncillaryOrderPaymentPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Image.asset(AppAssets.mada, width: 72.h, height: 25.h).toShimmer2(isShow: todoVM.isProcessingPayment), + LocaleKeys.visaOrMastercard.tr(context: context).toText16(isBold: true).toShimmer2(isShow: todoVM.isProcessingPayment), SizedBox(height: 16.h), - "Mada".needTranslation.toText16(isBold: true).toShimmer2(isShow: todoVM.isProcessingPayment), + LocaleKeys.mada.tr(context: context).toText16(isBold: true).toShimmer2(isShow: todoVM.isProcessingPayment), ], ), SizedBox(width: 8.h), @@ -152,7 +152,7 @@ class _AncillaryOrderPaymentPageState extends State { ], ).toShimmer2(isShow: todoVM.isProcessingPayment), SizedBox(height: 16.h), - "Visa or Mastercard".needTranslation.toText16(isBold: true).toShimmer2(isShow: todoVM.isProcessingPayment), + LocaleKeys.visaOrMastercard.tr(context: context).toText16(isBold: true).toShimmer2(isShow: todoVM.isProcessingPayment), ], ), SizedBox(width: 8.h), @@ -210,14 +210,14 @@ class _AncillaryOrderPaymentPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 24.h), - "Total amount to pay".needTranslation.toText18(isBold: true).paddingSymmetrical(24.h, 0.h), + LocaleKeys.totalAmountToPay.tr(context: context).toText18(isBold: true).paddingSymmetrical(24.h, 0.h), SizedBox(height: 17.h), // Amount before tax Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Amount before tax".needTranslation.toText14(isBold: true), + LocaleKeys.amountBeforeTax.tr(context: context).toText14(isBold: true), Utils.getPaymentAmountWithSymbol( amountBeforeTax.toStringAsFixed(2).toText16(isBold: true), AppColors.blackColor, @@ -231,7 +231,7 @@ class _AncillaryOrderPaymentPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "VAT 15%".needTranslation.toText14(isBold: true, color: AppColors.greyTextColor), + LocaleKeys.vat15.tr(context: context).toText14(isBold: true, color: AppColors.greyTextColor), Utils.getPaymentAmountWithSymbol( taxAmount.toStringAsFixed(2).toText14(isBold: true, color: AppColors.greyTextColor), AppColors.greyTextColor, @@ -247,7 +247,7 @@ class _AncillaryOrderPaymentPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "".needTranslation.toText14(isBold: true), + "".toText14(isBold: true), Utils.getPaymentAmountWithSymbol( widget.totalAmount.toStringAsFixed(2).toText24(isBold: true), AppColors.blackColor, @@ -346,7 +346,7 @@ class _AncillaryOrderPaymentPageState extends State { } void _checkPaymentStatus() { - LoaderBottomSheet.showLoader(loadingText: "Checking payment status, Please wait...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context)); todoSectionViewModel.checkPaymentStatus( transID: transID, @@ -384,7 +384,7 @@ class _AncillaryOrderPaymentPageState extends State { required String paymentReference, required String paymentMethod, }) { - LoaderBottomSheet.showLoader(loadingText: "Processing payment, Please wait...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.processingPaymentPleaseWait.tr(context: context)); final user = appState.getAuthenticatedUser(); @@ -426,7 +426,7 @@ class _AncillaryOrderPaymentPageState extends State { required String advanceNumber, required String paymentReference, }) { - LoaderBottomSheet.showLoader(loadingText: "Finalizing payment, Please wait...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.finalizingPaymentPleaseWait.tr(context: context)); final user = appState.getAuthenticatedUser(); @@ -450,7 +450,7 @@ class _AncillaryOrderPaymentPageState extends State { } void _autoGenerateInvoice() { - LoaderBottomSheet.showLoader(loadingText: "Generating invoice, Please wait...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.generatingInvoicePleaseWait.tr(context: context)); List selectedProcListAPI = widget.selectedProcedures.map((element) { return { @@ -496,7 +496,7 @@ class _AncillaryOrderPaymentPageState extends State { children: [ Row( children: [ - "Here is your invoice #: ".needTranslation.toText14( + LocaleKeys.hereIsYourInvoiceNumber.tr(context: context).toText14( color: AppColors.textColorLight, weight: FontWeight.w500, ), @@ -510,7 +510,7 @@ class _AncillaryOrderPaymentPageState extends State { Expanded( child: CustomButton( height: 56.h, - text: LocaleKeys.ok.tr(), + text: LocaleKeys.ok.tr(context: context), onPressed: () { Navigator.pushAndRemoveUntil( context, @@ -528,8 +528,8 @@ class _AncillaryOrderPaymentPageState extends State { ), ], ), - // title: "Payment Completed Successfully".needTranslation, - titleWidget: Utils.getSuccessWidget(loadingText: "Payment Completed Successfully".needTranslation), + // title: LocaleKeys.paymentCompletedSuccessfully.tr(context: context), + titleWidget: Utils.getSuccessWidget(loadingText: LocaleKeys.paymentCompletedSuccessfully.tr(context: context)), isCloseButtonVisible: false, isDismissible: false, isFullScreen: false, @@ -607,7 +607,7 @@ class _AncillaryOrderPaymentPageState extends State { Navigator.of(context).pop(); showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "Failed to initialize Apple Pay. Please try again.".needTranslation), + child: Utils.getErrorWidget(loadingText: LocaleKeys.failedToInitializeApplePay.tr(context: context)), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, diff --git a/lib/presentation/todo_section/ancillary_procedures_details_page.dart b/lib/presentation/todo_section/ancillary_procedures_details_page.dart index b7515afb..eba80cd4 100644 --- a/lib/presentation/todo_section/ancillary_procedures_details_page.dart +++ b/lib/presentation/todo_section/ancillary_procedures_details_page.dart @@ -102,14 +102,14 @@ class _AncillaryOrderDetailsListState extends State { String _getApprovalStatusText(AncillaryOrderProcDetail procedure) { if (procedure.isApprovalRequired == false) { - return "Cash"; + return LocaleKeys.cash.tr(context: context); } else { if (procedure.isApprovalCreated == true && procedure.approvalNo != 0) { - return "Approved"; + return LocaleKeys.approved.tr(context: context); } else if (procedure.isApprovalRequired == true && procedure.isApprovalCreated == true && procedure.approvalNo == 0) { - return "Approval Rejected - Please visit receptionist"; + return LocaleKeys.approvalRejectedPleaseVisitReceptionist.tr(context: context); } else { - return "Sent For Approval"; + return LocaleKeys.sentForApproval.tr(context: context); } } } @@ -135,7 +135,7 @@ class _AncillaryOrderDetailsListState extends State { children: [ Expanded( child: CollapsingListView( - title: "Ancillary Order Details".needTranslation, + title: LocaleKeys.ancillaryOrderDetails.tr(context: context), child: viewModel.isAncillaryDetailsProceduresLoading ? _buildLoadingShimmer().paddingSymmetrical(24.w, 0) : viewModel.patientAncillaryOrderProceduresList.isEmpty @@ -153,7 +153,7 @@ class _AncillaryOrderDetailsListState extends State { ), ), ), - if (orderData != null) _buildStickyPaymentButton(orderData), + if (orderData != null && !viewModel.isAncillaryDetailsProceduresLoading) _buildStickyPaymentButton(orderData), ], ); }), @@ -164,6 +164,7 @@ class _AncillaryOrderDetailsListState extends State { return ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), + padding: EdgeInsets.zero, itemCount: 3, itemBuilder: (context, index) { return AncillaryOrderCard( @@ -186,7 +187,7 @@ class _AncillaryOrderDetailsListState extends State { ), child: Utils.getNoDataWidget( context, - noDataText: "No Procedures available for the selected order.".needTranslation, + noDataText: LocaleKeys.noProceduresAvailableForSelectedOrder.tr(context: context), isSmallWidget: true, width: 62.w, height: 62.h, @@ -372,7 +373,7 @@ class _AncillaryOrderDetailsListState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Procedures".needTranslation.toText12( + LocaleKeys.procedures.tr(context: context).toText12( color: AppColors.textColorLight, fontWeight: FontWeight.w600, ), @@ -385,7 +386,7 @@ class _AncillaryOrderDetailsListState extends State { Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ - "Total Amount".needTranslation.toText12( + LocaleKeys.totalAmount.tr(context: context).toText12( color: AppColors.textColorLight, fontWeight: FontWeight.w600, ), @@ -535,7 +536,7 @@ class _AncillaryOrderDetailsListState extends State { // ), if (procedure.isCovered == true) AppCustomChipWidget( - labelText: "Covered".needTranslation, + labelText: LocaleKeys.covered.tr(context: context), backgroundColor: AppColors.successColor.withValues(alpha: 0.1), textColor: AppColors.successColor, ), @@ -551,7 +552,7 @@ class _AncillaryOrderDetailsListState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Price".needTranslation.toText10(color: AppColors.textColorLight), + LocaleKeys.price.tr(context: context).toText10(color: AppColors.textColorLight), SizedBox(height: 4.h), Row( children: [ @@ -570,7 +571,7 @@ class _AncillaryOrderDetailsListState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "VAT (15%)".needTranslation.toText10(color: AppColors.textColorLight), + LocaleKeys.vatPercent.tr(context: context).toText10(color: AppColors.textColorLight), SizedBox(height: 4.h), Row( children: [ @@ -589,7 +590,7 @@ class _AncillaryOrderDetailsListState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Total".needTranslation.toText10(color: AppColors.textColorLight), + LocaleKeys.total.tr(context: context).toText10(color: AppColors.textColorLight), SizedBox(height: 4.h), Row( children: [ @@ -654,9 +655,10 @@ class _AncillaryOrderDetailsListState extends State { CustomButton( borderWidth: 0, backgroundColor: AppColors.infoLightColor, - text: "Proceed to Payment".needTranslation, + text: LocaleKeys.proceedToPayment.tr(context: context), onPressed: () { // Navigate to payment page with selected procedures + todoSectionViewModel.setIsAncillaryOrdersNeedReloading(true); Navigator.of(context).push( CustomPageRoute( page: AncillaryOrderPaymentPage( diff --git a/lib/presentation/todo_section/todo_page.dart b/lib/presentation/todo_section/todo_page.dart index 0d2d8066..161ffaf9 100644 --- a/lib/presentation/todo_section/todo_page.dart +++ b/lib/presentation/todo_section/todo_page.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:developer'; +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'; @@ -9,6 +10,7 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/todo_section/models/resp_models/ancillary_order_list_response_model.dart'; import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/todo_section/ancillary_procedures_details_page.dart'; import 'package:hmg_patient_app_new/presentation/todo_section/widgets/ancillary_orders_list.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; @@ -61,7 +63,7 @@ class _ToDoPageState extends State { Widget build(BuildContext context) { appState = getIt.get(); return CollapsingListView( - title: "Ancillary Orders".needTranslation, + title: LocaleKeys.ancillaryOrdersList.tr(context: context), isLeading: true, child: SingleChildScrollView( child: Column( diff --git a/lib/presentation/todo_section/widgets/ancillary_orders_list.dart b/lib/presentation/todo_section/widgets/ancillary_orders_list.dart index 78d7e3ef..58ee6d46 100644 --- a/lib/presentation/todo_section/widgets/ancillary_orders_list.dart +++ b/lib/presentation/todo_section/widgets/ancillary_orders_list.dart @@ -71,19 +71,12 @@ class AncillaryOrdersList extends StatelessWidget { return Center( child: Padding( padding: EdgeInsets.symmetric(vertical: 40.h), - child: Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 12.r, - hasShadow: false, - ), - child: Utils.getNoDataWidget( - context, - noDataText: "You don't have any ancillary orders yet.".needTranslation, - isSmallWidget: true, - width: 62.w, - height: 62.h, - ), + child: Utils.getNoDataWidget( + context, + noDataText: LocaleKeys.youDontHaveAnyAncillaryOrdersYet.tr(context: context), + isSmallWidget: true, + width: 62.w, + height: 62.h, ), ), ); @@ -145,7 +138,7 @@ class AncillaryOrderCard extends StatelessWidget { if (!isLoading) ...[ Image.network( "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown.png", - width: 63.w, + width: 63.h, height: 63.h, fit: BoxFit.cover, ).circle(100.r), @@ -177,47 +170,45 @@ class AncillaryOrderCard extends StatelessWidget { labelText: order.projectName ?? '-', ).toShimmer2(isShow: isLoading), // orderNo - // if (order.orderNo != null || isLoading) - // AppCustomChipWidget( - // // icon: AppAssets.calendar, - // labelText: "${"Order# :".needTranslation}${order.orderNo ?? '-'}", - // ).toShimmer2(isShow: isLoading), + if (order.orderNo != null || isLoading) + AppCustomChipWidget( + // icon: AppAssets.calendar, + labelText: "${LocaleKeys.orderNumber.tr(context: context)}${order.orderNo ?? '-'}", + ).toShimmer2(isShow: isLoading), // Appointment Date if (order.appointmentDate != null || isLoading) AppCustomChipWidget( icon: AppAssets.appointment_calendar_icon, - labelText: isLoading ? "Date: Jan 20, 2024" : DateFormat('MMM dd, yyyy').format(order.appointmentDate!).needTranslation, + labelText: isLoading ? "Date: Jan 20, 2024" : DateFormat('MMM dd, yyyy').format(order.appointmentDate!), ).toShimmer2(isShow: isLoading), // Appointment Number if (order.appointmentNo != null || isLoading) AppCustomChipWidget( - labelText: isLoading ? "Appt# : 98765" : "Appt #: ${order.appointmentNo}".needTranslation, + labelText: isLoading ? "Appt# : 98765" : "Appt #: ${order.appointmentNo}", ).toShimmer2(isShow: isLoading), // Invoice Number if (order.invoiceNo != null || isLoading) AppCustomChipWidget( - labelText: isLoading ? "Invoice: 45678" : "Invoice: ${order.invoiceNo}".needTranslation, + labelText: isLoading ? "Invoice: 45678" : LocaleKeys.invoiceWithNumber.tr(namedArgs: {'invoiceNo': '${order.invoiceNo}'}), ).toShimmer2(isShow: isLoading), // Queued Status if (order.isQueued == true || isLoading) AppCustomChipWidget( - labelText: "Queued".needTranslation, + labelText: LocaleKeys.queued.tr(context: context), ).toShimmer2(isShow: isLoading), // Check-in Available Status if (order.isCheckInAllow == true || isLoading) AppCustomChipWidget( - labelText: "Check-in Ready".needTranslation, + labelText: LocaleKeys.checkInReady.tr(context: context), ).toShimmer2(isShow: isLoading), ], ), - - SizedBox(height: 12.h), - + SizedBox(height: 6.h), // Action Buttons Row( children: [ @@ -225,7 +216,7 @@ class AncillaryOrderCard extends StatelessWidget { if (order.isCheckInAllow == true || isLoading) Expanded( child: CustomButton( - text: "Check In".needTranslation, + text: LocaleKeys.checkIn.tr(context: context), onPressed: () { if (isLoading) { return; @@ -236,7 +227,7 @@ class AncillaryOrderCard extends StatelessWidget { backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, - fontSize: 14.f, + fontSize: (isFoldable || isTablet) ? 12.f : 14.f, fontWeight: FontWeight.w500, borderRadius: 10.r, padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), @@ -249,7 +240,7 @@ class AncillaryOrderCard extends StatelessWidget { // View Details Button Expanded( child: CustomButton( - text: "View Details".needTranslation, + text: LocaleKeys.viewDetails.tr(context: context), onPressed: () { if (isLoading) { return; @@ -260,7 +251,7 @@ class AncillaryOrderCard extends StatelessWidget { backgroundColor: Color(0xffFEE9EA), borderColor: Color(0xffFEE9EA), textColor: Color(0xffED1C2B), - fontSize: 14.f, + fontSize: (isFoldable || isTablet) ? 12.f : 14.f, fontWeight: FontWeight.w500, borderRadius: 10.r, padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), diff --git a/lib/presentation/todo_section/widgets/ancillary_procedures_list.dart b/lib/presentation/todo_section/widgets/ancillary_procedures_list.dart index ba2f94d9..2d99aa0f 100644 --- a/lib/presentation/todo_section/widgets/ancillary_procedures_list.dart +++ b/lib/presentation/todo_section/widgets/ancillary_procedures_list.dart @@ -7,6 +7,7 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/todo_section/models/resp_models/ancillary_order_list_response_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; @@ -73,7 +74,7 @@ class AncillaryProceduresList extends StatelessWidget { ), child: Utils.getNoDataWidget( context, - noDataText: "You don't have any ancillary orders yet.".needTranslation, + noDataText: LocaleKeys.youDontHaveAnyAncillaryOrdersYet.tr(context: context), isSmallWidget: true, width: 62.w, height: 62.h, @@ -118,7 +119,7 @@ class AncillaryOrderCard extends StatelessWidget { children: [ Row( children: [ - "Order #".needTranslation.toText14( + LocaleKeys.orderNumber.tr(context: context).toText14( color: AppColors.textColorLight, weight: FontWeight.w500, ), @@ -181,31 +182,31 @@ class AncillaryOrderCard extends StatelessWidget { AppCustomChipWidget( icon: AppAssets.calendar, labelText: - isLoading ? "Date: Jan 20, 2024" : "Date: ${DateFormat('MMM dd, yyyy').format(order.appointmentDate!)}".needTranslation, + isLoading ? "Date: Jan 20, 2024" : "Date: ${DateFormat('MMM dd, yyyy').format(order.appointmentDate!)}", ).toShimmer2(isShow: isLoading), // Appointment Number if (order.appointmentNo != null || isLoading) AppCustomChipWidget( - labelText: isLoading ? "Appt #: 98765" : "Appt #: ${order.appointmentNo}".needTranslation, + labelText: isLoading ? "Appt #: 98765" : "Appt #: ${order.appointmentNo}", ).toShimmer2(isShow: isLoading), // Invoice Number if (order.invoiceNo != null || isLoading) AppCustomChipWidget( - labelText: isLoading ? "Invoice: 45678" : "Invoice: ${order.invoiceNo}".needTranslation, + labelText: isLoading ? "Invoice: 45678" : LocaleKeys.invoiceWithNumber.tr(namedArgs: {'invoiceNo': '${order.invoiceNo}'}), ).toShimmer2(isShow: isLoading), // Queued Status if (order.isQueued == true || isLoading) AppCustomChipWidget( - labelText: "Queued".needTranslation, + labelText: LocaleKeys.queued.tr(context: context), ).toShimmer2(isShow: isLoading), // Check-in Available Status if (order.isCheckInAllow == true || isLoading) AppCustomChipWidget( - labelText: "Check-in Ready".needTranslation, + labelText: LocaleKeys.checkInReady.tr(context: context), ).toShimmer2(isShow: isLoading), ], ), @@ -219,7 +220,7 @@ class AncillaryOrderCard extends StatelessWidget { if (order.isCheckInAllow == true || isLoading) Expanded( child: CustomButton( - text: "Check In".needTranslation, + text: LocaleKeys.checkIn.tr(context: context), onPressed: () { if (isLoading) { return; @@ -243,7 +244,7 @@ class AncillaryOrderCard extends StatelessWidget { // View Details Button Expanded( child: CustomButton( - text: "View Details".needTranslation, + text: LocaleKeys.viewDetails.tr(context: context), onPressed: () { if (isLoading) { return; diff --git a/lib/presentation/vital_sign/vital_sign_details_page.dart b/lib/presentation/vital_sign/vital_sign_details_page.dart index f7aafa48..ca6e09a5 100644 --- a/lib/presentation/vital_sign/vital_sign_details_page.dart +++ b/lib/presentation/vital_sign/vital_sign_details_page.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -9,6 +10,7 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/vital_sign_respo_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/vital_sign_ui_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/graph/custom_graph.dart'; @@ -62,7 +64,7 @@ class _VitalSignDetailsPageState extends State { @override Widget build(BuildContext context) { return CollapsingListView( - title: 'Vital Sign Details'.needTranslation, + title: LocaleKeys.vitalSignDetails.tr(context: context), child: Consumer( builder: (context, viewModel, child) { final latest = viewModel.vitalSignList.isNotEmpty ? viewModel.vitalSignList.first : null; @@ -128,8 +130,8 @@ class _VitalSignDetailsPageState extends State { ), SizedBox(height: 8.h), (latestDate != null - ? ('Result of ${latestDate.toString().split(' ').first}'.needTranslation) - : ('Result of --'.needTranslation)) + ? LocaleKeys.resultOf.tr(namedArgs: {'date': latestDate.toString().split(' ').first}) + : LocaleKeys.resultOfNoDate.tr(context: context)) .toText11(weight: FontWeight.w500, color: AppColors.greyTextColor), ], ), @@ -146,6 +148,7 @@ class _VitalSignDetailsPageState extends State { isBold: true, color: scheme.iconFg, letterSpacing: -2, + isEnglishOnly: true ), ), SizedBox(width: 4.h), @@ -185,13 +188,23 @@ class _VitalSignDetailsPageState extends State { String _referenceText(BuildContext context) { if (args.low != null && args.high != null) { - return 'Reference range: ${args.low} – ${args.high} ${args.unit}'.needTranslation; + return LocaleKeys.referenceRangeBetween.tr(namedArgs: { + 'low': args.low.toString(), + 'high': args.high.toString(), + 'unit': args.unit ?? '' + }); } if (args.low != null) { - return 'Reference range: ≥ ${args.low} ${args.unit}'.needTranslation; + return LocaleKeys.referenceRangeMin.tr(namedArgs: { + 'low': args.low.toString(), + 'unit': args.unit ?? '' + }); } if (args.high != null) { - return 'Reference range: ≤ ${args.high} ${args.unit}'.needTranslation; + return LocaleKeys.referenceRangeMax.tr(namedArgs: { + 'high': args.high.toString(), + 'unit': args.unit ?? '' + }); } return ''; } @@ -208,7 +221,7 @@ class _VitalSignDetailsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, spacing: 8.h, children: [ - 'What is this result?'.needTranslation.toText16(weight: FontWeight.w600, color: AppColors.textColor), + LocaleKeys.whatIsThisResult.tr(context: context).toText16(weight: FontWeight.w600, color: AppColors.textColor), _descriptionText(context).toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), ], ), @@ -252,7 +265,7 @@ class _VitalSignDetailsPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - _isGraphVisible ? 'History flowchart'.needTranslation : 'History'.needTranslation, + _isGraphVisible ? LocaleKeys.historyFlowchart.tr(context: context) : LocaleKeys.history.tr(context: context), style: TextStyle( fontSize: 16, fontFamily: 'Poppins', @@ -297,7 +310,7 @@ class _VitalSignDetailsPageState extends State { ).paddingOnly(bottom: _isGraphVisible ? 16.h : 24.h), if (history.isEmpty) - Utils.getNoDataWidget(context, noDataText: 'No history available'.needTranslation, isSmallWidget: true) + Utils.getNoDataWidget(context, noDataText: LocaleKeys.noHistoryAvailable.tr(context: context), isSmallWidget: true) else if (_isGraphVisible) _buildHistoryGraph(graphHistory, secondaryHistory: secondaryHistory) else @@ -663,38 +676,38 @@ class _VitalSignDetailsPageState extends State { String _descriptionText(BuildContext context) { switch (args.metric) { case VitalSignMetric.bmi: - return 'BMI is a measurement based on height and weight that estimates body fat.'.needTranslation; + return LocaleKeys.bmiDescription.tr(context: context); case VitalSignMetric.height: - return 'Height is measured in centimeters and is used to calculate BMI and dosage recommendations.'.needTranslation; + return LocaleKeys.heightDescription.tr(context: context); case VitalSignMetric.weight: - return 'Weight helps track overall health, nutrition, and changes over time.'.needTranslation; + return LocaleKeys.weightDescription.tr(context: context); case VitalSignMetric.bloodPressure: - return 'Blood pressure reflects the force of blood against artery walls. It is shown as systolic/diastolic.'.needTranslation; + return LocaleKeys.bloodPressureDescription.tr(context: context); case VitalSignMetric.temperature: - return 'Body temperature reflects how hot your body is and may change with infection or inflammation.'.needTranslation; + return LocaleKeys.temperatureDescription.tr(context: context); case VitalSignMetric.heartRate: - return 'Heart rate refers to the number of heart beats per minute.'.needTranslation; + return LocaleKeys.heartRateDescriptionVital.tr(context: context); case VitalSignMetric.respiratoryRate: - return 'Respiratory rate is the number of breaths taken per minute.'.needTranslation; + return LocaleKeys.respiratoryRateDescription.tr(context: context); } } String _nextStepsText(BuildContext context) { switch (args.metric) { case VitalSignMetric.bmi: - return 'Maintain a balanced diet and regular activity. If your BMI is high or low, consider consulting your doctor.'.needTranslation; + return LocaleKeys.bmiAdvice.tr(context: context); case VitalSignMetric.height: - return 'No action is needed unless your measurement looks incorrect. Update it during your next visit.'.needTranslation; + return LocaleKeys.heightAdvice.tr(context: context); case VitalSignMetric.weight: - return 'Monitor weight changes. Sudden gain or loss may require medical advice.'.needTranslation; + return LocaleKeys.weightAdvice.tr(context: context); case VitalSignMetric.bloodPressure: - return 'Keep tracking your blood pressure. High or low readings should be discussed with your doctor.'.needTranslation; + return LocaleKeys.bloodPressureAdvice.tr(context: context); case VitalSignMetric.temperature: - return 'If you have a persistent fever or symptoms, contact your healthcare provider.'.needTranslation; + return LocaleKeys.temperatureAdvice.tr(context: context); case VitalSignMetric.heartRate: - return 'Track your heart rate trends. If you feel dizziness or chest pain, seek medical care.'.needTranslation; + return LocaleKeys.heartRateAdvice.tr(context: context); case VitalSignMetric.respiratoryRate: - return 'If you notice shortness of breath or abnormal breathing, seek medical advice.'.needTranslation; + return LocaleKeys.respiratoryRateAdvice.tr(context: context); } } @@ -709,7 +722,7 @@ class _VitalSignDetailsPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - 'What should I do next?'.needTranslation.toText16(weight: FontWeight.w600), + LocaleKeys.whatShouldIDoNext.tr(context: context).toText16(weight: FontWeight.w600), SizedBox(height: 8.h), _nextStepsText(context).toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500, maxLine: 10), ], diff --git a/lib/presentation/vital_sign/vital_sign_page.dart b/lib/presentation/vital_sign/vital_sign_page.dart index 04d36bd9..200a223e 100644 --- a/lib/presentation/vital_sign/vital_sign_page.dart +++ b/lib/presentation/vital_sign/vital_sign_page.dart @@ -1,13 +1,17 @@ import 'dart:ui'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/vital_sign_respo_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; @@ -55,7 +59,7 @@ class _VitalSignPageState extends State { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( - title: 'Vital Signs', + title: LocaleKeys.vitalSigns.tr(context: context), child: Consumer( builder: (context, viewModel, child) { @@ -95,14 +99,14 @@ class _VitalSignPageState extends State { // BMI Card _buildVitalSignCard( icon: AppAssets.bmiVital, - label: 'BMI', + label: LocaleKeys.bmi.tr(context: context), value: latestVitalSign?.bodyMassIndex?.toString() ?? '--', unit: '', status: VitalSignUiModel.bmiStatus(latestVitalSign?.bodyMassIndex), onTap: () => _openDetails( - const VitalSignDetailsArgs( + VitalSignDetailsArgs( metric: VitalSignMetric.bmi, - title: 'BMI', + title: LocaleKeys.bmi.tr(context: context), icon: AppAssets.bmiVital, unit: '', ), @@ -113,14 +117,14 @@ class _VitalSignPageState extends State { // Height Card _buildVitalSignCard( icon: AppAssets.heightVital, - label: 'Height', + label: LocaleKeys.height.tr(context: context), value: latestVitalSign?.heightCm?.toString() ?? '--', unit: 'cm', status: null, onTap: () => _openDetails( - const VitalSignDetailsArgs( + VitalSignDetailsArgs( metric: VitalSignMetric.height, - title: 'Height', + title: LocaleKeys.height.tr(context: context), icon: AppAssets.heightVital, unit: 'cm', ), @@ -131,14 +135,14 @@ class _VitalSignPageState extends State { // Weight Card _buildVitalSignCard( icon: AppAssets.weightVital, - label: 'Weight', + label: LocaleKeys.weight.tr(context: context), value: latestVitalSign?.weightKg?.toString() ?? '--', unit: 'kg', status: (latestVitalSign?.weightKg != null) ? 'Normal' : null, onTap: () => _openDetails( - const VitalSignDetailsArgs( + VitalSignDetailsArgs( metric: VitalSignMetric.weight, - title: 'Weight', + title: LocaleKeys.height.tr(context: context), icon: AppAssets.weightVital, unit: 'kg', ), @@ -149,7 +153,7 @@ class _VitalSignPageState extends State { // Blood Pressure Card _buildVitalSignCard( icon: AppAssets.bloodPressure, - label: 'Blood Pressure', + label: LocaleKeys.bloodPressure.tr(context: context), value: (latestVitalSign != null && _isValidValue(latestVitalSign.bloodPressureHigher) && _isValidValue(latestVitalSign.bloodPressureLower)) @@ -161,9 +165,9 @@ class _VitalSignPageState extends State { diastolic: latestVitalSign?.bloodPressureLower, ), onTap: () => _openDetails( - const VitalSignDetailsArgs( + VitalSignDetailsArgs( metric: VitalSignMetric.bloodPressure, - title: 'Blood Pressure', + title: LocaleKeys.bloodPressure.tr(context: context), icon: AppAssets.bloodPressure, unit: 'mmHg', low: 90, @@ -176,14 +180,14 @@ class _VitalSignPageState extends State { // Temperature Card _buildVitalSignCard( icon: AppAssets.temperature, - label: 'Temperature', + label: LocaleKeys.temperature.tr(context: context), value: latestVitalSign?.temperatureCelcius?.toString() ?? '--', unit: '°C', status: null, onTap: () => _openDetails( - const VitalSignDetailsArgs( + VitalSignDetailsArgs( metric: VitalSignMetric.temperature, - title: 'Temperature', + title: LocaleKeys.temperature.tr(context: context), icon: AppAssets.temperature, unit: '°C', low: 36.1, @@ -251,14 +255,14 @@ class _VitalSignPageState extends State { bottom: 12.h, child: _buildVitalSignCard( icon: AppAssets.heart, - label: 'Heart Rate', + label: LocaleKeys.heart.tr(context: context), value: latestVitalSign?.heartRate?.toString() ?? latestVitalSign?.pulseBeatPerMinute?.toString() ?? '--', unit: 'bpm', status: 'Normal', onTap: () => _openDetails( - const VitalSignDetailsArgs( + VitalSignDetailsArgs( metric: VitalSignMetric.heartRate, - title: 'Heart Rate', + title: LocaleKeys.heart.tr(context: context), icon: AppAssets.heart, unit: 'bpm', low: 60, @@ -275,15 +279,15 @@ class _VitalSignPageState extends State { // Respiratory rate Card _buildVitalSignCard( icon: AppAssets.respRate, - label: 'Respiratory rate', - value: latestVitalSign?.respirationBeatPerMinute?.toString() ?? '--', + label: LocaleKeys.respirationRate.tr(context: context), + value: latestVitalSign?.respirationBeatPerMinute?.toString() ?? '--', unit: 'bpm', status: 'Normal', onTap: () => _openDetails( - const VitalSignDetailsArgs( - metric: VitalSignMetric.respiratoryRate, - title: 'Respiratory rate', - icon: AppAssets.respRate, + VitalSignDetailsArgs( + metric: VitalSignMetric.respiratoryRate, + title: LocaleKeys.respirationRate.tr(context: context), + icon: AppAssets.respRate, unit: 'bpm', low: 12, high: 20, @@ -353,6 +357,9 @@ class _VitalSignPageState extends State { weight: FontWeight.w600, ), ), + Utils.buildSvgWithAssets( + icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward, + width: 18.w, height: 18.h), ], ), SizedBox(height: 14.h), @@ -376,6 +383,7 @@ class _VitalSignPageState extends State { value.toText17( isBold: true, color: AppColors.textColor, + isEnglishOnly: true ), if (unit.isNotEmpty) ...[ SizedBox(width: 3.w), diff --git a/lib/presentation/water_monitor/water_consumption_page.dart b/lib/presentation/water_monitor/water_consumption_page.dart index 2bd429c0..54dace58 100644 --- a/lib/presentation/water_monitor/water_consumption_page.dart +++ b/lib/presentation/water_monitor/water_consumption_page.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -9,6 +10,7 @@ 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/water_monitor/water_monitor_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/water_monitor/widgets/hydration_tips_widget.dart'; import 'package:hmg_patient_app_new/presentation/water_monitor/widgets/water_intake_summary_widget.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; @@ -100,7 +102,7 @@ class _WaterConsumptionPageState extends State { children: [ Row( children: [ - "History".needTranslation.toText16(isBold: true), + LocaleKeys.history.tr(context: context).toText16(isBold: true), SizedBox(width: 8.w), InkWell( onTap: () => _showHistoryDurationBottomsheet(context, viewModel), @@ -604,7 +606,7 @@ class _WaterConsumptionPageState extends State { final dialogService = getIt.get(); dialogService.showFamilyBottomSheetWithoutHWithChild( - label: title.needTranslation, + label: title, message: "", child: Container( padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), @@ -634,7 +636,7 @@ class _WaterConsumptionPageState extends State { void _showHistoryDurationBottomsheet(BuildContext context, WaterMonitorViewModel viewModel) { _showSelectionBottomSheet( context: context, - title: "Select Duration".needTranslation, + title: LocaleKeys.selectDuration.tr(context: context), items: viewModel.durationFilters, selectedValue: viewModel.selectedDurationFilter, onSelected: viewModel.setFilterDuration, @@ -655,10 +657,10 @@ class _WaterConsumptionPageState extends State { /// Show confirmation bottom sheet before cancelling reminders void _showCancelReminderConfirmation(WaterMonitorViewModel viewModel) { showCommonBottomSheetWithoutHeight( - title: 'Notice'.needTranslation, + title: LocaleKeys.notice.tr(context: context), context, child: Utils.getWarningWidget( - loadingText: "Are you sure you want to cancel all water reminders?".needTranslation, + loadingText: LocaleKeys.areYouSureYouWantToCancelAllWaterReminders.tr(context: context), isShowActionButtons: true, onCancelTap: () { Navigator.pop(context); @@ -694,7 +696,7 @@ class _WaterConsumptionPageState extends State { /// Show bottom sheet with scheduled reminder times void _showReminderScheduledDialog(List times) { showCommonBottomSheetWithoutHeight( - title: 'Reminders Set!'.needTranslation, + title: LocaleKeys.remindersSet.tr(context: context), context, isCloseButtonVisible: false, isDismissible: false, @@ -703,7 +705,7 @@ class _WaterConsumptionPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Utils.getSuccessWidget(loadingText: 'Daily water reminders scheduled at:'.needTranslation), + Utils.getSuccessWidget(loadingText: LocaleKeys.dailyWaterRemindersScheduledAt.tr(context: context)), SizedBox(height: 16.h), Wrap( spacing: 8.w, @@ -728,7 +730,7 @@ class _WaterConsumptionPageState extends State { Expanded( child: CustomButton( height: 56.h, - text: 'OK'.needTranslation, + text: LocaleKeys.ok.tr(context: context), onPressed: () => Navigator.of(context).pop(), textColor: AppColors.whiteColor, ), @@ -792,7 +794,7 @@ class _WaterConsumptionPageState extends State { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( - title: "Water Consumption".needTranslation, + title: LocaleKeys.waterConsumption.tr(context: context), bottomChild: Consumer( builder: (context, viewModel, child) { return Container( @@ -804,7 +806,7 @@ class _WaterConsumptionPageState extends State { child: Padding( padding: EdgeInsets.all(24.w), child: CustomButton( - text: viewModel.isWaterReminderEnabled ? "Cancel Reminders".needTranslation : "Set Reminder".needTranslation, + text: viewModel.isWaterReminderEnabled ? LocaleKeys.cancelReminder.tr(context: context) : LocaleKeys.setReminder.tr(context: context), textColor: viewModel.isWaterReminderEnabled ? AppColors.errorColor : AppColors.successColor, backgroundColor: viewModel.isWaterReminderEnabled ? AppColors.errorColor.withValues(alpha: 0.1) : AppColors.successLightBgColor, onPressed: () => _handleReminderButtonTap(viewModel), diff --git a/lib/presentation/water_monitor/water_monitor_settings_page.dart b/lib/presentation/water_monitor/water_monitor_settings_page.dart index 302940c9..a3c82c0c 100644 --- a/lib/presentation/water_monitor/water_monitor_settings_page.dart +++ b/lib/presentation/water_monitor/water_monitor_settings_page.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; @@ -6,6 +7,7 @@ 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/water_monitor/water_monitor_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; @@ -69,7 +71,7 @@ class _WaterMonitorSettingsPageState extends State { bool useUpperCase = false, }) { dialogService.showFamilyBottomSheetWithoutHWithChild( - label: title.needTranslation, + label: title, message: "", child: Container( padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), @@ -99,7 +101,7 @@ class _WaterMonitorSettingsPageState extends State { void _showGenderSelectionBottomsheet(BuildContext context, WaterMonitorViewModel viewModel) { _showSelectionBottomSheet( context: context, - title: "Select Gender".needTranslation, + title: LocaleKeys.selectGender.tr(context: context), items: viewModel.genderOptions, selectedValue: viewModel.selectedGender, onSelected: viewModel.setGender, @@ -109,7 +111,7 @@ class _WaterMonitorSettingsPageState extends State { void _showHeightUnitSelectionBottomSheet(BuildContext context, WaterMonitorViewModel viewModel) { _showSelectionBottomSheet( context: context, - title: "Select Unit".needTranslation, + title: LocaleKeys.selectUnit.tr(context: context), items: viewModel.heightUnits, selectedValue: viewModel.selectedHeightUnit, onSelected: viewModel.setHeightUnit, @@ -120,7 +122,7 @@ class _WaterMonitorSettingsPageState extends State { void _showWeightUnitSelectionBottomsheet(BuildContext context, WaterMonitorViewModel viewModel) { _showSelectionBottomSheet( context: context, - title: "Select Unit".needTranslation, + title: LocaleKeys.selectUnit.tr(context: context), items: viewModel.weightUnits, selectedValue: viewModel.selectedWeightUnit, onSelected: viewModel.setWeightUnit, @@ -131,7 +133,7 @@ class _WaterMonitorSettingsPageState extends State { void _showActivityLevelSelectionBottomsheet(BuildContext context, WaterMonitorViewModel viewModel) { _showSelectionBottomSheet( context: context, - title: "Select Activity Level".needTranslation, + title: LocaleKeys.selectActivityLevel.tr(context: context), items: viewModel.activityLevels, selectedValue: viewModel.selectedActivityLevel, onSelected: viewModel.setActivityLevel, @@ -141,7 +143,7 @@ class _WaterMonitorSettingsPageState extends State { void _showNumberOfRemindersSelectionBottomsheet(BuildContext context, WaterMonitorViewModel viewModel) { _showSelectionBottomSheet( context: context, - title: "Select Number of Reminders".needTranslation, + title: LocaleKeys.selectNumberOfReminders.tr(context: context), items: viewModel.reminderOptions, selectedValue: viewModel.selectedNumberOfReminders, onSelected: viewModel.setNumberOfReminders, @@ -256,7 +258,7 @@ class _WaterMonitorSettingsPageState extends State { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( - title: "H20 Settings".needTranslation, + title: LocaleKeys.h2oSettings.tr(context: context), bottomChild: Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, @@ -266,7 +268,7 @@ class _WaterMonitorSettingsPageState extends State { child: Padding( padding: EdgeInsets.all(24.w), child: CustomButton( - text: "Save".needTranslation, + text: LocaleKeys.save.tr(context: context), onPressed: () async { final success = await viewModel.saveSettings(); if (!success && viewModel.validationError != null) { @@ -277,7 +279,7 @@ class _WaterMonitorSettingsPageState extends State { showCommonBottomSheetWithoutHeight( context, child: Utils.getSuccessWidget( - loadingText: "Settings saved successfully".needTranslation, + loadingText: LocaleKeys.settingsSavedSuccessfully.tr(context: context), ), callBackFunc: () {}, isCloseButtonVisible: false, @@ -299,18 +301,18 @@ class _WaterMonitorSettingsPageState extends State { children: [ _buildSettingsRow( icon: AppAssets.profileIcon, - label: "Your Name".needTranslation, + label: LocaleKeys.yourName.tr(context: context), inputField: _buildTextField(viewModel.nameController, 'Guest'), ), _buildSettingsRow( icon: AppAssets.genderIcon, - label: "Select Gender".needTranslation, + label: LocaleKeys.selectGender.tr(context: context), value: viewModel.selectedGender, onRowTap: () => _showGenderSelectionBottomsheet(context, viewModel), ), _buildSettingsRow( icon: AppAssets.calendarGrey, - label: "Age (11-120) yrs".needTranslation, + label: LocaleKeys.ageYears.tr(context: context), inputField: _buildTextField( viewModel.ageController, '20', @@ -319,7 +321,7 @@ class _WaterMonitorSettingsPageState extends State { ), _buildSettingsRow( icon: AppAssets.heightIcon, - label: "Height".needTranslation, + label: LocaleKeys.height.tr(context: context), inputField: _buildTextField( viewModel.heightController, '175', @@ -330,7 +332,7 @@ class _WaterMonitorSettingsPageState extends State { ), _buildSettingsRow( icon: AppAssets.weightScaleIcon, - label: "Weight".needTranslation, + label: LocaleKeys.weight.tr(context: context), inputField: _buildTextField( viewModel.weightController, '75', @@ -341,13 +343,13 @@ class _WaterMonitorSettingsPageState extends State { ), _buildSettingsRow( icon: AppAssets.dumbellIcon, - label: "Activity Level".needTranslation, + label: LocaleKeys.activityLevel.tr(context: context), value: viewModel.selectedActivityLevel, onRowTap: () => _showActivityLevelSelectionBottomsheet(context, viewModel), ), _buildSettingsRow( icon: AppAssets.notificationIconGrey, - label: "Number of reminders in a day".needTranslation, + label: LocaleKeys.numberOfRemindersInADay.tr(context: context), value: viewModel.selectedNumberOfReminders, onRowTap: () => _showNumberOfRemindersSelectionBottomsheet(context, viewModel), showDivider: false, diff --git a/lib/presentation/water_monitor/widgets/cup_bottomsheet_widgets.dart b/lib/presentation/water_monitor/widgets/cup_bottomsheet_widgets.dart index 4ffa30d9..4ee6170e 100644 --- a/lib/presentation/water_monitor/widgets/cup_bottomsheet_widgets.dart +++ b/lib/presentation/water_monitor/widgets/cup_bottomsheet_widgets.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; @@ -7,6 +8,7 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/water_monitor/models/water_cup_model.dart'; import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; @@ -143,7 +145,7 @@ class SwitchCupBottomSheet extends StatelessWidget { child: Center(child: Utils.buildSvgWithAssets(icon: AppAssets.cupAdd, height: 30.h, width: 42.w)), ), SizedBox(height: 4.h), - 'Add'.needTranslation.toText10(weight: FontWeight.w500), + LocaleKeys.add.tr(context: context).toText10(weight: FontWeight.w500), ], ), ); @@ -157,7 +159,7 @@ void showCustomizeCupBottomSheet(BuildContext context, {WaterCupModel? cupToEdit titleWidget: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Customize your drink cup".needTranslation.toText20(weight: FontWeight.w600), + LocaleKeys.customizeDrinkCup.tr(context: context).toText20(weight: FontWeight.w600), ], ), child: CustomizeCupBottomSheet(cupToEdit: cupToEdit), @@ -294,7 +296,7 @@ class _CustomizeCupBottomSheetState extends State { SizedBox(height: 24.h), CustomButton( - text: 'Select'.needTranslation, + text: LocaleKeys.select.tr(context: context), onPressed: () { final newCup = WaterCupModel( id: widget.cupToEdit?.id ?? Uuid().v4(), diff --git a/lib/presentation/water_monitor/widgets/hydration_tips_widget.dart b/lib/presentation/water_monitor/widgets/hydration_tips_widget.dart index df558869..7c49550c 100644 --- a/lib/presentation/water_monitor/widgets/hydration_tips_widget.dart +++ b/lib/presentation/water_monitor/widgets/hydration_tips_widget.dart @@ -1,9 +1,11 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; class HydrationTipsWidget extends StatelessWidget { @@ -30,26 +32,26 @@ class HydrationTipsWidget extends StatelessWidget { height: 24.h, ), SizedBox(width: 8.w), - "Tips to stay hydrated".needTranslation.toText16(isBold: true), + LocaleKeys.tipsToStayHydrated.tr(context: context).toText16(isBold: true), ], ), SizedBox(height: 8.h), - " • ${"Drink before you feel thirsty"}".needTranslation.toText12( + " • ${LocaleKeys.drinkBeforeYouFeelThirsty.tr(context: context)}".toText12( fontWeight: FontWeight.w500, color: AppColors.textColorLight, ), SizedBox(height: 4.h), - " • ${"Keep a refillable bottle next to you"}".needTranslation.toText12( + " • ${LocaleKeys.keepRefillableBottleNextToYou.tr(context: context)}".toText12( fontWeight: FontWeight.w500, color: AppColors.textColorLight, ), SizedBox(height: 4.h), - " • ${"Track your daily intake to stay motivated"}".needTranslation.toText12( + " • ${LocaleKeys.trackYourDailyIntakeToStayMotivated.tr(context: context)}".toText12( fontWeight: FontWeight.w500, color: AppColors.textColorLight, ), SizedBox(height: 4.h), - " • ${"Choose sparkling water instead of soda"}".needTranslation.toText12( + " • ${LocaleKeys.chooseSparklingWaterInsteadOfSoda.tr(context: context)}".toText12( fontWeight: FontWeight.w500, color: AppColors.textColorLight, ), diff --git a/lib/presentation/water_monitor/widgets/water_action_buttons_widget.dart b/lib/presentation/water_monitor/widgets/water_action_buttons_widget.dart index 2359904b..115dd212 100644 --- a/lib/presentation/water_monitor/widgets/water_action_buttons_widget.dart +++ b/lib/presentation/water_monitor/widgets/water_action_buttons_widget.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; @@ -6,6 +7,7 @@ import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/water_monitor/widgets/cup_bottomsheet_widgets.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:provider/provider.dart'; @@ -83,19 +85,19 @@ class WaterActionButtonsWidget extends StatelessWidget { context: context, onTap: () => showSwitchCupBottomSheet(context), overlayWidget: AppAssets.refreshIcon, - title: "Switch Cup".needTranslation, + title: LocaleKeys.switchCup.tr(context: context), icon: Utils.buildSvgWithAssets(icon: AppAssets.glassIcon, height: 24.w, width: 24.w), ), _buildActionButton( context: context, onTap: () async {}, - title: "Plain Water".needTranslation, + title: LocaleKeys.plainWater.tr(context: context), icon: Utils.buildSvgWithAssets(icon: AppAssets.glassIcon, height: 24.w, width: 24.w), ), _buildActionButton( context: context, onTap: () => context.navigateWithName(AppRoutes.waterMonitorSettingsPage), - title: "Settings".needTranslation, + title: LocaleKeys.settings.tr(context: context), icon: Icon( Icons.settings, color: AppColors.blueColor, diff --git a/lib/presentation/water_monitor/widgets/water_intake_summary_widget.dart b/lib/presentation/water_monitor/widgets/water_intake_summary_widget.dart index 137f6a38..ec796abc 100644 --- a/lib/presentation/water_monitor/widgets/water_intake_summary_widget.dart +++ b/lib/presentation/water_monitor/widgets/water_intake_summary_widget.dart @@ -1,7 +1,9 @@ +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/features/water_monitor/water_monitor_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/water_monitor/widgets/water_action_buttons_widget.dart'; import 'package:hmg_patient_app_new/presentation/water_monitor/widgets/water_bottle_widget.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -51,7 +53,6 @@ class WaterIntakeSummaryWidget extends StatelessWidget { if (!vm.nextDrinkTime.toLowerCase().contains('goal achieved')) // Show "Tomorrow" if nextDrinkTime contains "tomorrow", otherwise "Next Drink Time" (vm.nextDrinkTime.toLowerCase().contains('tomorrow') ? "Tomorrow" : "Next Drink Time") - .needTranslation .toText18(weight: FontWeight.w600, color: AppColors.textColor), // Extract only time if "tomorrow" is present, otherwise show as is @@ -61,14 +62,17 @@ class WaterIntakeSummaryWidget extends StatelessWidget { .toText32(weight: FontWeight.w600, color: AppColors.blueColor), SizedBox(height: 12.h), - _buildStatusColumn(title: "Your Goal".needTranslation, subTitle: "${goalMl}ml"), - SizedBox(height: 8.h), - _buildStatusColumn(title: "Remaining".needTranslation, subTitle: "${remaining}ml"), - SizedBox(height: 8.h), - _buildStatusColumn(title: "Completed".needTranslation, subTitle: completedPercent, subTitleColor: AppColors.successColor), - SizedBox(height: 8.h), + Row( + children: [ + _buildStatusColumn(title: LocaleKeys.yourGoal.tr(context: context), subTitle: "${goalMl}ml"), + SizedBox(width: 16.w), + _buildStatusColumn(title: LocaleKeys.remaining.tr(context: context), subTitle: "${remaining}ml"), + SizedBox(width: 16.w), + _buildStatusColumn(title: LocaleKeys.completed.tr(context: context), subTitle: completedPercent, subTitleColor: AppColors.successColor), + ], + ), _buildStatusColumn( - title: "Hydration Status".needTranslation, + title: LocaleKeys.hydrationStatus.tr(context: context), subTitle: vm.hydrationStatus, subTitleColor: vm.hydrationStatusColor, ), diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index ebc71655..c09fab67 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -5,9 +5,11 @@ import 'package:hmg_patient_app_new/presentation/authentication/register.dart'; import 'package:hmg_patient_app_new/presentation/authentication/register_step2.dart'; import 'package:hmg_patient_app_new/presentation/blood_donation/blood_donation_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart'; +import 'package:hmg_patient_app_new/presentation/child_vaccine/my_child_vaccine_page.dart'; import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/comprehensive_checkup_page.dart'; import 'package:hmg_patient_app_new/presentation/covid19test/covid19_landing_page.dart'; import 'package:hmg_patient_app_new/presentation/e_referral/new_e_referral.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/emergency_services_page.dart'; import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculators_page.dart'; import 'package:hmg_patient_app_new/presentation/health_trackers/add_health_tracker_entry_page.dart'; import 'package:hmg_patient_app_new/presentation/health_trackers/health_tracker_detail_page.dart'; @@ -15,8 +17,9 @@ import 'package:hmg_patient_app_new/presentation/health_trackers/health_trackers import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/presentation/home_health_care/hhc_procedures_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; +import 'package:hmg_patient_app_new/presentation/servicesPriceList/services_price_list_page.dart'; import 'package:hmg_patient_app_new/presentation/smartwatches/huawei_health_example.dart'; -import 'package:hmg_patient_app_new/presentation/smartwatches/smartwatch_instructions_page.dart'; +import 'package:hmg_patient_app_new/presentation/smartwatches/smartwatch_home_page.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/organ_selector_screen.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/possible_conditions_screen.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/risk_factors_screen.dart'; @@ -30,17 +33,14 @@ import 'package:hmg_patient_app_new/presentation/vital_sign/vital_sign_page.dart import 'package:hmg_patient_app_new/presentation/water_monitor/water_consumption_page.dart'; import 'package:hmg_patient_app_new/presentation/water_monitor/water_monitor_settings_page.dart'; import 'package:hmg_patient_app_new/splashPage.dart'; - -import '../features/qr_parking/qr_parking_view_model.dart'; -import '../presentation/covid19test/covid19_landing_page.dart'; +import 'package:provider/provider.dart'; import '../core/dependencies.dart'; import '../features/monthly_reports/monthly_reports_repo.dart'; import '../features/monthly_reports/monthly_reports_view_model.dart'; -import '../presentation/monthly_reports/monthly_reports_page.dart'; +import '../features/qr_parking/qr_parking_view_model.dart'; import '../presentation/parking/paking_page.dart'; import '../services/error_handler_service.dart'; -import 'package:provider/provider.dart'; class AppRoutes { static const String initialRoute = '/initialRoute'; @@ -56,6 +56,7 @@ class AppRoutes { static const String homeHealthCarePage = '/homeHealthCarePage'; static const String zoomCallPage = '/zoomCallPage'; static const String bloodDonationPage = '/bloodDonationPage'; + static const String myChildVaccine = '/myChildVaccine'; static const String smartWatches = '/smartWatches'; static const String huaweiHealthExample = '/huaweiHealthExample'; static const String covid19Test = '/covid19Test'; @@ -85,8 +86,13 @@ class AppRoutes { static const String addHealthTrackerEntryPage = '/addHealthTrackerEntryPage'; static const String healthTrackerDetailPage = '/healthTrackerDetailPage'; - static Map get routes => - { + // Emergency Services + static const String emergencyServicesPage = '/emergencyServicesPage'; + + // Services Price List + static const String servicesPriceListPage = '/servicesPriceListPage'; + + static Map get routes => { initialRoute: (context) => SplashPage(), loginScreen: (context) => LoginScreen(), landingScreen: (context) => LandingNavigation(), @@ -104,10 +110,11 @@ class AppRoutes { possibleConditionsPage: (context) => PossibleConditionsPage(), triagePage: (context) => TriagePage(), bloodDonationPage: (context) => BloodDonationPage(), + myChildVaccine: (context) => MyChildVaccinePage(), bookAppointmentPage: (context) => BookAppointmentPage(), - userInfoSelection: (context) => UserInfoSelectionScreen(), + userInfoSelection: (context) => UserInfoSelectionPage(), userInfoFlowManager: (context) => UserInfoFlowManager(), - smartWatches: (context) => SmartwatchInstructionsPage(), + smartWatches: (context) => SmartwatchHomePage(), huaweiHealthExample: (context) => HuaweiHealthExample(), covid19Test: (context) => Covid19LandingPage(), waterConsumptionPage: (context) => WaterConsumptionPage(), @@ -116,35 +123,29 @@ class AppRoutes { healthConvertersPage: (context) => HealthCalculatorsPage(type: HealthCalConEnum.converter), healthTrackersPage: (context) => HealthTrackersPage(), vitalSign: (context) => VitalSignPage(), + emergencyServicesPage: (context) => EmergencyServicesPage(), + servicesPriceListPage: (context) => ServicesPriceListPage(), addHealthTrackerEntryPage: (context) { - final args = ModalRoute - .of(context) - ?.settings - .arguments as HealthTrackerTypeEnum?; + final args = ModalRoute.of(context)?.settings.arguments as HealthTrackerTypeEnum?; return AddHealthTrackerEntryPage( trackerType: args ?? HealthTrackerTypeEnum.bloodSugar, ); }, healthTrackerDetailPage: (context) { - final args = ModalRoute - .of(context) - ?.settings - .arguments as HealthTrackerTypeEnum?; + final args = ModalRoute.of(context)?.settings.arguments as HealthTrackerTypeEnum?; return HealthTrackerDetailPage( trackerType: args ?? HealthTrackerTypeEnum.bloodSugar, ); }, - - monthlyReports: (context) => ChangeNotifierProvider( - create: (_) => MonthlyReportsViewModel( - monthlyReportsRepo: getIt(), - errorHandlerService: getIt(), - ), - child: const MonthlyReportsPage(), - ), - qrParking: (context) => ChangeNotifierProvider( - create: (_) => getIt(), - child: const ParkingPage(), + monthlyReports: (context) => ChangeNotifierProvider( + create: (_) => MonthlyReportsViewModel( + monthlyReportsRepo: getIt(), + errorHandlerService: getIt(), + ), + ), + qrParking: (context) => ChangeNotifierProvider( + create: (_) => getIt(), + child: const ParkingPage(), ) }; } diff --git a/lib/services/dialog_service.dart b/lib/services/dialog_service.dart index 30926749..6347a389 100644 --- a/lib/services/dialog_service.dart +++ b/lib/services/dialog_service.dart @@ -2,10 +2,12 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/family_file_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/my_family/my_family.dart'; import 'package:hmg_patient_app_new/presentation/my_family/widget/family_cards.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -21,22 +23,25 @@ abstract class DialogService { Future showExceptionBottomSheet({required String message, required Function() onOkPressed, Function()? onCancelPressed}); - Future showCommonBottomSheetWithoutH({String? label, required String message, required Function() onOkPressed, Function()? onCancelPressed}); + Future showCommonBottomSheetWithoutH({String? label, required String message, String? okLabel, String? cancelLabel, required Function() onOkPressed, Function()? onCancelPressed}); + + Future showSuccessBottomSheetWithoutH({String? label, required String message, required Function() onOkPressed, Function()? onCancelPressed}); Future showFamilyBottomSheetWithoutH( {String? label, required String message, required Function(FamilyFileResponseModelLists response) onSwitchPress, - required List profiles}); + required List profiles, + bool isShowManageButton = false, + bool isForWalletRecharge = false}); - Future showFamilyBottomSheetWithoutHWithChild( - {String? label, required String message, Widget? child, required Function() onOkPressed, Function()? onCancelPressed}); + Future showFamilyBottomSheetWithoutHWithChild({String? label, required String message, Widget? child, required Function() onOkPressed, Function()? onCancelPressed}); Future showPhoneNumberPickerSheet({String? label, String? message, required Function() onSMSPress, required Function() onWhatsappPress}); Future showAddFamilyFileSheet({String? label, String? message, required Function() onVerificationPress}); - Future showReminderBottomSheetWithoutHWithChild({String? label, required String message, Widget? child, required Function() onOkPressed, Function()? onCancelPressed}); + Future showReminderBottomSheetWithoutHWithChild({String? label, required String message, Widget? child, required Function() onOkPressed, Function()? onCancelPressed}); // TODO : Need to be Fixed showPhoneNumberPickerSheet ( From Login ADn Signup Bottom Sheet Move Here } @@ -96,28 +101,50 @@ class DialogServiceImp implements DialogService { shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), - builder: (_) => ExceptionBottomSheet( + builder: (_) => StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return ExceptionBottomSheet( + message: message, + showCancel: onCancelPressed != null ? true : false, + onOkPressed: onOkPressed, + onCancelPressed: () { + if (onCancelPressed != null) { + Navigator.of(context).pop(); + } + }, + ); + }, + ), + ); + } + + @override + Future showCommonBottomSheetWithoutH({String? label, required String message, String? okLabel, String? cancelLabel, required Function() onOkPressed, Function()? onCancelPressed}) async { + final context = navigationService.navigatorKey.currentContext; + if (context == null) return; + showCommonBottomSheetWithoutHeight( + context, + title: label ?? "", + child: exceptionBottomSheetWidget( + context: context, message: message, - showCancel: onCancelPressed != null ? true : false, + okLabel: okLabel, + cancelLabel: cancelLabel, onOkPressed: onOkPressed, - onCancelPressed: () { - if (onCancelPressed != null) { - Navigator.of(context).pop(); - } - }, + onCancelPressed: onCancelPressed, ), + callBackFunc: () {}, ); } @override - Future showCommonBottomSheetWithoutH( - {String? label, required String message, required Function() onOkPressed, Function()? onCancelPressed}) async { + Future showSuccessBottomSheetWithoutH({String? label, required String message, required Function() onOkPressed, Function()? onCancelPressed}) async { final context = navigationService.navigatorKey.currentContext; if (context == null) return; showCommonBottomSheetWithoutHeight( context, title: label ?? "", - child: exceptionBottomSheetWidget(context: context, message: message, onOkPressed: onOkPressed, onCancelPressed: onCancelPressed), + child: Utils.getSuccessWidget(loadingText: message.needTranslation), callBackFunc: () {}, ); } @@ -127,7 +154,9 @@ class DialogServiceImp implements DialogService { {String? label, required String message, required Function(FamilyFileResponseModelLists response) onSwitchPress, - required List profiles}) async { + required List profiles, + bool isShowManageButton = false, + bool isForWalletRecharge = false}) async { final context = navigationService.navigatorKey.currentContext; if (context == null) return; showCommonBottomSheetWithoutHeight(context, @@ -136,7 +165,7 @@ class DialogServiceImp implements DialogService { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - (message).toText16(isBold: false, color: AppColors.textColor), + if (message != null) (message).toText16(isBold: false, color: AppColors.textColor), SizedBox(height: 24.h), FamilyCards( profiles: profiles, @@ -145,15 +174,27 @@ class DialogServiceImp implements DialogService { }, onRemove: (FamilyFileResponseModelLists profile) {}, isShowDetails: false, + isForWalletRecharge: isForWalletRecharge, ), + SizedBox(height: isShowManageButton ? 15.h : 24.h), + if (isShowManageButton) + CustomButton( + text: LocaleKeys.manageFiles.tr(context: context), + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedBorderColor, + height: 48.h, + onPressed: () { + navigationService.pop(); + navigationService.pushPage(page: FamilyMedicalScreen()); + }) ], ), callBackFunc: () {}); } @override - Future showFamilyBottomSheetWithoutHWithChild( - {String? label, required String message, Widget? child, required Function() onOkPressed, Function()? onCancelPressed}) async { + Future showFamilyBottomSheetWithoutHWithChild({String? label, required String message, Widget? child, required Function() onOkPressed, Function()? onCancelPressed}) async { final context = navigationService.navigatorKey.currentContext; if (context == null) return; showCommonBottomSheetWithoutHeight( @@ -165,7 +206,7 @@ class DialogServiceImp implements DialogService { } @override - Future showReminderBottomSheetWithoutHWithChild({String? label, required String message, Widget? child, required Function() onOkPressed, Function()? onCancelPressed}) async { + Future showReminderBottomSheetWithoutHWithChild({String? label, required String message, Widget? child, required Function() onOkPressed, Function()? onCancelPressed}) async { final context = navigationService.navigatorKey.currentContext; if (context == null) return; showCommonBottomSheetWithoutHeight( @@ -177,14 +218,11 @@ class DialogServiceImp implements DialogService { } @override - Future showPhoneNumberPickerSheet( - {String? label, String? message, required Function() onSMSPress, required Function() onWhatsappPress}) async { + Future showPhoneNumberPickerSheet({String? label, String? message, required Function() onSMSPress, required Function() onWhatsappPress}) async { final context = navigationService.navigatorKey.currentContext; if (context == null) return; showCommonBottomSheetWithoutHeight(context, - title: label ?? "", - child: showPhoneNumberPickerWidget(context: context, message: message, onSMSPress: onSMSPress, onWhatsappPress: onWhatsappPress), - callBackFunc: () {}); + title: label ?? "", child: showPhoneNumberPickerWidget(context: context, message: message, onSMSPress: onSMSPress, onWhatsappPress: onWhatsappPress), callBackFunc: () {}); } @override @@ -200,8 +238,7 @@ class DialogServiceImp implements DialogService { } } -Widget exceptionBottomSheetWidget( - {required BuildContext context, required String message, required Function() onOkPressed, Function()? onCancelPressed}) { +Widget exceptionBottomSheetWidget({required BuildContext context, required String message, String? okLabel, String? cancelLabel, required Function() onOkPressed, Function()? onCancelPressed}) { return Column( children: [ (message).toText16(isBold: false, color: AppColors.textColor), @@ -212,7 +249,7 @@ Widget exceptionBottomSheetWidget( children: [ Expanded( child: CustomButton( - text: LocaleKeys.cancel.tr(), + text: cancelLabel ?? LocaleKeys.cancel.tr(context: context), onPressed: () { Navigator.of(context).pop(); }, @@ -226,7 +263,7 @@ Widget exceptionBottomSheetWidget( SizedBox(width: 10.h), Expanded( child: CustomButton( - text: LocaleKeys.confirm.tr(), + text: okLabel ?? LocaleKeys.confirm.tr(context: context), onPressed: onOkPressed, backgroundColor: AppColors.bgGreenColor, borderColor: AppColors.bgGreenColor, @@ -240,7 +277,7 @@ Widget exceptionBottomSheetWidget( Padding( padding: EdgeInsets.only(bottom: 10.h), child: CustomButton( - text: LocaleKeys.cancel.tr(), + text: cancelLabel ?? LocaleKeys.cancel.tr(context: context), onPressed: (onCancelPressed == null) ? () { context.pop(); @@ -256,8 +293,7 @@ Widget exceptionBottomSheetWidget( ); } -Widget showPhoneNumberPickerWidget( - {required BuildContext context, String? message, required Function() onSMSPress, required Function() onWhatsappPress}) { +Widget showPhoneNumberPickerWidget({required BuildContext context, String? message, required Function() onSMSPress, required Function() onWhatsappPress}) { return StatefulBuilder(builder: (BuildContext context, StateSetter setModalState) { return Column( children: [ @@ -266,7 +302,7 @@ Widget showPhoneNumberPickerWidget( Padding( padding: EdgeInsets.only(bottom: 10.h), child: CustomButton( - text: LocaleKeys.sendOTPSMS.tr(), + text: LocaleKeys.sendOTPSMS.tr(context: context), onPressed: onSMSPress, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedBorderColor, @@ -280,14 +316,14 @@ Widget showPhoneNumberPickerWidget( children: [ Padding( padding: EdgeInsets.symmetric(horizontal: 8.h), - child: LocaleKeys.oR.tr().toText16(color: AppColors.textColor), + child: LocaleKeys.oR.tr(context: context).toText16(color: AppColors.textColor), ), ], ), Padding( padding: EdgeInsets.only(bottom: 10.h, top: 10.h), child: CustomButton( - text: LocaleKeys.sendOTPWHATSAPP.tr(), + text: LocaleKeys.sendOTPWHATSAPP.tr(context: context), onPressed: onWhatsappPress, backgroundColor: Colors.white, borderColor: AppColors.borderOnlyColor, diff --git a/lib/services/livecare_permission_service.dart b/lib/services/livecare_permission_service.dart new file mode 100644 index 00000000..a9a5d294 --- /dev/null +++ b/lib/services/livecare_permission_service.dart @@ -0,0 +1,92 @@ +import 'dart:async'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:permission_handler/permission_handler.dart'; + +class LiveCarePermissionService { + LiveCarePermissionService._(); + + static final LiveCarePermissionService instance = LiveCarePermissionService._(); + + // Guard to prevent concurrent permission requests + Completer>? _ongoingRequest; + + /// Requests camera, microphone and notification permissions together and + /// returns the final map of permission statuses. + /// This method does NOT show any dialogs — UI decisions must be done by caller. + Future> requestCameraMicAndNotification(BuildContext context) async { + if (_ongoingRequest != null) { + return _ongoingRequest!.future; + } + + _ongoingRequest = Completer>(); + final completer = _ongoingRequest!; + + final permissions = [ + Permission.camera, + Permission.microphone, + Permission.notification, + ]; + + try { + final statuses = await permissions.request(); + + // Ensure all requested keys exist in the returned map (some platforms may omit) + final result = {}; + for (final p in permissions) { + result[p] = statuses[p] ?? await p.status; + } + + if (!completer.isCompleted) completer.complete(result); + return completer.future; + } catch (e) { + if (!completer.isCompleted) completer.complete({}); + return completer.future; + } finally { + _ongoingRequest = null; + } + } + + /// Show an "open settings" dialog (kept as helper for the UI layer). + Future showOpenSettingsDialog(BuildContext context, {required String title, required String message}) async { + return showCommonBottomSheetWithoutHeight( + title: title, + context, + child: Utils.getWarningWidget( + loadingText: message, + isShowActionButtons: true, + onCancelTap: () { + Navigator.of(context).pop(); + }, + onConfirmTap: () async { + openAppSettings(); + Navigator.of(context).pop(); + }), + isFullScreen: false, + isCloseButtonVisible: true, + callBackFunc: () {}, + ); + // return showDialog( + // context: context, + // builder: (c) => ConfirmDialog( + // title: '$title Permission', + // message: message, + // onTap: () { + // openAppSettings(); + // Navigator.of(c).pop(); + // }, + // okTitle: LocaleKeys.ok.tr(context: c), + // ), + // ); + } + + String friendlyName(Permission permission) { + if (permission == Permission.camera) return 'Camera'; + if (permission == Permission.microphone) return 'Microphone'; + if (permission == Permission.notification) return 'Notifications'; + return permission.toString(); + } +} diff --git a/lib/services/navigation_service.dart b/lib/services/navigation_service.dart index fe951400..da4a75b9 100644 --- a/lib/services/navigation_service.dart +++ b/lib/services/navigation_service.dart @@ -34,6 +34,13 @@ class NavigationService { ); } + void replaceAllRoutesAndNavigateToLandingAuth() { + navigatorKey.currentState?.pushNamedAndRemoveUntil( + AppRoutes.landingScreen, + (Route route) => false, + ); + } + void pushAndReplace(String routeName) { navigatorKey.currentState?.pushReplacementNamed(routeName); } diff --git a/lib/services/zoom_service.dart b/lib/services/zoom_service.dart new file mode 100644 index 00000000..9af543dc --- /dev/null +++ b/lib/services/zoom_service.dart @@ -0,0 +1,33 @@ +// lib/services/zoom_service.dart +import 'package:flutter_zoom_videosdk/native/zoom_videosdk.dart'; + +class ZoomService { + static final ZoomService _instance = ZoomService._internal(); + factory ZoomService() => _instance; + ZoomService._internal(); + + ZoomVideoSdk? _zoom; + bool _isInitialized = false; + + Future initializeZoomSDK() async { + if (_isInitialized) return; + + try { + _zoom = ZoomVideoSdk(); + InitConfig initConfig = InitConfig( + domain: "zoom.us", + enableLog: true, // Enable for debugging + ); + + await _zoom!.initSdk(initConfig); + _isInitialized = true; + print("Zoom SDK initialized successfully"); + } catch (e) { + print("Error initializing Zoom SDK: $e"); + rethrow; + } + } + + ZoomVideoSdk? get zoom => _zoom; + bool get isInitialized => _isInitialized; +} \ No newline at end of file diff --git a/lib/splashPage.dart b/lib/splashPage.dart index b5366618..dc7a7c98 100644 --- a/lib/splashPage.dart +++ b/lib/splashPage.dart @@ -19,6 +19,7 @@ import 'package:hmg_patient_app_new/presentation/tele_consultation/zoom/call_scr import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/services/notification_service.dart'; +import 'package:hmg_patient_app_new/services/zoom_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:lottie/lottie.dart'; @@ -46,6 +47,7 @@ class _SplashScreenState extends State { PushNotificationHandler().init(context); // Asyncronously }, ); + // TODO: This next line should be tested. This stops the app at this step, sometimes await authVm.getServicePrivilege(); Timer(Duration(seconds: 2, milliseconds: 500), () async { bool isAppOpenedFromCall = getIt.get().getBool(key: CacheConst.isAppOpenedFromCall) ?? false; @@ -55,7 +57,7 @@ class _SplashScreenState extends State { await notificationService.initialize(onNotificationClick: (payload) { // Handle notification click here }); - + ZoomService().initializeZoomSDK(); if (isAppOpenedFromCall) { navigateToTeleConsult(); } else { @@ -68,12 +70,12 @@ class _SplashScreenState extends State { } } }); - var zoom = ZoomVideoSdk(); - InitConfig initConfig = InitConfig( - domain: "zoom.us", - enableLog: false, - ); - zoom.initSdk(initConfig); + // var zoom = ZoomVideoSdk(); + // InitConfig initConfig = InitConfig( + // domain: "zoom.us", + // enableLog: false, + // ); + // zoom.initSdk(initConfig); } navigateToTeleConsult() async { diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index 9fbdc17b..f80085aa 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -92,13 +92,14 @@ class AppColors { // SymptomsChecker static const Color chipColorSeekMedicalAdvice = Color(0xFFFFAF15); // #FFAF15 - static const Color chipTextColorSeekMedicalAdvice = Color(0xFFAB7103); // #AB7103 + static const Color chipTextColorSeekMedicalAdvice = Color(0xFFD48D05); // #AB7103 static const Color chipColorMonitor = Color(0xFF18C273); // #18C273 static const Color chipColorEmergency = Color(0xFFED1C2B); // #ED1C2B // Services Page Colors static const Color eReferralCardColor = Color(0xFFFF8012); // #FF8012 static const Color bloodDonationCardColor = Color(0xFFFF5662); // #FF5662 + static const Color myChildVaccineCardColor = Color(0xFFFF2D78); // #FF5662 static const Color bookAppointment = Color(0xFF415364); // #415364 // Water Monitor @@ -112,4 +113,6 @@ class AppColors { static const Color covid29Color = Color(0xff2563EB); // #2563EB static const Color lightGreyTextColor = Color(0xFF959595); static const Color labelColorYellow = Color(0xFFFBCB6E); + + static const LinearGradient aiLinearGradient = LinearGradient(colors: [Color(0xFF8A38F5), Color(0xFFE20BBB)], begin: Alignment.topLeft, end: Alignment.bottomRight); } diff --git a/lib/widgets/app_language_change.dart b/lib/widgets/app_language_change.dart index de9cda50..e75ff6f6 100644 --- a/lib/widgets/app_language_change.dart +++ b/lib/widgets/app_language_change.dart @@ -4,9 +4,14 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/int_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:provider/provider.dart'; class AppLanguageChange extends StatefulWidget { AppLanguageChange({Key? key}) : super(key: key); @@ -20,6 +25,11 @@ class AppLanguageChange extends StatefulWidget { class _AppLanguageChangeState extends State { String? selectedValue; + late MyAppointmentsViewModel myAppointmentsViewModel; + late ImmediateLiveCareViewModel immediateLiveCareViewModel; + late EmergencyServicesViewModel emergencyServicesViewModel; + late TodoSectionViewModel todoSectionViewModel; + @override void initState() { super.initState(); @@ -33,6 +43,12 @@ class _AppLanguageChangeState extends State { @override Widget build(BuildContext context) { selectedValue ??= context.locale.languageCode; + + myAppointmentsViewModel = Provider.of(context, listen: false); + immediateLiveCareViewModel = Provider.of(context, listen: false); + emergencyServicesViewModel = Provider.of(context, listen: false); + todoSectionViewModel = Provider.of(context, listen: false); + return Column( spacing: 24.h, children: [ @@ -41,16 +57,28 @@ class _AppLanguageChangeState extends State { decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), child: Column( children: [ - languageItem("English".needTranslation, "en"), + languageItem("English", "en"), 1.divider, - languageItem("العربية".needTranslation, "ar"), + languageItem("العربية", "ar"), ], ), ), CustomButton( - text: LocaleKeys.save.tr(), + text: LocaleKeys.save.tr(context: context), onPressed: () { context.setLocale(selectedValue == 'en' ? Locale('en', 'US') : Locale('ar', 'SA')).then((val) { + //Reload My Appointments Data + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); + + //Reload Immediate LiveCare Data + immediateLiveCareViewModel.initImmediateLiveCare(); + immediateLiveCareViewModel.getPatientLiveCareHistory(); + + //Reload Emergency Services Data + emergencyServicesViewModel.checkPatientERAdvanceBalance(); + Navigator.pop(context); }); }), diff --git a/lib/widgets/appbar/collapsing_list_view.dart b/lib/widgets/appbar/collapsing_list_view.dart index 0580776c..25221962 100644 --- a/lib/widgets/appbar/collapsing_list_view.dart +++ b/lib/widgets/appbar/collapsing_list_view.dart @@ -1,4 +1,4 @@ - +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -8,6 +8,7 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import '../../core/dependencies.dart'; @@ -18,10 +19,12 @@ class CollapsingListView extends StatelessWidget { VoidCallback? search; VoidCallback? report; VoidCallback? logout; + VoidCallback? aiOverview; VoidCallback? history; VoidCallback? instructions; VoidCallback? requests; VoidCallback? sendEmail; + VoidCallback? doctorResponse; Widget? bottomChild; Widget? trailing; bool isClose; @@ -38,10 +41,12 @@ class CollapsingListView extends StatelessWidget { this.bottomChild, this.report, this.logout, + this.aiOverview, this.history, this.instructions, this.requests, this.sendEmail, + this.doctorResponse, this.isLeading = true, this.trailing, this.leadingCallback, @@ -62,60 +67,67 @@ class CollapsingListView extends StatelessWidget { controller: _controller, slivers: [ SliverAppBar( - automaticallyImplyLeading: isLeading, - pinned: true, - // toolbarHeight: isLeading ? 24.h : kToolbarHeight, - leadingWidth: isLeading ? null : double.infinity, - systemOverlayStyle: SystemUiOverlayStyle(statusBarBrightness: Brightness.light), - surfaceTintColor: Colors.transparent, - backgroundColor: AppColors.bgScaffoldColor, - bottom: isLeading - ? ScrollAnimatedTitle( - title: title, - showBack: true, - controller: _controller, - search: search, - report: report, - logout: logout, - history: history, - instructions: instructions, - requests: requests, - sendEmail: sendEmail, - bottomChild: bottomChild, - trailing: trailing, - ) - : null, - leading: isLeading - ? Transform.flip( + automaticallyImplyLeading: isLeading, + pinned: true, + toolbarHeight: 40.h, + leadingWidth: isLeading ? 40.w + 32.w : double.infinity, + systemOverlayStyle: SystemUiOverlayStyle(statusBarBrightness: Brightness.light), + surfaceTintColor: Colors.transparent, + backgroundColor: AppColors.bgScaffoldColor, + bottom: + // isLeading + // ? + ScrollAnimatedTitle( + title: title, + showBack: true, + controller: _controller, + search: search, + report: report, + logout: logout, + history: history, + instructions: instructions, + requests: requests, + sendEmail: sendEmail, + doctorResponse: doctorResponse, + bottomChild: bottomChild, + trailing: trailing, + aiOverview: aiOverview, + ), + // : null, + leading: Visibility( + visible: isLeading, + child: Container( + height: 32.h, + width: 32.w, + alignment: appState.isArabic() ? Alignment.centerRight : Alignment.centerLeft, + margin: EdgeInsets.fromLTRB(24.w, 0, 24.w, 0), + child: Transform.flip( flipX: appState.isArabic(), - child: IconButton( - icon: Utils.buildSvgWithAssets(icon: isClose ? AppAssets.closeBottomNav : AppAssets.forward_top_nav_icon, width: 24.h, height: 24.h), - padding: EdgeInsets.only(left: 12), - onPressed: () { - if (leadingCallback != null) { - leadingCallback!(); - } else { - context.pop(); - } - }, - highlightColor: Colors.transparent, - ), - ) - : ScrollAnimatedTitle( - title: title, - showBack: false, - controller: _controller, - search: search, - report: report, - logout: logout, - history: history, - instructions: instructions, - requests: requests, - sendEmail: sendEmail, - bottomChild: bottomChild, - trailing: trailing, + child: Utils.buildSvgWithAssets(icon: isClose ? AppAssets.close_bottom_nav_trans : AppAssets.arrow_back_new, width: 24.h, height: 24.h), ), - ), + ).onPress(() { + if (leadingCallback != null) { + leadingCallback!(); + } else { + context.pop(); + } + }), + ) + // : ScrollAnimatedTitle( + // title: title, + // showBack: false, + // controller: _controller, + // search: search, + // report: report, + // logout: logout, + // history: history, + // instructions: instructions, + // requests: requests, + // sendEmail: sendEmail, + // bottomChild: bottomChild, + // trailing: trailing, + // ), + ), SliverList( delegate: SliverChildBuilderDelegate( (context, index) => child, @@ -182,10 +194,12 @@ class ScrollAnimatedTitle extends StatefulWidget implements PreferredSizeWidget VoidCallback? search; VoidCallback? report; VoidCallback? logout; + VoidCallback? aiOverview; VoidCallback? history; VoidCallback? instructions; VoidCallback? requests; VoidCallback? sendEmail; + VoidCallback? doctorResponse; Widget? bottomChild; Widget? trailing; @@ -197,16 +211,18 @@ class ScrollAnimatedTitle extends StatefulWidget implements PreferredSizeWidget this.search, this.report, this.logout, + this.aiOverview, this.history, this.instructions, this.requests, this.sendEmail, + this.doctorResponse, this.bottomChild, this.trailing, }); @override - Size get preferredSize => const Size.fromHeight(50); + Size get preferredSize => const Size.fromHeight(56.0); @override State createState() => _ScrollAnimatedTitleState(); @@ -250,11 +266,12 @@ class _ScrollAnimatedTitleState extends State { @override Widget build(BuildContext context) { - final isRtl = Directionality.of(context) == TextDirection.rtl; + final isRtl = Directionality.of(context) == TextDirection.RTL; return Container( - height: (widget.preferredSize.height - _fontSize / 2).h, + // height: (widget.preferredSize.height - _fontSize / 2).h, + height: 56.h, alignment: isRtl ? (widget.showBack ? Alignment.topRight : Alignment.centerRight) : (widget.showBack ? Alignment.topLeft : Alignment.centerLeft), - padding: EdgeInsets.fromLTRB(24, 0, 24, 0), + padding: EdgeInsets.fromLTRB(24.w, 0, 24.w, 0), child: Row( spacing: 4.h, children: [ @@ -263,19 +280,21 @@ class _ScrollAnimatedTitleState extends State { maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( - fontSize: _fontSize, + fontSize: _fontSize.f, fontWeight: FontWeight.bold, - letterSpacing: -1.0, + letterSpacing: -0.5, ), ).expanded, ...[ - if (widget.logout != null) actionButton(context, t, title: "Logout".needTranslation, icon: AppAssets.logout).onPress(widget.logout!), - if (widget.report != null) actionButton(context, t, title: "Feedback".needTranslation, icon: AppAssets.report_icon).onPress(widget.report!), - if (widget.history != null) actionButton(context, t, title: "History".needTranslation, icon: AppAssets.insurance_history_icon).onPress(widget.history!), - if (widget.instructions != null) actionButton(context, t, title: "Instructions".needTranslation, icon: AppAssets.requests).onPress(widget.instructions!), - if (widget.requests != null) actionButton(context, t, title: "Requests".needTranslation, icon: AppAssets.insurance_history_icon).onPress(widget.requests!), - if (widget.sendEmail != null) actionButton(context, t, title: "Send Email".needTranslation, icon: AppAssets.email).onPress(widget.sendEmail!), + if (widget.logout != null) actionButton(context, t, title: LocaleKeys.logout.tr(context: context), icon: AppAssets.logout).onPress(widget.logout!), + if (widget.report != null) actionButton(context, t, title: LocaleKeys.feedback.tr(context: context), icon: AppAssets.report_icon).onPress(widget.report!), + if (widget.history != null) actionButton(context, t, title: LocaleKeys.history.tr(context: context), icon: AppAssets.insurance_history_icon).onPress(widget.history!), + if (widget.instructions != null) actionButton(context, t, title: LocaleKeys.instructions.tr(context: context), icon: AppAssets.requests).onPress(widget.instructions!), + if (widget.requests != null) actionButton(context, t, title: LocaleKeys.requests.tr(context: context), icon: AppAssets.insurance_history_icon).onPress(widget.requests!), + if (widget.sendEmail != null) actionButton(context, t, title: LocaleKeys.sendEmail.tr(context: context), icon: AppAssets.email).onPress(widget.sendEmail!), + if (widget.doctorResponse != null) actionButton(context, t, title: LocaleKeys.doctorResponses.tr(context: context), icon: AppAssets.doctorResponseIcon).onPress(widget.doctorResponse!), if (widget.search != null) Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(widget.search!), + if (widget.aiOverview != null) actionButton(context, t, title: LocaleKeys.aiOverView.tr(context: context), icon: AppAssets.aiOverView, isAiButton: true).onPress(widget.aiOverview!), if (widget.trailing != null) widget.trailing!, ] ], @@ -283,40 +302,77 @@ class _ScrollAnimatedTitleState extends State { ); } - Widget actionButton(BuildContext context, double t, {required String title, required String icon}) { - return AnimatedSize( - duration: Duration(milliseconds: 150), - child: Container( - height: 36.h + (4.h * t), - padding: EdgeInsets.all(8.w), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.secondaryLightRedColor, - borderRadius: 10.r, - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - spacing: 8.h, - children: [ - Utils.buildSvgWithAssets(icon: icon, iconColor: AppColors.primaryRedColor), - if (t >= .5) - Text( - title, - style: context.dynamicTextStyle( - color: AppColors.primaryRedColor, - letterSpacing: -0.4, - fontSize: (12 - (2 * (1 - t))).f, - fontWeight: FontWeight.lerp( - FontWeight.w400, - FontWeight.w500, - t, - )!, + Widget actionButton(BuildContext context, double t, {required String title, required String icon, bool isAiButton = false}) { + return isAiButton + ? AnimatedSize( + duration: Duration(milliseconds: 150), + child: Center( + child: Container( + height: 40.h, + padding: EdgeInsets.all(8.w), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 8.r, side: BorderSide(width: 1, color: AppColors.borderGrayColor)), + child: ShaderMask( + blendMode: BlendMode.srcIn, + shaderCallback: (bounds) => AppColors.aiLinearGradient.createShader(bounds), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + spacing: 8.h, + children: [ + Utils.buildSvgWithAssets(icon: icon, iconColor: AppColors.primaryRedColor, height: 24.h, width: 24.w), + Text( + title, + style: context.dynamicTextStyle( + color: AppColors.primaryRedColor, + letterSpacing: -0.4, + fontSize: (14 - (2 * (1 - t))).f, + fontWeight: FontWeight.lerp( + FontWeight.w400, + FontWeight.w500, + t, + )!, + ), + ), + ], + ), ), ), - ], - ), - ), - ); + ), + ) + : AnimatedSize( + duration: Duration(milliseconds: 150), + child: Container( + height: 40.h, + padding: EdgeInsets.all(8.w), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.secondaryLightRedColor, + borderRadius: 10.r, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + spacing: 8.h, + children: [ + Utils.buildSvgWithAssets(icon: icon, iconColor: AppColors.primaryRedColor, height: 24.h, width: 24.w), + if (t >= .5) + Text( + title, + style: context.dynamicTextStyle( + color: AppColors.primaryRedColor, + letterSpacing: -0.4, + fontSize: (12 - (2 * (1 - t))).f, + fontWeight: FontWeight.lerp( + FontWeight.w400, + FontWeight.w500, + t, + )!, + ), + ), + ], + ), + ), + ); } } diff --git a/lib/widgets/appbar/collapsing_toolbar.dart b/lib/widgets/appbar/collapsing_toolbar.dart index 87cf15a4..8bf1e4b4 100644 --- a/lib/widgets/appbar/collapsing_toolbar.dart +++ b/lib/widgets/appbar/collapsing_toolbar.dart @@ -1,5 +1,6 @@ import 'dart:ui'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -8,6 +9,7 @@ import 'package:hmg_patient_app_new/core/app_state.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/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import '../../core/dependencies.dart'; @@ -140,11 +142,11 @@ class _CollapsingToolbarState extends State { color: AppColors.blackColor, letterSpacing: -0.5), ).expanded, - if (widget.logout != null) actionButton(context, t, title: "Logout".needTranslation, icon: AppAssets.logout).onPress(widget.logout!), - if (widget.report != null) actionButton(context, t, title: "Report".needTranslation, icon: AppAssets.report_icon).onPress(widget.report!), - if (widget.history != null) actionButton(context, t, title: "History".needTranslation, icon: AppAssets.insurance_history_icon).onPress(widget.history!), - if (widget.instructions != null) actionButton(context, t, title: "Instructions".needTranslation, icon: AppAssets.requests).onPress(widget.instructions!), - if (widget.requests != null) actionButton(context, t, title: "Requests".needTranslation, icon: AppAssets.insurance_history_icon).onPress(widget.requests!), + if (widget.logout != null) actionButton(context, t, title: LocaleKeys.logout.tr(context: context), icon: AppAssets.logout).onPress(widget.logout!), + if (widget.report != null) actionButton(context, t, title: LocaleKeys.report.tr(context: context), icon: AppAssets.report_icon).onPress(widget.report!), + if (widget.history != null) actionButton(context, t, title: LocaleKeys.history.tr(context: context), icon: AppAssets.insurance_history_icon).onPress(widget.history!), + if (widget.instructions != null) actionButton(context, t, title: LocaleKeys.instructions.tr(context: context), icon: AppAssets.requests).onPress(widget.instructions!), + if (widget.requests != null) actionButton(context, t, title: LocaleKeys.requests.tr(context: context), icon: AppAssets.insurance_history_icon).onPress(widget.requests!), if (widget.search != null) Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(widget.search!).paddingOnly(right: 24), if (widget.trailing != null) widget.trailing!, ], diff --git a/lib/widgets/bottom_navigation/bottom_navigation.dart b/lib/widgets/bottom_navigation/bottom_navigation.dart index 2da1d443..bbe8d81f 100644 --- a/lib/widgets/bottom_navigation/bottom_navigation.dart +++ b/lib/widgets/bottom_navigation/bottom_navigation.dart @@ -6,6 +6,7 @@ import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -19,26 +20,28 @@ class BottomNavigation extends StatelessWidget { Widget build(BuildContext context) { AppState appState = getIt.get(); final items = [ - BottomNavItem(icon: AppAssets.homeBottom, label: LocaleKeys.home.tr(context: context)), + BottomNavItem(icon: AppAssets.homeBottom, fillIcon: AppAssets.homeBottomFill, label: LocaleKeys.home.tr(context: context)), appState.isAuthenticated - ? BottomNavItem(icon: AppAssets.myFilesBottom, label: LocaleKeys.medicalFile.tr(context: context)) - : BottomNavItem(icon: AppAssets.feedback, label: LocaleKeys.feedback.tr()), + ? BottomNavItem(icon: AppAssets.myFilesBottom, fillIcon: AppAssets.myFilesBottomFill, label: LocaleKeys.medicalFile.tr(context: context)) + : BottomNavItem(icon: AppAssets.feedback, fillIcon: AppAssets.feedbackFill, label: LocaleKeys.feedback.tr()), BottomNavItem( icon: AppAssets.bookAppoBottom, + fillIcon: AppAssets.bookAppoBottom, label: LocaleKeys.appointment.tr(context: context), - iconSize: 27, + iconSize: 32, isSpecial: true, ), appState.isAuthenticated // ? BottomNavItem(icon: AppAssets.toDoBottom, label: LocaleKeys.todoList.tr(context: context)) - ? BottomNavItem(icon: AppAssets.symptomCheckerBottomIcon, label: "Symptoms") - : BottomNavItem(icon: AppAssets.news, label: LocaleKeys.news.tr()), - BottomNavItem(icon: AppAssets.servicesBottom, label: LocaleKeys.services2.tr(context: context)), + ? BottomNavItem(icon: AppAssets.symptomCheckerBottomIcon, fillIcon: AppAssets.symptomCheckerBottomFillIcon, label: "Symptoms") + : BottomNavItem(icon: AppAssets.news, fillIcon: AppAssets.newsFill, label: LocaleKeys.news.tr()), + BottomNavItem(icon: AppAssets.servicesBottom, fillIcon: AppAssets.servicesBottomFill, label: LocaleKeys.services2.tr(context: context)), ]; return Container( decoration: _containerDecoration, padding: _containerPadding, + // height: 84.h, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: List.generate( @@ -55,28 +58,33 @@ class BottomNavigation extends StatelessWidget { return GestureDetector( onTap: () => onTap(index), behavior: HitTestBehavior.opaque, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Center( - child: Utils.buildSvgWithAssets( - icon: item.icon, + child: SizedBox( + height: 56.h, + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Utils.buildSvgWithAssets( + icon: isSelected ? item.fillIcon : item.icon, height: item.iconSize.h, width: item.iconSize.w, // iconColor: isSelected ? Colors.black87 : Colors.black87, ), - ), - const SizedBox(height: 10), - item.label.toText11(weight: FontWeight.w500), - SizedBox(height: item.isSpecial ? 5 : 0) - ], + // const SizedBox(height: 10), + SizedBox(height: item.isSpecial ? 0 : 8.h), + item.label.toText11(weight: FontWeight.w500), + // SizedBox(height: item.isSpecial ? 5 : 0) + ], + ), ), - ); + ).expanded; } } class BottomNavItem { final String icon; + final String fillIcon; final String label; final double iconSize; final bool isSpecial; @@ -84,6 +92,7 @@ class BottomNavItem { const BottomNavItem({ required this.icon, + required this.fillIcon, required this.label, this.iconSize = 24, this.isSpecial = false, diff --git a/lib/widgets/bottomsheet/exception_bottom_sheet.dart b/lib/widgets/bottomsheet/exception_bottom_sheet.dart index fe232648..16531bdb 100644 --- a/lib/widgets/bottomsheet/exception_bottom_sheet.dart +++ b/lib/widgets/bottomsheet/exception_bottom_sheet.dart @@ -10,7 +10,7 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; -class ExceptionBottomSheet extends StatelessWidget { +class ExceptionBottomSheet extends StatefulWidget { final String? title; final String message; final bool showOKButton; @@ -28,6 +28,11 @@ class ExceptionBottomSheet extends StatelessWidget { this.onCancelPressed, }); + @override + State createState() => _ExceptionBottomSheetState(); +} + +class _ExceptionBottomSheetState extends State { @override Widget build(BuildContext context) { return SafeArea( @@ -53,7 +58,7 @@ class ExceptionBottomSheet extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - (title ?? LocaleKeys.notice.tr()).toText28(), + (widget.title ?? LocaleKeys.notice.tr()).toText28(), InkWell( onTap: () { Navigator.of(context).pop(); @@ -66,18 +71,18 @@ class ExceptionBottomSheet extends StatelessWidget { ], ), SizedBox(height: 10.h), - (message).toText16(isBold: false, color: AppColors.textColor), + (widget.message).toText16(isBold: false, color: AppColors.textColor), SizedBox(height: 10.h), SizedBox(height: 24.h), - if (showOKButton && showCancel) + if (widget.showOKButton && widget.showCancel) Row( children: [ Expanded( child: CustomButton( height: 56.h, text: LocaleKeys.cancel.tr(), - onPressed: onCancelPressed != null - ? onCancelPressed! + onPressed: widget.onCancelPressed != null + ? widget.onCancelPressed! : () { Navigator.of(context).pop(); }, @@ -92,8 +97,8 @@ class ExceptionBottomSheet extends StatelessWidget { Expanded( child: CustomButton( height: 56.h, - text: showCancel ? LocaleKeys.confirm.tr() : LocaleKeys.ok.tr(), - onPressed: onOkPressed, + text: widget.showCancel ? LocaleKeys.confirm.tr() : LocaleKeys.ok.tr(), + onPressed: widget.onOkPressed, backgroundColor: AppColors.bgGreenColor, borderColor: AppColors.bgGreenColor, textColor: Colors.white, @@ -102,13 +107,13 @@ class ExceptionBottomSheet extends StatelessWidget { ), ], ), - if (showOKButton && !showCancel) + if (widget.showOKButton && !widget.showCancel) Padding( padding: EdgeInsets.only(bottom: 10.h), child: CustomButton( height: 56.h, text: LocaleKeys.ok.tr(), - onPressed: onOkPressed, + onPressed: widget.onOkPressed, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedBorderColor, textColor: Colors.white, diff --git a/lib/widgets/bottomsheet/generic_bottom_sheet.dart b/lib/widgets/bottomsheet/generic_bottom_sheet.dart index b6be36c5..fd79b7c1 100644 --- a/lib/widgets/bottomsheet/generic_bottom_sheet.dart +++ b/lib/widgets/bottomsheet/generic_bottom_sheet.dart @@ -158,9 +158,10 @@ class GenericBottomSheetState extends State { prefix: widget.isForEmail ? null : widget.countryCode, isBorderAllowed: false, isAllowLeadingIcon: true, - fontSize: 13, + fontSize: 18.f, isCountryDropDown: widget.isEnableCountryDropdown, leadingIcon: widget.isForEmail ? AppAssets.email : AppAssets.smart_phone, + fontFamily: "Poppins", ) : SizedBox(), ], diff --git a/lib/widgets/buttons/custom_button.dart b/lib/widgets/buttons/custom_button.dart index 08d281f7..8500ce8e 100644 --- a/lib/widgets/buttons/custom_button.dart +++ b/lib/widgets/buttons/custom_button.dart @@ -75,25 +75,22 @@ class CustomButton extends StatelessWidget { children: [ if (icon != null) Padding( - padding: text.isNotEmpty ? EdgeInsets.only(right: 8.w, left: 8.w) : EdgeInsets.zero, + padding: text.isNotEmpty ? EdgeInsets.only(right: 4.w, left: 4.w) : EdgeInsets.zero, child: Utils.buildSvgWithAssets(icon: icon!, iconColor: iconColor, isDisabled: isDisabled, width: iconS, height: iconS), ), - Visibility( - visible: text.isNotEmpty, - child: Padding( - padding: EdgeInsets.only(top: 0), - child: Text( - text, - overflow: textOverflow, - style: context.dynamicTextStyle( - fontSize: fontS, - color: isDisabled ? AppColors.greyTextColor : textColor, - letterSpacing: 0, - fontWeight: fontWeight, - ), + if (text.isNotEmpty) + Text( + text, + overflow: textOverflow ?? TextOverflow.ellipsis, + maxLines: 1, + textAlign: TextAlign.center, + style: context.dynamicTextStyle( + fontSize: fontS, + color: isDisabled ? AppColors.greyTextColor : textColor, + letterSpacing: 0, + fontWeight: fontWeight, ), ), - ), ], ), )); diff --git a/lib/widgets/common_bottom_sheet.dart b/lib/widgets/common_bottom_sheet.dart index 6ff5cc59..fb51f28e 100644 --- a/lib/widgets/common_bottom_sheet.dart +++ b/lib/widgets/common_bottom_sheet.dart @@ -1,5 +1,6 @@ import 'dart:io' show Platform; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; @@ -7,14 +8,14 @@ import 'package:hmg_patient_app_new/core/utils/calender_utils_new.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/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_reminder_view.dart'; import 'package:hmg_patient_app_new/services/permission_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:permission_handler/permission_handler.dart'; - -class BottomSheetUtils{ - showReminderBottomSheet(BuildContext context, DateTime dateTime, String doctorName, String eventId, String appoDateFormatted, String appoTimeFormatted, +class BottomSheetUtils { + showReminderBottomSheet(BuildContext context, DateTime dateTime, String doctorName, String eventId, String appoDateFormatted, String appoTimeFormatted, {required Function() onSuccess, String? title, String? description, Function(int)? onMultiDateSuccess, bool isMultiAllowed = false}) async { if (Platform.isAndroid) { if (await PermissionService.isCalendarPermissionEnabled()) { @@ -40,13 +41,13 @@ class BottomSheetUtils{ Future _showReminderBottomSheet(BuildContext providedContext, DateTime dateTime, String doctorName, String eventId, String appoDateFormatted, String appoTimeFormatted, {required Function onSuccess, String? title, String? description, Function(int)? onMultiDateSuccess, bool? isMultiAllowed}) async { - showCommonBottomSheetWithoutHeight(providedContext, title: "Set the timer of reminder".needTranslation, child: PrescriptionReminderView( + showCommonBottomSheetWithoutHeight(providedContext, title: LocaleKeys.setTimerOfReminder.tr(context: providedContext), child: PrescriptionReminderView( setReminder: (int value) async { if (!isMultiAllowed!) { if (onMultiDateSuccess == null) { - CalenderUtilsNew calendarUtils = CalenderUtilsNew.instance; + CalenderUtilsNew calendarUtils = CalenderUtilsNew.instance; await calendarUtils.createOrUpdateEvent( - title: title ?? "You have appointment with Dr. ".needTranslation + doctorName, + title: title ?? LocaleKeys.youHaveAppointmentWithDr.tr(context: providedContext) + doctorName, description: description ?? "At " + appoDateFormatted + " " + appoTimeFormatted, scheduleDateTime: dateTime, eventId: eventId, @@ -60,6 +61,7 @@ class BottomSheetUtils{ ), callBackFunc: () {}, isFullScreen: false); } } + void showCommonBottomSheet(BuildContext context, {required Widget child, Function(String?)? callBackFunc, @@ -108,7 +110,7 @@ class ButtonSheetContent extends StatelessWidget { @override Widget build(BuildContext context) { return Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( height: 20.h, @@ -127,9 +129,7 @@ class ButtonSheetContent extends StatelessWidget { // Close button isCloseButtonVisible && isFullScreen - ? Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ + ? Row(mainAxisAlignment: MainAxisAlignment.end, children: [ Padding( padding: EdgeInsets.symmetric( horizontal: 16, @@ -168,7 +168,7 @@ void showCommonBottomSheetWithoutHeight( Widget? titleWidget, bool useSafeArea = false, bool hasBottomPadding = true, - EdgeInsets? padding, + EdgeInsets? padding, Color backgroundColor = AppColors.bottomSheetBgColor, VoidCallback? onCloseClicked, }) { @@ -177,10 +177,8 @@ void showCommonBottomSheetWithoutHeight( duration: Duration(milliseconds: 500), reverseDuration: Duration(milliseconds: 300), ), - constraints: BoxConstraints( - - maxWidth: MediaQuery.sizeOf(context).width//MediaQuery.of(context).size.width, // Full width - ), + constraints: BoxConstraints(maxWidth: MediaQuery.sizeOf(context).width //MediaQuery.of(context).size.width, // Full width + ), context: context, isScrollControlled: true, showDragHandle: false, @@ -201,46 +199,48 @@ void showCommonBottomSheetWithoutHeight( physics: ClampingScrollPhysics(), child: isCloseButtonVisible ? Container( - padding: padding ?? const EdgeInsets.only( - left: 24, - top: 24, - right: 24, - bottom: 12, - ), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.bottomSheetBgColor, - borderRadius: 24.h, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Padding( - padding: padding != null? EdgeInsets.symmetric(horizontal: 24.w): EdgeInsets.zero, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + padding: padding ?? + const EdgeInsets.only( + left: 24, + top: 24, + right: 24, + bottom: 12, + ), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.bottomSheetBgColor, + borderRadius: 24.h, + ), + child: Column( + mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: [ - titleWidget ?? - Expanded( - child: title.toText20(weight: FontWeight.w600), - ), - if (isCloseButtonVisible) ...[ - Utils.buildSvgWithAssets( - icon: AppAssets.close_bottom_sheet_icon, - iconColor: Color(0xff2B353E), - ).onPress(() { - onCloseClicked?.call(); - Navigator.of(context).pop(); - }),], + Padding( + padding: padding != null ? EdgeInsets.symmetric(horizontal: 24.w) : EdgeInsets.zero, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + titleWidget ?? + Expanded( + child: title.toText20(weight: FontWeight.w600), + ), + if (isCloseButtonVisible) ...[ + Utils.buildSvgWithAssets( + icon: AppAssets.close_bottom_sheet_icon, + iconColor: Color(0xff2B353E), + ).onPress(() { + onCloseClicked?.call(); + Navigator.of(context).pop(); + }), + ], + ], + ), + ), + isCloseButtonVisible ? SizedBox(height: 16.h) : SizedBox.shrink(), + child, ], ), - ), - isCloseButtonVisible ? SizedBox(height: 16.h) : SizedBox.shrink(), - child, - ], - ), - ) + ) : child, ), ), diff --git a/lib/widgets/countdown_timer.dart b/lib/widgets/countdown_timer.dart index 165a8330..1722ebfa 100644 --- a/lib/widgets/countdown_timer.dart +++ b/lib/widgets/countdown_timer.dart @@ -1,6 +1,8 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; Widget buildTime(Duration duration, {bool isHomePage = false}) { String twoDigits(int n) => n.toString().padLeft(2, '0'); @@ -11,9 +13,9 @@ Widget buildTime(Duration duration, {bool isHomePage = false}) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - buildTimeColumn(hours, "Hours".needTranslation), - buildTimeColumn(minutes, "Mins".needTranslation), - buildTimeColumn(seconds, "Secs".needTranslation, isLast: true), + buildTimeColumn(hours, LocaleKeys.hours.tr()), + buildTimeColumn(minutes, LocaleKeys.mins.tr()), + buildTimeColumn(seconds, LocaleKeys.secs.tr(), isLast: true), ], ); } diff --git a/lib/widgets/date_range_selector/date_range_calender.dart b/lib/widgets/date_range_selector/date_range_calender.dart index debc0692..89725f32 100644 --- a/lib/widgets/date_range_selector/date_range_calender.dart +++ b/lib/widgets/date_range_selector/date_range_calender.dart @@ -71,7 +71,7 @@ class _DateRangeSelectorState extends State { children: [ fromDateComponent(), Text( - LocaleKeys.to.tr(), + LocaleKeys.to.tr(context: context), style: TextStyle( color: AppColors.calenderTextColor, fontSize: 14.h, @@ -168,7 +168,7 @@ class _DateRangeSelectorState extends State { children: [ Expanded( child: CustomButton( - text: LocaleKeys.cancel.tr(), + text: LocaleKeys.cancel.tr(context: context), onPressed: () { _calendarController.selectedRange = null; _calendarController.selectedDate = null; @@ -192,7 +192,7 @@ class _DateRangeSelectorState extends State { ), Expanded( child: CustomButton( - text: LocaleKeys.search.tr(), + text: LocaleKeys.search.tr(context: context), onPressed: () { Navigator.of(context).pop(); widget.onRangeSelected(model.fromDate, model.toDate); @@ -216,7 +216,7 @@ class _DateRangeSelectorState extends State { fromDateComponent() { return Consumer( builder: (_, model, __) { - return displayDate(LocaleKeys.startDate.tr(), + return displayDate(LocaleKeys.startDate.tr(context: context), model.getDateString(model.fromDate), model.fromDate == null); }, ); @@ -225,7 +225,7 @@ class _DateRangeSelectorState extends State { toDateComponent() { return Consumer( builder: (_, model, __) { - return displayDate(LocaleKeys.endDate.tr(), + return displayDate(LocaleKeys.endDate.tr(context: context), model.getDateString(model.toDate), model.toDate == null); }, ); @@ -270,7 +270,7 @@ class _DateRangeSelectorState extends State { spacing: 8.h, children: [ AppCustomChipWidget( - labelText: "This Week".needTranslation, + labelText: LocaleKeys.thisWeek.tr(context: context), backgroundColor: model.currentlySelectedRange == Range.WEEKLY ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.whiteColor, @@ -288,7 +288,7 @@ class _DateRangeSelectorState extends State { model.calculateDatesFromRange(); }), AppCustomChipWidget( - labelText: "Last Month".needTranslation, + labelText: LocaleKeys.lastMonth.tr(context: context), backgroundColor: model.currentlySelectedRange == Range.LAST_MONTH ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.whiteColor, @@ -306,7 +306,7 @@ class _DateRangeSelectorState extends State { model.calculateDatesFromRange(); }), AppCustomChipWidget( - labelText: "Last 6 Months".needTranslation, + labelText: LocaleKeys.lastSixMonths.tr(context: context), backgroundColor: model.currentlySelectedRange == Range.LAST_6MONTH ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.whiteColor, diff --git a/lib/widgets/dropdown/country_dropdown_widget.dart b/lib/widgets/dropdown/country_dropdown_widget.dart index b6191edd..1cd387f9 100644 --- a/lib/widgets/dropdown/country_dropdown_widget.dart +++ b/lib/widgets/dropdown/country_dropdown_widget.dart @@ -100,7 +100,7 @@ class CustomCountryDropdownState extends State { children: [ Text( selectedCountry!.countryCode, - style: TextStyle(fontSize: 12.f, fontWeight: FontWeight.w600, letterSpacing: -0.4, height: 1.5), + style: TextStyle(fontSize: 12.f, fontWeight: FontWeight.w600, letterSpacing: -0.4, height: 1.5, fontFamily: "Poppins"), ), SizedBox(width: 4.h), if (widget.isEnableTextField) @@ -111,7 +111,7 @@ class CustomCountryDropdownState extends State { alignment: Alignment.centerLeft, child: TextField( focusNode: textFocusNode, - style: TextStyle(fontSize: 12.f, fontWeight: FontWeight.w600, letterSpacing: -0.4, height: 1.5), + style: TextStyle(fontSize: 12.f, fontWeight: FontWeight.w600, letterSpacing: -0.4, height: 1.5, fontFamily: "Poppins"), decoration: InputDecoration(hintText: "", isDense: true, border: InputBorder.none, contentPadding: EdgeInsets.zero), keyboardType: TextInputType.phone, onChanged: widget.onPhoneNumberChanged, diff --git a/lib/widgets/expandable_list_widget.dart b/lib/widgets/expandable_list_widget.dart index b9482993..db4e3980 100644 --- a/lib/widgets/expandable_list_widget.dart +++ b/lib/widgets/expandable_list_widget.dart @@ -282,9 +282,9 @@ class ExpandableListTheme { dividerColor: dividerColor ?? Colors.grey.shade300, defaultTrailingIcon: defaultTrailingIcon ?? Icon(Icons.keyboard_arrow_down, color: Colors.black, size: 24), - itemPadding: itemPadding ?? const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), - contentPadding: contentPadding ?? const EdgeInsets.only(left: 16.0, right: 16.0, bottom: 16.0), - leadingSpacing: leadingSpacing ?? 12.0, + itemPadding: itemPadding ?? EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), + contentPadding: contentPadding ?? EdgeInsets.only(left: 16.w, right: 16.w, bottom: 16.h), + leadingSpacing: leadingSpacing ?? 12.w, ); } } diff --git a/lib/widgets/family_files/family_file_add_widget.dart b/lib/widgets/family_files/family_file_add_widget.dart index 4840ba77..15751e93 100644 --- a/lib/widgets/family_files/family_file_add_widget.dart +++ b/lib/widgets/family_files/family_file_add_widget.dart @@ -14,24 +14,29 @@ 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/dropdown/country_dropdown_widget.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:provider/provider.dart'; -class FamilyFileAddWidget extends StatelessWidget { +class FamilyFileAddWidget extends StatefulWidget { final Function()? onVerificationPress; final String message; const FamilyFileAddWidget(this.onVerificationPress, this.message, {super.key}); + @override + State createState() => _FamilyFileAddWidgetState(); +} + +class _FamilyFileAddWidgetState extends State { @override Widget build(BuildContext context) { AuthenticationViewModel authVm = getIt.get(); - MedicalFileViewModel? medicalVM = getIt.get(); // TODO: implement build return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - message.toText16(color: AppColors.textColor, weight: FontWeight.w500), + widget.message.toText16(color: AppColors.textColor, weight: FontWeight.w500), SizedBox(height: 20.h), Container( decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(24)), @@ -54,30 +59,37 @@ class FamilyFileAddWidget extends StatelessWidget { isAllowLeadingIcon: true, autoFocus: true, keyboardType: TextInputType.number, + fontFamily: "Poppins", padding: EdgeInsets.symmetric(vertical: 8.h), leadingIcon: AppAssets.student_card, ).paddingOnly(top: 8.h, bottom: 8.h), Divider(height: 1.h, color: AppColors.spacerLineColor), - TextInputWidget( - labelText: LocaleKeys.phoneNumber.tr(), - hintText: "", - controller: authVm.phoneNumberController, - isEnable: true, - prefix: authVm.selectedCountrySignup.countryCode, - isAllowRadius: true, - isBorderAllowed: false, - isAllowLeadingIcon: true, - autoFocus: true, - keyboardType: TextInputType.number, - padding: EdgeInsets.symmetric(vertical: 8.h), - leadingIcon: AppAssets.smart_phone, - ).paddingOnly(top: 8.h, bottom: 4.h), + Selector( + selector: (_, model) => model.selectedCountrySignup.countryCode, + builder: (context, countryCode, child) { + return TextInputWidget( + labelText: LocaleKeys.phoneNumber.tr(), + hintText: "", + controller: authVm.phoneNumberController, + isEnable: true, + prefix: countryCode, + isAllowRadius: true, + isBorderAllowed: false, + isAllowLeadingIcon: true, + autoFocus: true, + keyboardType: TextInputType.number, + fontFamily: "Poppins", + padding: EdgeInsets.symmetric(vertical: 8.h), + leadingIcon: AppAssets.smart_phone, + ).paddingOnly(top: 8.h, bottom: 4.h); + }, + ), ], ), ), SizedBox(height: 20.h), CustomButton( - text: "Verify the member".needTranslation, + text: LocaleKeys.pleaseVerify.tr(context: context), onPressed: () { FocusScope.of(context).unfocus(); if (ValidationUtils.isValidatedIdAndPhoneWithCountryValidation( @@ -89,8 +101,8 @@ class FamilyFileAddWidget extends StatelessWidget { }, )) { // authVm.addFamilyMember(otpTypeEnum: OTPTypeEnum.sms, isExcludedUser: true); - if (onVerificationPress != null) { - onVerificationPress!(); + if (widget.onVerificationPress != null) { + widget.onVerificationPress!(); } } }, diff --git a/lib/widgets/input_widget.dart b/lib/widgets/input_widget.dart index 9f101d47..aae196cf 100644 --- a/lib/widgets/input_widget.dart +++ b/lib/widgets/input_widget.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:hijri_gregorian_calendar/hijri_gregorian_calendar.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; @@ -11,6 +12,7 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/dropdown/country_dropdown_widget.dart'; import 'package:hmg_patient_app_new/widgets/time_picker_widget.dart'; +import 'dart:ui' as ui; class TextInputWidget extends StatelessWidget { final String labelText; @@ -48,6 +50,8 @@ class TextInputWidget extends StatelessWidget { final bool? isHideSwitcher; final bool? isArrowTrailing; + final String? fontFamily; + // final List countryList; // final Function(Country)? onCountryChange; @@ -86,6 +90,7 @@ class TextInputWidget extends StatelessWidget { this.maxLines = 6, this.isHideSwitcher, this.isArrowTrailing, + this.fontFamily, // this.countryList = const [], // this.onCountryChange, }); @@ -125,7 +130,7 @@ class TextInputWidget extends StatelessWidget { children: [ Container( padding: padding, - height: isMultiline ? null : 64.h, + // height: isMultiline ? null : 64.h, alignment: Alignment.center, decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: Colors.white, @@ -153,10 +158,18 @@ class TextInputWidget extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildLabelText(labelColor).paddingOnly(right: (appState.getLanguageCode() == "ar" ? 10 : 0)), + _buildLabelText(labelColor).paddingOnly( + right: (appState.getLanguageCode() == "ar" ? 10 : 0), + left: (appState.getLanguageCode() == "en" ? 10 : 0), + ), Row( children: [ - Expanded(child: _buildTextField(context)), + Expanded( + child: _buildTextField(context).paddingOnly( + right: (appState.getLanguageCode() == "ar" ? 10 : 0), + left: (appState.getLanguageCode() == "en" ? 10 : 0), + ), + ) ], ), ], @@ -191,7 +204,7 @@ class TextInputWidget extends StatelessWidget { return Container( height: 40.h, width: 40.h, - margin: EdgeInsets.only(right: 10.h), + // margin: EdgeInsets.symmetric(horizontal: 10.w), padding: EdgeInsets.all(8.h), decoration: RoundedRectangleBorder().toSmoothCornerDecoration( borderRadius: 12.r, @@ -204,7 +217,7 @@ class TextInputWidget extends StatelessWidget { final AppState appState = getIt.get(); return Container( height: 40.h, - width: 40.h, + width: 40.w, margin: EdgeInsets.zero, padding: EdgeInsets.all(8.h), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 12.r, color: AppColors.whiteColor), @@ -218,11 +231,8 @@ class TextInputWidget extends StatelessWidget { initialDate: DateTime.now(), showCalendarToggle: isHideSwitcher == true ? false : true, fontFamily: appState.getLanguageCode() == "ar" ? "GESSTwo" : "Poppins", - okWidget: - Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.confirm, width: 24.h, height: 24.h)), - cancelWidget: Padding( - padding: EdgeInsets.only(right: 8.h), - child: Utils.buildSvgWithAssets(icon: AppAssets.cancel, iconColor: Colors.white, width: 24.h, height: 24.h)), + okWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.confirm, width: 24.h, height: 24.h)), + cancelWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.cancel, iconColor: Colors.white, width: 24.h, height: 24.h)), onCalendarTypeChanged: (bool value) { isGregorian = value; }); @@ -292,50 +302,103 @@ class TextInputWidget extends StatelessWidget { Widget _buildTextField(BuildContext context) { double fontS = fontSize ?? 14.f; - return TextField( - enabled: isEnable, - scrollPadding: EdgeInsets.zero, - keyboardType: isMultiline ? TextInputType.multiline : keyboardType, - controller: controller, - readOnly: isReadOnly, - textAlignVertical: TextAlignVertical.top, - textAlign: TextAlign.left, - textDirection: TextDirection.ltr, - onChanged: onChange, - focusNode: focusNode ?? _focusNode, - autofocus: autoFocus, - textInputAction: TextInputAction.done, - cursorHeight: isWalletAmountInput! ? 40.h : 20.h, - onTapOutside: (event) { - FocusManager.instance.primaryFocus?.unfocus(); + return Builder( + builder: (context) { + return Localizations.override( + context: context, + locale: const Locale('en', 'US'), // Force English locale for TextField + child: TextField( + hintLocales: const [Locale('en', 'US')], + enabled: isEnable, + scrollPadding: EdgeInsets.zero, + keyboardType: isMultiline ? TextInputType.multiline : keyboardType, + controller: controller, + readOnly: isReadOnly, + textAlignVertical: TextAlignVertical.top, + textAlign: TextAlign.left, + textDirection: TextDirection.ltr, + onChanged: onChange, + focusNode: focusNode ?? _focusNode, + autofocus: autoFocus, + textInputAction: TextInputAction.done, + cursorHeight: isWalletAmountInput! ? 40.h : 20.h, + onTapOutside: (event) { + FocusManager.instance.primaryFocus?.unfocus(); + }, + onSubmitted: onSubmitted, + minLines: isMultiline ? minLines : 1, + maxLines: isMultiline ? maxLines : 1, + style: TextStyle( + fontSize: fontS, + height: isMultiline ? 1.2 : (isWalletAmountInput! ? 1 / 4 : 0), + fontWeight: FontWeight.w500, + color: AppColors.textColor, + letterSpacing: -1, + // fontFamily: keyboardType == TextInputType.number ? getIt.get().isArabic() ? 'GESSTwo' : 'Poppins' : 'Poppins', + fontFamily: fontFamily, + locale: const Locale('en', 'US'), // Force English locale for text style + ), + decoration: InputDecoration( + isDense: true, + hintText: hintText, + hintStyle: TextStyle(fontSize: 14.f, height: 21 / 16, fontWeight: FontWeight.w500, color: hintColor != null ? AppColors.textColor : Color(0xff898A8D), letterSpacing: -0.75), + prefixIconConstraints: BoxConstraints(minWidth: 30.h), + prefixIcon: prefix == null ? null : "+${prefix!}".toText14(letterSpacing: -1, color: AppColors.textColor, weight: FontWeight.w500), + contentPadding: EdgeInsets.zero, + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + ), + ), + ); }, - onSubmitted: onSubmitted, - minLines: isMultiline ? minLines : 1, - maxLines: isMultiline ? maxLines : 1, - style: TextStyle( - fontSize: fontS, - height: isMultiline ? 1.2 : (isWalletAmountInput! ? 1 / 4 : 0), - fontWeight: FontWeight.w500, - color: AppColors.textColor, - letterSpacing: -1, - ), - decoration: InputDecoration( - isDense: true, - hintText: hintText, - hintStyle: TextStyle( - fontSize: 14.f, - height: 21 / 16, - fontWeight: FontWeight.w500, - color: hintColor != null ? AppColors.textColor : Color(0xff898A8D), - letterSpacing: -0.75), - prefixIconConstraints: BoxConstraints(minWidth: 30.h), - prefixIcon: prefix == null ? null : "+${prefix!}".toText14(letterSpacing: -1, color: AppColors.textColor, weight: FontWeight.w500), - contentPadding: EdgeInsets.zero, - border: InputBorder.none, - focusedBorder: InputBorder.none, - enabledBorder: InputBorder.none, - ), ); + + // TextField( + // hintLocales: const [Locale('en', 'US')], + // enabled: isEnable, + // scrollPadding: EdgeInsets.zero, + // keyboardType: isMultiline ? TextInputType.multiline : keyboardType, + // controller: controller, + // readOnly: isReadOnly, + // textAlignVertical: TextAlignVertical.top, + // textAlign: TextAlign.left, + // textDirection: TextDirection.ltr, + // onChanged: onChange, + // focusNode: focusNode ?? _focusNode, + // autofocus: autoFocus, + // textInputAction: TextInputAction.done, + // cursorHeight: isWalletAmountInput! ? 40.h : 20.h, + // onTapOutside: (event) { + // FocusManager.instance.primaryFocus?.unfocus(); + // }, + // onSubmitted: onSubmitted, + // minLines: isMultiline ? minLines : 1, + // maxLines: isMultiline ? maxLines : 1, + // style: TextStyle( + // fontSize: fontS, + // height: isMultiline ? 1.2 : (isWalletAmountInput! ? 1 / 4 : 0), + // fontWeight: FontWeight.w500, + // color: AppColors.textColor, + // letterSpacing: -1, + // ), + // decoration: InputDecoration( + // isDense: true, + // hintText: hintText, + // hintStyle: TextStyle( + // fontSize: 14.f, + // height: 21 / 16, + // fontWeight: FontWeight.w500, + // color: hintColor != null ? AppColors.textColor : Color(0xff898A8D), + // letterSpacing: -0.75), + // prefixIconConstraints: BoxConstraints(minWidth: 30.h), + // prefixIcon: prefix == null ? null : "+${prefix!}".toText14(letterSpacing: -1, color: AppColors.textColor, weight: FontWeight.w500), + // contentPadding: EdgeInsets.zero, + // border: InputBorder.none, + // focusedBorder: InputBorder.none, + // enabledBorder: InputBorder.none, + // ), + // ); } _buildTrailingIconForSearch(BuildContext context) { diff --git a/lib/widgets/language_switcher.dart b/lib/widgets/language_switcher.dart index 91e45b22..033eaaff 100644 --- a/lib/widgets/language_switcher.dart +++ b/lib/widgets/language_switcher.dart @@ -59,14 +59,15 @@ class _LanguageSelectorState extends State { Utils.buildSvgWithAssets(icon: AppAssets.language), SizedBox(width: 6.h), Text( - currentLangData['name']?.toUpperCase() ?? 'EN', + // currentLangData['name']?.toUpperCase() ?? 'EN', + widget.currentLanguage == 'ar' ? "ENGLISH" : "العربية", style: context.dynamicTextStyle( fontWeight: FontWeight.w500, fontSize: 14.f, color: AppColors.primaryRedColor, letterSpacing: 0.1, isLanguageSwitcher: true, - ), + fontFamily: widget.currentLanguage == 'ar' ? "Poppins" : "GESSTwo"), ), ], ), diff --git a/lib/widgets/loader/bottomsheet_loader.dart b/lib/widgets/loader/bottomsheet_loader.dart index 813ad5b5..71b83415 100644 --- a/lib/widgets/loader/bottomsheet_loader.dart +++ b/lib/widgets/loader/bottomsheet_loader.dart @@ -3,7 +3,9 @@ import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; class LoaderBottomSheet { @@ -31,7 +33,7 @@ class LoaderBottomSheet { ), child: Center( child: Utils.getLoadingWidget(loadingText: loadingText), - ), + ).paddingSymmetrical(24.w, 0), ); }, ).whenComplete(() { diff --git a/lib/widgets/map/location_map_widget.dart b/lib/widgets/map/location_map_widget.dart index c0eb431b..e3139a93 100644 --- a/lib/widgets/map/location_map_widget.dart +++ b/lib/widgets/map/location_map_widget.dart @@ -1,12 +1,16 @@ +import 'dart:io'; + +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/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/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; -import 'package:maps_launcher/maps_launcher.dart'; +import 'package:map_launcher/map_launcher.dart'; /// A reusable location map widget that displays a static map with address and directions button /// Can be used in any review screen (HHC, CMC, etc.) @@ -54,9 +58,19 @@ class LocationMapWidget extends StatelessWidget { this.padding, }); - void _defaultLaunchDirections() { + void _defaultLaunchDirections() async { if (latitude != 0.0 && longitude != 0.0) { - MapsLauncher.launchCoordinates(latitude, longitude, address); + await MapLauncher.showMarker( + mapType: MapType.google, + coords: Coords(latitude, longitude), + title: address, + ).catchError((err) { + MapLauncher.showMarker( + mapType: Platform.isIOS ? MapType.apple : MapType.google, + coords: Coords(latitude, longitude), + title: address, + ); + }); } } @@ -154,7 +168,7 @@ class LocationMapWidget extends StatelessWidget { child: SizedBox( width: MediaQuery.of(context).size.width * 0.785, child: CustomButton( - text: "Get Directions".needTranslation, + text: LocaleKeys.getDirections.tr(context: context), onPressed: onDirectionsTap ?? _defaultLaunchDirections, backgroundColor: AppColors.textColor.withValues(alpha: 0.8), borderColor: AppColors.textColor.withValues(alpha: 0.01), diff --git a/lib/widgets/map/map_utility_screen.dart b/lib/widgets/map/map_utility_screen.dart index 19823da8..cb3eb8ef 100644 --- a/lib/widgets/map/map_utility_screen.dart +++ b/lib/widgets/map/map_utility_screen.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; @@ -5,6 +6,7 @@ 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/location/location_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/widgets/location_input_bottom_sheet.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -126,14 +128,14 @@ class MapUtilityScreen extends StatelessWidget { weight: FontWeight.w600, color: AppColors.textColor, ), - subTitleString.needTranslation.toText12( + subTitleString.toText12( fontWeight: FontWeight.w500, color: AppColors.greyTextColor, ) ], ), CustomButton( - text: confirmButtonString.needTranslation, + text: confirmButtonString, onPressed: () { if (onSubmitted != null) { onSubmitted!(); @@ -172,8 +174,8 @@ class MapUtilityScreen extends StatelessWidget { return SizedBox( width: MediaQuery.sizeOf(context).width, child: TextInputWidget( - labelText: "Enter Pickup Location Manually".needTranslation, - hintText: "Enter Pickup Location".needTranslation, + labelText: LocaleKeys.enterPickupLocationManually.tr(context: context), + hintText: LocaleKeys.enterPickupLocation.tr(context: context), controller: TextEditingController( text: vm.geocodeResponse?.results.first.formattedAddress ?? vm.selectedPrediction?.description, ), @@ -203,7 +205,7 @@ class MapUtilityScreen extends StatelessWidget { openLocationInputBottomSheet(BuildContext context) { context.read().flushSearchPredictions(); showCommonBottomSheetWithoutHeight( - title: "".needTranslation, + title: "", context, child: SizedBox( height: MediaQuery.sizeOf(context).height * .8, diff --git a/lib/widgets/time_picker_widget.dart b/lib/widgets/time_picker_widget.dart index 71d9ab2e..bc996de6 100644 --- a/lib/widgets/time_picker_widget.dart +++ b/lib/widgets/time_picker_widget.dart @@ -1,9 +1,11 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -150,7 +152,7 @@ class _TimePickerBottomSheetState extends State<_TimePickerBottomSheet> { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Select Time".needTranslation.toText18( + LocaleKeys.selectTime.tr(context: context).toText18( weight: FontWeight.w600, color: AppColors.textColor, ), @@ -318,7 +320,7 @@ class _TimePickerBottomSheetState extends State<_TimePickerBottomSheet> { Expanded( child: CustomButton( height: 56.h, - text: "Cancel".needTranslation, + text: LocaleKeys.cancel.tr(context: context), onPressed: () => Navigator.pop(context), textColor: AppColors.textColor, backgroundColor: AppColors.greyColor, @@ -329,7 +331,7 @@ class _TimePickerBottomSheetState extends State<_TimePickerBottomSheet> { Expanded( child: CustomButton( height: 56.h, - text: "Confirm".needTranslation, + text: LocaleKeys.confirm.tr(context: context), onPressed: () { Navigator.pop(context, _getCurrentTime()); }, diff --git a/pubspec.lock b/pubspec.lock deleted file mode 100644 index 7e5aaf52..00000000 --- a/pubspec.lock +++ /dev/null @@ -1,2073 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - _flutterfire_internals: - dependency: transitive - description: - name: _flutterfire_internals - sha256: ff0a84a2734d9e1089f8aedd5c0af0061b82fb94e95260d943404e0ef2134b11 - url: "https://pub.dev" - source: hosted - version: "1.3.59" - adaptive_number: - dependency: transitive - description: - name: adaptive_number - sha256: "3a567544e9b5c9c803006f51140ad544aedc79604fd4f3f2c1380003f97c1d77" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - amazon_payfort: - dependency: "direct main" - description: - name: amazon_payfort - sha256: "7732df0764aecbb814f910db36d0dca2f696e7e5ea380b49aa3ec62965768b33" - url: "https://pub.dev" - source: hosted - version: "1.1.4" - archive: - dependency: transitive - description: - name: archive - sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" - url: "https://pub.dev" - source: hosted - version: "4.0.7" - args: - dependency: transitive - description: - name: args - sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 - url: "https://pub.dev" - source: hosted - version: "2.7.0" - async: - dependency: transitive - description: - name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" - url: "https://pub.dev" - source: hosted - version: "2.13.0" - audio_session: - dependency: transitive - description: - name: audio_session - sha256: "8f96a7fecbb718cb093070f868b4cdcb8a9b1053dce342ff8ab2fde10eb9afb7" - url: "https://pub.dev" - source: hosted - version: "0.2.2" - auto_size_text: - dependency: "direct main" - description: - name: auto_size_text - sha256: "3f5261cd3fb5f2a9ab4e2fc3fba84fd9fcaac8821f20a1d4e71f557521b22599" - url: "https://pub.dev" - source: hosted - version: "3.0.0" - barcode_scan2: - dependency: "direct main" - description: - name: barcode_scan2 - sha256: "50b286021c644deee71e20a06c1709adc6594e39d65024ced0458cc1e3ff298e" - url: "https://pub.dev" - source: hosted - version: "4.6.0" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - cached_network_image: - dependency: "direct main" - description: - name: cached_network_image - sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" - url: "https://pub.dev" - source: hosted - version: "3.4.1" - cached_network_image_platform_interface: - dependency: transitive - description: - name: cached_network_image_platform_interface - sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" - url: "https://pub.dev" - source: hosted - version: "4.1.1" - cached_network_image_web: - dependency: transitive - description: - name: cached_network_image_web - sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" - url: "https://pub.dev" - source: hosted - version: "1.3.1" - carp_serializable: - dependency: transitive - description: - name: carp_serializable - sha256: f039f8ea22e9437aef13fe7e9743c3761c76d401288dcb702eadd273c3e4dcef - url: "https://pub.dev" - source: hosted - version: "2.0.1" - characters: - dependency: transitive - description: - name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 - url: "https://pub.dev" - source: hosted - version: "1.4.0" - chewie: - dependency: transitive - description: - name: chewie - sha256: "44bcfc5f0dfd1de290c87c9d86a61308b3282a70b63435d5557cfd60f54a69ca" - url: "https://pub.dev" - source: hosted - version: "1.13.0" - clock: - dependency: transitive - description: - name: clock - sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" - source: hosted - version: "1.1.2" - collection: - dependency: transitive - description: - name: collection - sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" - source: hosted - version: "1.19.1" - connectivity_plus: - dependency: "direct main" - description: - name: connectivity_plus - sha256: b5e72753cf63becce2c61fd04dfe0f1c430cc5278b53a1342dc5ad839eab29ec - url: "https://pub.dev" - source: hosted - version: "6.1.5" - connectivity_plus_platform_interface: - dependency: transitive - description: - name: connectivity_plus_platform_interface - sha256: "42657c1715d48b167930d5f34d00222ac100475f73d10162ddf43e714932f204" - url: "https://pub.dev" - source: hosted - version: "2.0.1" - convert: - dependency: transitive - description: - name: convert - sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 - url: "https://pub.dev" - source: hosted - version: "3.1.2" - cross_file: - dependency: transitive - description: - name: cross_file - sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" - url: "https://pub.dev" - source: hosted - version: "0.3.4+2" - crypto: - dependency: transitive - description: - name: crypto - sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" - url: "https://pub.dev" - source: hosted - version: "3.0.6" - csslib: - dependency: transitive - description: - name: csslib - sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" - url: "https://pub.dev" - source: hosted - version: "1.0.2" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 - url: "https://pub.dev" - source: hosted - version: "1.0.8" - dart_jsonwebtoken: - dependency: "direct main" - description: - name: dart_jsonwebtoken - sha256: "0de65691c1d736e9459f22f654ddd6fd8368a271d4e41aa07e53e6301eff5075" - url: "https://pub.dev" - source: hosted - version: "3.3.1" - dartz: - dependency: "direct main" - description: - name: dartz - sha256: e6acf34ad2e31b1eb00948692468c30ab48ac8250e0f0df661e29f12dd252168 - url: "https://pub.dev" - source: hosted - version: "0.10.1" - dbus: - dependency: transitive - description: - name: dbus - sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" - url: "https://pub.dev" - source: hosted - version: "0.7.11" - device_calendar: - dependency: "direct main" - description: - path: "." - ref: HEAD - resolved-ref: "5ea5ed9e2bb499c0633383b53103f2920b634755" - url: "https://github.com/bardram/device_calendar" - source: git - version: "4.3.1" - device_calendar_plus: - dependency: "direct main" - description: - name: device_calendar_plus - sha256: d11a70d98eb123e8eb09fdcfaf220ca4f1aa65a1512e12092f176f4b54983507 - url: "https://pub.dev" - source: hosted - version: "0.3.3" - device_calendar_plus_android: - dependency: transitive - description: - name: device_calendar_plus_android - sha256: a341ef29fa0251251287d63c1d009dfd35c1459dc6a129fd5e03f5ac92d8d7ff - url: "https://pub.dev" - source: hosted - version: "0.3.3" - device_calendar_plus_ios: - dependency: transitive - description: - name: device_calendar_plus_ios - sha256: "3b2f84ce1ed002be8460e214a3229e66748bbaad4077603f2c734d67c42033ff" - url: "https://pub.dev" - source: hosted - version: "0.3.3" - device_calendar_plus_platform_interface: - dependency: transitive - description: - name: device_calendar_plus_platform_interface - sha256: "0ce7511c094ca256831a48e16efe8f1e97e7bd00a5ff3936296ffd650a1d76b5" - url: "https://pub.dev" - source: hosted - version: "0.3.3" - device_info_plus: - dependency: "direct main" - description: - name: device_info_plus - sha256: "98f28b42168cc509abc92f88518882fd58061ea372d7999aecc424345c7bff6a" - url: "https://pub.dev" - source: hosted - version: "11.5.0" - device_info_plus_platform_interface: - dependency: transitive - description: - name: device_info_plus_platform_interface - sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f - url: "https://pub.dev" - source: hosted - version: "7.0.3" - dropdown_search: - dependency: "direct main" - description: - name: dropdown_search - sha256: c29b3e5147a82a06a4a08b3b574c51cb48cc17ad89893d53ee72a6f86643622e - url: "https://pub.dev" - source: hosted - version: "6.0.2" - easy_localization: - dependency: "direct main" - description: - name: easy_localization - sha256: "2ccdf9db8fe4d9c5a75c122e6275674508fd0f0d49c827354967b8afcc56bbed" - url: "https://pub.dev" - source: hosted - version: "3.0.8" - easy_logger: - dependency: transitive - description: - name: easy_logger - sha256: c764a6e024846f33405a2342caf91c62e357c24b02c04dbc712ef232bf30ffb7 - url: "https://pub.dev" - source: hosted - version: "0.0.2" - ed25519_edwards: - dependency: transitive - description: - name: ed25519_edwards - sha256: "6ce0112d131327ec6d42beede1e5dfd526069b18ad45dcf654f15074ad9276cd" - url: "https://pub.dev" - source: hosted - version: "0.3.1" - equatable: - dependency: "direct main" - description: - name: equatable - sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" - url: "https://pub.dev" - source: hosted - version: "2.0.7" - fake_async: - dependency: transitive - description: - name: fake_async - sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" - source: hosted - version: "1.3.3" - ffi: - dependency: transitive - description: - name: ffi - sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - file: - dependency: transitive - description: - name: file - sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.dev" - source: hosted - version: "7.0.1" - file_picker: - dependency: "direct main" - description: - name: file_picker - sha256: f2d9f173c2c14635cc0e9b14c143c49ef30b4934e8d1d274d6206fcb0086a06f - url: "https://pub.dev" - source: hosted - version: "10.3.3" - file_selector_linux: - dependency: transitive - description: - name: file_selector_linux - sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" - url: "https://pub.dev" - source: hosted - version: "0.9.3+2" - file_selector_macos: - dependency: transitive - description: - name: file_selector_macos - sha256: "19124ff4a3d8864fdc62072b6a2ef6c222d55a3404fe14893a3c02744907b60c" - url: "https://pub.dev" - source: hosted - version: "0.9.4+4" - file_selector_platform_interface: - dependency: transitive - description: - name: file_selector_platform_interface - sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b - url: "https://pub.dev" - source: hosted - version: "2.6.2" - file_selector_windows: - dependency: transitive - description: - name: file_selector_windows - sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" - url: "https://pub.dev" - source: hosted - version: "0.9.3+4" - firebase_analytics: - dependency: "direct main" - description: - name: firebase_analytics - sha256: "4f85b161772e1d54a66893ef131c0a44bd9e552efa78b33d5f4f60d2caa5c8a3" - url: "https://pub.dev" - source: hosted - version: "11.6.0" - firebase_analytics_platform_interface: - dependency: transitive - description: - name: firebase_analytics_platform_interface - sha256: a44b6d1155ed5cae7641e3de7163111cfd9f6f6c954ca916dc6a3bdfa86bf845 - url: "https://pub.dev" - source: hosted - version: "4.4.3" - firebase_analytics_web: - dependency: transitive - description: - name: firebase_analytics_web - sha256: c7d1ed1f86ae64215757518af5576ff88341c8ce5741988c05cc3b2e07b0b273 - url: "https://pub.dev" - source: hosted - version: "0.5.10+16" - firebase_core: - dependency: "direct main" - description: - name: firebase_core - sha256: "7be63a3f841fc9663342f7f3a011a42aef6a61066943c90b1c434d79d5c995c5" - url: "https://pub.dev" - source: hosted - version: "3.15.2" - firebase_core_platform_interface: - dependency: transitive - description: - name: firebase_core_platform_interface - sha256: "5873a370f0d232918e23a5a6137dbe4c2c47cf017301f4ea02d9d636e52f60f0" - url: "https://pub.dev" - source: hosted - version: "6.0.1" - firebase_core_web: - dependency: transitive - description: - name: firebase_core_web - sha256: "0ed0dc292e8f9ac50992e2394e9d336a0275b6ae400d64163fdf0a8a8b556c37" - url: "https://pub.dev" - source: hosted - version: "2.24.1" - firebase_messaging: - dependency: "direct main" - description: - name: firebase_messaging - sha256: "60be38574f8b5658e2f22b7e311ff2064bea835c248424a383783464e8e02fcc" - url: "https://pub.dev" - source: hosted - version: "15.2.10" - firebase_messaging_platform_interface: - dependency: transitive - description: - name: firebase_messaging_platform_interface - sha256: "685e1771b3d1f9c8502771ccc9f91485b376ffe16d553533f335b9183ea99754" - url: "https://pub.dev" - source: hosted - version: "4.6.10" - firebase_messaging_web: - dependency: transitive - description: - name: firebase_messaging_web - sha256: "0d1be17bc89ed3ff5001789c92df678b2e963a51b6fa2bdb467532cc9dbed390" - url: "https://pub.dev" - source: hosted - version: "3.10.10" - fixnum: - dependency: transitive - description: - name: fixnum - sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be - url: "https://pub.dev" - source: hosted - version: "1.1.1" - fl_chart: - dependency: "direct main" - description: - name: fl_chart - sha256: "577aeac8ca414c25333334d7c4bb246775234c0e44b38b10a82b559dd4d764e7" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_cache_manager: - dependency: transitive - description: - name: flutter_cache_manager - sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" - url: "https://pub.dev" - source: hosted - version: "3.4.1" - flutter_callkit_incoming: - dependency: "direct main" - description: - name: flutter_callkit_incoming - sha256: "3589deb8b71e43f2d520a9c8a5240243f611062a8b246cdca4b1fda01fbbf9b8" - url: "https://pub.dev" - source: hosted - version: "3.0.0" - flutter_hooks: - dependency: transitive - description: - name: flutter_hooks - sha256: cde36b12f7188c85286fba9b38cc5a902e7279f36dd676967106c041dc9dde70 - url: "https://pub.dev" - source: hosted - version: "0.20.5" - flutter_inappwebview: - dependency: "direct main" - description: - name: flutter_inappwebview - sha256: "80092d13d3e29b6227e25b67973c67c7210bd5e35c4b747ca908e31eb71a46d5" - url: "https://pub.dev" - source: hosted - version: "6.1.5" - flutter_inappwebview_android: - dependency: transitive - description: - name: flutter_inappwebview_android - sha256: "62557c15a5c2db5d195cb3892aab74fcaec266d7b86d59a6f0027abd672cddba" - url: "https://pub.dev" - source: hosted - version: "1.1.3" - flutter_inappwebview_internal_annotations: - dependency: transitive - description: - name: flutter_inappwebview_internal_annotations - sha256: "787171d43f8af67864740b6f04166c13190aa74a1468a1f1f1e9ee5b90c359cd" - url: "https://pub.dev" - source: hosted - version: "1.2.0" - flutter_inappwebview_ios: - dependency: transitive - description: - name: flutter_inappwebview_ios - sha256: "5818cf9b26cf0cbb0f62ff50772217d41ea8d3d9cc00279c45f8aabaa1b4025d" - url: "https://pub.dev" - source: hosted - version: "1.1.2" - flutter_inappwebview_macos: - dependency: transitive - description: - name: flutter_inappwebview_macos - sha256: c1fbb86af1a3738e3541364d7d1866315ffb0468a1a77e34198c9be571287da1 - url: "https://pub.dev" - source: hosted - version: "1.1.2" - flutter_inappwebview_platform_interface: - dependency: transitive - description: - name: flutter_inappwebview_platform_interface - sha256: cf5323e194096b6ede7a1ca808c3e0a078e4b33cc3f6338977d75b4024ba2500 - url: "https://pub.dev" - source: hosted - version: "1.3.0+1" - flutter_inappwebview_web: - dependency: transitive - description: - name: flutter_inappwebview_web - sha256: "55f89c83b0a0d3b7893306b3bb545ba4770a4df018204917148ebb42dc14a598" - url: "https://pub.dev" - source: hosted - version: "1.1.2" - flutter_inappwebview_windows: - dependency: transitive - description: - name: flutter_inappwebview_windows - sha256: "8b4d3a46078a2cdc636c4a3d10d10f2a16882f6be607962dbfff8874d1642055" - url: "https://pub.dev" - source: hosted - version: "0.6.0" - flutter_ios_voip_kit_karmm: - dependency: "direct main" - description: - name: flutter_ios_voip_kit_karmm - sha256: "31a445d78aacacdf128a0354efb9f4e424285dfe4c0af3ea872e64f03e6f6bfc" - url: "https://pub.dev" - source: hosted - version: "0.8.0" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" - url: "https://pub.dev" - source: hosted - version: "5.0.0" - flutter_local_notifications: - dependency: "direct main" - description: - name: flutter_local_notifications - sha256: "7ed76be64e8a7d01dfdf250b8434618e2a028c9dfa2a3c41dc9b531d4b3fc8a5" - url: "https://pub.dev" - source: hosted - version: "19.4.2" - flutter_local_notifications_linux: - dependency: transitive - description: - name: flutter_local_notifications_linux - sha256: e3c277b2daab8e36ac5a6820536668d07e83851aeeb79c446e525a70710770a5 - url: "https://pub.dev" - source: hosted - version: "6.0.0" - flutter_local_notifications_platform_interface: - dependency: transitive - description: - name: flutter_local_notifications_platform_interface - sha256: "277d25d960c15674ce78ca97f57d0bae2ee401c844b6ac80fcd972a9c99d09fe" - url: "https://pub.dev" - source: hosted - version: "9.1.0" - flutter_local_notifications_windows: - dependency: transitive - description: - name: flutter_local_notifications_windows - sha256: "8d658f0d367c48bd420e7cf2d26655e2d1130147bca1eea917e576ca76668aaf" - url: "https://pub.dev" - source: hosted - version: "1.0.3" - flutter_localizations: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_nfc_kit: - dependency: "direct main" - description: - name: flutter_nfc_kit - sha256: "3cc4059626fa672031261512299458dd274de4ccb57a7f0ee0951ddd70a048e5" - url: "https://pub.dev" - source: hosted - version: "3.6.0" - flutter_plugin_android_lifecycle: - dependency: transitive - description: - name: flutter_plugin_android_lifecycle - sha256: b0694b7fb1689b0e6cc193b3f1fcac6423c4f93c74fb20b806c6b6f196db0c31 - url: "https://pub.dev" - source: hosted - version: "2.0.30" - flutter_rating_bar: - dependency: "direct main" - description: - name: flutter_rating_bar - sha256: d2af03469eac832c591a1eba47c91ecc871fe5708e69967073c043b2d775ed93 - url: "https://pub.dev" - source: hosted - version: "4.0.1" - flutter_staggered_animations: - dependency: "direct main" - description: - name: flutter_staggered_animations - sha256: "81d3c816c9bb0dca9e8a5d5454610e21ffb068aedb2bde49d2f8d04f75538351" - url: "https://pub.dev" - source: hosted - version: "1.1.1" - flutter_svg: - dependency: "direct main" - description: - name: flutter_svg - sha256: b9c2ad5872518a27507ab432d1fb97e8813b05f0fc693f9d40fad06d073e0678 - url: "https://pub.dev" - source: hosted - version: "2.2.1" - flutter_swiper_view: - dependency: "direct main" - description: - name: flutter_swiper_view - sha256: "2a165b259e8a4c49d4da5626b967ed42a73dac2d075bd9e266ad8d23b9f01879" - url: "https://pub.dev" - source: hosted - version: "1.1.8" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - flutter_web_plugins: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - flutter_widget_from_html: - dependency: "direct main" - description: - name: flutter_widget_from_html - sha256: "7f1daefcd3009c43c7e7fb37501e6bb752d79aa7bfad0085fb0444da14e89bd0" - url: "https://pub.dev" - source: hosted - version: "0.17.1" - flutter_widget_from_html_core: - dependency: transitive - description: - name: flutter_widget_from_html_core - sha256: "1120ee6ed3509ceff2d55aa6c6cbc7b6b1291434422de2411b5a59364dd6ff03" - url: "https://pub.dev" - source: hosted - version: "0.17.0" - flutter_zoom_videosdk: - dependency: "direct main" - description: - name: flutter_zoom_videosdk - sha256: "46a4dea664b1c969099328a499c198a1755adf9ac333dea28bea5187910b3bf9" - url: "https://pub.dev" - source: hosted - version: "2.1.10" - fluttertoast: - dependency: "direct main" - description: - name: fluttertoast - sha256: "90778fe0497fe3a09166e8cf2e0867310ff434b794526589e77ec03cf08ba8e8" - url: "https://pub.dev" - source: hosted - version: "8.2.14" - fwfh_cached_network_image: - dependency: transitive - description: - name: fwfh_cached_network_image - sha256: "484cb5f8047f02cfac0654fca5832bfa91bb715fd7fc651c04eb7454187c4af8" - url: "https://pub.dev" - source: hosted - version: "0.16.1" - fwfh_chewie: - dependency: transitive - description: - name: fwfh_chewie - sha256: ae74fc26798b0e74f3983f7b851e74c63b9eeb2d3015ecd4b829096b2c3f8818 - url: "https://pub.dev" - source: hosted - version: "0.16.1" - fwfh_just_audio: - dependency: transitive - description: - name: fwfh_just_audio - sha256: dfd622a0dfe049ac647423a2a8afa7f057d9b2b93d92710b624e3d370b1ac69a - url: "https://pub.dev" - source: hosted - version: "0.17.0" - fwfh_svg: - dependency: transitive - description: - name: fwfh_svg - sha256: "2e6bb241179eeeb1a7941e05c8c923b05d332d36a9085233e7bf110ea7deb915" - url: "https://pub.dev" - source: hosted - version: "0.16.1" - fwfh_url_launcher: - dependency: transitive - description: - name: fwfh_url_launcher - sha256: c38aa8fb373fda3a89b951fa260b539f623f6edb45eee7874cb8b492471af881 - url: "https://pub.dev" - source: hosted - version: "0.16.1" - fwfh_webview: - dependency: transitive - description: - name: fwfh_webview - sha256: f71b0aa16e15d82f3c017f33560201ff5ae04e91e970cab5d12d3bcf970b870c - url: "https://pub.dev" - source: hosted - version: "0.15.6" - geoclue: - dependency: transitive - description: - name: geoclue - sha256: c2a998c77474fc57aa00c6baa2928e58f4b267649057a1c76738656e9dbd2a7f - url: "https://pub.dev" - source: hosted - version: "0.1.1" - geolocator: - dependency: "direct main" - description: - name: geolocator - sha256: "79939537046c9025be47ec645f35c8090ecadb6fe98eba146a0d25e8c1357516" - url: "https://pub.dev" - source: hosted - version: "14.0.2" - geolocator_android: - dependency: transitive - description: - name: geolocator_android - sha256: "179c3cb66dfa674fc9ccbf2be872a02658724d1c067634e2c427cf6df7df901a" - url: "https://pub.dev" - source: hosted - version: "5.0.2" - geolocator_apple: - dependency: transitive - description: - name: geolocator_apple - sha256: dbdd8789d5aaf14cf69f74d4925ad1336b4433a6efdf2fce91e8955dc921bf22 - url: "https://pub.dev" - source: hosted - version: "2.3.13" - geolocator_linux: - dependency: transitive - description: - name: geolocator_linux - sha256: c4e966f0a7a87e70049eac7a2617f9e16fd4c585a26e4330bdfc3a71e6a721f3 - url: "https://pub.dev" - source: hosted - version: "0.2.3" - geolocator_platform_interface: - dependency: transitive - description: - name: geolocator_platform_interface - sha256: "30cb64f0b9adcc0fb36f628b4ebf4f731a2961a0ebd849f4b56200205056fe67" - url: "https://pub.dev" - source: hosted - version: "4.2.6" - geolocator_web: - dependency: transitive - description: - name: geolocator_web - sha256: b1ae9bdfd90f861fde8fd4f209c37b953d65e92823cb73c7dee1fa021b06f172 - url: "https://pub.dev" - source: hosted - version: "4.1.3" - geolocator_windows: - dependency: transitive - description: - name: geolocator_windows - sha256: "175435404d20278ffd220de83c2ca293b73db95eafbdc8131fe8609be1421eb6" - url: "https://pub.dev" - source: hosted - version: "0.2.5" - get_it: - dependency: "direct main" - description: - name: get_it - sha256: a4292e7cf67193f8e7c1258203104eb2a51ec8b3a04baa14695f4064c144297b - url: "https://pub.dev" - source: hosted - version: "8.2.0" - gms_check: - dependency: "direct main" - description: - name: gms_check - sha256: b3fc08fd41da233f9761f9981303346aa9778b4802e90ce9bd8122674fcca6f0 - url: "https://pub.dev" - source: hosted - version: "1.0.4" - google_api_availability: - dependency: "direct main" - description: - name: google_api_availability - sha256: "2ffdc91e1e0cf4e7974fef6c2988a24cefa81f03526ff04b694df6dc0fcbca03" - url: "https://pub.dev" - source: hosted - version: "5.0.1" - google_api_availability_android: - dependency: transitive - description: - name: google_api_availability_android - sha256: "4794147f43a8f3eee6b514d3ae30dbe6f7b9048cae8cd2a74cb4055cd28d74a8" - url: "https://pub.dev" - source: hosted - version: "1.1.1" - google_api_availability_platform_interface: - dependency: transitive - description: - name: google_api_availability_platform_interface - sha256: "65b7da62fe5b582bb3d508628ad827d36d890710ea274766a992a56fa5420da6" - url: "https://pub.dev" - source: hosted - version: "1.0.1" - google_maps: - dependency: transitive - description: - name: google_maps - sha256: "5d410c32112d7c6eb7858d359275b2aa04778eed3e36c745aeae905fb2fa6468" - url: "https://pub.dev" - source: hosted - version: "8.2.0" - google_maps_flutter: - dependency: "direct main" - description: - name: google_maps_flutter - sha256: c389e16fafc04b37a4105e0757ecb9d59806026cee72f408f1ba68811d01bfe6 - url: "https://pub.dev" - source: hosted - version: "2.13.1" - google_maps_flutter_android: - dependency: transitive - description: - name: google_maps_flutter_android - sha256: a6c9d43f6a944ff4bae5c3deb34817970ac3d591dcd7f5bd2ea450ab9e9c514a - url: "https://pub.dev" - source: hosted - version: "2.18.2" - google_maps_flutter_ios: - dependency: transitive - description: - name: google_maps_flutter_ios - sha256: ca02463b19a9abc7d31fcaf22631d021d647107467f741b917a69fa26659fd75 - url: "https://pub.dev" - source: hosted - version: "2.15.5" - google_maps_flutter_platform_interface: - dependency: transitive - description: - name: google_maps_flutter_platform_interface - sha256: f4b9b44f7b12a1f6707ffc79d082738e0b7e194bf728ee61d2b3cdf5fdf16081 - url: "https://pub.dev" - source: hosted - version: "2.14.0" - google_maps_flutter_web: - dependency: transitive - description: - name: google_maps_flutter_web - sha256: "53e5dbf73ff04153acc55a038248706967c21d5b6ef6657a57fce2be73c2895a" - url: "https://pub.dev" - source: hosted - version: "0.5.14+2" - gsettings: - dependency: transitive - description: - name: gsettings - sha256: "1b0ce661f5436d2db1e51f3c4295a49849f03d304003a7ba177d01e3a858249c" - url: "https://pub.dev" - source: hosted - version: "0.2.8" - health: - dependency: "direct main" - description: - name: health - sha256: "320633022fb2423178baa66508001c4ca5aee5806ffa2c913e66488081e9fd47" - url: "https://pub.dev" - source: hosted - version: "13.1.4" - hijri_gregorian_calendar: - dependency: "direct main" - description: - name: hijri_gregorian_calendar - sha256: aecdbe3c9365fac55f17b5e1f24086a81999b1e5c9372cb08888bfbe61e07fa1 - url: "https://pub.dev" - source: hosted - version: "0.1.1" - html: - dependency: transitive - description: - name: html - sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" - url: "https://pub.dev" - source: hosted - version: "0.15.6" - http: - dependency: "direct main" - description: - name: http - sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 - url: "https://pub.dev" - source: hosted - version: "1.5.0" - http_parser: - dependency: transitive - description: - name: http_parser - sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" - url: "https://pub.dev" - source: hosted - version: "4.1.2" - huawei_health: - dependency: "direct main" - description: - name: huawei_health - sha256: "52fb9990e1fc857e2fa1b1251dde63b2146086a13b2d9c50bdfc3c4f715c8a12" - url: "https://pub.dev" - source: hosted - version: "6.16.0+300" - huawei_location: - dependency: "direct main" - description: - name: huawei_location - sha256: "3100d6b2b11df56481b8deade71baa84970e0bae0ade6ec56407be2b036af355" - url: "https://pub.dev" - source: hosted - version: "6.14.2+301" - huawei_map: - dependency: "direct main" - description: - path: flutter-hms-map - ref: HEAD - resolved-ref: "9a16541e4016e3bf58a2571e6aa658a4751af399" - url: "https://github.com/fleoparra/hms-flutter-plugin.git" - source: git - version: "6.11.2+303" - image_picker: - dependency: "direct main" - description: - name: image_picker - sha256: "736eb56a911cf24d1859315ad09ddec0b66104bc41a7f8c5b96b4e2620cf5041" - url: "https://pub.dev" - source: hosted - version: "1.2.0" - image_picker_android: - dependency: transitive - description: - name: image_picker_android - sha256: "28f3987ca0ec702d346eae1d90eda59603a2101b52f1e234ded62cff1d5cfa6e" - url: "https://pub.dev" - source: hosted - version: "0.8.13+1" - image_picker_for_web: - dependency: transitive - description: - name: image_picker_for_web - sha256: "40c2a6a0da15556dc0f8e38a3246064a971a9f512386c3339b89f76db87269b6" - url: "https://pub.dev" - source: hosted - version: "3.1.0" - image_picker_ios: - dependency: transitive - description: - name: image_picker_ios - sha256: eb06fe30bab4c4497bad449b66448f50edcc695f1c59408e78aa3a8059eb8f0e - url: "https://pub.dev" - source: hosted - version: "0.8.13" - image_picker_linux: - dependency: transitive - description: - name: image_picker_linux - sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" - url: "https://pub.dev" - source: hosted - version: "0.2.2" - image_picker_macos: - dependency: transitive - description: - name: image_picker_macos - sha256: d58cd9d67793d52beefd6585b12050af0a7663c0c2a6ece0fb110a35d6955e04 - url: "https://pub.dev" - source: hosted - version: "0.2.2" - image_picker_platform_interface: - dependency: transitive - description: - name: image_picker_platform_interface - sha256: "9f143b0dba3e459553209e20cc425c9801af48e6dfa4f01a0fcf927be3f41665" - url: "https://pub.dev" - source: hosted - version: "2.11.0" - image_picker_windows: - dependency: transitive - description: - name: image_picker_windows - sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae - url: "https://pub.dev" - source: hosted - version: "0.2.2" - intl: - dependency: "direct main" - description: - name: intl - sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" - url: "https://pub.dev" - source: hosted - version: "0.20.2" - jiffy: - dependency: "direct main" - description: - name: jiffy - sha256: "9bafbfe6d97587048bf449165e050029e716a12438f54a3d39e7e3a256decdac" - url: "https://pub.dev" - source: hosted - version: "6.4.3" - json_annotation: - dependency: transitive - description: - name: json_annotation - sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" - url: "https://pub.dev" - source: hosted - version: "4.9.0" - just_audio: - dependency: "direct main" - description: - name: just_audio - sha256: "9694e4734f515f2a052493d1d7e0d6de219ee0427c7c29492e246ff32a219908" - url: "https://pub.dev" - source: hosted - version: "0.10.5" - just_audio_platform_interface: - dependency: transitive - description: - name: just_audio_platform_interface - sha256: "2532c8d6702528824445921c5ff10548b518b13f808c2e34c2fd54793b999a6a" - url: "https://pub.dev" - source: hosted - version: "4.6.0" - just_audio_web: - dependency: transitive - description: - name: just_audio_web - sha256: "6ba8a2a7e87d57d32f0f7b42856ade3d6a9fbe0f1a11fabae0a4f00bb73f0663" - url: "https://pub.dev" - source: hosted - version: "0.4.16" - keyboard_actions: - dependency: "direct main" - description: - name: keyboard_actions - sha256: "31e0ab2a706ac8f58887efa60efc1f19aecdf37d8ab0f665a0f156d1fbeab650" - url: "https://pub.dev" - source: hosted - version: "4.2.0" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.dev" - source: hosted - version: "11.0.2" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.dev" - source: hosted - version: "3.0.10" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.dev" - source: hosted - version: "3.0.2" - lints: - dependency: transitive - description: - name: lints - sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 - url: "https://pub.dev" - source: hosted - version: "5.1.1" - local_auth: - dependency: "direct main" - description: - name: local_auth - sha256: "434d854cf478f17f12ab29a76a02b3067f86a63a6d6c4eb8fbfdcfe4879c1b7b" - url: "https://pub.dev" - source: hosted - version: "2.3.0" - local_auth_android: - dependency: transitive - description: - name: local_auth_android - sha256: "48924f4a8b3cc45994ad5993e2e232d3b00788a305c1bf1c7db32cef281ce9a3" - url: "https://pub.dev" - source: hosted - version: "1.0.52" - local_auth_darwin: - dependency: transitive - description: - name: local_auth_darwin - sha256: "0e9706a8543a4a2eee60346294d6a633dd7c3ee60fae6b752570457c4ff32055" - url: "https://pub.dev" - source: hosted - version: "1.6.0" - local_auth_platform_interface: - dependency: transitive - description: - name: local_auth_platform_interface - sha256: "1b842ff177a7068442eae093b64abe3592f816afd2a533c0ebcdbe40f9d2075a" - url: "https://pub.dev" - source: hosted - version: "1.0.10" - local_auth_windows: - dependency: transitive - description: - name: local_auth_windows - sha256: bc4e66a29b0fdf751aafbec923b5bed7ad6ed3614875d8151afe2578520b2ab5 - url: "https://pub.dev" - source: hosted - version: "1.0.11" - location: - dependency: "direct main" - description: - name: location - sha256: b080053c181c7d152c43dd576eec6436c40e25f326933051c330da563ddd5333 - url: "https://pub.dev" - source: hosted - version: "8.0.1" - location_platform_interface: - dependency: transitive - description: - name: location_platform_interface - sha256: ca8700bb3f6b1e8b2afbd86bd78b2280d116c613ca7bfa1d4d7b64eba357d749 - url: "https://pub.dev" - source: hosted - version: "6.0.1" - location_web: - dependency: transitive - description: - name: location_web - sha256: b8e3add5efe0d65c5e692b7a135d80a4015c580d3ea646fa71973e97668dd868 - url: "https://pub.dev" - source: hosted - version: "6.0.1" - logger: - dependency: "direct main" - description: - name: logger - sha256: "55d6c23a6c15db14920e037fe7e0dc32e7cdaf3b64b4b25df2d541b5b6b81c0c" - url: "https://pub.dev" - source: hosted - version: "2.6.1" - logging: - dependency: transitive - description: - name: logging - sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 - url: "https://pub.dev" - source: hosted - version: "1.3.0" - lottie: - dependency: "direct main" - description: - name: lottie - sha256: c5fa04a80a620066c15cf19cc44773e19e9b38e989ff23ea32e5903ef1015950 - url: "https://pub.dev" - source: hosted - version: "3.3.1" - manage_calendar_events: - dependency: "direct main" - description: - name: manage_calendar_events - sha256: f17600fcb7dc7047120c185993045e493d686930237b4e3c2689c26a64513d66 - url: "https://pub.dev" - source: hosted - version: "2.0.3" - maps_launcher: - dependency: "direct main" - description: - name: maps_launcher - sha256: dac4c609720211fa6336b5903d917fe45e545c6b5665978efc3db2a3f436b1ae - url: "https://pub.dev" - source: hosted - version: "3.0.0+1" - matcher: - dependency: transitive - description: - name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 - url: "https://pub.dev" - source: hosted - version: "0.12.17" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec - url: "https://pub.dev" - source: hosted - version: "0.11.1" - meta: - dependency: transitive - description: - name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c - url: "https://pub.dev" - source: hosted - version: "1.16.0" - mime: - dependency: transitive - description: - name: mime - sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" - url: "https://pub.dev" - source: hosted - version: "2.0.0" - ndef: - dependency: transitive - description: - name: ndef - sha256: "5083507cff4bb823b2a198a27ea2c70c4d6bc27a97b66097d966a250e1615d54" - url: "https://pub.dev" - source: hosted - version: "0.3.4" - nested: - dependency: transitive - description: - name: nested - sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - network_info_plus: - dependency: "direct main" - description: - name: network_info_plus - sha256: f926b2ba86aa0086a0dfbb9e5072089bc213d854135c1712f1d29fc89ba3c877 - url: "https://pub.dev" - source: hosted - version: "6.1.4" - network_info_plus_platform_interface: - dependency: transitive - description: - name: network_info_plus_platform_interface - sha256: "7e7496a8a9d8136859b8881affc613c4a21304afeb6c324bcefc4bd0aff6b94b" - url: "https://pub.dev" - source: hosted - version: "2.0.2" - nm: - dependency: transitive - description: - name: nm - sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254" - url: "https://pub.dev" - source: hosted - version: "0.5.0" - octo_image: - dependency: transitive - description: - name: octo_image - sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" - url: "https://pub.dev" - source: hosted - version: "2.1.0" - open_filex: - dependency: "direct main" - description: - name: open_filex - sha256: "9976da61b6a72302cf3b1efbce259200cd40232643a467aac7370addf94d6900" - url: "https://pub.dev" - source: hosted - version: "4.7.0" - package_info_plus: - dependency: transitive - description: - name: package_info_plus - sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968" - url: "https://pub.dev" - source: hosted - version: "8.3.1" - package_info_plus_platform_interface: - dependency: transitive - description: - name: package_info_plus_platform_interface - sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" - url: "https://pub.dev" - source: hosted - version: "3.2.1" - path: - dependency: transitive - description: - name: path - sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" - source: hosted - version: "1.9.1" - path_parsing: - dependency: transitive - description: - name: path_parsing - sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - path_provider: - dependency: "direct main" - description: - name: path_provider - sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" - url: "https://pub.dev" - source: hosted - version: "2.1.5" - path_provider_android: - dependency: transitive - description: - name: path_provider_android - sha256: "993381400e94d18469750e5b9dcb8206f15bc09f9da86b9e44a9b0092a0066db" - url: "https://pub.dev" - source: hosted - version: "2.2.18" - path_provider_foundation: - dependency: transitive - description: - name: path_provider_foundation - sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd" - url: "https://pub.dev" - source: hosted - version: "2.4.2" - path_provider_linux: - dependency: transitive - description: - name: path_provider_linux - sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 - url: "https://pub.dev" - source: hosted - version: "2.2.1" - path_provider_platform_interface: - dependency: transitive - description: - name: path_provider_platform_interface - sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - path_provider_windows: - dependency: transitive - description: - name: path_provider_windows - sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 - url: "https://pub.dev" - source: hosted - version: "2.3.0" - permission_handler: - dependency: "direct main" - description: - name: permission_handler - sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1 - url: "https://pub.dev" - source: hosted - version: "12.0.1" - permission_handler_android: - dependency: transitive - description: - name: permission_handler_android - sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6" - url: "https://pub.dev" - source: hosted - version: "13.0.1" - permission_handler_apple: - dependency: transitive - description: - name: permission_handler_apple - sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 - url: "https://pub.dev" - source: hosted - version: "9.4.7" - permission_handler_html: - dependency: transitive - description: - name: permission_handler_html - sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" - url: "https://pub.dev" - source: hosted - version: "0.1.3+5" - permission_handler_platform_interface: - dependency: transitive - description: - name: permission_handler_platform_interface - sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 - url: "https://pub.dev" - source: hosted - version: "4.3.0" - permission_handler_windows: - dependency: transitive - description: - name: permission_handler_windows - sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" - url: "https://pub.dev" - source: hosted - version: "0.2.1" - petitparser: - dependency: transitive - description: - name: petitparser - sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" - url: "https://pub.dev" - source: hosted - version: "7.0.1" - platform: - dependency: transitive - description: - name: platform - sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" - url: "https://pub.dev" - source: hosted - version: "3.1.6" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" - source: hosted - version: "2.1.8" - pointycastle: - dependency: transitive - description: - name: pointycastle - sha256: "92aa3841d083cc4b0f4709b5c74fd6409a3e6ba833ffc7dc6a8fee096366acf5" - url: "https://pub.dev" - source: hosted - version: "4.0.0" - posix: - dependency: transitive - description: - name: posix - sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" - url: "https://pub.dev" - source: hosted - version: "6.0.3" - protobuf: - dependency: transitive - description: - name: protobuf - sha256: "75ec242d22e950bdcc79ee38dd520ce4ee0bc491d7fadc4ea47694604d22bf06" - url: "https://pub.dev" - source: hosted - version: "6.0.0" - provider: - dependency: "direct main" - description: - name: provider - sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" - url: "https://pub.dev" - source: hosted - version: "6.1.5+1" - quiver: - dependency: transitive - description: - name: quiver - sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 - url: "https://pub.dev" - source: hosted - version: "3.2.2" - rrule: - dependency: transitive - description: - name: rrule - sha256: b7425410c594d4b6717c9f17ec8ef83c9d1ff2e513c428a135b5924fc2e8e045 - url: "https://pub.dev" - source: hosted - version: "0.2.17" - rxdart: - dependency: transitive - description: - name: rxdart - sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" - url: "https://pub.dev" - source: hosted - version: "0.28.0" - sanitize_html: - dependency: transitive - description: - name: sanitize_html - sha256: "12669c4a913688a26555323fb9cec373d8f9fbe091f2d01c40c723b33caa8989" - url: "https://pub.dev" - source: hosted - version: "2.1.0" - scrollable_positioned_list: - dependency: "direct main" - description: - name: scrollable_positioned_list - sha256: "1b54d5f1329a1e263269abc9e2543d90806131aa14fe7c6062a8054d57249287" - url: "https://pub.dev" - source: hosted - version: "0.3.8" - share_plus: - dependency: "direct main" - description: - name: share_plus - sha256: d7dc0630a923883c6328ca31b89aa682bacbf2f8304162d29f7c6aaff03a27a1 - url: "https://pub.dev" - source: hosted - version: "11.1.0" - share_plus_platform_interface: - dependency: transitive - description: - name: share_plus_platform_interface - sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a" - url: "https://pub.dev" - source: hosted - version: "6.1.0" - shared_preferences: - dependency: "direct main" - description: - name: shared_preferences - sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" - url: "https://pub.dev" - source: hosted - version: "2.5.3" - shared_preferences_android: - dependency: transitive - description: - name: shared_preferences_android - sha256: bd14436108211b0d4ee5038689a56d4ae3620fd72fd6036e113bf1345bc74d9e - url: "https://pub.dev" - source: hosted - version: "2.4.13" - shared_preferences_foundation: - dependency: transitive - description: - name: shared_preferences_foundation - sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" - url: "https://pub.dev" - source: hosted - version: "2.5.4" - shared_preferences_linux: - dependency: transitive - description: - name: shared_preferences_linux - sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - shared_preferences_platform_interface: - dependency: transitive - description: - name: shared_preferences_platform_interface - sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - shared_preferences_web: - dependency: transitive - description: - name: shared_preferences_web - sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 - url: "https://pub.dev" - source: hosted - version: "2.4.3" - shared_preferences_windows: - dependency: transitive - description: - name: shared_preferences_windows - sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - shimmer: - dependency: "direct main" - description: - name: shimmer - sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9" - url: "https://pub.dev" - source: hosted - version: "3.0.0" - sizer: - dependency: "direct main" - description: - name: sizer - sha256: "9963c89e4d30d7c2108de3eafc0a7e6a4a8009799376ea6be5ef0a9ad87cfbad" - url: "https://pub.dev" - source: hosted - version: "3.1.3" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - smooth_corner: - dependency: "direct main" - description: - name: smooth_corner - sha256: "112d7331f82ead81ec870c5d1eb0624f2e7e367eccd166c2fffe4c11d4f87c4f" - url: "https://pub.dev" - source: hosted - version: "1.1.1" - sms_otp_auto_verify: - dependency: "direct main" - description: - name: sms_otp_auto_verify - sha256: ee02af0d6b81d386ef70d7d0317a1929bc0b4a3a30a451284450bbcf6901ba1a - url: "https://pub.dev" - source: hosted - version: "2.2.0" - source_span: - dependency: transitive - description: - name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" - url: "https://pub.dev" - source: hosted - version: "1.10.1" - sprintf: - dependency: transitive - description: - name: sprintf - sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" - url: "https://pub.dev" - source: hosted - version: "7.0.0" - sqflite: - dependency: transitive - description: - name: sqflite - sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 - url: "https://pub.dev" - source: hosted - version: "2.4.2" - sqflite_android: - dependency: transitive - description: - name: sqflite_android - sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - sqflite_common: - dependency: transitive - description: - name: sqflite_common - sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6" - url: "https://pub.dev" - source: hosted - version: "2.5.6" - sqflite_darwin: - dependency: transitive - description: - name: sqflite_darwin - sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" - url: "https://pub.dev" - source: hosted - version: "2.4.2" - sqflite_platform_interface: - dependency: transitive - description: - name: sqflite_platform_interface - sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" - url: "https://pub.dev" - source: hosted - version: "2.4.0" - stack_trace: - dependency: transitive - description: - name: stack_trace - sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" - source: hosted - version: "1.12.1" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - stream_transform: - dependency: transitive - description: - name: stream_transform - sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 - url: "https://pub.dev" - source: hosted - version: "2.1.1" - string_scanner: - dependency: transitive - description: - name: string_scanner - sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" - source: hosted - version: "1.4.1" - syncfusion_flutter_calendar: - dependency: "direct main" - description: - name: syncfusion_flutter_calendar - sha256: "8e8a4eef01d6a82ae2c17e76d497ff289ded274de014c9f471ffabc12d1e2e71" - url: "https://pub.dev" - source: hosted - version: "30.2.7" - syncfusion_flutter_core: - dependency: transitive - description: - name: syncfusion_flutter_core - sha256: bfd026c0f9822b49ff26fed11cd3334519acb6a6ad4b0c81d9cd18df6af1c4c0 - url: "https://pub.dev" - source: hosted - version: "30.2.7" - syncfusion_flutter_datepicker: - dependency: transitive - description: - name: syncfusion_flutter_datepicker - sha256: b5f35cc808e91b229d41613efe71dadab1549a35bfd493f922fc06ccc2fe908c - url: "https://pub.dev" - source: hosted - version: "30.2.7" - syncfusion_localizations: - dependency: transitive - description: - name: syncfusion_localizations - sha256: bb32b07879b4c1dee5d4c8ad1c57343a4fdae55d65a87f492727c11b68f23164 - url: "https://pub.dev" - source: hosted - version: "30.2.7" - synchronized: - dependency: transitive - description: - name: synchronized - sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 - url: "https://pub.dev" - source: hosted - version: "3.4.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" - source: hosted - version: "1.2.2" - test_api: - dependency: transitive - description: - name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" - url: "https://pub.dev" - source: hosted - version: "0.7.6" - time: - dependency: transitive - description: - name: time - sha256: "370572cf5d1e58adcb3e354c47515da3f7469dac3a95b447117e728e7be6f461" - url: "https://pub.dev" - source: hosted - version: "2.1.5" - timezone: - dependency: "direct main" - description: - name: timezone - sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1 - url: "https://pub.dev" - source: hosted - version: "0.10.1" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.dev" - source: hosted - version: "1.4.0" - url_launcher: - dependency: "direct main" - description: - name: url_launcher - sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 - url: "https://pub.dev" - source: hosted - version: "6.3.2" - url_launcher_android: - dependency: transitive - description: - name: url_launcher_android - sha256: "81777b08c498a292d93ff2feead633174c386291e35612f8da438d6e92c4447e" - url: "https://pub.dev" - source: hosted - version: "6.3.20" - url_launcher_ios: - dependency: transitive - description: - name: url_launcher_ios - sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7 - url: "https://pub.dev" - source: hosted - version: "6.3.4" - url_launcher_linux: - dependency: transitive - description: - name: url_launcher_linux - sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" - url: "https://pub.dev" - source: hosted - version: "3.2.1" - url_launcher_macos: - dependency: transitive - description: - name: url_launcher_macos - sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f - url: "https://pub.dev" - source: hosted - version: "3.2.3" - url_launcher_platform_interface: - dependency: transitive - description: - name: url_launcher_platform_interface - sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" - url: "https://pub.dev" - source: hosted - version: "2.3.2" - url_launcher_web: - dependency: transitive - description: - name: url_launcher_web - sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - url_launcher_windows: - dependency: transitive - description: - name: url_launcher_windows - sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" - url: "https://pub.dev" - source: hosted - version: "3.1.4" - uuid: - dependency: "direct main" - description: - name: uuid - sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff - url: "https://pub.dev" - source: hosted - version: "4.5.1" - vector_graphics: - dependency: transitive - description: - name: vector_graphics - sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6 - url: "https://pub.dev" - source: hosted - version: "1.1.19" - vector_graphics_codec: - dependency: transitive - description: - name: vector_graphics_codec - sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" - url: "https://pub.dev" - source: hosted - version: "1.1.13" - vector_graphics_compiler: - dependency: transitive - description: - name: vector_graphics_compiler - sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc - url: "https://pub.dev" - source: hosted - version: "1.1.19" - vector_math: - dependency: transitive - description: - name: vector_math - sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.dev" - source: hosted - version: "2.2.0" - video_player: - dependency: transitive - description: - name: video_player - sha256: "0d55b1f1a31e5ad4c4967bfaa8ade0240b07d20ee4af1dfef5f531056512961a" - url: "https://pub.dev" - source: hosted - version: "2.10.0" - video_player_android: - dependency: transitive - description: - name: video_player_android - sha256: "6cfe0b1e102522eda1e139b82bf00602181c5844fd2885340f595fb213d74842" - url: "https://pub.dev" - source: hosted - version: "2.8.14" - video_player_avfoundation: - dependency: transitive - description: - name: video_player_avfoundation - sha256: f9a780aac57802b2892f93787e5ea53b5f43cc57dc107bee9436458365be71cd - url: "https://pub.dev" - source: hosted - version: "2.8.4" - video_player_platform_interface: - dependency: transitive - description: - name: video_player_platform_interface - sha256: cf2a1d29a284db648fd66cbd18aacc157f9862d77d2cc790f6f9678a46c1db5a - url: "https://pub.dev" - source: hosted - version: "6.4.0" - video_player_web: - dependency: transitive - description: - name: video_player_web - sha256: "9f3c00be2ef9b76a95d94ac5119fb843dca6f2c69e6c9968f6f2b6c9e7afbdeb" - url: "https://pub.dev" - source: hosted - version: "2.4.0" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 - url: "https://pub.dev" - source: hosted - version: "15.0.0" - wakelock_plus: - dependency: transitive - description: - name: wakelock_plus - sha256: "61713aa82b7f85c21c9f4cd0a148abd75f38a74ec645fcb1e446f882c82fd09b" - url: "https://pub.dev" - source: hosted - version: "1.3.3" - wakelock_plus_platform_interface: - dependency: transitive - description: - name: wakelock_plus_platform_interface - sha256: "036deb14cd62f558ca3b73006d52ce049fabcdcb2eddfe0bf0fe4e8a943b5cf2" - url: "https://pub.dev" - source: hosted - version: "1.3.0" - web: - dependency: "direct main" - description: - name: web - sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.dev" - source: hosted - version: "1.1.1" - webview_flutter: - dependency: transitive - description: - name: webview_flutter - sha256: c3e4fe614b1c814950ad07186007eff2f2e5dd2935eba7b9a9a1af8e5885f1ba - url: "https://pub.dev" - source: hosted - version: "4.13.0" - webview_flutter_android: - dependency: transitive - description: - name: webview_flutter_android - sha256: "9a25f6b4313978ba1c2cda03a242eea17848174912cfb4d2d8ee84a556f248e3" - url: "https://pub.dev" - source: hosted - version: "4.10.1" - webview_flutter_platform_interface: - dependency: transitive - description: - name: webview_flutter_platform_interface - sha256: "63d26ee3aca7256a83ccb576a50272edd7cfc80573a4305caa98985feb493ee0" - url: "https://pub.dev" - source: hosted - version: "2.14.0" - webview_flutter_wkwebview: - dependency: transitive - description: - name: webview_flutter_wkwebview - sha256: fb46db8216131a3e55bcf44040ca808423539bc6732e7ed34fb6d8044e3d512f - url: "https://pub.dev" - source: hosted - version: "3.23.0" - win32: - dependency: transitive - description: - name: win32 - sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03" - url: "https://pub.dev" - source: hosted - version: "5.14.0" - win32_registry: - dependency: transitive - description: - name: win32_registry - sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae" - url: "https://pub.dev" - source: hosted - version: "2.1.0" - xdg_directories: - dependency: transitive - description: - name: xdg_directories - sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - xml: - dependency: transitive - description: - name: xml - sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" - url: "https://pub.dev" - source: hosted - version: "6.6.1" -sdks: - dart: ">=3.8.1 <4.0.0" - flutter: ">=3.32.0" diff --git a/pubspec.yaml b/pubspec.yaml index cdad394c..19acdf31 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -43,7 +43,7 @@ dependencies: lottie: ^3.3.1 flutter_ios_voip_kit_karmm: ^0.8.0 image_picker: ^1.2.0 - file_picker: ^10.3.2 + file_picker: 10.3.8 local_auth: ^2.3.0 share_plus: ^11.1.0 device_calendar_plus: ^0.3.1 @@ -73,7 +73,7 @@ dependencies: web: any flutter_staggered_animations: ^1.1.1 smooth_corner: ^1.1.1 - maps_launcher: ^3.0.0+1 + map_launcher: ^4.4.3 amazon_payfort: ^1.1.4 network_info_plus: ^6.1.4 flutter_nfc_kit: ^3.6.0 @@ -83,6 +83,7 @@ dependencies: open_filex: ^4.7.0 flutter_swiper_view: ^1.1.8 flutter_callkit_incoming: ^3.0.0 +# device_preview: ^1.3.1 location: ^8.0.1 gms_check: ^1.0.4 @@ -115,6 +116,7 @@ flutter: - assets/images/png/body_parts/female/ - assets/images/svg/ - assets/images/png/ + - assets/images/clinicIcons/ - assets/images/png/zoom/ - assets/images/png/smartwatches/ - assets/animations/